The goal of this article is to show you a set of patterns and best practices for modular ES6 development. You may either just want to know what the best practices are for developing an NPM module, or you may be building a large SPA that you would like to keep modular. In case you've never built a modular SPA, then it's worth stating the benefits of this approach:
- Because app components also run in isolation, they can be unit tested rather than integration tested, which has the following benefits:
- The application services your component depends on will have been de-coupled, allowing you to exhaustively given/when/then test your code, rather than happy-path testing an app connected to real back-end server(s).
- Because your component works in isolation, you can avoid slow, brittle & unreliable selenium tests — you'll be able to get by with a single integration smoke-test.
- Developer productivity is improved since the browser refresh time is much lower for a single component with mocked services than it is for loading the entire app.
- The client-side developers can create components before the server-side guys have finished creating the back-end services.
- You can have multiple globally distributed teams creating the components that will comprise your application.
- You can remove a component from your application by unlinking it from the app, knowing that no other component will directly depend on that component, and that the code for the component isn't smeared around the app code base.
The following module types will be supported
- ES5 Module
- ES6 Module
- Multi-Asset ES5 Module
- Multi-Asset ES6 Module
Having the following features:
- Should be possible for non NPM users to consume the module.
- Module should have fast reliable Node.js tests that the developer can regularly run.
- Module should have browser tests the developer can run too.
- Module should be approachable:
npm install
to install,npm test
to test &npm publish
to publish. - The test command should include a linting step so submitted patches will automatically conform to the module's code style guide, and to help catch errors.
- Multi-Asset libraries should have an
npm run workbench
command that allows the component to be worked on in isolation.
The following client app types will be supported:
- NPM Client App
- JSPM Client App
Having the following features:
- It should be easy for app developers to assemble apps using a number of internally and externally developed components.
- It should be possible for internally developed components to be hooked up directly, so that component changes are immediately visible in the app.
- There should be some browser based integration tests that verify that everything works in the complete app.
- The app should be approachable;
npm install
to install &npm test
to test. - There should be an
npm start
command that allows the app to be used during development. - There should be an
npm run build
command that allows the app to be deployed.
The ES5 module uses the following best-of-breed tools:
- NPM is used for both dependency management and as a build tool.
- browserify is used to allow the library to be used outside of Node.js, within a browser.
- eslint is used to lint the source code.
- chai is the assertion framework used for all tests.
- mocha is used to run the tests within Node.js.
- Karma is used to run the tests within the browser.
- mkdirp is used for cross-platform nested directory creation.
and is comprised of the following files and directories:
dist
: where any derived distribution packages will be placed.src
: where the source code will be placed.test
: where the tests will be placed..eslintrc
: contains the linting settings.karma.conf.js
: contains the browser testing settings.package.json
: the project build configuration.
where the package.json
file defines the following build tasks:
prepublish
: causesdist/es5-module-standalone.js
to be created when the project is either installed or published.test
: runs thetest:lint
,test:node
andtest:browser
tasks.build:module
: causesdist/es5-module-standalone.js
to be created.test:lint
: causes all code withinsrc
andtest
to be linted using the configuration within.eslintrc
.test:node
: causes all of the tests withintest
to be run on Node.js.test:browser
: causes Karma to be started using the configuration defined withinkarma.conf.js
.
Keith Cirkel's How to Use npm as a Build Tool article provides a good justification for why NPM is used as the build tool, rather than grunt or gulp, etc. Additionally, although it's theoretically possible to create reliable Selenium tests, as lots of people have found, it's really complicated and time-consuming to do it in practice, so it's actually better practice to componentize your app and then unit test the components.
In addition to what's used for the ES5 module, the ES6 module uses the following best-of-breed tools:
- Babel is used to convert the ES6 source code to ES5 so that other NPM users can still consume the module.
- babelify is used to make Babel available within browserify, so that the library can continue to be used outside of Node.js.
where the package.json
file defines the same tasks as the ES5 module, plus these addtional or modified tasks:
prepublish
: does the same as for the ES5 module, but also runs thebuild:es6
task.build:es6
: causes all the source code to be separately transpiled so that other NPM modules can depend on ES5 code.
There are a couple of noteworthy changes in package.json
when compared to the ES5 module. Firstly, the main
property now points to the transpiled ES5 code:
"main": "dist/module.js",
and secondly, we use a jspm
section to re-point main
back to the original ES6 source code, so that JSPM users can transpile the ES6 source code themselves:
"jspm": {
"main": "src/module.js"
},
This is slightly beneficial since ES6 modules have live-bindings, so that when a module updates an exported value that update is immediately available within dependent modules. Additionally, in the future it's likely that people will stop transpiling ES6 code when running tests on native ES6 platforms, since un-transpiled code is easier to debug. Here again, by having access to the native ES6 source code, JSPM users will more quickly have the flexibility to consider doing that.
Although the Multi-Asset ES5 Module is working, it currently relies on JSPM which precludes these modules from being consumed by the NPM Client App. I'm currently investigating a tool to make it possible to create this module type using NPM alone.
Although the Multi-Asset ES5 Module is working, it currently relies on JSPM which precludes these modules from being consumed by the NPM Client App. I'm currently investigating a tool to make it possible to create this module type using NPM alone.
To be written once this app type is capable of consuming multi-asset modules.
To be written once this app type has been created.