From 90b0b47a47868018699428b16711ec12d85709c6 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Mon, 26 Aug 2019 17:51:16 -0400 Subject: [PATCH 01/13] fall back to eval for application/javascript on module types --- package.json | 4 +- src/features/script-load.js | 35 ++++++++---- test/browser/core.js | 9 ++- test/fixtures/browser/importmap.json | 1 + test/fixtures/css-modules/a.css | 5 +- test/fixtures/css-modules/javascript.css | 5 ++ test/server.js | 72 ++++++++++++++++++++++++ test/test.html | 8 ++- 8 files changed, 122 insertions(+), 17 deletions(-) create mode 100644 test/fixtures/css-modules/javascript.css create mode 100644 test/server.js diff --git a/package.json b/package.json index 53613ba09..54df430e3 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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 && npm run test-browser", + "test-browser": "node test/server.js", "prepublish": "npm run build" } } diff --git a/src/features/script-load.js b/src/features/script-load.js index 3ffb5f410..bb0dea1f6 100644 --- a/src/features/script-load.js +++ b/src/features/script-load.js @@ -12,11 +12,11 @@ systemJSPrototype.register = function (deps, declare) { systemJSPrototype.instantiate = function (url, firstParentUrl) { const loader = this; if (url.slice(-5) === '.json') { - return loadDynamicModule(url, function (_export, source) { + return loadDynamicModule(function (_export, source) { _export('default', JSON.parse(source)); }); } else if (url.slice(-4) === '.css') { - return loadDynamicModule(url, function (_export, source) { + return loadDynamicModule(function (_export, source) { // Relies on a Constructable Stylesheet polyfill const stylesheet = new CSSStyleSheet(); stylesheet.replaceSync(source); @@ -41,7 +41,7 @@ systemJSPrototype.instantiate = function (url, firstParentUrl) { script.crossOrigin = 'anonymous'; script.addEventListener('error', function () { window.removeEventListener('error', windowErrorListener); - reject(Error('Error loading ' + url + (firstParentUrl ? ' from ' + firstParentUrl : ''))); + reject(loadError('')); }); script.addEventListener('load', function () { window.removeEventListener('error', windowErrorListener); @@ -59,14 +59,25 @@ systemJSPrototype.instantiate = function (url, firstParentUrl) { document.head.appendChild(script); }); } + function loadError (msg) { + return Error('Error loading ' + url + (firstParentUrl ? ' from ' + firstParentUrl : '') + msg); + } + function loadDynamicModule (createExec) { + return fetch(url).then(function (res) { + if (!res.ok) + throw loadError(', ' + res.statusText); + return res.text().then(function (source) { + 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(;|$)/)) { + (0, eval)(source); + return loader.getRegister(); + } + return [[], function (_export) { + return {execute: createExec(_export, source)}; + }]; + }); + }); + } }; -function loadDynamicModule (url, createExec) { - return fetch(url).then(function (resp) { - return resp.text(); - }).then(function (source) { - return [[], function (_export) { - return {execute: createExec(_export, source)}; - }]; - }); -} diff --git a/test/browser/core.js b/test/browser/core.js index 8534354f4..49f7de523 100644 --- a/test/browser/core.js +++ b/test/browser/core.js @@ -160,9 +160,16 @@ suite('SystemJS Standard Tests', function() { }); test('should load a css module', async function () { - const m = await System.import('./css-modules/a.css') + const m = await System.import('fixturesbase/css-modules/a.css'); assert.ok(m); assert.ok(m.default instanceof CSSStyleSheet); + document.adoptedStyleSheets = [...document.adoptedStyleSheets, m.default]; + }); + + test('should support application/javascript css module override', async function () { + const m = await System.import('fixturesbase/css-modules/javascript.css'); + assert.ok(!m); + assert.ok(m.css, 'module'); }); test('should throw when trying to load an HTML module', function () { diff --git a/test/fixtures/browser/importmap.json b/test/fixtures/browser/importmap.json index ae9ce9296..6fe9b4736 100644 --- a/test/fixtures/browser/importmap.json +++ b/test/fixtures/browser/importmap.json @@ -1,6 +1,7 @@ { "imports": { "fixtures/": "./", + "fixturesbase/": "../", "a": "/b", "a/": "./a/", "f": "a:", diff --git a/test/fixtures/css-modules/a.css b/test/fixtures/css-modules/a.css index 2b3c82b3b..f8b3ad928 100644 --- a/test/fixtures/css-modules/a.css +++ b/test/fixtures/css-modules/a.css @@ -1,3 +1,6 @@ .hello { background-color: peru; -} \ No newline at end of file +} +body { + background-color: lightblue; +} diff --git a/test/fixtures/css-modules/javascript.css b/test/fixtures/css-modules/javascript.css new file mode 100644 index 000000000..ca30fb166 --- /dev/null +++ b/test/fixtures/css-modules/javascript.css @@ -0,0 +1,5 @@ +System.register([], function (_export) { + return { execute: function () { + _export('css', 'module'); + }}; +}); diff --git a/test/server.js b/test/server.js new file mode 100644 index 000000000..1ee5e935e --- /dev/null +++ b/test/server.js @@ -0,0 +1,72 @@ +const http = require('http'); +const fs = require('fs'); +const path = require('path'); +const { once } = require('events'); +const { pathToFileURL, fileURLToPath } = require('url'); +const opn = require('opn'); + +const port = 8080; + +const systemJSURL = pathToFileURL(path.resolve(__dirname, '..') + '/'); + +const mimes = { + '.html': 'text/html', + '.css': 'text/css', + '.js': 'application/javascript', + '.mjs': 'application/javascript', + '.json': 'application/json', + '.wasm': 'application/wasm' +}; + +http.createServer(async function (req, res) { + if (req.url === '/done') { + console.log('Tests completed successfully.'); + process.exit(); + return; + } + else if (req.url === '/error') { + console.log('\033[31mTest failures found.\033[0m'); + } + + const url = new URL(req.url[0] === '/' ? req.url.slice(1) : req.url, systemJSURL); + const filePath = fileURLToPath(url); + + // redirect to test/test.html file by default + if (url.href === systemJSURL.href) { + res.writeHead(301, { + 'location': '/test/test.html' + }); + res.end(); + return; + } + + const fileStream = fs.createReadStream(filePath); + try { + await once(fileStream, 'readable'); + } + catch (e) { + if (e.code === 'EISDIR' || e.code === 'ENOENT') { + res.writeHead(404, { + 'content-type': 'text/html' + }); + res.end(`File not found.`); + } + return; + } + + let mime; + if (filePath.endsWith('javascript.css')) + mime = 'application/javascript'; + else + mime = mimes[path.extname(filePath)] || 'text/plain'; + + res.writeHead(200, { + 'content-type': mime + }); + fileStream.pipe(res); + await once(fileStream, 'end'); + res.end(); +}).listen(port); + +console.log(`Test server listening on http://localhost:${port}\n`); +opn(`http://localhost:${port}/test/test.html`); diff --git a/test/test.html b/test/test.html index 8722d2ec0..8d93aa86f 100644 --- a/test/test.html +++ b/test/test.html @@ -8,7 +8,7 @@ if (typeof fetch === 'undefined') document.write(' - + ``` -_Note that this polyfill does not currently work in IE11._ \ No newline at end of file +_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 + + +``` + +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 + ) +) +```