The jspm 0.16 release is finally out of beta, so I thought it would be worth summarizing some of the architectural shifts that have been happening in jspm and SystemJS and their motivations.

The primary development focus for these projects is towards architectural stability, and that means ensuring that we're on a trajectory to align with the loader specification work as smoothly as possible in future.

Upgrading to a maturing SystemJS API foundation is the most important part of that, by going through a minimal number of breaking API changes. This is one of those changes. The next one will likely be when we upgrade to the WhatWG loader specification when that is ready, which still looks like it is a number of months away.

#SystemJS Upgrade

The 0.17 and 0.18 (a correction) releases introduced a number of API changes including a bunch of breaking changes and features together, while retaining as much backwards-compatibility as possible.

Already released over two months ago, this is an important upgrade and makes the project mostly feature-complete at this point.

The main forces behind these API changes were from the following arguments made at the spec level:

  • It was argued that URLs should be supported as module names since having a different naming system for modules that was incompatible with URLs was deemed inappropriate for the web loader standard. Up until now, importing a URL inside a module has not been supported at all in SystemJS, so a normalization rewrite was necessary to make this possible.
  • Having module names as URLs then means allowing including the .js file extension when requiring a module. That is, we should be able to write import './module.js'. Automatic extension adding can still be possible with custom implementations, but the default browser loader spec will likely not provide this.
  • The in-progress WhatWG loader spec doesn't currently include a locate hook. This SystemJS release makes the first steps towards deprecating the locate hook entirely. There is still some debate over whether this hook should be included, as it would enable custom abstract names as the keys in the loader registry instead of URLs. The problems then become issues of interop between custom naming systems and URLs in determining unique module keys. For more information on this, read the discussion at the spec issue.

The route that SystemJS has taken in adapting to these changes is roughly in line with how the browser loader spec will likely handle this:

  • Every module corresponds to a fully-normalized absolute URL in the module registry.
  • Module specifiers are split into two classes - URL-like names (relative or absolute - ./module.js, /module.js or http://module.com/module.js), and plain names (jquery, babel-helpers/helpers/extends.js).
  • When normalizing module specifiers, URL-like names are normalized to the parent URL with standard URL normalization, while plain names go through the loader map, paths and baseURL rules.

The above works out because we can take advantage of the fact that ./rel and rel are equivalent relative specifiers in URLs, so that the module system can provide its custom behaviours for the plain name rel, while retaining full URL-style normalization compatibility as a subset with ./rel.

#Breaking Changes

The following breaking changes in SystemJS are then a direct result of the above:

  • Automatic .js extensions are now provided by a compatibility mode, System.defaultJSExtensions = true, which defaults to false.
  • Setting .js extensions via paths rules like paths['*'] = '*.js' is no longer supported as it won't apply to relative module specifiers.
  • All configuration must be provided via System.config({ map: { a: 'b' } }) instead of System.map['a'] = 'b' because we now normalize configurations into URL-space within the System.config call.
  • Since we normalize configurations the baseURL needs to be provided along with the first System.config({...}) call.
  • System.import('./x') now imports URL-relative to the current page / environment baseURI.

#Features

Along with this break, there was the opportunity to also include a number of long-awaited API changes.

These provide some more flexible and powerful configuration options with greater config modularity through package configuration , to demonstrate by example what is possible:


System.config({
  baseURL: '/lib',

  map: {
    jquery: '//code.jquery.com/jquery-2.1.4.min.js',
  },

  packages: {
    // System.import("app") -> /lib/app/index.ts
    'app': {
      main: 'index.ts',
      // overrides the defaultJSExtensions compatibility mode
      defaultExtension: 'ts',
      meta: {
        '*.ts': {
          // run all ts files through a typescript plugin
          loader: 'typescript'
        },
      },
      map: {
        // at /lib/plugins/typescript-plugin.js
        typescript: 'plugins/typescript-plugin.js'
      }
    },

    // System.import("bootstrap") -> /lib/bootstrap/js/bootstrap.js
    'bootstrap': {
      main: 'js/bootstrap.js',
      format: 'global'
      meta: {
        'js/bootstrap.js': {
          deps: ['jquery'],
          exports: '$'
        }
      }
    }
  }
});

The above configuration allows a package at System.import('app') to load the main entry point /lib/app/index.js and to load and transpile that module and its dependencies with a TypeScript plugin (for example). We can also import other modules within the package via System.import('app/feature.ts'). Then the package at System.import('bootstrap') will load Bootstrap, returning the jQuery instance and ensuring jQuery is loaded from CDN first.

It is now advisable to use the module extension when importing any module that is not a package. For example import 'bootstrap/js/bootstrap.js' or import './dep.js' should include the extension as we are referencing a module. On the other hand import 'bootstrap' doesn't need to because we are importing the main package by name, which will be turned into bootstrap/js/bootstrap.js by the main entry point package configuration option.

The full list of changes can be found at the detailed release notes.
See also the SystemJS API docs.

#jspm Upgrade

If you haven't already been using the beta release, upgrading to jspm 0.16 involves just running jspm init to upgrade the configuration file to the new SystemJS config.

In addition if running a custom build of jspm with SystemJS builder via require('systemjs-builder'), it is advisable to update this to use require('jspm').Builder which will ensure the baseURL is set correctly for building. See the jspm build api docs for more info.

The full list of new features can be found at the 0.16 release notes.

The jspm upgrade path to this new SystemJS 0.18 is a two-step process:

  1. The current jspm 0.16 comes with the upgrades to SystemJS 0.18 and SystemJS Builder 0.13 with the defaultJSExtensions compatibility mode enabled.
  2. jspm 0.17 coming next will then include the changes necessary to disable the defaultJSExtensions compatibility mode, along with taking full advantage of the new SystemJS API features.

#jspm 0.17 Roadmap

While jspm 0.17 is some way away, there are going to be some exciting changes coming:

  • The jspm package.json configuration will be designed to exactly mirror the SystemJS packages configuration. This will allow fully-modular sharing of SystemJS config including transpilation options, to install and build third-party packages from their original sources (optionally when this is beneficial).
  • jspm will no longer do any rewriting of package files on download, and will use SystemJS configuration instead to handle converting npm packages. This will then enable buildless linking workflows just using symlinks.
  • Unfortunately the above changes form a breaking change against the jspm CDN. So the corresponding CDN upgrade will be moving to a new home - jsDelivr have kindly offered their CDN for jspm use. This will provide much-needed speed, latency and reliability improvements for this service. This CDN upgrade is also one of the reasons why the jspm 0.17 release will take some time to land.

Thanks everyone for all your support of the project to date.