mirror of
https://github.com/denoland/deno.git
synced 2025-01-21 21:50:00 -05:00
fix(runtime): support source maps with Deno.emit() and bundle (#10510)
Closes: #10413
This commit is contained in:
parent
7cf674d411
commit
5127bb0d89
4 changed files with 102 additions and 15 deletions
|
@ -205,6 +205,9 @@ pub struct EmitOptions {
|
||||||
/// Should the source map be inlined in the emitted code file, or provided
|
/// Should the source map be inlined in the emitted code file, or provided
|
||||||
/// as a separate file. Defaults to `true`.
|
/// as a separate file. Defaults to `true`.
|
||||||
pub inline_source_map: bool,
|
pub inline_source_map: bool,
|
||||||
|
// Should a corresponding .map file be created for the output. This should be
|
||||||
|
// false if inline_source_map is true. Defaults to `false`.
|
||||||
|
pub source_map: bool,
|
||||||
/// When transforming JSX, what value should be used for the JSX factory.
|
/// When transforming JSX, what value should be used for the JSX factory.
|
||||||
/// Defaults to `React.createElement`.
|
/// Defaults to `React.createElement`.
|
||||||
pub jsx_factory: String,
|
pub jsx_factory: String,
|
||||||
|
@ -222,6 +225,7 @@ impl Default for EmitOptions {
|
||||||
emit_metadata: false,
|
emit_metadata: false,
|
||||||
imports_not_used_as_values: ImportsNotUsedAsValues::Remove,
|
imports_not_used_as_values: ImportsNotUsedAsValues::Remove,
|
||||||
inline_source_map: true,
|
inline_source_map: true,
|
||||||
|
source_map: false,
|
||||||
jsx_factory: "React.createElement".into(),
|
jsx_factory: "React.createElement".into(),
|
||||||
jsx_fragment_factory: "React.Fragment".into(),
|
jsx_fragment_factory: "React.Fragment".into(),
|
||||||
transform_jsx: true,
|
transform_jsx: true,
|
||||||
|
@ -244,6 +248,7 @@ impl From<config_file::TsConfig> for EmitOptions {
|
||||||
emit_metadata: options.emit_decorator_metadata,
|
emit_metadata: options.emit_decorator_metadata,
|
||||||
imports_not_used_as_values,
|
imports_not_used_as_values,
|
||||||
inline_source_map: options.inline_source_map,
|
inline_source_map: options.inline_source_map,
|
||||||
|
source_map: options.source_map,
|
||||||
jsx_factory: options.jsx_factory,
|
jsx_factory: options.jsx_factory,
|
||||||
jsx_fragment_factory: options.jsx_fragment_factory,
|
jsx_fragment_factory: options.jsx_fragment_factory,
|
||||||
transform_jsx: options.jsx == "react",
|
transform_jsx: options.jsx == "react",
|
||||||
|
|
|
@ -24,6 +24,7 @@ pub struct EmitConfigOptions {
|
||||||
pub emit_decorator_metadata: bool,
|
pub emit_decorator_metadata: bool,
|
||||||
pub imports_not_used_as_values: String,
|
pub imports_not_used_as_values: String,
|
||||||
pub inline_source_map: bool,
|
pub inline_source_map: bool,
|
||||||
|
pub source_map: bool,
|
||||||
pub jsx: String,
|
pub jsx: String,
|
||||||
pub jsx_factory: String,
|
pub jsx_factory: String,
|
||||||
pub jsx_fragment_factory: String,
|
pub jsx_fragment_factory: String,
|
||||||
|
|
|
@ -769,7 +769,8 @@ impl Graph {
|
||||||
"checkJs": false,
|
"checkJs": false,
|
||||||
"emitDecoratorMetadata": false,
|
"emitDecoratorMetadata": false,
|
||||||
"importsNotUsedAsValues": "remove",
|
"importsNotUsedAsValues": "remove",
|
||||||
"inlineSourceMap": true,
|
"inlineSourceMap": false,
|
||||||
|
"sourceMap": false,
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"jsxFactory": "React.createElement",
|
"jsxFactory": "React.createElement",
|
||||||
"jsxFragmentFactory": "React.Fragment",
|
"jsxFragmentFactory": "React.Fragment",
|
||||||
|
@ -777,7 +778,7 @@ impl Graph {
|
||||||
let maybe_ignored_options = ts_config
|
let maybe_ignored_options = ts_config
|
||||||
.merge_tsconfig_from_config_file(options.maybe_config_file.as_ref())?;
|
.merge_tsconfig_from_config_file(options.maybe_config_file.as_ref())?;
|
||||||
|
|
||||||
let s = self.emit_bundle(
|
let (src, _) = self.emit_bundle(
|
||||||
&root_specifier,
|
&root_specifier,
|
||||||
&ts_config.into(),
|
&ts_config.into(),
|
||||||
&BundleType::Module,
|
&BundleType::Module,
|
||||||
|
@ -787,7 +788,7 @@ impl Graph {
|
||||||
("Total time".to_string(), start.elapsed().as_millis() as u32),
|
("Total time".to_string(), start.elapsed().as_millis() as u32),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Ok((s, stats, maybe_ignored_options))
|
Ok((src, stats, maybe_ignored_options))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type check the module graph, corresponding to the options provided.
|
/// Type check the module graph, corresponding to the options provided.
|
||||||
|
@ -945,6 +946,7 @@ impl Graph {
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"importsNotUsedAsValues": "remove",
|
"importsNotUsedAsValues": "remove",
|
||||||
"inlineSourceMap": false,
|
"inlineSourceMap": false,
|
||||||
|
"sourceMap": false,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"jsxFactory": "React.createElement",
|
"jsxFactory": "React.createElement",
|
||||||
|
@ -958,6 +960,8 @@ impl Graph {
|
||||||
let opts = match options.bundle_type {
|
let opts = match options.bundle_type {
|
||||||
BundleType::Module | BundleType::Classic => json!({
|
BundleType::Module | BundleType::Classic => json!({
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
|
"removeComments": true,
|
||||||
|
"sourceMap": true,
|
||||||
}),
|
}),
|
||||||
BundleType::None => json!({
|
BundleType::None => json!({
|
||||||
"outDir": "deno://",
|
"outDir": "deno://",
|
||||||
|
@ -1008,12 +1012,15 @@ impl Graph {
|
||||||
"Only a single root module supported."
|
"Only a single root module supported."
|
||||||
);
|
);
|
||||||
let specifier = &graph.roots[0];
|
let specifier = &graph.roots[0];
|
||||||
let s = graph.emit_bundle(
|
let (src, maybe_src_map) = graph.emit_bundle(
|
||||||
specifier,
|
specifier,
|
||||||
&config.into(),
|
&config.into(),
|
||||||
&options.bundle_type,
|
&options.bundle_type,
|
||||||
)?;
|
)?;
|
||||||
emitted_files.insert("deno:///bundle.js".to_string(), s);
|
emitted_files.insert("deno:///bundle.js".to_string(), src);
|
||||||
|
if let Some(src_map) = maybe_src_map {
|
||||||
|
emitted_files.insert("deno:///bundle.js.map".to_string(), src_map);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
BundleType::None => {
|
BundleType::None => {
|
||||||
for emitted_file in &response.emitted_files {
|
for emitted_file in &response.emitted_files {
|
||||||
|
@ -1060,13 +1067,16 @@ impl Graph {
|
||||||
"Only a single root module supported."
|
"Only a single root module supported."
|
||||||
);
|
);
|
||||||
let specifier = &self.roots[0];
|
let specifier = &self.roots[0];
|
||||||
let s = self.emit_bundle(
|
let (src, maybe_src_map) = self.emit_bundle(
|
||||||
specifier,
|
specifier,
|
||||||
&config.into(),
|
&config.into(),
|
||||||
&options.bundle_type,
|
&options.bundle_type,
|
||||||
)?;
|
)?;
|
||||||
emit_count += 1;
|
emit_count += 1;
|
||||||
emitted_files.insert("deno:///bundle.js".to_string(), s);
|
emitted_files.insert("deno:///bundle.js".to_string(), src);
|
||||||
|
if let Some(src_map) = maybe_src_map {
|
||||||
|
emitted_files.insert("deno:///bundle.js.map".to_string(), src_map);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
BundleType::None => {
|
BundleType::None => {
|
||||||
let emit_options: ast::EmitOptions = config.into();
|
let emit_options: ast::EmitOptions = config.into();
|
||||||
|
@ -1118,7 +1128,7 @@ impl Graph {
|
||||||
specifier: &ModuleSpecifier,
|
specifier: &ModuleSpecifier,
|
||||||
emit_options: &ast::EmitOptions,
|
emit_options: &ast::EmitOptions,
|
||||||
bundle_type: &BundleType,
|
bundle_type: &BundleType,
|
||||||
) -> Result<String, AnyError> {
|
) -> Result<(String, Option<String>), AnyError> {
|
||||||
let cm = Rc::new(swc_common::SourceMap::new(
|
let cm = Rc::new(swc_common::SourceMap::new(
|
||||||
swc_common::FilePathMapping::empty(),
|
swc_common::FilePathMapping::empty(),
|
||||||
));
|
));
|
||||||
|
@ -1150,13 +1160,17 @@ impl Graph {
|
||||||
.bundle(entries)
|
.bundle(entries)
|
||||||
.context("Unable to output bundle during Graph::bundle().")?;
|
.context("Unable to output bundle during Graph::bundle().")?;
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
|
let mut src_map_buf = Vec::new();
|
||||||
{
|
{
|
||||||
let mut emitter = swc_ecmascript::codegen::Emitter {
|
let mut emitter = swc_ecmascript::codegen::Emitter {
|
||||||
cfg: swc_ecmascript::codegen::Config { minify: false },
|
cfg: swc_ecmascript::codegen::Config { minify: false },
|
||||||
cm: cm.clone(),
|
cm: cm.clone(),
|
||||||
comments: None,
|
comments: None,
|
||||||
wr: Box::new(swc_ecmascript::codegen::text_writer::JsWriter::new(
|
wr: Box::new(swc_ecmascript::codegen::text_writer::JsWriter::new(
|
||||||
cm, "\n", &mut buf, None,
|
cm.clone(),
|
||||||
|
"\n",
|
||||||
|
&mut buf,
|
||||||
|
Some(&mut src_map_buf),
|
||||||
)),
|
)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1164,8 +1178,24 @@ impl Graph {
|
||||||
.emit_module(&output[0].module)
|
.emit_module(&output[0].module)
|
||||||
.context("Unable to emit bundle during Graph::bundle().")?;
|
.context("Unable to emit bundle during Graph::bundle().")?;
|
||||||
}
|
}
|
||||||
|
let mut src = String::from_utf8(buf)
|
||||||
|
.context("Emitted bundle is an invalid utf-8 string.")?;
|
||||||
|
let mut map: Option<String> = None;
|
||||||
|
{
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
cm.build_source_map_from(&mut src_map_buf, None)
|
||||||
|
.to_writer(&mut buf)?;
|
||||||
|
|
||||||
String::from_utf8(buf).context("Emitted bundle is an invalid utf-8 string.")
|
if emit_options.inline_source_map {
|
||||||
|
src.push_str("//# sourceMappingURL=data:application/json;base64,");
|
||||||
|
let encoded_map = base64::encode(buf);
|
||||||
|
src.push_str(&encoded_map);
|
||||||
|
} else if emit_options.source_map {
|
||||||
|
map = Some(String::from_utf8(buf)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((src, map))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the handler with any modules that are marked as _dirty_ and update
|
/// Update the handler with any modules that are marked as _dirty_ and update
|
||||||
|
@ -1606,6 +1636,7 @@ impl Graph {
|
||||||
"emitDecoratorMetadata": false,
|
"emitDecoratorMetadata": false,
|
||||||
"importsNotUsedAsValues": "remove",
|
"importsNotUsedAsValues": "remove",
|
||||||
"inlineSourceMap": true,
|
"inlineSourceMap": true,
|
||||||
|
"sourceMap": false,
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"jsxFactory": "React.createElement",
|
"jsxFactory": "React.createElement",
|
||||||
"jsxFragmentFactory": "React.Fragment",
|
"jsxFragmentFactory": "React.Fragment",
|
||||||
|
@ -2413,9 +2444,10 @@ pub mod tests {
|
||||||
.expect("should have emitted");
|
.expect("should have emitted");
|
||||||
assert!(result_info.diagnostics.is_empty());
|
assert!(result_info.diagnostics.is_empty());
|
||||||
assert!(result_info.maybe_ignored_options.is_none());
|
assert!(result_info.maybe_ignored_options.is_none());
|
||||||
assert_eq!(emitted_files.len(), 1);
|
assert_eq!(emitted_files.len(), 2);
|
||||||
let actual = emitted_files.get("deno:///bundle.js");
|
let actual = emitted_files.get("deno:///bundle.js");
|
||||||
assert!(actual.is_some());
|
assert!(actual.is_some());
|
||||||
|
assert!(emitted_files.contains_key("deno:///bundle.js.map"));
|
||||||
let actual = actual.unwrap();
|
let actual = actual.unwrap();
|
||||||
assert!(actual.contains("const b = \"b\";"));
|
assert!(actual.contains("const b = \"b\";"));
|
||||||
assert!(actual.contains("console.log(mod);"));
|
assert!(actual.contains("console.log(mod);"));
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import {
|
import {
|
||||||
assert,
|
assert,
|
||||||
assertEquals,
|
assertEquals,
|
||||||
|
assertStringIncludes,
|
||||||
assertThrowsAsync,
|
assertThrowsAsync,
|
||||||
} from "../../test_util/std/testing/asserts.ts";
|
} from "../../test_util/std/testing/asserts.ts";
|
||||||
|
|
||||||
|
@ -188,7 +189,10 @@ Deno.test({
|
||||||
assertEquals(diagnostics.length, 0);
|
assertEquals(diagnostics.length, 0);
|
||||||
assert(!ignoredOptions);
|
assert(!ignoredOptions);
|
||||||
assertEquals(stats.length, 12);
|
assertEquals(stats.length, 12);
|
||||||
assertEquals(Object.keys(files), ["deno:///bundle.js"]);
|
assertEquals(
|
||||||
|
Object.keys(files).sort(),
|
||||||
|
["deno:///bundle.js", "deno:///bundle.js.map"].sort(),
|
||||||
|
);
|
||||||
assert(files["deno:///bundle.js"].includes(`const bar1 = "bar"`));
|
assert(files["deno:///bundle.js"].includes(`const bar1 = "bar"`));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -205,7 +209,10 @@ Deno.test({
|
||||||
assertEquals(diagnostics.length, 0);
|
assertEquals(diagnostics.length, 0);
|
||||||
assert(!ignoredOptions);
|
assert(!ignoredOptions);
|
||||||
assertEquals(stats.length, 12);
|
assertEquals(stats.length, 12);
|
||||||
assertEquals(Object.keys(files), ["deno:///bundle.js"]);
|
assertEquals(
|
||||||
|
Object.keys(files).sort(),
|
||||||
|
["deno:///bundle.js", "deno:///bundle.js.map"].sort(),
|
||||||
|
);
|
||||||
assert(files["deno:///bundle.js"].length);
|
assert(files["deno:///bundle.js"].length);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -226,7 +233,10 @@ Deno.test({
|
||||||
assertEquals(diagnostics.length, 0);
|
assertEquals(diagnostics.length, 0);
|
||||||
assert(!ignoredOptions);
|
assert(!ignoredOptions);
|
||||||
assertEquals(stats.length, 12);
|
assertEquals(stats.length, 12);
|
||||||
assertEquals(Object.keys(files), ["deno:///bundle.js"]);
|
assertEquals(
|
||||||
|
Object.keys(files).sort(),
|
||||||
|
["deno:///bundle.js.map", "deno:///bundle.js"].sort(),
|
||||||
|
);
|
||||||
assert(files["deno:///bundle.js"].includes(`const bar1 = "bar"`));
|
assert(files["deno:///bundle.js"].includes(`const bar1 = "bar"`));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -333,9 +343,10 @@ Deno.test({
|
||||||
});
|
});
|
||||||
assert(diagnostics);
|
assert(diagnostics);
|
||||||
assertEquals(diagnostics.length, 0);
|
assertEquals(diagnostics.length, 0);
|
||||||
assertEquals(Object.keys(files).length, 1);
|
assertEquals(Object.keys(files).length, 2);
|
||||||
assert(files["deno:///bundle.js"].startsWith("(function() {\n"));
|
assert(files["deno:///bundle.js"].startsWith("(function() {\n"));
|
||||||
assert(files["deno:///bundle.js"].endsWith("})();\n"));
|
assert(files["deno:///bundle.js"].endsWith("})();\n"));
|
||||||
|
assert(files["deno:///bundle.js.map"]);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -357,3 +368,41 @@ Deno.test({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: `Deno.emit() - support source maps with bundle option`,
|
||||||
|
async fn() {
|
||||||
|
{
|
||||||
|
const { diagnostics, files } = await Deno.emit("/a.ts", {
|
||||||
|
bundle: "classic",
|
||||||
|
sources: {
|
||||||
|
"/a.ts": `import { b } from "./b.ts";
|
||||||
|
console.log(b);`,
|
||||||
|
"/b.ts": `export const b = "b";`,
|
||||||
|
},
|
||||||
|
compilerOptions: {
|
||||||
|
inlineSourceMap: true,
|
||||||
|
sourceMap: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert(diagnostics);
|
||||||
|
assertEquals(diagnostics.length, 0);
|
||||||
|
assertEquals(Object.keys(files).length, 1);
|
||||||
|
assertStringIncludes(files["deno:///bundle.js"], "sourceMappingURL");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { diagnostics, files } = await Deno.emit("/a.ts", {
|
||||||
|
bundle: "classic",
|
||||||
|
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, 2);
|
||||||
|
assert(files["deno:///bundle.js"]);
|
||||||
|
assert(files["deno:///bundle.js.map"]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue