forked from denoland/deno
-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add bundle loader (denoland/std#480)
Original: denoland/std@fe7f6e1
- Loading branch information
Showing
6 changed files
with
314 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# bundle | ||
|
||
These are modules that help support bundling with Deno. | ||
|
||
## Usage | ||
|
||
The main usage is to load and run bundles. For example, to run a bundle named | ||
`bundle.js` in your current working directory: | ||
|
||
```sh | ||
deno run https://deno.land/std/bundle/run.ts bundle.js | ||
``` | ||
|
||
--- | ||
|
||
Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. | ||
|
||
import { evaluate, instantiate, load } from "./utils.ts"; | ||
|
||
async function main(args: string[]): Promise<void> { | ||
const text = await load(args); | ||
const result = evaluate(text); | ||
instantiate(...result); | ||
} | ||
|
||
main(Deno.args); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. | ||
|
||
import { test } from "../testing/mod.ts"; | ||
import { | ||
assert, | ||
AssertionError, | ||
assertStrictEq, | ||
assertThrowsAsync | ||
} from "../testing/asserts.ts"; | ||
import { assertEquals } from "../testing/pretty.ts"; | ||
import { evaluate, instantiate, load, ModuleMetaData } from "./utils.ts"; | ||
|
||
/* eslint-disable @typescript-eslint/no-namespace */ | ||
declare global { | ||
namespace globalThis { | ||
var __results: [string, string] | undefined; | ||
} | ||
} | ||
/* eslint-enable @typescript-eslint/no-namespace */ | ||
|
||
const fixture = ` | ||
define("data", [], { "baz": "qat" }); | ||
define("modB", ["require", "exports", "data"], function(require, exports, data) { | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.foo = "bar"; | ||
exports.baz = data.baz; | ||
}); | ||
define("modA", ["require", "exports", "modB"], function(require, exports, modB) { | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
globalThis.__results = [modB.foo, modB.baz]; | ||
}); | ||
`; | ||
|
||
const fixtureQueue = ["data", "modB", "modA"]; | ||
const fixtureModules = new Map<string, ModuleMetaData>(); | ||
fixtureModules.set("data", { | ||
dependencies: [], | ||
factory: { | ||
baz: "qat" | ||
}, | ||
exports: {} | ||
}); | ||
fixtureModules.set("modB", { | ||
dependencies: ["require", "exports", "data"], | ||
factory(_require, exports, data): void { | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.foo = "bar"; | ||
exports.baz = data.baz; | ||
}, | ||
exports: {} | ||
}); | ||
fixtureModules.set("modA", { | ||
dependencies: ["require", "exports", "modB"], | ||
factory(_require, exports, modB): void { | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
globalThis.__results = [modB.foo, modB.baz]; | ||
}, | ||
exports: {} | ||
}); | ||
|
||
test(async function loadBundle(): Promise<void> { | ||
const result = await load(["", "./bundle/testdata/bundle.js"]); | ||
assert(result != null); | ||
assert( | ||
result.includes( | ||
`define("subdir/print_hello", ["require", "exports"], function(` | ||
) | ||
); | ||
}); | ||
|
||
test(async function loadBadArgs(): Promise<void> { | ||
await assertThrowsAsync( | ||
async (): Promise<void> => { | ||
await load(["bundle/test.ts"]); | ||
}, | ||
AssertionError, | ||
"Expected exactly two arguments." | ||
); | ||
}); | ||
|
||
test(async function loadMissingBundle(): Promise<void> { | ||
await assertThrowsAsync( | ||
async (): Promise<void> => { | ||
await load([".", "bad_bundle.js"]); | ||
}, | ||
AssertionError, | ||
`Expected "bad_bundle.js" to exist.` | ||
); | ||
}); | ||
|
||
test(async function evaluateBundle(): Promise<void> { | ||
assert(globalThis.define == null, "Expected 'define' to be undefined"); | ||
const [queue, modules] = evaluate(fixture); | ||
assert(globalThis.define == null, "Expected 'define' to be undefined"); | ||
assertEquals(queue, ["data", "modB", "modA"]); | ||
assert(modules.has("modA")); | ||
assert(modules.has("modB")); | ||
assert(modules.has("data")); | ||
assertStrictEq(modules.size, 3); | ||
}); | ||
|
||
test(async function instantiateBundle(): Promise<void> { | ||
assert(globalThis.__results == null); | ||
instantiate(fixtureQueue, fixtureModules); | ||
assertEquals(globalThis.__results, ["bar", "qat"]); | ||
delete globalThis.__results; | ||
}); |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. | ||
|
||
import { assertStrictEq, assert } from "../testing/asserts.ts"; | ||
import { exists } from "../fs/exists.ts"; | ||
|
||
export interface DefineFactory { | ||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ | ||
(...args: any): object | void; | ||
} | ||
|
||
export interface ModuleMetaData { | ||
dependencies: string[]; | ||
factory?: DefineFactory | object; | ||
exports: object; | ||
} | ||
|
||
type Define = ( | ||
id: string, | ||
dependencies: string[], | ||
factory: DefineFactory | ||
) => void; | ||
|
||
/* eslint-disable @typescript-eslint/no-namespace */ | ||
declare global { | ||
namespace globalThis { | ||
var define: Define | undefined; | ||
} | ||
} | ||
/* eslint-enable @typescript-eslint/no-namespace */ | ||
|
||
/** Evaluate the bundle, returning a queue of module IDs and their data to | ||
* instantiate. | ||
*/ | ||
export function evaluate( | ||
text: string | ||
): [string[], Map<string, ModuleMetaData>] { | ||
const queue: string[] = []; | ||
const modules = new Map<string, ModuleMetaData>(); | ||
|
||
globalThis.define = function define( | ||
id: string, | ||
dependencies: string[], | ||
factory: DefineFactory | ||
): void { | ||
modules.set(id, { | ||
dependencies, | ||
factory, | ||
exports: {} | ||
}); | ||
queue.push(id); | ||
}; | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
(Deno as any).core.evalContext(text); | ||
// Deleting `define()` so it isn't accidentally there when the modules | ||
// instantiate. | ||
delete globalThis.define; | ||
|
||
return [queue, modules]; | ||
} | ||
|
||
/** Drain the queue of module IDs while instantiating the modules. */ | ||
export function instantiate( | ||
queue: string[], | ||
modules: Map<string, ModuleMetaData> | ||
): void { | ||
let id: string | undefined; | ||
while ((id = queue.shift())) { | ||
const module = modules.get(id)!; | ||
assert(module != null); | ||
assert(module.factory != null); | ||
|
||
const dependencies = module.dependencies.map( | ||
(id): object => { | ||
if (id === "require") { | ||
// TODO(kitsonk) support dynamic import by passing a `require()` that | ||
// can return a local module or dynamically import one. | ||
return (): void => {}; | ||
} else if (id === "exports") { | ||
return module.exports; | ||
} | ||
const dep = modules.get(id)!; | ||
assert(dep != null); | ||
return dep.exports; | ||
} | ||
); | ||
|
||
if (typeof module.factory === "function") { | ||
module.factory!(...dependencies); | ||
} else if (module.factory) { | ||
// when bundling JSON, TypeScript just emits it as an object/array as the | ||
// third argument of the `define()`. | ||
module.exports = module.factory; | ||
} | ||
delete module.factory; | ||
} | ||
} | ||
|
||
/** Load the bundle and return the contents asynchronously. */ | ||
export async function load(args: string[]): Promise<string> { | ||
// TODO(kitsonk) allow loading of remote bundles via fetch. | ||
assertStrictEq(args.length, 2, "Expected exactly two arguments."); | ||
const [, bundleFileName] = args; | ||
assert( | ||
await exists(bundleFileName), | ||
`Expected "${bundleFileName}" to exist.` | ||
); | ||
return new TextDecoder().decode(await Deno.readFile(bundleFileName)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters