ES6 Imports for ToDoMVC

Source code

As we saw in ES6 Imports with Babel, Babel is a transpiler that opens the door to Pythonic JS. Let’s switch our ToDoMVC codebase over to use a tiny part: ES6 Modules and imports. Along the way, we’ll refactor our todo.js code a bit.

Steps

  1. Install dependencies:

    npm install babel-preset-es2015 babel-loader --save-dev
    
  2. Our webpack.config.js needs to be taught to transpile code using Babel when Webpack does its bundling:

    ToDo webpack.config.js
    module.exports = {
        context: __dirname + '/app',
        entry: './app.js',
        module: {
            loaders: [
                {
                    test: /\.js$/,
                    loader: 'babel-loader'
                }
            ]
        },
        devtool: 'source-map'
    };
    
  3. Webpack’s use of Babel requires a .babelrc configuration file:

    ToDo .babelrc
    {
      "presets": ["es2015"]
    }
    
  4. Our .eslintrc file needs to be told to lint using ES6 features:

    ToDo .eslintrc
    {
      "rules": {
        "strict": 0,
        "quotes": [
          1,
          "single"
        ]
      },
      "ecmaFeatures": {
        "modules": true
      },
      "env": {
        "browser": true,
        "jquery": true,
        "es6": true
      }
    }
    
  5. The tooling is setup, let’s change app/todo.js to use ES6 imports and export a function. Let’s also re-organize the code to get rid of the $(document).ready top-level handler:

    ToDo app/todo.js
    import $ from 'jquery';
    import tmpl from './tmpl';
    
    export default function () {
    
        var newName = $('#newName'),
            todoList = $('#todoList'),
            template = tmpl('list_todos');
    
        var todos;
    
        function refreshToDos () {
            /* Fetch the list of todos and re-draw the listing */
            $.get('http://localhost:5000/api/todo', function (data) {
                todos = data['objects'];
                todoList.find('ul')
                    .replaceWith(template({todos: todos}));
            });
        }
    
        // Create a new to do
        newName.change(function () {
            var payload = JSON.stringify({name: newName.val()});
            $.post('http://localhost:5000/api/todo', payload, function () {
                refreshToDos();
                newName.val('');
            })
        });
    
        // Edit a to do
        todoList.on('click', '.edit', function () {
            // Toggle the <input> for this todo
            todoList.find('li').removeAttr('editing');
            var li = $(this).closest('li').first().attr('editing', '1');
        });
        todoList.on('change', 'input', function () {
            // When the revealed-input changes, update using PATCH
            var todoId = $(this).closest('li')[0].id,
                data = JSON.stringify({name: $(this).val()});
            $.ajax({url: 'http://localhost:5000/api/todo/' + todoId, type: 'PATCH', data: data})
                .done(function () {
                    refreshToDos();
                });
        });
    
        // Delete an existing to do
        todoList.on('click', '.delete', function () {
            var todoId = $(this).closest('li')[0].id;
            $.ajax({url: 'http://localhost:5000/api/todo/' + todoId, type: 'DELETE'})
                .done(function () {
                    refreshToDos();
                });
        });
    
        // On startup, go fetch the list of todos and re-draw
        refreshToDos();
    };
    
  6. app/tmpl.js now exports its tmpl function via an ES6 module default:

    ToDo app/tmpl.js
    // John Resig jQuery Microtemplating
    
    /*eslint-disable */
    export default function tmpl (str, data) {
        // Figure out if we're getting a template, or if we need to
        // load the template - and be sure to cache the result.
        var cache = {};
    
        var fn = !/\W/.test(str) ?
            cache[str] = cache[str] ||
                tmpl(document.getElementById(str).innerHTML) :
    
            // Generate a reusable function that will serve as a template
            // generator (and which will be cached).
            new Function("obj",
                "var p=[],print=function(){p.push.apply(p,arguments);};" +
    
                    // Introduce the data as local variables using with(){}
                "with(obj){p.push('" +
    
                    // Convert the template into pure JavaScript
                str
                    .replace(/[\r\t\n]/g, " ")
                    .split("<%").join("\t")
                    .replace(/((^|%>)[^\t]*)'/g, "$1\r")
                    .replace(/\t=(.*?)%>/g, "',$1,'")
                    .split("\t").join("');")
                    .split("%>").join("p.push('")
                    .split("\r").join("\\'")
                + "');}return p.join('');");
    
        // Provide some basic currying to the user
        return data ? fn(data) : fn;
    };
    /*eslint-enable */
    
  7. Finally, app/app.js switches to ES6 imports. Plus, it runs the function exported by todo.js. Note that we can name this function whatever we want on the import side:

    ToDo app/app.js
    import $ from 'jquery';
    import initToDo from './todo';
    
    $(document).ready(function () {
    
        // All REST requests should send content type, and log failures
        $.ajaxSetup({contentType: 'application/json'});
        $(document).ajaxError(function (event, jqxhr, settings, thrownError) {
            console.error('Ajax call failed:',
                settings.type, settings.url, thrownError);
        });
    
        initToDo();
    
    });
    
  8. Restart/reload. Webpack needs to get the new configuration changes in webpack.config.js. Restart your npm start tool window, then reload your browser.