SystemJS 0.20 has just been released, a full rewrite and specification correction to the project, while ES modules are finally here, in browsers.
SystemJS was originally developed back in 2013 for jspm, at a time when RequireJS was the predominant module loader. ES6 was coming together at a rapid pace, and ES6 modules still seemed an intangible dream. The idea was simple and compelling - modules were coming to the browser, so we should be able to load any module at any time from the browser making for a very easy development workflow.
In the face of ES modules, it had become clear that RequireJS wasn't going to stand the test of time. By building a loader on top of the early ES module loader specs, it would be possible to help provide tangible spec feedback on modules while also gaining the benefit of a stable loader to work with.
Almost 4 years later and the project is seeing just under half a million downloads on npm each month.
It was always a given that following an unstable specification would mean breaking changes for SystemJS, but the goal of the project has been to track these changes. Since specifications in general move slowly, the assumption was that major breaks would actually be less frequent than most other projects.
That has mostly that has been the case, with SystemJS 0.20 representing one such spec adjustment release.
This post summarises some of the specification changes that have happened, what we might expect from future browser modules specifications, and where SystemJS sits in all of that.
#Specification Alignment
The path is finally clear
for <script type="module">
and dynamic import()
syntax,
thanks to the hard work of specification authors.
SystemJS has always presented itself as following the WhatWG loader specification, which
has actually been on hold for some time. What we've seen with the <script type="module">
and dynamic import()
module specs is the fruits of what was originally this WhatWG loader work
but coming into form through smaller phased specifications.
These features have always been on the WhatWG loader roadmap,
and represent the gradual specification of loader features - a train model like the ES7+ specifications, over the
monolithic ES6 specification model.
The individual specs that we've seen so far include:
- Module Scripts: Specified in WhatWG HTML spec, implemented in Safari Preview and Edge Insider.
- Module Workers: Specified in WhatWG HTML spec, being implemented currently.
- Dynamic Import Syntax: Stage 3 TC39 specification, being implemented currently.
Other features from the WhatWG loader spec or discussed as part of it that have not yet been specified but that could be in future include:
- Import metadata: The ability to get the current module path like
__filename
in NodeJS. - Resolver hooks: The ability to hook into the browser resolver.
Currently
import 'lodash'
will fail for script modules according to the WhatWG Module Script resolution. A resolve hook can allow the ability to tell the browserresolve('lodash') -> 'https://cdn.com/lodash/4.17.4/lodash.js'
. One huge benefit of this is caching - the localapp.js
importing lodash can be cached, regardless of updates to lodash. - Registry API: Exposes the ability to inspect and modify the registry map of URLs to module namespaces. Allows dynamically creating module namespaces setting them into the registry. The registry API can also enable hot reloading workflows if dependency metadata is exposed.
- Instantiate hook: This is a hook that makes it possible to hook into the loading process itself and set what module should be returned for a given URL. It also enables legacy module support in the loader - the ability to load AMD or CommonJS modules.
What is missing from the above features is the loader hooks - "normalize, locate, fetch, translate, instantiate". It has become clear that this original canon won't be specified or implemented anymore.
SystemJS followed this path very closely, so removing these represents one of the breaks of the project - it is no
longer possible to hook into System.fetch
or System.translate
. Rather System[System.constructor.resolve]
and
System.instantiate[System.constructor.instantiate]
have become the new only two hooks.
In short, SystemJS 0.20 is built to the simplified assumptions of a resolver hook and registry API in browsers, based on the specifications and proposals of the above listed future features.
For exact details of these APIs see the module loader project at https://github.com/ModuleLoader/es-module-loader, which closely details all these specification decisions and tradeoffs.
#NodeJS Modules Compatibility
NodeJS side has made significant progress in working out its adoption path for modules. The SystemJS module format interop is largely aligned with these directions, but there is one adjustment here which forms one of the bigger breaking changes of the release.
This change is that named exports will no longer be permitted when importing a CommonJS module from an ES module, and is discussed at https://github.com/nodejs/CTC/pull/60/files#diff-2b572743d67d8a47685ae4bcb9bec651R217.
That is, import { name } from 'cjs.js'
, where cjs.js
is a CommonJS module will no longer be supported, and
instead will require import cjs from 'cjs.js'; cjs.name
. This will be a tough break affecting NodeJS and Babel users
in many places, so SystemJS 0.20 is taking that hit as of right now.
We will continue to support the __esModule
flag in interop though, allowing lifting of named exports for these cases.
So if the cjs.js
module was written:
exports.__esModule = true;
exports.name = function () { ... }
then it would be possible to have import { name } from 'cjs.js';
, even though cjs.js
is a CommonJS module,
although this __esModule
will eventually in the longer term be deprecated as well.
#Production Build
Taking the reduction of the project a step further, a new production build of SystemJS has been designed for optimal loading and performance in production.
The development loader has to deal with the problem that when loading an unknown module there is a configuration problem - how do you get the configuration for a package you have not yet loaded? And so SystemJS got a full-featured package configuration system, including being able to dynamically load package configuration files in the browser as part of its resolution just like package.json files are loaded when in NodeJS.
Removing all these development conveniences reduces the production build down to just 5KB. This provides loading for System.register and System.registerDynamic modules, with just baseURL, paths, map, contextual map, bundles and depCache support.
#Isomorphic Browser Modules - a polyfill-like workflow
SystemJS aims to enable polyfill workflows for modules as they become supported in browsers, while also continuing to provide the core features of map, contextual map and depCache support.
For example, you may want to provide ES modules to browsers with ES module support, while reverting to a SystemJS production loader workflow in older browsers.
I refer to this approach as isomorphic browser modules because by using System.register as a module format, the polyfill version can maintain exact execution semantics as ES module loading in supported browsers.
A demo workflow here based on the SystemJS 0.20 production build has been put together at https://github.com/guybedford/isomorphic-browser-modules demonstrating this mechanism of loading ES modules in the Safari Technology Preview and falling back to SystemJS when native modules are not supported.
#Improving modular workflows
As SystemJS continues down this path of optimizing production loading and aligning with modules in browsers, the workflows around native modules will hopefully also continue to develop. One of the weak points of SystemJS at the moment is the lack of tooling around the project from the development server to production optimization.
There's a lot of interesting work to still be done in this area, and always space for new ideas and contributions to the tooling ecosystem as well.
For some updated thoughts on modular production tooling, see my latest conference talk from DotJS where I discuss build optimization techniques for browsers supporting ES modules.
Thanks to everyone who has supported and contributed to SystemJS. If you would like to contribute in any way,
there is always space for contributors, and donations are also welcome.