mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 09:31:22 -05:00
Make bundles fully standalone (#3325)
- Bundles are fully standalone. They now include the shared loader with `deno_typescript`. - Refactor of the loader in `deno_typescript` to perform module instantiation in a more - Change of behaviour when an output file is not specified on the CLI. Previously a default name was determined and the bundle written to that file, now the bundle will be sent to `stdout`. - Refactors in the TypeScript compiler to be able to support the concept of a request type. This provides a cleaner abstraction and makes it easier to support things like single module transpiles to the userland. - Remove a "dangerous" circular dependency between `os.ts` and `deno.ts`, and define `pid` and `noColor` in a better way. - Don't bind early to `console` in `repl.ts`. - Add an integration test for generating a bundle.
This commit is contained in:
parent
ee1b8dc883
commit
8d03397293
21 changed files with 335 additions and 479 deletions
|
@ -156,20 +156,23 @@ impl CompiledFileMetadata {
|
|||
}
|
||||
/// Creates the JSON message send to compiler.ts's onmessage.
|
||||
fn req(
|
||||
request_type: msg::CompilerRequestType,
|
||||
root_names: Vec<String>,
|
||||
compiler_config: CompilerConfig,
|
||||
bundle: Option<String>,
|
||||
out_file: Option<String>,
|
||||
) -> Buf {
|
||||
let j = match (compiler_config.path, compiler_config.content) {
|
||||
(Some(config_path), Some(config_data)) => json!({
|
||||
"type": request_type as i32,
|
||||
"rootNames": root_names,
|
||||
"bundle": bundle,
|
||||
"outFile": out_file,
|
||||
"configPath": config_path,
|
||||
"config": str::from_utf8(&config_data).unwrap(),
|
||||
}),
|
||||
_ => json!({
|
||||
"type": request_type as i32,
|
||||
"rootNames": root_names,
|
||||
"bundle": bundle,
|
||||
"outFile": out_file,
|
||||
}),
|
||||
};
|
||||
|
||||
|
@ -250,7 +253,7 @@ impl TsCompiler {
|
|||
self: &Self,
|
||||
global_state: ThreadSafeGlobalState,
|
||||
module_name: String,
|
||||
out_file: String,
|
||||
out_file: Option<String>,
|
||||
) -> impl Future<Item = (), Error = ErrBox> {
|
||||
debug!(
|
||||
"Invoking the compiler to bundle. module_name: {}",
|
||||
|
@ -258,7 +261,12 @@ impl TsCompiler {
|
|||
);
|
||||
|
||||
let root_names = vec![module_name.clone()];
|
||||
let req_msg = req(root_names, self.config.clone(), Some(out_file));
|
||||
let req_msg = req(
|
||||
msg::CompilerRequestType::Bundle,
|
||||
root_names,
|
||||
self.config.clone(),
|
||||
out_file,
|
||||
);
|
||||
|
||||
let worker = TsCompiler::setup_worker(global_state.clone());
|
||||
let worker_ = worker.clone();
|
||||
|
@ -360,7 +368,12 @@ impl TsCompiler {
|
|||
);
|
||||
|
||||
let root_names = vec![module_url.to_string()];
|
||||
let req_msg = req(root_names, self.config.clone(), None);
|
||||
let req_msg = req(
|
||||
msg::CompilerRequestType::Compile,
|
||||
root_names,
|
||||
self.config.clone(),
|
||||
None,
|
||||
);
|
||||
|
||||
let worker = TsCompiler::setup_worker(global_state.clone());
|
||||
let worker_ = worker.clone();
|
||||
|
@ -709,7 +722,7 @@ mod tests {
|
|||
.bundle_async(
|
||||
state.clone(),
|
||||
module_name,
|
||||
String::from("$deno$/bundle.js"),
|
||||
Some(String::from("$deno$/bundle.js")),
|
||||
)
|
||||
.then(|result| {
|
||||
assert!(result.is_ok());
|
||||
|
|
48
cli/flags.rs
48
cli/flags.rs
|
@ -6,7 +6,6 @@ use clap::Arg;
|
|||
use clap::ArgMatches;
|
||||
use clap::Shell;
|
||||
use clap::SubCommand;
|
||||
use deno::ModuleSpecifier;
|
||||
use log::Level;
|
||||
use std;
|
||||
use std::str;
|
||||
|
@ -259,11 +258,16 @@ compiler.",
|
|||
SubCommand::with_name("bundle")
|
||||
.about("Bundle module and dependencies into single file")
|
||||
.long_about(
|
||||
"Output a single JavaScript file with all dependencies
|
||||
"Output a single JavaScript file with all dependencies.
|
||||
|
||||
If a out_file argument is omitted, the output of the bundle will be sent to
|
||||
standard out.
|
||||
|
||||
Example:
|
||||
|
||||
deno bundle https://deno.land/std/examples/colors.ts"
|
||||
deno bundle https://deno.land/std/examples/colors.ts
|
||||
|
||||
deno bundle https://deno.land/std/examples/colors.ts colors.bundle.js"
|
||||
)
|
||||
.arg(Arg::with_name("source_file").takes_value(true).required(true))
|
||||
.arg(Arg::with_name("out_file").takes_value(true).required(false)),
|
||||
|
@ -793,32 +797,6 @@ pub enum DenoSubcommand {
|
|||
Version,
|
||||
}
|
||||
|
||||
fn get_default_bundle_filename(source_file: &str) -> String {
|
||||
let specifier = ModuleSpecifier::resolve_url_or_path(source_file).unwrap();
|
||||
let path_segments = specifier.as_url().path_segments().unwrap();
|
||||
let file_name = path_segments.filter(|s| !s.is_empty()).last().unwrap();
|
||||
let file_stem = file_name.trim_end_matches(".ts").trim_end_matches(".js");
|
||||
format!("{}.bundle.js", file_stem)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_default_bundle_filename() {
|
||||
assert_eq!(get_default_bundle_filename("blah.ts"), "blah.bundle.js");
|
||||
assert_eq!(
|
||||
get_default_bundle_filename("http://example.com/blah.ts"),
|
||||
"blah.bundle.js"
|
||||
);
|
||||
assert_eq!(get_default_bundle_filename("blah.js"), "blah.bundle.js");
|
||||
assert_eq!(
|
||||
get_default_bundle_filename("http://example.com/blah.js"),
|
||||
"blah.bundle.js"
|
||||
);
|
||||
assert_eq!(
|
||||
get_default_bundle_filename("http://zombo.com/stuff/"),
|
||||
"stuff.bundle.js"
|
||||
);
|
||||
}
|
||||
|
||||
pub fn flags_from_vec(
|
||||
args: Vec<String>,
|
||||
) -> (DenoFlags, DenoSubcommand, Vec<String>) {
|
||||
|
@ -835,11 +813,13 @@ pub fn flags_from_vec(
|
|||
("bundle", Some(bundle_match)) => {
|
||||
flags.allow_write = true;
|
||||
let source_file: &str = bundle_match.value_of("source_file").unwrap();
|
||||
let out_file = bundle_match
|
||||
.value_of("out_file")
|
||||
.map(String::from)
|
||||
.unwrap_or_else(|| get_default_bundle_filename(source_file));
|
||||
argv.extend(vec![source_file.to_string(), out_file.to_string()]);
|
||||
let out_file = bundle_match.value_of("out_file").map(String::from);
|
||||
match out_file {
|
||||
Some(out_file) => {
|
||||
argv.extend(vec![source_file.to_string(), out_file.to_string()])
|
||||
}
|
||||
_ => argv.extend(vec![source_file.to_string()]),
|
||||
}
|
||||
DenoSubcommand::Bundle
|
||||
}
|
||||
("completions", Some(completions_match)) => {
|
||||
|
|
|
@ -31,6 +31,13 @@ enum MediaType {
|
|||
Unknown = 5
|
||||
}
|
||||
|
||||
// Warning! The values in this enum are duplicated in cli/msg.rs
|
||||
// Update carefully!
|
||||
enum CompilerRequestType {
|
||||
Compile = 0,
|
||||
Bundle = 1
|
||||
}
|
||||
|
||||
// Startup boilerplate. This is necessary because the compiler has its own
|
||||
// snapshot. (It would be great if we could remove these things or centralize
|
||||
// them somewhere else.)
|
||||
|
@ -44,16 +51,23 @@ window["denoMain"] = denoMain;
|
|||
|
||||
const ASSETS = "$asset$";
|
||||
const OUT_DIR = "$deno$";
|
||||
const BUNDLE_LOADER = "bundle_loader.js";
|
||||
|
||||
/** The format of the work message payload coming from the privileged side */
|
||||
interface CompilerReq {
|
||||
type CompilerRequest = {
|
||||
rootNames: string[];
|
||||
bundle?: string;
|
||||
// TODO(ry) add compiler config to this interface.
|
||||
// options: ts.CompilerOptions;
|
||||
configPath?: string;
|
||||
config?: string;
|
||||
}
|
||||
} & (
|
||||
| {
|
||||
type: CompilerRequestType.Compile;
|
||||
}
|
||||
| {
|
||||
type: CompilerRequestType.Bundle;
|
||||
outFile?: string;
|
||||
});
|
||||
|
||||
interface ConfigureResponse {
|
||||
ignoredOptions?: string[];
|
||||
|
@ -271,7 +285,7 @@ function fetchSourceFiles(
|
|||
async function processImports(
|
||||
specifiers: Array<[string, string]>,
|
||||
referrer = ""
|
||||
): Promise<void> {
|
||||
): Promise<SourceFileJson[]> {
|
||||
if (!specifiers.length) {
|
||||
return;
|
||||
}
|
||||
|
@ -287,6 +301,7 @@ async function processImports(
|
|||
await processImports(sourceFile.imports(), sourceFile.url);
|
||||
}
|
||||
}
|
||||
return sourceFiles;
|
||||
}
|
||||
|
||||
/** Utility function to turn the number of bytes into a human readable
|
||||
|
@ -314,16 +329,36 @@ function cache(extension: string, moduleId: string, contents: string): void {
|
|||
const encoder = new TextEncoder();
|
||||
|
||||
/** Given a fileName and the data, emit the file to the file system. */
|
||||
function emitBundle(fileName: string, data: string): void {
|
||||
function emitBundle(
|
||||
rootNames: string[],
|
||||
fileName: string | undefined,
|
||||
data: string,
|
||||
sourceFiles: readonly ts.SourceFile[]
|
||||
): void {
|
||||
// For internal purposes, when trying to emit to `$deno$` just no-op
|
||||
if (fileName.startsWith("$deno$")) {
|
||||
if (fileName && fileName.startsWith("$deno$")) {
|
||||
console.warn("skipping emitBundle", fileName);
|
||||
return;
|
||||
}
|
||||
const encodedData = encoder.encode(data);
|
||||
console.log(`Emitting bundle to "${fileName}"`);
|
||||
writeFileSync(fileName, encodedData);
|
||||
console.log(`${humanFileSize(encodedData.length)} emitted.`);
|
||||
const loader = fetchAsset(BUNDLE_LOADER);
|
||||
// when outputting to AMD and a single outfile, TypeScript makes up the module
|
||||
// specifiers which are used to define the modules, and doesn't expose them
|
||||
// publicly, so we have to try to replicate
|
||||
const sources = sourceFiles.map(sf => sf.fileName);
|
||||
const sharedPath = util.commonPath(sources);
|
||||
rootNames = rootNames.map(id =>
|
||||
id.replace(sharedPath, "").replace(/\.\w+$/i, "")
|
||||
);
|
||||
const instantiate = `instantiate(${JSON.stringify(rootNames)});\n`;
|
||||
const bundle = `${loader}\n${data}\n${instantiate}`;
|
||||
if (fileName) {
|
||||
const encodedData = encoder.encode(bundle);
|
||||
console.warn(`Emitting bundle to "${fileName}"`);
|
||||
writeFileSync(fileName, encodedData);
|
||||
console.warn(`${humanFileSize(encodedData.length)} emitted.`);
|
||||
} else {
|
||||
console.log(bundle);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the TypeScript Extension enum for a given media type. */
|
||||
|
@ -380,17 +415,23 @@ class Host implements ts.CompilerHost {
|
|||
|
||||
/** Provides the `ts.HostCompiler` interface for Deno.
|
||||
*
|
||||
* @param _rootNames A set of modules that are the ones that should be
|
||||
* instantiated first. Used when generating a bundle.
|
||||
* @param _bundle Set to a string value to configure the host to write out a
|
||||
* bundle instead of caching individual files.
|
||||
*/
|
||||
constructor(private _bundle?: string) {
|
||||
if (this._bundle) {
|
||||
constructor(
|
||||
private _requestType: CompilerRequestType,
|
||||
private _rootNames: string[],
|
||||
private _outFile?: string
|
||||
) {
|
||||
if (this._requestType === CompilerRequestType.Bundle) {
|
||||
// options we need to change when we are generating a bundle
|
||||
const bundlerOptions: ts.CompilerOptions = {
|
||||
module: ts.ModuleKind.AMD,
|
||||
inlineSourceMap: true,
|
||||
outDir: undefined,
|
||||
outFile: `${OUT_DIR}/bundle.js`,
|
||||
// disabled until we have effective way to modify source maps
|
||||
sourceMap: false
|
||||
};
|
||||
Object.assign(this._options, bundlerOptions);
|
||||
|
@ -531,10 +572,11 @@ class Host implements ts.CompilerHost {
|
|||
): void {
|
||||
util.log("compiler::host.writeFile", fileName);
|
||||
try {
|
||||
if (this._bundle) {
|
||||
emitBundle(this._bundle, data);
|
||||
assert(sourceFiles != null);
|
||||
if (this._requestType === CompilerRequestType.Bundle) {
|
||||
emitBundle(this._rootNames, this._outFile, data, sourceFiles!);
|
||||
} else {
|
||||
assert(sourceFiles != null && sourceFiles.length == 1);
|
||||
assert(sourceFiles.length == 1);
|
||||
const url = sourceFiles![0].fileName;
|
||||
const sourceFile = SourceFile.get(url);
|
||||
|
||||
|
@ -579,16 +621,29 @@ class Host implements ts.CompilerHost {
|
|||
// lazy instantiating the compiler web worker
|
||||
window.compilerMain = function compilerMain(): void {
|
||||
// workerMain should have already been called since a compiler is a worker.
|
||||
window.onmessage = async ({ data }: { data: CompilerReq }): Promise<void> => {
|
||||
const { rootNames, configPath, config, bundle } = data;
|
||||
util.log(">>> compile start", { rootNames, bundle });
|
||||
window.onmessage = async ({
|
||||
data: request
|
||||
}: {
|
||||
data: CompilerRequest;
|
||||
}): Promise<void> => {
|
||||
const { rootNames, configPath, config } = request;
|
||||
util.log(">>> compile start", {
|
||||
rootNames,
|
||||
type: CompilerRequestType[request.type]
|
||||
});
|
||||
|
||||
// This will recursively analyse all the code for other imports, requesting
|
||||
// those from the privileged side, populating the in memory cache which
|
||||
// will be used by the host, before resolving.
|
||||
await processImports(rootNames.map(rootName => [rootName, rootName]));
|
||||
const resolvedRootModules = (await processImports(
|
||||
rootNames.map(rootName => [rootName, rootName])
|
||||
)).map(info => info.url);
|
||||
|
||||
const host = new Host(bundle);
|
||||
const host = new Host(
|
||||
request.type,
|
||||
resolvedRootModules,
|
||||
request.type === CompilerRequestType.Bundle ? request.outFile : undefined
|
||||
);
|
||||
let emitSkipped = true;
|
||||
let diagnostics: ts.Diagnostic[] | undefined;
|
||||
|
||||
|
@ -642,8 +697,9 @@ window.compilerMain = function compilerMain(): void {
|
|||
|
||||
// We will only proceed with the emit if there are no diagnostics.
|
||||
if (diagnostics && diagnostics.length === 0) {
|
||||
if (bundle) {
|
||||
console.log(`Bundling "${bundle}"`);
|
||||
if (request.type === CompilerRequestType.Bundle) {
|
||||
// warning so it goes to stderr instead of stdout
|
||||
console.warn(`Bundling "${resolvedRootModules.join(`", "`)}"`);
|
||||
}
|
||||
const emitResult = program.emit();
|
||||
emitSkipped = emitResult.emitSkipped;
|
||||
|
@ -662,7 +718,10 @@ window.compilerMain = function compilerMain(): void {
|
|||
|
||||
postMessage(result);
|
||||
|
||||
util.log("<<< compile end", { rootNames, bundle });
|
||||
util.log("<<< compile end", {
|
||||
rootNames,
|
||||
type: CompilerRequestType[request.type]
|
||||
});
|
||||
|
||||
// The compiler isolate exits after a single message.
|
||||
workerClose();
|
||||
|
|
|
@ -112,9 +112,3 @@ export let pid: number;
|
|||
|
||||
/** Reflects the NO_COLOR environment variable: https://no-color.org/ */
|
||||
export let noColor: boolean;
|
||||
|
||||
// TODO(ry) This should not be exposed to Deno.
|
||||
export function _setGlobals(pid_: number, noColor_: boolean): void {
|
||||
pid = pid_;
|
||||
noColor = noColor_;
|
||||
}
|
||||
|
|
14
cli/js/os.ts
14
cli/js/os.ts
|
@ -7,9 +7,6 @@ import * as util from "./util.ts";
|
|||
import { window } from "./window.ts";
|
||||
import { OperatingSystem, Arch } from "./build.ts";
|
||||
|
||||
// builtin modules
|
||||
import { _setGlobals } from "./deno.ts";
|
||||
|
||||
/** Check if running in terminal.
|
||||
*
|
||||
* console.log(Deno.isTTY().stdout);
|
||||
|
@ -103,14 +100,15 @@ export function start(preserveDenoNamespace = true, source?: string): Start {
|
|||
// First we send an empty `Start` message to let the privileged side know we
|
||||
// are ready. The response should be a `StartRes` message containing the CLI
|
||||
// args and other info.
|
||||
const s = sendSync(dispatch.OP_START);
|
||||
const startResponse = sendSync(dispatch.OP_START);
|
||||
const { pid, noColor, debugFlag } = startResponse;
|
||||
|
||||
util.setLogDebug(s.debugFlag, source);
|
||||
util.setLogDebug(debugFlag, source);
|
||||
|
||||
// pid and noColor need to be set in the Deno module before it's set to be
|
||||
// frozen.
|
||||
_setGlobals(s.pid, s.noColor);
|
||||
delete window.Deno._setGlobals;
|
||||
util.immutableDefine(window.Deno, "pid", pid);
|
||||
util.immutableDefine(window.Deno, "noColor", noColor);
|
||||
Object.freeze(window.Deno);
|
||||
|
||||
if (preserveDenoNamespace) {
|
||||
|
@ -126,7 +124,7 @@ export function start(preserveDenoNamespace = true, source?: string): Start {
|
|||
assert(window.Deno === undefined);
|
||||
}
|
||||
|
||||
return s;
|
||||
return startResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,8 +8,6 @@ import { stringifyArgs } from "./console.ts";
|
|||
import * as dispatch from "./dispatch.ts";
|
||||
import { sendSync, sendAsync } from "./dispatch_json.ts";
|
||||
|
||||
const { console } = window;
|
||||
|
||||
/**
|
||||
* REPL logging.
|
||||
* In favor of console.log to avoid unwanted indentation
|
||||
|
@ -106,6 +104,7 @@ function evaluate(code: string): boolean {
|
|||
|
||||
// @internal
|
||||
export async function replLoop(): Promise<void> {
|
||||
const { console } = window;
|
||||
Object.defineProperties(window, replCommands);
|
||||
|
||||
const historyFile = "deno_history.txt";
|
||||
|
|
|
@ -223,3 +223,33 @@ export function splitNumberToParts(n: number): number[] {
|
|||
const higher = (n - lower) / 0x100000000;
|
||||
return [lower, higher];
|
||||
}
|
||||
|
||||
/** Return the common path shared by the `paths`.
|
||||
*
|
||||
* @param paths The set of paths to compare.
|
||||
* @param sep An optional separator to use. Defaults to `/`.
|
||||
* @internal
|
||||
*/
|
||||
export function commonPath(paths: string[], sep = "/"): string {
|
||||
const [first = "", ...remaining] = paths;
|
||||
if (first === "" || remaining.length === 0) {
|
||||
return "";
|
||||
}
|
||||
const parts = first.split(sep);
|
||||
|
||||
let endOfPrefix = parts.length;
|
||||
for (const path of remaining) {
|
||||
const compare = path.split(sep);
|
||||
for (let i = 0; i < endOfPrefix; i++) {
|
||||
if (compare[i] !== parts[i]) {
|
||||
endOfPrefix = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (endOfPrefix === 0) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
const prefix = parts.slice(0, endOfPrefix).join(sep);
|
||||
return prefix.endsWith(sep) ? prefix : `${prefix}${sep}`;
|
||||
}
|
||||
|
|
|
@ -325,8 +325,11 @@ fn bundle_command(flags: DenoFlags, argv: Vec<String>) {
|
|||
let (worker, state) = create_worker_and_state(flags, argv);
|
||||
|
||||
let main_module = state.main_module.as_ref().unwrap().clone();
|
||||
assert!(state.argv.len() >= 3);
|
||||
let out_file = state.argv[2].clone();
|
||||
assert!(state.argv.len() >= 2);
|
||||
let out_file = match state.argv.len() {
|
||||
3 => Some(state.argv[2].clone()),
|
||||
_ => None,
|
||||
};
|
||||
debug!(">>>>> bundle_async START");
|
||||
// NOTE: we need to poll `worker` otherwise TS compiler worker won't run properly
|
||||
let main_future = lazy(move || {
|
||||
|
|
10
cli/msg.rs
10
cli/msg.rs
|
@ -87,3 +87,13 @@ pub fn enum_name_media_type(mt: MediaType) -> &'static str {
|
|||
MediaType::Unknown => "Unknown",
|
||||
}
|
||||
}
|
||||
|
||||
// Warning! The values in this enum are duplicated in js/compiler.ts
|
||||
// Update carefully!
|
||||
#[allow(non_camel_case_types)]
|
||||
#[repr(i8)]
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
pub enum CompilerRequestType {
|
||||
Compile = 0,
|
||||
Bundle = 1,
|
||||
}
|
||||
|
|
18
cli/tests/bundle.test.out
Normal file
18
cli/tests/bundle.test.out
Normal file
|
@ -0,0 +1,18 @@
|
|||
[WILDCARD]
|
||||
let define;
|
||||
[WILDCARD]
|
||||
let instantiate;
|
||||
[WILDCARD]
|
||||
(function() {
|
||||
[WILDCARD]
|
||||
})();
|
||||
|
||||
define("print_hello", ["require", "exports"], function (require, exports) {
|
||||
[WILDCARD]
|
||||
});
|
||||
define("mod1", ["require", "exports", "subdir2/mod2"], function (require, exports, mod2_ts_1) {
|
||||
[WILDCARD]
|
||||
});
|
||||
|
||||
instantiate(["mod1"]);
|
||||
|
|
@ -391,6 +391,11 @@ itest!(async_error {
|
|||
output: "async_error.ts.out",
|
||||
});
|
||||
|
||||
itest!(bundle {
|
||||
args: "bundle subdir/mod1.ts",
|
||||
output: "bundle.test.out",
|
||||
});
|
||||
|
||||
itest!(circular1 {
|
||||
args: "run --reload circular1.js",
|
||||
output: "circular1.js.out",
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
// A very very basic AMD preamble to support the output of TypeScript outFile
|
||||
// bundles.
|
||||
|
||||
/**
|
||||
* @type {(name: string) => any}
|
||||
*/
|
||||
let require;
|
||||
|
||||
/**
|
||||
* @type {(name: string, deps: ReadonlyArray<string>, factory: (...deps: any[]) => void) => void}
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
let define;
|
||||
|
||||
(function() {
|
||||
/**
|
||||
* @type {Map<string, { name: string, exports: any }>}
|
||||
*/
|
||||
const modules = new Map();
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
*/
|
||||
function createOrLoadModule(name) {
|
||||
let m = modules.get(name);
|
||||
if (!m) {
|
||||
m = { name, exports: {} };
|
||||
modules.set(name, m);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
require = name => {
|
||||
return createOrLoadModule(name).exports;
|
||||
};
|
||||
|
||||
define = (name, deps, factory) => {
|
||||
const currentModule = createOrLoadModule(name);
|
||||
const localExports = currentModule.exports;
|
||||
const args = deps.map(dep => {
|
||||
if (dep === "require") {
|
||||
return require;
|
||||
} else if (dep === "exports") {
|
||||
return localExports;
|
||||
} else {
|
||||
const depModule = createOrLoadModule(dep);
|
||||
return depModule.exports;
|
||||
}
|
||||
});
|
||||
factory(...args);
|
||||
};
|
||||
})();
|
124
deno_typescript/bundle_loader.js
Normal file
124
deno_typescript/bundle_loader.js
Normal file
|
@ -0,0 +1,124 @@
|
|||
// Copyright 2018-2019 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.
|
||||
|
||||
/**
|
||||
* @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 | string[]) => void}
|
||||
*/
|
||||
// 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;
|
||||
if (Array.isArray(dep)) {
|
||||
for (const d of dep) {
|
||||
getExports(d);
|
||||
}
|
||||
} else {
|
||||
getExports(dep);
|
||||
}
|
||||
// clean up, or otherwise these end up in the runtime environment
|
||||
instantiate = undefined;
|
||||
};
|
||||
})();
|
|
@ -22,7 +22,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 AMD_RUNTIME_CODE: &str = include_str!("amd_runtime.js");
|
||||
static BUNDLE_LOADER: &str = include_str!("bundle_loader.js");
|
||||
|
||||
pub fn ts_version() -> String {
|
||||
let data = include_str!("typescript/package.json");
|
||||
|
@ -181,11 +181,13 @@ pub fn mksnapshot_bundle(
|
|||
let source_code_vec = std::fs::read(bundle)?;
|
||||
let source_code = std::str::from_utf8(&source_code_vec)?;
|
||||
|
||||
js_check(runtime_isolate.execute("amd_runtime.js", AMD_RUNTIME_CODE));
|
||||
js_check(runtime_isolate.execute("bundle_loader.js", BUNDLE_LOADER));
|
||||
js_check(runtime_isolate.execute(&bundle.to_string_lossy(), &source_code));
|
||||
|
||||
let main = state.lock().unwrap().main_module_name();
|
||||
js_check(runtime_isolate.execute("anon", &format!("require('{}')", main)));
|
||||
js_check(
|
||||
runtime_isolate.execute("anon", &format!("instantiate('{}')", main)),
|
||||
);
|
||||
|
||||
write_snapshot(runtime_isolate, bundle)?;
|
||||
|
||||
|
@ -202,12 +204,14 @@ pub fn mksnapshot_bundle_ts(
|
|||
let source_code_vec = std::fs::read(bundle)?;
|
||||
let source_code = std::str::from_utf8(&source_code_vec)?;
|
||||
|
||||
js_check(runtime_isolate.execute("amd_runtime.js", AMD_RUNTIME_CODE));
|
||||
js_check(runtime_isolate.execute("bundle_loader.js", BUNDLE_LOADER));
|
||||
js_check(runtime_isolate.execute("typescript.js", TYPESCRIPT_CODE));
|
||||
js_check(runtime_isolate.execute(&bundle.to_string_lossy(), &source_code));
|
||||
|
||||
let main = state.lock().unwrap().main_module_name();
|
||||
js_check(runtime_isolate.execute("anon", &format!("require('{}')", main)));
|
||||
js_check(
|
||||
runtime_isolate.execute("anon", &format!("instantiate('{}')", main)),
|
||||
);
|
||||
|
||||
write_snapshot(runtime_isolate, bundle)?;
|
||||
|
||||
|
@ -249,6 +253,7 @@ pub fn get_asset(name: &str) -> Option<&'static str> {
|
|||
};
|
||||
}
|
||||
match name {
|
||||
"bundle_loader.js" => Some(include_str!("bundle_loader.js")),
|
||||
"lib.deno_core.d.ts" => Some(include_str!("lib.deno_core.d.ts")),
|
||||
"typescript.d.ts" => inc!("typescript.d.ts"),
|
||||
"lib.esnext.d.ts" => inc!("lib.esnext.d.ts"),
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
# 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.
|
|
@ -1,8 +0,0 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
import { evaluate, instantiate, load } from "./utils.ts";
|
||||
|
||||
const args = Deno.args;
|
||||
const text = await load(args);
|
||||
const result = evaluate(text);
|
||||
instantiate(...result);
|
|
@ -1,116 +0,0 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
import { test } from "../testing/mod.ts";
|
||||
import {
|
||||
assert,
|
||||
AssertionError,
|
||||
assertEquals,
|
||||
assertThrowsAsync
|
||||
} from "../testing/asserts.ts";
|
||||
import { instantiate, load, ModuleMetaData } from "./utils.ts";
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-namespace */
|
||||
declare global {
|
||||
namespace globalThis {
|
||||
// eslint-disable-next-line no-var
|
||||
var __results: [string, string] | undefined;
|
||||
}
|
||||
}
|
||||
/* eslint-disable max-len */
|
||||
/* 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];
|
||||
});
|
||||
`;
|
||||
*/
|
||||
/* eslint-enable max-len */
|
||||
|
||||
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", "--foo"]);
|
||||
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 at least 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.`
|
||||
);
|
||||
});
|
||||
|
||||
/* TODO re-enable test
|
||||
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;
|
||||
});
|
67
std/bundle/testdata/bundle.js
vendored
67
std/bundle/testdata/bundle.js
vendored
|
@ -1,67 +0,0 @@
|
|||
define("subdir/print_hello", ["require", "exports"], function(
|
||||
require,
|
||||
exports
|
||||
) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
function printHello() {
|
||||
console.log("Hello");
|
||||
}
|
||||
exports.printHello = printHello;
|
||||
});
|
||||
define("subdir/subdir2/mod2", [
|
||||
"require",
|
||||
"exports",
|
||||
"subdir/print_hello"
|
||||
], function(require, exports, print_hello_ts_1) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
function returnsFoo() {
|
||||
return "Foo";
|
||||
}
|
||||
exports.returnsFoo = returnsFoo;
|
||||
function printHello2() {
|
||||
print_hello_ts_1.printHello();
|
||||
}
|
||||
exports.printHello2 = printHello2;
|
||||
});
|
||||
define("subdir/mod1", ["require", "exports", "subdir/subdir2/mod2"], function(
|
||||
require,
|
||||
exports,
|
||||
mod2_ts_1
|
||||
) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
function returnsHi() {
|
||||
return "Hi";
|
||||
}
|
||||
exports.returnsHi = returnsHi;
|
||||
function returnsFoo2() {
|
||||
return mod2_ts_1.returnsFoo();
|
||||
}
|
||||
exports.returnsFoo2 = returnsFoo2;
|
||||
function printHello3() {
|
||||
mod2_ts_1.printHello2();
|
||||
}
|
||||
exports.printHello3 = printHello3;
|
||||
function throwsError() {
|
||||
throw Error("exception from mod1");
|
||||
}
|
||||
exports.throwsError = throwsError;
|
||||
});
|
||||
define("005_more_imports", ["require", "exports", "subdir/mod1"], function(
|
||||
require,
|
||||
exports,
|
||||
mod1_ts_1
|
||||
) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
mod1_ts_1.printHello3();
|
||||
if (mod1_ts_1.returnsHi() !== "Hi") {
|
||||
throw Error("Unexpected");
|
||||
}
|
||||
if (mod1_ts_1.returnsFoo2() !== "Foo") {
|
||||
throw Error("Unexpected");
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVuZGxlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZmlsZTovLy9Vc2Vycy9ra2VsbHkvZ2l0aHViL2Rlbm8vdGVzdHMvc3ViZGlyL3ByaW50X2hlbGxvLnRzIiwiZmlsZTovLy9Vc2Vycy9ra2VsbHkvZ2l0aHViL2Rlbm8vdGVzdHMvc3ViZGlyL3N1YmRpcjIvbW9kMi50cyIsImZpbGU6Ly8vVXNlcnMva2tlbGx5L2dpdGh1Yi9kZW5vL3Rlc3RzL3N1YmRpci9tb2QxLnRzIiwiZmlsZTovLy9Vc2Vycy9ra2VsbHkvZ2l0aHViL2Rlbm8vdGVzdHMvMDA1X21vcmVfaW1wb3J0cy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7SUFBQSxTQUFnQixVQUFVO1FBQ3hCLE9BQU8sQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDdkIsQ0FBQztJQUZELGdDQUVDOzs7OztJQ0FELFNBQWdCLFVBQVU7UUFDeEIsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0lBRkQsZ0NBRUM7SUFFRCxTQUFnQixXQUFXO1FBQ3pCLDJCQUFVLEVBQUUsQ0FBQztJQUNmLENBQUM7SUFGRCxrQ0FFQzs7Ozs7SUNORCxTQUFnQixTQUFTO1FBQ3ZCLE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUZELDhCQUVDO0lBRUQsU0FBZ0IsV0FBVztRQUN6QixPQUFPLG9CQUFVLEVBQUUsQ0FBQztJQUN0QixDQUFDO0lBRkQsa0NBRUM7SUFFRCxTQUFnQixXQUFXO1FBQ3pCLHFCQUFXLEVBQUUsQ0FBQztJQUNoQixDQUFDO0lBRkQsa0NBRUM7SUFFRCxTQUFnQixXQUFXO1FBQ3pCLE1BQU0sS0FBSyxDQUFDLHFCQUFxQixDQUFDLENBQUM7SUFDckMsQ0FBQztJQUZELGtDQUVDOzs7OztJQ2RELHFCQUFXLEVBQUUsQ0FBQztJQUVkLElBQUksbUJBQVMsRUFBRSxLQUFLLElBQUksRUFBRTtRQUN4QixNQUFNLEtBQUssQ0FBQyxZQUFZLENBQUMsQ0FBQztLQUMzQjtJQUVELElBQUkscUJBQVcsRUFBRSxLQUFLLEtBQUssRUFBRTtRQUMzQixNQUFNLEtBQUssQ0FBQyxZQUFZLENBQUMsQ0FBQztLQUMzQiIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBmdW5jdGlvbiBwcmludEhlbGxvKCk6IHZvaWQge1xuICBjb25zb2xlLmxvZyhcIkhlbGxvXCIpO1xufVxuIiwiaW1wb3J0IHsgcHJpbnRIZWxsbyB9IGZyb20gXCIuLi9wcmludF9oZWxsby50c1wiO1xuXG5leHBvcnQgZnVuY3Rpb24gcmV0dXJuc0ZvbygpOiBzdHJpbmcge1xuICByZXR1cm4gXCJGb29cIjtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHByaW50SGVsbG8yKCk6IHZvaWQge1xuICBwcmludEhlbGxvKCk7XG59XG4iLCJpbXBvcnQgeyByZXR1cm5zRm9vLCBwcmludEhlbGxvMiB9IGZyb20gXCIuL3N1YmRpcjIvbW9kMi50c1wiO1xuXG5leHBvcnQgZnVuY3Rpb24gcmV0dXJuc0hpKCk6IHN0cmluZyB7XG4gIHJldHVybiBcIkhpXCI7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiByZXR1cm5zRm9vMigpOiBzdHJpbmcge1xuICByZXR1cm4gcmV0dXJuc0ZvbygpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gcHJpbnRIZWxsbzMoKTogdm9pZCB7XG4gIHByaW50SGVsbG8yKCk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiB0aHJvd3NFcnJvcigpOiB2b2lkIHtcbiAgdGhyb3cgRXJyb3IoXCJleGNlcHRpb24gZnJvbSBtb2QxXCIpO1xufVxuIiwiaW1wb3J0IHsgcmV0dXJuc0hpLCByZXR1cm5zRm9vMiwgcHJpbnRIZWxsbzMgfSBmcm9tIFwiLi9zdWJkaXIvbW9kMS50c1wiO1xuXG5wcmludEhlbGxvMygpO1xuXG5pZiAocmV0dXJuc0hpKCkgIT09IFwiSGlcIikge1xuICB0aHJvdyBFcnJvcihcIlVuZXhwZWN0ZWRcIik7XG59XG5cbmlmIChyZXR1cm5zRm9vMigpICE9PSBcIkZvb1wiKSB7XG4gIHRocm93IEVycm9yKFwiVW5leHBlY3RlZFwiKTtcbn1cbiJdfQ==
|
|
@ -1,109 +0,0 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
import { 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 {
|
||||
// eslint-disable-next-line no-var
|
||||
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.
|
||||
assert(args.length >= 2, "Expected at least two arguments.");
|
||||
const [, bundleFileName] = args;
|
||||
assert(
|
||||
await exists(bundleFileName),
|
||||
`Expected "${bundleFileName}" to exist.`
|
||||
);
|
||||
return new TextDecoder().decode(await Deno.readFile(bundleFileName));
|
||||
}
|
|
@ -793,9 +793,8 @@ Particularly useful ones:
|
|||
|
||||
### Bundling
|
||||
|
||||
`deno bundle [URL]` will output a single JavaScript file, using
|
||||
[AMD](https://en.wikipedia.org/wiki/Asynchronous_module_definition), which
|
||||
includes all dependencies of the specified input.
|
||||
`deno bundle [URL]` will output a single JavaScript file, which includes all
|
||||
dependencies of the specified input. For example:
|
||||
|
||||
```
|
||||
> deno bundle https://deno.land/std/examples/colors.ts
|
||||
|
@ -804,32 +803,21 @@ Emitting bundle to "colors.bundle.js"
|
|||
9.2 kB emitted.
|
||||
```
|
||||
|
||||
To run then bundle in Deno use
|
||||
The bundle can just be run as any other module in Deno would:
|
||||
|
||||
```
|
||||
deno https://deno.land/std/bundle/run.ts colors.bundle.js
|
||||
deno colors.bundle.js
|
||||
```
|
||||
|
||||
Bundles can also be loaded in the web browser with the assistance of
|
||||
[RequireJS](https://requirejs.org/). Suppose we have a bundle called
|
||||
`website.bundle.js`, then the following HTML should be able to load it:
|
||||
Bundles can also be loaded in the web browser. For example:
|
||||
|
||||
```html
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js"></script>
|
||||
<script src="website.bundle.js"></script>
|
||||
<script>
|
||||
requirejs(["website"], website => website.main());
|
||||
</script>
|
||||
```
|
||||
|
||||
Here we assume there's an exported function `main()` from `website.ts`.
|
||||
|
||||
```js
|
||||
// website.ts
|
||||
export main() {
|
||||
console.log("hello from the web browser");
|
||||
}
|
||||
```
|
||||
Bundles, whether loaded in the web browser, or in Deno, would run the root
|
||||
module which is specified on the command line when creating the bundle, so put
|
||||
any initiation logic in that module.
|
||||
|
||||
### Installing executable scripts
|
||||
|
||||
|
|
|
@ -201,8 +201,8 @@ def bundle_benchmark(deno_exe):
|
|||
|
||||
for name, url in bundles.items():
|
||||
# bundle
|
||||
run([deno_exe, "bundle", url])
|
||||
path = name + ".bundle.js"
|
||||
run([deno_exe, "bundle", url, path])
|
||||
# get size of bundle
|
||||
assert os.path.exists(path)
|
||||
sizes[name] = os.path.getsize(path)
|
||||
|
|
Loading…
Add table
Reference in a new issue