diff --git a/cli/compilers/ts.rs b/cli/compilers/ts.rs index 327b3fbeb7..cac3826597 100644 --- a/cli/compilers/ts.rs +++ b/cli/compilers/ts.rs @@ -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, compiler_config: CompilerConfig, - bundle: Option, + out_file: Option, ) -> 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, ) -> impl Future { 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()); diff --git a/cli/flags.rs b/cli/flags.rs index bcd0695afd..470d0cf10d 100644 --- a/cli/flags.rs +++ b/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, ) -> (DenoFlags, DenoSubcommand, Vec) { @@ -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)) => { diff --git a/cli/js/compiler.ts b/cli/js/compiler.ts index e4953cee24..179f2af6b5 100644 --- a/cli/js/compiler.ts +++ b/cli/js/compiler.ts @@ -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 { +): Promise { 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 => { - const { rootNames, configPath, config, bundle } = data; - util.log(">>> compile start", { rootNames, bundle }); + window.onmessage = async ({ + data: request + }: { + data: CompilerRequest; + }): Promise => { + 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(); diff --git a/cli/js/deno.ts b/cli/js/deno.ts index cac730249e..2a72747271 100644 --- a/cli/js/deno.ts +++ b/cli/js/deno.ts @@ -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_; -} diff --git a/cli/js/os.ts b/cli/js/os.ts index fada0cb771..89e5652cb5 100644 --- a/cli/js/os.ts +++ b/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; } /** diff --git a/cli/js/repl.ts b/cli/js/repl.ts index 966e809e87..cf8c9d103c 100644 --- a/cli/js/repl.ts +++ b/cli/js/repl.ts @@ -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 { + const { console } = window; Object.defineProperties(window, replCommands); const historyFile = "deno_history.txt"; diff --git a/cli/js/util.ts b/cli/js/util.ts index 013dc7ee12..77dc7db5bb 100644 --- a/cli/js/util.ts +++ b/cli/js/util.ts @@ -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}`; +} diff --git a/cli/lib.rs b/cli/lib.rs index 637986f9fc..17ca94b555 100644 --- a/cli/lib.rs +++ b/cli/lib.rs @@ -325,8 +325,11 @@ fn bundle_command(flags: DenoFlags, argv: Vec) { 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 || { diff --git a/cli/msg.rs b/cli/msg.rs index 5e9053a41b..7599d874be 100644 --- a/cli/msg.rs +++ b/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, +} diff --git a/cli/tests/bundle.test.out b/cli/tests/bundle.test.out new file mode 100644 index 0000000000..d6f6e8a622 --- /dev/null +++ b/cli/tests/bundle.test.out @@ -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"]); + diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index fc059b1b39..b5b9271728 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -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", diff --git a/deno_typescript/amd_runtime.js b/deno_typescript/amd_runtime.js deleted file mode 100644 index 1c4f0007a2..0000000000 --- a/deno_typescript/amd_runtime.js +++ /dev/null @@ -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, factory: (...deps: any[]) => void) => void} - */ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -let define; - -(function() { - /** - * @type {Map} - */ - 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); - }; -})(); diff --git a/deno_typescript/bundle_loader.js b/deno_typescript/bundle_loader.js new file mode 100644 index 0000000000..6827546283 --- /dev/null +++ b/deno_typescript/bundle_loader.js @@ -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, 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} dependencies + * @property {(Factory | object)=} factory + * @property {object} exports + */ + +(function() { + /** + * @type {Map} + */ + 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; + }; +})(); diff --git a/deno_typescript/lib.rs b/deno_typescript/lib.rs index d130ee4c60..509bce56f0 100644 --- a/deno_typescript/lib.rs +++ b/deno_typescript/lib.rs @@ -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"), diff --git a/std/bundle/README.md b/std/bundle/README.md deleted file mode 100644 index ecf105d15e..0000000000 --- a/std/bundle/README.md +++ /dev/null @@ -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. diff --git a/std/bundle/run.ts b/std/bundle/run.ts deleted file mode 100644 index b5fc433c98..0000000000 --- a/std/bundle/run.ts +++ /dev/null @@ -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); diff --git a/std/bundle/test.ts b/std/bundle/test.ts deleted file mode 100644 index 504e449a67..0000000000 --- a/std/bundle/test.ts +++ /dev/null @@ -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(); -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 { - 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 { - await assertThrowsAsync( - async (): Promise => { - await load(["bundle/test.ts"]); - }, - AssertionError, - "Expected at least two arguments." - ); -}); - -test(async function loadMissingBundle(): Promise { - await assertThrowsAsync( - async (): Promise => { - await load([".", "bad_bundle.js"]); - }, - AssertionError, - `Expected "bad_bundle.js" to exist.` - ); -}); - -/* TODO re-enable test -test(async function evaluateBundle(): Promise { - 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 { - assert(globalThis.__results == null); - instantiate(fixtureQueue, fixtureModules); - assertEquals(globalThis.__results, ["bar", "qat"]); - delete globalThis.__results; -}); diff --git a/std/bundle/testdata/bundle.js b/std/bundle/testdata/bundle.js deleted file mode 100644 index 6758fd2781..0000000000 --- a/std/bundle/testdata/bundle.js +++ /dev/null @@ -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== diff --git a/std/bundle/utils.ts b/std/bundle/utils.ts deleted file mode 100644 index 062c622313..0000000000 --- a/std/bundle/utils.ts +++ /dev/null @@ -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] { - const queue: string[] = []; - const modules = new Map(); - - 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 -): 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 { - // 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)); -} diff --git a/std/manual.md b/std/manual.md index a5083d577d..f5aed463cd 100644 --- a/std/manual.md +++ b/std/manual.md @@ -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 - - ``` -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 diff --git a/tools/benchmark.py b/tools/benchmark.py index 141e3b1873..372258a935 100755 --- a/tools/benchmark.py +++ b/tools/benchmark.py @@ -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)