Skip to content

Commit

Permalink
Module Types Extra (#2006)
Browse files Browse the repository at this point in the history
  • Loading branch information
guybedford committed Aug 27, 2019
1 parent 79cb488 commit 36854be
Show file tree
Hide file tree
Showing 14 changed files with 315 additions and 142 deletions.
22 changes: 14 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ _[For the previous release see the SystemJS 0.21.x branch](https://github.com/sy

_SystemJS is [currently sponsored by Canopy Tax](https://canopytax.github.io/post/systemjs-sponsorship/?utm_source=systemjs)._

For discussion, join the [Gitter Room](https://gitter.im/systemjs/systemjs).

## Overview

SystemJS provides two hookable base builds:

#### 1. s.js minimal loader
Expand All @@ -21,33 +25,35 @@ The minimal [1.5KB s.js loader](dist/s.min.js) provides a workflow where code wr
Since the ES module semantics such as live bindings, circular references, contextual metadata, dynamic import and top-level await [can all be fully supported this way](docs/system-register.md#semantics), while supporting CSP and cross-origin support, this workflow can be relied upon as a polyfill-like path.

* Loads and resolves modules as URLs, throwing for bare specifier names (eg `import 'lodash'`) like the native module loader.
* Loads System.register, JSON, and CSS modules.
* Loads System.register modules.
* Core hookable extensible loader supporting [custom extensions](docs/hooks.md).

#### 2. system.js loader

The [3KB system.js loader](dist/system.min.js) loader builds on the s.js core and adds support for upcoming module specifications (currently [import maps](https://github.com/domenic/import-maps) and [WASM integration](https://github.com/WebAssembly/esm-integration) with module loading) as well as development and convenience features.
The [3KB system.js loader](dist/system.min.js) loader builds on the s.js core and adds support for upcoming module specifications (currently [import maps](https://github.com/domenic/import-maps) and [Wasm integration](https://github.com/WebAssembly/esm-integration) with module loading) as well as development and convenience features.

* Support for loading [bare specifier names](docs/import-maps.md) through import maps (formerly package maps, formerly map configuration), loaded via `<script type="system-importmap">` (requires a `fetch` polyfill for eg IE11).
* Includes the [global loading extra](#extras) for loading global scripts, useful for loading library dependencies traditionally loaded with script tags.
* [Tracing hooks](docs/hooks.md#trace-hooks) and [registry deletion API](docs/api.md#registry) for reloading workflows
* Supports loading WASM based on the `.wasm` file extension
* [Tracing hooks](docs/hooks.md#trace-hooks) and [registry deletion API](docs/api.md#registry) for reloading workflows.
* Supports loading Wasm, CSS and JSON [module types](docs/module-types.md).

#### Extras

The following [pluggable extras](dist/extras) are provided which can be dropped in with either the s.js or system.js loader:
The following [pluggable extras](dist/extras) can be dropped in with either the s.js or system.js loader:

* [AMD loading](dist/extras/amd.js) support (through `Window.define` which is created).
* [Global loading](dist/extras/global.js) support for loading global scripts and detecting the defined global as the default export. Useful for loading common library scripts from CDN like `System.import('//unpkg.com/lodash')`. _(Already included in the system.js loader build)_.
* [Named exports](dist/extras/named-exports.js) convenience extension support for global and AMD module formats (`import { x } from './global.js'` instead of `import G from './global.js'; G.x`)
* [Named register](dist/extras/named-register.js) supports `System.register('name', ...)` named bundles which can then be imported as `System.import('name')` (as well as AMD named define support)
* [Transform loader](dist/extras/transform.js) support, using fetch and eval, supporting a hookable `loader.transform`
* [Use default](dist/extras/use-default.js) provides a convenience interop for AMD modules to return the direct AMD
binding instead of a `{ default: amdModule }` object from `System.import`.

Since all loader features are hookable, custom extensions can be easily made following the same approach as the bundled extras. See the [hooks documentation](docs/hooks.md) for more information.
The following extras are included in system.js loader by default, and can be added to the s.js loader for a smaller tailored footprint:

For discussion, join the [Gitter Room](https://gitter.im/systemjs/systemjs).
* [Global loading](dist/extras/global.js) support for loading global scripts and detecting the defined global as the default export. Useful for loading common library scripts from CDN like `System.import('//unpkg.com/lodash')`.
* [Module Types](dist/extras/module-types.js) `.css`, `.wasm`, `.json` [module type](docs/module-types.md) loading support in line with the existing modules specifications.

Since all loader features are hookable, custom extensions can be easily made following the same approach as the bundled extras. See the [hooks documentation](docs/hooks.md) for more information.

## Installation

Expand Down
65 changes: 53 additions & 12 deletions docs/module-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ SystemJS supports loading modules that are in the following formats:
| Module Format | s.js | system.js | File Extension |
| ------------- | ---- | --------- | -------------- |
| [System.register](/docs/system-register.md) | :heavy_check_mark: | :heavy_check_mark: | * |
| [JSON Modules](https://github.com/whatwg/html/pull/4407) | :heavy_check_mark: | :heavy_check_mark: | *.json |
| [CSS Modules](https://github.com/w3c/webcomponents/blob/gh-pages/proposals/css-modules-v1-explainer.md) | :heavy_check_mark: | :heavy_check_mark: | *.css |
| [Web Assembly](https://github.com/WebAssembly/esm-integration/tree/master/proposals/esm-integration) | :x: | :heavy_check_mark: | *.wasm |
| Global variable | [global extra](/README.md#extras) | :heavy_check_mark: | * |
| [AMD](https://github.com/amdjs/amdjs-api/wiki/AMD) | [AMD extra](/README.md#extras) | [AMD extra](/README.md#extras) | * |
| [UMD](https://github.com/umdjs/umd) | [AMD extra](/README.md#extras) | [AMD extra](/README.md#extras) | * |
| [JSON Modules](https://github.com/whatwg/html/pull/4407) | [Module Types extra](/dist/extras/module-types.js) | :heavy_check_mark: | *.json |
| [CSS Modules](https://github.com/w3c/webcomponents/blob/gh-pages/proposals/css-modules-v1-explainer.md) | [Module Types extra](/dist/extras/module-types.js) | :heavy_check_mark: | *.css |
| [Web Assembly](https://github.com/WebAssembly/esm-integration/tree/master/proposals/esm-integration) | [Module Types extra](/dist/extras/module-types.js) | :heavy_check_mark: | *.wasm |
| Global variable | [global extra](/dist/extras/global.js) | :heavy_check_mark: | * |
| [AMD](https://github.com/amdjs/amdjs-api/wiki/AMD) | [AMD extra](/dist/extras/amd.js) | [AMD extra](/dist/extras/amd.js) | * |
| [UMD](https://github.com/umdjs/umd) | [AMD extra](/dist/extras/amd.js) | [AMD extra](/dist/extras/amd.js) | * |

### File Extension Limitations

Expand All @@ -20,7 +20,7 @@ Instead of reading the MIME type, the file extension is thus used specifically f

## JSON Modules

[JSON modules](https://github.com/whatwg/html/pull/4407) are supported in both s.js and system.js.
[JSON modules](https://github.com/whatwg/html/pull/4407) support importing a JSON file as the default export.

### Example

Expand All @@ -39,11 +39,9 @@ System.import('file.json').then(function (module) {

## CSS Modules

[CSS Modules](https://github.com/w3c/webcomponents/blob/gh-pages/proposals/css-modules-v1-explainer.md) are supported in both
s.js and system.js, [when a Constructable Style Sheets polyfill is present for browsers other than Chromium](#constructed-style-sheets-polyfill).
[CSS Modules](https://github.com/w3c/webcomponents/blob/gh-pages/proposals/css-modules-v1-explainer.md) are supported [when a Constructable Style Sheets polyfill is present for browsers other than Chromium](#constructed-style-sheets-polyfill).

Note that the term CSS Modules refers to two separate things: (1) the browser spec, or (2) the webpack / postcss plugin.
The CSS modules implemented by SystemJS are the browser spec.
Note that the term CSS Modules refers to two separate things: (1) the browser spec, or (2) the Webpack / PostCSS plugin. The CSS modules implemented by SystemJS are the browser spec.

### Example
```css
Expand Down Expand Up @@ -73,4 +71,47 @@ The polyfill can be conditionally loaded with an approach like:
<script defer src="https://unpkg.com/construct-style-sheets-polyfill@2.1.0/adoptedStyleSheets.min.js"></script>
```

_Note that this polyfill does not currently work in IE11._
_Note that this polyfill does not currently work in IE11._

## Web Assembly Modules

[Web Assembly Modules](https://github.com/WebAssembly/esm-integration/tree/master/proposals/esm-integration) support importing Web Assembly with Web Assembly in turn supporting other modules.

### Example

```html
<script type="systemjs-importmap">
{
"imports": {
"example": "./wasm-dependency.js"
}
}
</script>
<script>
System.import('/wasm-module.wasm').then(function (m) {
// calls wasm-dependency square function through Wasm
m.exampleExport(5); // 25
});
</script>
```

wasm-dependency.js
```js
// function called from Wasm
export function exampleImport (num) {
return num * num;
}
```

where `wasm-module.wasm` is generated from:

**wasm-module.wat**
```wat
(module
(func $exampleImport (import "example" "exampleImport") (param i32) (result i32))
(func $exampleExport (export "exampleExport") (param $value i32) (result i32)
get_local $value
call $exampleImport
)
)
```
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"construct-style-sheets-polyfill": "^2.1.0",
"esm": "^3.2.25",
"mocha": "^5.2.0",
"opn": "^6.0.0",
"rollup": "^0.64.1",
"rollup-plugin-replace": "^2.0.0",
"terser": "^3.8.1",
Expand All @@ -33,7 +34,8 @@
"footprint": "npm run footprint:systemjs && npm run footprint:sjs",
"footprint:systemjs": "terser dist/system.js -c passes=2 -m | gzip -9f | wc -c",
"footprint:sjs": "terser dist/s.js -c passes=2 -m | gzip -9f | wc -c",
"test": "mocha -b -r esm",
"test": "mocha -b -r esm test/import-map.js test/system-core.js test/url-resolution.js && npm run test-browser",
"test-browser": "node test/server.js",
"prepublish": "npm run build"
}
}
92 changes: 92 additions & 0 deletions src/extras/module-types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Loads JSON, CSS, Wasm module types based on file extensions
* Supports application/javascript falling back to JS eval
*/
import { systemJSPrototype } from '../system-core';
const instantiate = systemJSPrototype.instantiate;
systemJSPrototype.instantiate = function (url, parent) {
const loader = this;
const ext = url.slice(url.lastIndexOf('.'));
switch (ext) {
case '.css':
return loadDynamicModule(function (_export, source) {
// Relies on a Constructable Stylesheet polyfill
const stylesheet = new CSSStyleSheet();
stylesheet.replaceSync(source);
_export('default', stylesheet);
});
case '.html':
return getSourceRes().then(function (res) {
return maybeJSFallback(res) || loadError("'.html' modules not implemented");
});
case '.json':
return loadDynamicModule(function (_export, source) {
_export('default', JSON.parse(source));
});
case '.wasm':
return getSourceRes().then(function (res) {
return maybeJSFallback(res) ||
(WebAssembly.compileStreaming ? WebAssembly.compileStreaming(res) : res.arrayBuffer().then(WebAssembly.compile));
})
.then(function (module) {
const deps = [];
const setters = [];
const importObj = {};

// we can only set imports if supported (eg early Safari doesnt support)
if (WebAssembly.Module.imports)
WebAssembly.Module.imports(module).forEach(function (impt) {
const key = impt.module;
if (deps.indexOf(key) === -1) {
deps.push(key);
setters.push(function (m) {
importObj[key] = m;
});
}
});

return [deps, function (_export) {
return {
setters: setters,
execute: function () {
return WebAssembly.instantiate(module, importObj)
.then(function (instance) {
_export(instance.exports);
});
}
};
}];
});
}
return instantiate.apply(this, arguments);

function getSourceRes () {
return fetch(url).then(function (res) {
if (!res.ok)
loadError(res.status + ' ' + res.statusText);
return res;
});
}
function maybeJSFallback (res) {
const contentType = res.headers.get('content-type');
// if the resource is sent as application/javascript, support eval-based execution
if (contentType && contentType.match(/^application\/javascript(;|$)/)) {
return res.text().then(function (source) {
(0, eval)(source);
return loader.getRegister();
});
}
}
function loadDynamicModule (createExec) {
return getSourceRes().then(function (res) {
return maybeJSFallback(res) || res.text().then(function (source) {
return [[], function (_export) {
return { execute: createExec(_export, source) };
}];
});
});
}
function loadError (msg) {
throw Error(msg + ', loading ' + url + (parent ? ' from ' + parent : ''));
}
};
82 changes: 29 additions & 53 deletions src/features/script-load.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,62 +11,38 @@ systemJSPrototype.register = function (deps, declare) {

systemJSPrototype.instantiate = function (url, firstParentUrl) {
const loader = this;
if (url.slice(-5) === '.json') {
return loadDynamicModule(url, function (_export, source) {
_export('default', JSON.parse(source));
});
} else if (url.slice(-4) === '.css') {
return loadDynamicModule(url, function (_export, source) {
// Relies on a Constructable Stylesheet polyfill
const stylesheet = new CSSStyleSheet();
stylesheet.replaceSync(source);
_export('default', stylesheet);
});
} else if (url.slice(-5) === '.html') {
return Promise.reject(Error("Error loading " + url + ". '.html' modules not implemented."));
} else {
return new Promise(function (resolve, reject) {
let err;
return new Promise(function (resolve, reject) {
let err;

function windowErrorListener(evt) {
if (evt.filename === url)
err = evt.error;
}
function windowErrorListener(evt) {
if (evt.filename === url)
err = evt.error;
}

window.addEventListener('error', windowErrorListener);
window.addEventListener('error', windowErrorListener);

const script = document.createElement('script');
script.charset = 'utf-8';
script.async = true;
script.crossOrigin = 'anonymous';
script.addEventListener('error', function () {
window.removeEventListener('error', windowErrorListener);
reject(Error('Error loading ' + url + (firstParentUrl ? ' from ' + firstParentUrl : '')));
});
script.addEventListener('load', function () {
window.removeEventListener('error', windowErrorListener);
document.head.removeChild(script);
// Note that if an error occurs that isn't caught by this if statement,
// that getRegister will return null and a "did not instantiate" error will be thrown.
if (err) {
reject(err);
}
else {
resolve(loader.getRegister());
}
});
script.src = url;
document.head.appendChild(script);
const script = document.createElement('script');
script.charset = 'utf-8';
script.async = true;
script.crossOrigin = 'anonymous';
script.addEventListener('error', function () {
window.removeEventListener('error', windowErrorListener);
reject(Error('Error loading ' + url + (firstParentUrl ? ' from ' + firstParentUrl : '')));
});
script.addEventListener('load', function () {
window.removeEventListener('error', windowErrorListener);
document.head.removeChild(script);
// Note that if an error occurs that isn't caught by this if statement,
// that getRegister will return null and a "did not instantiate" error will be thrown.
if (err) {
reject(err);
}
else {
resolve(loader.getRegister());
}
});
}
script.src = url;
document.head.appendChild(script);
});
};

function loadDynamicModule (url, createExec) {
return fetch(url).then(function (resp) {
return resp.text();
}).then(function (source) {
return [[], function (_export) {
return {execute: createExec(_export, source)};
}];
});
}

0 comments on commit 36854be

Please sign in to comment.