Org Mode ES2015+ Code Blocks (updated)


Babel 6x is a significant change from Babel 5x, as the cli is now a separate node module called babel-cli and transforms are now also delivered as separate packages. First make a few changes to the emacs environment so you can use JavaScript in org mode, as well as find local node.js modules you have installed. Replace ~/org/node_modules in the configuration below with the location of any local node modules you want to use. Using this approach you don't have to pollute the global node_module directory if you don't want to.

#+begin_src js :cmd "org-babel-node" :results output drawer
  let arr = [1, 2]; 
  let [x, y] = arr;

   console.log(x);
   console.log(y);
#+end_src

:RESULTS:
1
2
:END:

Note: In this post I have updated the instructions for use with Babel 6x. For Babel 5x see the original post.

Add the following to your emacs init.el file and evaluate it (or restart emacs). Make sure the $HOME environment variable is set in your environment; or you could just hard code the absolute path to the node_modules directory in the code below.

   (setenv "NODE_PATH"
      (concat
       (getenv "HOME") "/org/node_modules"  ":"
       (getenv "NODE_PATH")
      )
    )

    (org-babel-do-load-languages
     'org-babel-load-languages
     '((js . t)))

You can choose to install the Babel modules globally, or you can do it locally. In this example I will install them locally in the org directory (~/org/node_modules).

Next install the babel-cli module, this allows you to call Babel from the command line. You will also want to install the transforms you plan to use, the example below installs the common preset transforms used with Babel 6. Also install any local modules you need to use. I chose to install them from the /org directory where my notes are kept, but anywhere works as long as you have the correct path to node_modules set above.

    npm install --save-dev babel-cli
    npm install --save-dev babel-preset-es2015
    npm install --save-dev babel-preset-stage-0
    npm install --save-dev babel-preset-stage-1
    npm install --save-dev babel-preset-stage-2
    npm install --save-dev babel-preset-stage-3
    npm install --save-dev babel-preset-react
    npm install --save-dev mongodb
    npm install --save-dev bluebird

Next you want to set up a symbolic link to the babel-cli script you just installed so it can be found from the command line. I decided to call it org-babel-node so it won't interfere with a babel-node executable linked to a global module of the same name.

  ln -s ~/org/node_modules/babel-cli/bin/babel-node.js /usr/local/bin/org-babel-node

The default org-mode interpreter for JavaScript is Node, so you need to have that installed and its path set. But the cool thing is that you can change the interpreter inline in your code blocks in the cases you want to experiment with upcoming language features. To do this you simply tell org mode to use an alternative interpreter; in this case the babel-node transpiler installed earlier and linked as org-babel-node.

Adding :cmd "org-babel-node" after the #+begin_src js tells org mode to use the org-babel-node transpiler instead of the default JavaScript interpreter. Because Babel 6 uses external modules for transforms you need to also tell Babel which preset or plugins you wish to use with either the --presets or --plugins options. The :results output tells org mode that the results will be from an output statement (using console.log() for JavaScript)

       #+name: db.activities.findOne
       #+begin_src js :cmd "org-babel-node --presets stage-1" :results output drawer
          (async function(){
              try {
                  let MongoDB = require('mongodb');
                  let Promise = require('bluebird');
                  Promise.promisifyAll(MongoDB);
                  let db = await MongoDB.MongoClient.connectAsync(process.env.MY_DB);
                  let activityCol = db.collection('activities')
                  let result = await activityCol.findOne();
                  db.close();
                  console.log(`The first activity name is ${result.name}`);
                }
               catch(err){
                 consule.log(err);
                 throw err;
               }
             })()
       #+end_src

Now, simply place the cursor anywhere in the block and execute it using C-c C-c. The results will be placed under the block in second block entitled: #+RESULTS:

The block can be executed as often as you like and the results will be refreshed. See the updated example with its results block below.

       #+name: db.activities.findOne
       #+begin_src js :cmd "org-babel-node --presets stage-1" :results output drawer
          (async function(){
              try {
                  let MongoDB = require('mongodb');
                  let Promise = require('bluebird');
                  Promise.promisifyAll(MongoDB);
                  let db = await MongoDB.MongoClient.connectAsync(process.env.MY_DB);
                  let activityCol = db.collection('activities')
                  let result = await activityCol.findOne();
                  db.close();
                  console.log(`The first activity name is ${result.name}`);
                }
               catch(err){
                 consule.log(err);
                 throw err;
               }
             })()
       #+end_src

       #+RESULTS: db.activities.findOne
       :RESULTS:
       The first activity name is Shopping
       :END:

You can see several new and experimental language features including async functions, the await keyword for performing a non-blocking wait on a promise to complete, and template strings.

comments powered by Disqus