From e8c040d5f827f8064bcfb0ff1e7cd83393d8c03a Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Sun, 20 Oct 2019 01:57:46 -0400 Subject: [PATCH 1/2] implement fetch hook --- README.md | 32 ++++++++++++++++++++++++++++++++ src/es-module-shims.js | 3 ++- test/fetch-hook.js | 30 ++++++++++++++++++++++++++++++ test/fixtures/json-or-js.js | 1 + test/test.html | 4 ++-- 5 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 test/fetch-hook.js create mode 100644 test/fixtures/json-or-js.js diff --git a/README.md b/README.md index e850f361..d5b7cfa8 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ This includes support for: * Importing JSON * Importing Web Assembly (note, there is an [open issue on how to handle the 4KB imposed limit](https://github.com/guybedford/es-module-shims/issues/1)) +In addition a custom [fetch hook](#fetch-hook) can be implemented allowing for streaming in-browser transform workflows. + Because we are still using the native module loader the edge cases work out comprehensively, including: * Live bindings in ES modules @@ -158,6 +160,36 @@ This matches the specification for ES module workers, supporting all features of > Module workers are only supported in browsers that provide dynamic import in worker environments, which is only Chrome currently. +### Fetch Hook + +The ES Module Shims fetch hook can be used to implement transform plugins. + +For example: + +```js +importShim.fetch = async function (url) { + const response = await fetch(url); + if (response.url.endsWith('.ts')) { + const source = await response.body(); + const transformed = tsCompile(source); + return new Response(URL.createObjectURL(new Blob([transformed], { type: 'application/javascript' })), { status: 200 }); + } + return response; +}; +``` + +Because the dependency analysis applies by ES Module Shims takes care of ensuring all dependencies run through the same fetch hook, +the above is all that is needed to implement custom plugins. + +Streaming support can be handled through the above as well, although most compilers likely want synchronous sources as in the above. + +#### Plugins + +Since the Fetch Hook is very new, there are no plugin examples of it yet, but it should be easy to support various workflows +such as TypeScript and new JS features this way. + +If you work on something here please do share to link to from here. + ## Implementation Details ### Import Rewriting diff --git a/src/es-module-shims.js b/src/es-module-shims.js index 35e569e7..f709f6b4 100755 --- a/src/es-module-shims.js +++ b/src/es-module-shims.js @@ -45,6 +45,7 @@ Object.defineProperties(importShim, { l: { value: undefined, writable: true }, e: { value: undefined, writable: true } }); +importShim.fetch = url => fetch(url); let lastLoad; function resolveDeps (load, seen) { @@ -161,7 +162,7 @@ function getOrCreateLoad (url, source) { load.f = (async () => { if (!source) { - const res = await fetch(url); + const res = await importShim.fetch(url); if (!res.ok) throw new Error(`${res.status} ${res.statusText} ${res.url}`); diff --git a/test/fetch-hook.js b/test/fetch-hook.js new file mode 100644 index 00000000..74911c38 --- /dev/null +++ b/test/fetch-hook.js @@ -0,0 +1,30 @@ + +suite('Fetch hook', () => { + importShim.fetch = async function (url) { + if (!url.endsWith('json-or-js.js')) + return fetch(url); + const response = await fetch(url); + const reader = response.body.getReader(); + console.log('--'); + return new Response(new ReadableStream({ + async start (controller) { + let done, value; + while (({ done, value } = await reader.read()) && !done) { + controller.enqueue(value); + } + controller.close(); + } + }), { + status: 200, + statusText: 'CRAP', + headers: { + "Content-Type": "application/json" + } + }); + } + test('Should hook fetch', async function () { + var m = await importShim('./fixtures/json-or-js.js'); + assert(m.default); + assert.equal(m.default.a, 'b'); + }); +}); diff --git a/test/fixtures/json-or-js.js b/test/fixtures/json-or-js.js new file mode 100644 index 00000000..b6f13e15 --- /dev/null +++ b/test/fixtures/json-or-js.js @@ -0,0 +1 @@ +{ "a": "b" } diff --git a/test/test.html b/test/test.html index 1ecf130b..fd104d13 100755 --- a/test/test.html +++ b/test/test.html @@ -45,9 +45,9 @@ throw new Error(msg); }; - const suites = ['browser-modules']; + const suites = ['browser-modules', 'fetch-hook']; function runNextSuite () { - mocha.suite.suites.shift(); + mocha.suite.suites = []; const suite = suites.shift(); if (suite) importShim('./' + suite + '.js') From 0d57f72e774da55aa81961ec262d9f93fad3c59c Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Mon, 21 Oct 2019 11:08:45 -0400 Subject: [PATCH 2/2] fixup blob example --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d5b7cfa8..64b83313 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,8 @@ This matches the specification for ES module workers, supporting all features of ### Fetch Hook +> Note: This hook is non spec-compliant, but is provided as a convenience feature since the pipeline handles the same data URL rewriting and circular handling of the module graph that applies when trying to implement any module transform system. + The ES Module Shims fetch hook can be used to implement transform plugins. For example: @@ -172,7 +174,7 @@ importShim.fetch = async function (url) { if (response.url.endsWith('.ts')) { const source = await response.body(); const transformed = tsCompile(source); - return new Response(URL.createObjectURL(new Blob([transformed], { type: 'application/javascript' })), { status: 200 }); + return new Response(new Blob([transformed], { type: 'application/javascript' })); } return response; };