Modern tooling 2016-11-27

The Stet.io editor is written in Javascript with the pages are served from a Python backend. Python is also my language of choice which has lead to the tooling around the Javascript editor being written in Python. This is not an ideal solution, and following advances in Javascript tooling it is time to upgrade.

The old system was based on Python {Paver} and Javascript {Uglify-JS, Uglify-CSS, JSHint} as in the following snippet. Where files is a list of the .js files to build and lint,

  
@task
def lint():
    sh('jshint ' + ' '.join(files))


@task
def build_css(files, target, options=[]):
    sh("uglifycss %s > %s" % (' '.join(files), target))


@task
def build_js(files, target, options=[]):
    sh("uglifyjs %s -o %s" % (' '.join(files), target))
  

This system has the downsides of requiring global npm installs that were not version controlled alongside mixing the tooling and development langauges. Additionally the test setup, using mocha, was difficult to use (not command line based) and therefor poorly maintained.

I'm choosing to modernize by using NPM and ES6, i.e. fully Javascript tooling. To begin with it seems setting up the package.json is key, ignoring some details I've gone with,

  
{
  "scripts": {
    "build": "webpack --config webpack.config.js",
    "lint": "eslint src/**/*.js test/**/*.js",
    "test": "mocha --compilers js:babel-core/register test/**/*.spec.js"
  },
  "private": true,
  "devDependencies": {
    "babel-core": "^6.18.2",
    "babel-loader": "^6.2.8",
    "babel-preset-es2015": "^6.18.0",
    "chai": "^3.5.0",
    "eslint": "^3.10.2",
    "mocha": "^3.2.0",
    "webpack": "^1.13.3"
  },
  "babel": {
    "presets": [
      "es2015"
    ]
  }
}
  

Structured such that the source and test code are in the src and test sub directories respectively. I'm also following the convention that the test code mirrors the source code structure and filenames, only with the .spec.js suffix. Note that I've also switched from JSHint to ESLint for my linting needs, as the latter seems to support ES6 better.

The build system uses webpack, as configured below, note the heavy use of babel to avoid ES6 compatibility issues. Ideally I'd not bother with this, but UglifyJS and Mocha both struggle with the syntax mandating its use.

  
module.exports = {
  entry: "./src/index.js",
  filename: "build/filters.min.js";
  module: {
    loaders: [{
      test: /\.js?$/,
      loader: 'babel-loader',
      query: {
        presets: ['es2015']
      }
    }]
  },
  output: {
    filename: "filters.js"
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin({minimize: true})
  ]
}
  

The very last piece of config is for ESLint, which I've setup as below. The unused args pattern is particularly useful for my Python background as it allows `_` prefix args to be ignored. The only other key is the sourceType setting in order to use ES6 module import syntax (although I've not used it in the webpack setup).

  
{
  "env": {
    "browser": true,
    "es6": true
  },
  "extends": "eslint:recommended",
  "parserOptions": {
    "sourceType": "module"
  },
  "rules": {
    "indent": [
      "error",
      2
    ],
    "linebreak-style": [
      "error",
      "unix"
    ],
    "no-unused-vars": [
      "error",
      { "argsIgnorePattern": "^_" }
    ],
    "quotes": [
      "error",
      "double"
    ],
    "semi": [
      "error",
      "always"
    ]
  }
}
  

Now instead of paver build, paver lint I have npm run build, npm run lint, npm run test which I consider to be an improvement. Aside from the improvments to development this has allowed much improved test coverage and a host of bug fixes that go live today with 0.7.2.

Back to blog