diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts index c73703368ac69d..92798f73f034af 100644 --- a/cli/dts/lib.deno.unstable.d.ts +++ b/cli/dts/lib.deno.unstable.d.ts @@ -498,8 +498,10 @@ declare namespace Deno { interface EmitOptions { /** Indicate that the source code should be emitted to a single file - * JavaScript bundle that is an ES module (`"esm"`). */ - bundle?: "esm"; + * JavaScript bundle that is a single ES module (`"esm"`) or a single file + * self contained script we executes in an immediately invoked function + * when loaded (`"iife"`). */ + bundle?: "esm" | "iife"; /** If `true` then the sources will be typed checked, returning any * diagnostic errors in the result. If `false` type checking will be * skipped. Defaults to `true`. diff --git a/cli/module_graph.rs b/cli/module_graph.rs index d726e21a0c8a0f..643ae960984c59 100644 --- a/cli/module_graph.rs +++ b/cli/module_graph.rs @@ -614,8 +614,10 @@ pub enum BundleType { /// Return the emitted contents of the program as a single "flattened" ES /// module. Esm, - // TODO(@kitsonk) once available in swc - // Iife, + /// Return the emitted contents of the program as a single script that + /// executes the program using an immediately invoked function execution + /// (IIFE). + Iife, /// Do not bundle the emit, instead returning each of the modules that are /// part of the program as individual files. None, @@ -780,7 +782,8 @@ impl Graph { let maybe_ignored_options = ts_config.merge_tsconfig(options.maybe_config_path)?; - let s = self.emit_bundle(&root_specifier, &ts_config.into())?; + let s = + self.emit_bundle(&root_specifier, &ts_config.into(), &BundleType::Esm)?; let stats = Stats(vec![ ("Files".to_string(), self.modules.len() as u32), ("Total time".to_string(), start.elapsed().as_millis() as u32), @@ -950,7 +953,7 @@ impl Graph { "target": "esnext", })); let opts = match options.bundle_type { - BundleType::Esm => json!({ + BundleType::Esm | BundleType::Iife => json!({ "noEmit": true, }), BundleType::None => json!({ @@ -991,7 +994,7 @@ impl Graph { let graph = graph.lock().unwrap(); match options.bundle_type { - BundleType::Esm => { + BundleType::Esm | BundleType::Iife => { assert!( response.emitted_files.is_empty(), "No files should have been emitted from tsc." @@ -1002,7 +1005,11 @@ impl Graph { "Only a single root module supported." ); let specifier = &graph.roots[0]; - let s = graph.emit_bundle(specifier, &config.into())?; + let s = graph.emit_bundle( + specifier, + &config.into(), + &options.bundle_type, + )?; emitted_files.insert("deno:///bundle.js".to_string(), s); } BundleType::None => { @@ -1043,14 +1050,18 @@ impl Graph { let start = Instant::now(); let mut emit_count = 0_u32; match options.bundle_type { - BundleType::Esm => { + BundleType::Esm | BundleType::Iife => { assert_eq!( self.roots.len(), 1, "Only a single root module supported." ); let specifier = &self.roots[0]; - let s = self.emit_bundle(specifier, &config.into())?; + let s = self.emit_bundle( + specifier, + &config.into(), + &options.bundle_type, + )?; emit_count += 1; emitted_files.insert("deno:///bundle.js".to_string(), s); } @@ -1103,6 +1114,7 @@ impl Graph { &self, specifier: &ModuleSpecifier, emit_options: &ast::EmitOptions, + bundle_type: &BundleType, ) -> Result { let cm = Rc::new(swc_common::SourceMap::new( swc_common::FilePathMapping::empty(), @@ -1110,12 +1122,20 @@ impl Graph { let globals = swc_common::Globals::new(); let loader = BundleLoader::new(self, emit_options, &globals, cm.clone()); let hook = Box::new(BundleHook); + let module = match bundle_type { + BundleType::Esm => swc_bundler::ModuleType::Es, + BundleType::Iife => swc_bundler::ModuleType::Iife, + _ => unreachable!("invalid bundle type"), + }; let bundler = swc_bundler::Bundler::new( &globals, cm.clone(), loader, self, - swc_bundler::Config::default(), + swc_bundler::Config { + module, + ..Default::default() + }, hook, ); let mut entries = HashMap::new(); diff --git a/cli/ops/runtime_compiler.rs b/cli/ops/runtime_compiler.rs index 481b29a110a0a1..48d4246ff60c2d 100644 --- a/cli/ops/runtime_compiler.rs +++ b/cli/ops/runtime_compiler.rs @@ -34,6 +34,8 @@ pub fn init(rt: &mut deno_core::JsRuntime) { enum RuntimeBundleType { #[serde(rename = "esm")] Esm, + #[serde(rename = "iife")] + Iife, } #[derive(Debug, Deserialize)] @@ -97,7 +99,8 @@ async fn op_emit( builder.add(&root_specifier, is_dynamic).await?; let bundle_type = match args.bundle { Some(RuntimeBundleType::Esm) => BundleType::Esm, - _ => BundleType::None, + Some(RuntimeBundleType::Iife) => BundleType::Iife, + None => BundleType::None, }; let graph = builder.get_graph(); let debug = program_state.flags.log_level == Some(log::Level::Debug); diff --git a/cli/tests/compiler_api_test.ts b/cli/tests/compiler_api_test.ts index 17c02b0e5029fe..e343926b810441 100644 --- a/cli/tests/compiler_api_test.ts +++ b/cli/tests/compiler_api_test.ts @@ -292,3 +292,21 @@ Deno.test({ assert(diagnostics[0].messageText.includes("This import is never used")); }, }); + +Deno.test({ + name: `Deno.emit() - bundle supports iife`, + async fn() { + const { diagnostics, files } = await Deno.emit("/a.ts", { + bundle: "iife", + sources: { + "/a.ts": `import { b } from "./b.ts"; + console.log(b);`, + "/b.ts": `export const b = "b";`, + }, + }); + assert(diagnostics); + assertEquals(diagnostics.length, 0); + assertEquals(Object.keys(files).length, 1); + assert(files["deno:///bundle.js"].startsWith("function() {\n")); + }, +});