diff options
Diffstat (limited to 'deno_typescript')
-rw-r--r-- | deno_typescript/README.md | 59 | ||||
-rw-r--r-- | deno_typescript/bundle_loader.js | 121 | ||||
-rw-r--r-- | deno_typescript/lib.rs | 24 | ||||
-rw-r--r-- | deno_typescript/system_loader.js | 110 |
4 files changed, 120 insertions, 194 deletions
diff --git a/deno_typescript/README.md b/deno_typescript/README.md index c2fb7f5a5..220b04ae4 100644 --- a/deno_typescript/README.md +++ b/deno_typescript/README.md @@ -5,3 +5,62 @@ This crate provides utilities to compile typescript, bundle it up, and create a V8 snapshot, all during build. Snapshots allow the executable to startup fast. + +## `system_loader.js` + +This is a minimalistic implementation of a +[System](https://github.com/systemjs/systemjs) module loader. It is specifically +designed to load modules that are emitted from TypeScript the module format is +`"system"` and a single `"outfile"` is supplied, which is commonly refereed to +as a bundle. + +Because this loader becomes part of an emitted bundle under `Deno.bundle()` and +`deno bundle`, it has minimal comments and very terse and cryptic syntax, which +isn't very self documenting. Because of this, a guide to this file is provided +here. + +A bundle of System modules expects a `System.register()` function to be in scope +for registering the modules. Modules that are emitted from TypeScript in a +single out file always pass 3 arguments, the module specifier, an array of +strings of modules specifiers that this module depends upon, and finally a +module factory. + +The module factory requires two arguments to be passed, a function for exporting +values and a context object. We have to bind to some information in the +environment to provide these, so `gC` gets the context and `gE` gets the export +function to be passed to a factory. The context contains information like the +module specifier, a reference to the dynamic `import()` and the equivalent of +`import.meta`. The export function takes either two arguments of an named export +and its value, or an object record of keys of the named exports and the values +of the exports. + +The running of the factories is handled by `rF()`. When the factory is run, it +returns an object with two keys, `execute` and `setters`. `execute` is a +function which finalises that instantiation of the module, and `setters` which +is an array of functions that sets the value of the exports of the dependent +module. + +The `gExp()` and `gExpA()` are the recursive functions which returns the exports +of a given module. It will determine if the module has been fully initialized, +and if not, it will gather the exports of the dependencies, set those exports in +the module via the `setters` and run the modules `execute()`. It will then +always return or resolve with the exports of the module. + +As of TypeScript 3.8, top level await is supported when emitting ES or System +modules. When Deno creates a module bundle, it creates a valid, self-contained +ES module which exports the exports of the "main" module that was used when the +bundle was created. If a module in the bundle requires top-level-await, then the +`execute()` function is emitted as an async function, returning a promise. This +means that in order to export the values of the main module, the instantiation +needs to utilise top-level-await as well. + +At the time of this writing, while V8 and other JavaScript engines have +implemented top-level-await, no browsers have it implemented, meaning that most +browsers could not consume modules that require top-level-await. + +In order to facilitate this, there are two functions that are in the scope of +the module in addition to the `System.register()` method. `__instantiate(main)` +will bootstrap everything synchronously and `__instantiate(main)` will do so +asynchronously. When emitting a bundle that contains a module that requires +top-level-await, Deno will detect this and utilise +`await __instantiateAsync(main)` instead. diff --git a/deno_typescript/bundle_loader.js b/deno_typescript/bundle_loader.js deleted file mode 100644 index 5b4b5ec16..000000000 --- a/deno_typescript/bundle_loader.js +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -// A script preamble that provides the ability to load a single outfile -// TypeScript "bundle" where a main module is loaded which recursively -// instantiates all the other modules in the bundle. This code is used to load -// bundles when creating snapshots, but is also used when emitting bundles from -// Deno cli. - -// @ts-nocheck - -/** - * @type {(name: string, deps: ReadonlyArray<string>, factory: (...deps: any[]) => void) => void=} - */ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -let define; - -/** - * @type {(mod: string) => any=} - */ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -let instantiate; - -/** - * @callback Factory - * @argument {...any[]} args - * @returns {object | void} - */ - -/** - * @typedef ModuleMetaData - * @property {ReadonlyArray<string>} dependencies - * @property {(Factory | object)=} factory - * @property {object} exports - */ - -(function() { - /** - * @type {Map<string, ModuleMetaData>} - */ - const modules = new Map(); - - /** - * Bundles in theory can support "dynamic" imports, but for internal bundles - * we can't go outside to fetch any modules that haven't been statically - * defined. - * @param {string[]} deps - * @param {(...deps: any[]) => void} resolve - * @param {(err: any) => void} reject - */ - const require = (deps, resolve, reject) => { - try { - if (deps.length !== 1) { - throw new TypeError("Expected only a single module specifier."); - } - if (!modules.has(deps[0])) { - throw new RangeError(`Module "${deps[0]}" not defined.`); - } - resolve(getExports(deps[0])); - } catch (e) { - if (reject) { - reject(e); - } else { - throw e; - } - } - }; - - define = (id, dependencies, factory) => { - if (modules.has(id)) { - throw new RangeError(`Module "${id}" has already been defined.`); - } - modules.set(id, { - dependencies, - factory, - exports: {} - }); - }; - - /** - * @param {string} id - * @returns {any} - */ - function getExports(id) { - const module = modules.get(id); - if (!module) { - // because `$deno$/ts_global.d.ts` looks like a real script, it doesn't - // get erased from output as an import, but it doesn't get defined, so - // we don't have a cache for it, so because this is an internal bundle - // we can just safely return an empty object literal. - return {}; - } - if (!module.factory) { - return module.exports; - } else if (module.factory) { - const { factory, exports } = module; - delete module.factory; - if (typeof factory === "function") { - const dependencies = module.dependencies.map(id => { - if (id === "require") { - return require; - } else if (id === "exports") { - return exports; - } - return getExports(id); - }); - factory(...dependencies); - } else { - Object.assign(exports, factory); - } - return exports; - } - } - - instantiate = dep => { - define = undefined; - const result = getExports(dep); - // clean up, or otherwise these end up in the runtime environment - instantiate = undefined; - return result; - }; -})(); diff --git a/deno_typescript/lib.rs b/deno_typescript/lib.rs index f1b0dd4da..d88932eeb 100644 --- a/deno_typescript/lib.rs +++ b/deno_typescript/lib.rs @@ -25,7 +25,7 @@ use std::sync::Mutex; static TYPESCRIPT_CODE: &str = include_str!("typescript/lib/typescript.js"); static COMPILER_CODE: &str = include_str!("compiler_main.js"); -static BUNDLE_LOADER: &str = include_str!("bundle_loader.js"); +static SYSTEM_LOADER: &str = include_str!("system_loader.js"); pub fn ts_version() -> String { let data = include_str!("typescript/package.json"); @@ -143,20 +143,20 @@ pub fn compile_bundle( let config_json = serde_json::json!({ "compilerOptions": { - "strict": true, "declaration": true, - "lib": ["esnext"], - "module": "amd", - "target": "esnext", - "listFiles": true, - "listEmittedFiles": true, - // "types" : ["typescript.d.ts"], - "typeRoots" : ["$typeRoots$"], // Emit the source alongside the sourcemaps within a single file; // requires --inlineSourceMap or --sourceMap to be set. // "inlineSources": true, - "sourceMap": true, + "lib": ["esnext"], + "listEmittedFiles": true, + "listFiles": true, + "module": "system", "outFile": bundle_filename, + "removeComments": true, + "sourceMap": true, + "strict": true, + "target": "esnext", + "typeRoots" : ["$typeRoots$"], }, }); @@ -198,13 +198,13 @@ pub fn mksnapshot_bundle( bundle_filename: &Path, main_module_name: &str, ) -> Result<(), ErrBox> { - js_check(isolate.execute("bundle_loader.js", BUNDLE_LOADER)); + js_check(isolate.execute("system_loader.js", SYSTEM_LOADER)); let source_code_vec = std::fs::read(bundle_filename).unwrap(); let bundle_source_code = std::str::from_utf8(&source_code_vec).unwrap(); js_check( isolate.execute(&bundle_filename.to_string_lossy(), bundle_source_code), ); - let script = &format!("instantiate('{}')", main_module_name); + let script = &format!("__instantiate(\"{}\");", main_module_name); js_check(isolate.execute("anon", script)); write_snapshot(isolate, snapshot_filename)?; Ok(()) diff --git a/deno_typescript/system_loader.js b/deno_typescript/system_loader.js index 18a08107e..41748c46e 100644 --- a/deno_typescript/system_loader.js +++ b/deno_typescript/system_loader.js @@ -4,30 +4,29 @@ // @ts-nocheck /* eslint-disable */ - -let System, __inst, __inst_s; +let System, __instantiateAsync, __instantiate; (() => { - const mMap = new Map(); + const r = new Map(); + System = { register(id, d, f) { - mMap.set(id, { id, d, f, exp: {} }); + r.set(id, { d, f, exp: {} }); } }; - const gC = (data, main) => { - const { id } = data; + function gC(id, main) { return { id, - import: async id => mMap.get(id)?.exp, + import: async id => r.get(id)?.exp, meta: { url: id, main } }; - }; + } - const gE = ({ exp }) => { + function gE(exp) { return (id, v) => { - const vs = typeof id === "string" ? { [id]: v } : id; - for (const [id, value] of Object.entries(vs)) { + v = typeof id === "string" ? { [id]: v } : id; + for (const [id, value] of Object.entries(v)) { Object.defineProperty(exp, id, { value, writable: true, @@ -35,65 +34,54 @@ let System, __inst, __inst_s; }); } }; - }; - - const iQ = []; - - const enq = ids => { - for (const id of ids) { - if (!iQ.includes(id)) { - const { d } = mMap.get(id); - iQ.push(id); - enq(d); - } - } - }; + } - const gRQ = main => { - const rQ = []; - let id; - while ((id = iQ.pop())) { - const m = mMap.get(id), - { f } = m; - if (!f) return; - rQ.push([m.d, f(gE(m), gC(m, id === main))]); + function rF(main) { + for (const [id, m] of r.entries()) { + const { f, exp } = m; + const { execute: e, setters: s } = f(gE(exp), gC(id, id === main)); delete m.f; + m.e = e; + m.s = s; } - return rQ; - }; + } - const dr = async main => { - const rQ = gRQ(main); - let r; - while ((r = rQ.shift())) { - const [d, { execute, setters }] = r; - for (let i = 0; i < d.length; i++) setters[i](mMap.get(d[i])?.exp); - const e = execute(); - if (e) await e; + async function gExpA(id) { + if (!r.has(id)) return; + const m = r.get(id); + if (m.s) { + const { d, e, s } = m; + delete m.s; + delete m.e; + for (let i = 0; i < s.length; i++) s[i](await gExpA(d[i])); + const r = e(); + if (r) await r; } - }; + return m.exp; + } - const dr_s = main => { - const rQ = gRQ(main); - let r; - while ((r = rQ.shift())) { - const [d, { execute, setters }] = r; - for (let i = 0; i < d.length; i++) setters[i](mMap.get(d[i])?.exp); - execute(); + function gExp(id) { + if (!r.has(id)) return; + const m = r.get(id); + if (m.s) { + const { d, e, s } = m; + delete m.s; + delete m.e; + for (let i = 0; i < s.length; i++) s[i](gExp(d[i])); + e(); } - }; + return m.exp; + } - __inst = async id => { - System = __inst = __inst_s = undefined; - enq([id]); - await dr(id); - return mMap.get(id)?.exp; + __instantiateAsync = async m => { + System = __instantiateAsync = __instantiate = undefined; + rF(m); + return gExpA(m); }; - __inst_s = id => { - System = __inst = __inst_s = undefined; - enq([id]); - dr_s(id); - return mMap.get(id)?.exp; + __instantiate = m => { + System = __instantiateAsync = __instantiate = undefined; + rF(m); + return gExp(m); }; })(); |