summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/compilers/ts.rs27
-rw-r--r--cli/flags.rs48
-rw-r--r--cli/js/compiler.ts109
-rw-r--r--cli/js/deno.ts6
-rw-r--r--cli/js/os.ts14
-rw-r--r--cli/js/repl.ts3
-rw-r--r--cli/js/util.ts30
-rw-r--r--cli/lib.rs7
-rw-r--r--cli/msg.rs10
-rw-r--r--cli/tests/bundle.test.out18
-rw-r--r--cli/tests/integration_tests.rs5
-rw-r--r--deno_typescript/amd_runtime.js54
-rw-r--r--deno_typescript/bundle_loader.js124
-rw-r--r--deno_typescript/lib.rs15
-rw-r--r--std/bundle/README.md16
-rw-r--r--std/bundle/run.ts8
-rw-r--r--std/bundle/test.ts116
-rw-r--r--std/bundle/testdata/bundle.js67
-rw-r--r--std/bundle/utils.ts109
-rw-r--r--std/manual.md28
-rwxr-xr-xtools/benchmark.py2
21 files changed, 336 insertions, 480 deletions
diff --git a/cli/compilers/ts.rs b/cli/compilers/ts.rs
index 327b3fbeb..cac382659 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<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());
diff --git a/cli/flags.rs b/cli/flags.rs
index bcd0695af..470d0cf10 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<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)) => {
diff --git a/cli/js/compiler.ts b/cli/js/compiler.ts
index e4953cee2..179f2af6b 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<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 host = new Host(bundle);
+ const resolvedRootModules = (await processImports(
+ rootNames.map(rootName => [rootName, rootName])
+ )).map(info => info.url);
+
+ 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 cac730249..2a7274727 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 fada0cb77..89e5652cb 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 966e809e8..cf8c9d103 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<void> {
+ 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 013dc7ee1..77dc7db5b 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 637986f9f..17ca94b55 100644
--- a/cli/lib.rs
+++ b/cli/lib.rs
@@ -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 || {
diff --git a/cli/msg.rs b/cli/msg.rs
index 5e9053a41..7599d874b 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 000000000..d6f6e8a62
--- /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 fc059b1b3..b5b927172 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 1c4f0007a..000000000
--- 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<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);
- };
-})();
diff --git a/deno_typescript/bundle_loader.js b/deno_typescript/bundle_loader.js
new file mode 100644
index 000000000..682754628
--- /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<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;
+ };
+})();
diff --git a/deno_typescript/lib.rs b/deno_typescript/lib.rs
index d130ee4c6..509bce56f 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 ecf105d15..000000000
--- 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 b5fc433c9..000000000
--- 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 504e449a6..000000000
--- 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<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;
-});
diff --git a/std/bundle/testdata/bundle.js b/std/bundle/testdata/bundle.js
deleted file mode 100644
index 6758fd278..000000000
--- 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 062c62231..000000000
--- 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<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));
-}
diff --git a/std/manual.md b/std/manual.md
index a5083d577..f5aed463c 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
-<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
diff --git a/tools/benchmark.py b/tools/benchmark.py
index 141e3b187..372258a93 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)