diff options
Diffstat (limited to 'cli')
-rw-r--r-- | cli/compilers/ts.rs | 27 | ||||
-rw-r--r-- | cli/flags.rs | 48 | ||||
-rw-r--r-- | cli/js/compiler.ts | 109 | ||||
-rw-r--r-- | cli/js/deno.ts | 6 | ||||
-rw-r--r-- | cli/js/os.ts | 14 | ||||
-rw-r--r-- | cli/js/repl.ts | 3 | ||||
-rw-r--r-- | cli/js/util.ts | 30 | ||||
-rw-r--r-- | cli/lib.rs | 7 | ||||
-rw-r--r-- | cli/msg.rs | 10 | ||||
-rw-r--r-- | cli/tests/bundle.test.out | 18 | ||||
-rw-r--r-- | cli/tests/integration_tests.rs | 5 |
11 files changed, 193 insertions, 84 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", |