SystemJS

The system loader mode of an application: System.register outputs loaded by SystemJS 6.15.1, which each release stores as its own loader.js, and how to load the same outputs from a page of your own.

  • Availability: Experimental
  • Evidence: Recorded run
  • How-to guide

Starting state

An application chooses one loader mode in its settings:

loader Meaning
esm (default) Native ES modules with an import map. See Native ESM and import maps.
system System.register outputs loaded by SystemJS, which the release stores as its own loader.js

The mode is read when a release is prepared. Changing it affects releases prepared afterwards; an existing release keeps the mode it was prepared with. Choose system for browsers without import maps; native ESM stays the default because it transfers fewer bytes (see the measurements).

The loader a release ships

A release prepared with loader: system stores, under its release prefix:

Document Content
index.html The shell: eager stylesheets, the import map inline as <script type="systemjs-importmap"> with release-bound addresses, <link rel="preload" as="script"> for the eager modules, then loader.js and bootstrap.js
loader.js SystemJS 6.15.1: its s.min.js build, its named-register extra and a one-line namespace extra of the CDN
bootstrap.js A classic script that calls System.import(entry) and links the lazy stylesheets once the entry has loaded

The loader is part of the release, like every other document: no page reaches a third-party origin at run time, and a release keeps the loader it was validated with after the service is upgraded.

Why this build:

  • s.js holds what the System.register outputs of Packages need: anonymous registration, setters and live bindings, export *, cycles, context.import() for a dynamic import(), context.meta.url, and import maps with scopes, inline or loaded with src.
  • The full system.js build is not used: its global-script loading would turn a script that registers nothing into an empty module instead of an error, and its CSS module type would apply a stylesheet because of an extension. A release links its stylesheets explicitly instead.
  • named-register accepts bundles that name their registrations, System.register('name', …).
  • The namespace extra gives an importer the namespace of a module without exports when it links, as native ESM does. Without it, import * as x of a side-effect module stays undefined in SystemJS.

SystemJS's own errors reach the page unchanged, for example its error #8 for a specifier outside the import map, #2 for a script that registers nothing and #7 for a module that does not answer.

The request

The identity, the path and every other option are the same as in the ESM mode. Only format changes:

Text
/m/@example/[email protected]/modules/core/router?target=browser&format=system

A published service serves it like any other prepared output: a valid request for a module whose system output was not prepared is 404 OUTPUT_NOT_AVAILABLE. A development server converts its ES module to System.register on request, updates included, so the same page works against both origins.

Loading from a page of your own

Outside the generated shell, load SystemJS from the release, then give it the release's import map with src. The map's addresses are relative to its URL, so SystemJS resolves them on the release base without your page knowing any module address:

HTMLsystem.html
<!doctype html>
<html lang="en">
	<head>
		<meta charset="utf-8" />
		<title>SystemJS from a release</title>
		<!--
			https://app.example/_r/12 is the base of one release prepared in the `system` loader mode: the
			application host followed by /_r/<release number>. Replace it with yours; it is the only value
			that changes between releases and origins.

			1. The import map of the release, loaded by SystemJS itself. Its addresses are relative to the
			   map's URL, so every module is requested from the release base.
			2. SystemJS 6.15.1 as the release stores it. Do not load another copy.
		-->
		<script type="systemjs-importmap" src="https://app.example/_r/12/importmap.json?target=browser&format=system"></script>
		<script src="https://app.example/_r/12/loader.js"></script>
	</head>
	<body>
		<pre id="output">Loading…</pre>
		<script>
			// System.import waits for the import map, then loads the module and what it imports.
			System.import('@example/shared/text').then(
				module => (document.getElementById('output').textContent = Object.keys(module).join('\n')),
				error => (document.getElementById('output').textContent = String(error))
			);
		</script>
	</body>
</html>

loader.js is a classic script, so it loads from another origin without special headers; the modules and the map are cross-origin readable. Do not add another copy of SystemJS: one page has one System registry, and the release's loader is the one its outputs were validated with.

A custom loader

A custom loader needs the same two things: the resolution document of the release, to turn a public specifier into an origin-relative URL, and a base origin to prepend. Resolution.resolve(specifier, importer) applies scopes for you. Keep the public specifiers and the module boundaries: a loader that rewrites a public import into a path to an internal file breaks the contract the modules were compiled against.

Limits

If it fails

Answer Meaning
400 OPTION_UNSUPPORTED This service does not produce format=system at all
404 OUTPUT_NOT_AVAILABLE The service can hold it, and this release was not prepared with it
SystemJS error #8 The specifier is not in the import map: a map of another release, or a module the resolution does not list