mirror of
https://github.com/denoland/deno.git
synced 2024-12-21 23:04:45 -05:00
refactor: use deno_emit (#14737)
This commit is contained in:
parent
01e5bbadac
commit
0cfdf5cede
4 changed files with 25 additions and 305 deletions
15
Cargo.lock
generated
15
Cargo.lock
generated
|
@ -748,6 +748,7 @@ dependencies = [
|
|||
"deno_core",
|
||||
"deno_crypto",
|
||||
"deno_doc",
|
||||
"deno_emit",
|
||||
"deno_fetch",
|
||||
"deno_graph",
|
||||
"deno_lint",
|
||||
|
@ -921,6 +922,20 @@ dependencies = [
|
|||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deno_emit"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d43a724dec6898f53984acc966d4ccf24d4d4c0a568db8e4429055166e3c86d"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.13.0",
|
||||
"deno_ast",
|
||||
"deno_graph",
|
||||
"futures",
|
||||
"parking_lot 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deno_fetch"
|
||||
version = "0.77.0"
|
||||
|
|
|
@ -48,6 +48,7 @@ winres = "=0.1.12"
|
|||
deno_ast = { version = "0.15.0", features = ["bundler", "cjs", "codegen", "dep_graph", "module_specifier", "proposal", "react", "sourcemap", "transforms", "transpiling", "typescript", "view", "visit"] }
|
||||
deno_core = { version = "0.136.0", path = "../core" }
|
||||
deno_doc = "0.35.0"
|
||||
deno_emit = "0.2.0"
|
||||
deno_graph = "0.27.0"
|
||||
deno_lint = { version = "0.30.0", features = ["docs"] }
|
||||
deno_runtime = { version = "0.62.0", path = "../runtime" }
|
||||
|
|
295
cli/emit.rs
295
cli/emit.rs
|
@ -15,28 +15,12 @@ use crate::diagnostics::Diagnostics;
|
|||
use crate::flags;
|
||||
use crate::graph_util::GraphData;
|
||||
use crate::graph_util::ModuleEntry;
|
||||
use crate::text_encoding::strip_bom;
|
||||
use crate::tsc;
|
||||
use crate::version;
|
||||
|
||||
use deno_ast::get_syntax;
|
||||
use deno_ast::swc;
|
||||
use deno_ast::swc::bundler::Hook;
|
||||
use deno_ast::swc::bundler::ModuleRecord;
|
||||
use deno_ast::swc::common::comments::SingleThreadedComments;
|
||||
use deno_ast::swc::common::FileName;
|
||||
use deno_ast::swc::common::Mark;
|
||||
use deno_ast::swc::common::SourceMap;
|
||||
use deno_ast::swc::common::Span;
|
||||
use deno_ast::swc::common::Spanned;
|
||||
use deno_ast::swc::parser::error::Error as SwcError;
|
||||
use deno_ast::swc::parser::lexer::Lexer;
|
||||
use deno_ast::swc::parser::StringInput;
|
||||
use deno_ast::Diagnostic;
|
||||
use deno_ast::LineAndColumnDisplay;
|
||||
use deno_ast::SourceRangedForSpanned;
|
||||
use deno_core::anyhow::anyhow;
|
||||
use deno_core::anyhow::Context;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::parking_lot::RwLock;
|
||||
use deno_core::serde::Deserialize;
|
||||
|
@ -55,18 +39,10 @@ use deno_graph::ResolutionError;
|
|||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
const IGNORE_DIRECTIVES: &[&str] = &[
|
||||
"// deno-fmt-ignore-file",
|
||||
"// deno-lint-ignore-file",
|
||||
"// This code was bundled using `deno bundle` and it's not recommended to edit it manually",
|
||||
""
|
||||
];
|
||||
|
||||
/// Represents the "default" type library that should be used when type
|
||||
/// checking the code in the module graph. Note that a user provided config
|
||||
/// of `"lib"` would override this value.
|
||||
|
@ -487,277 +463,6 @@ pub fn check_and_maybe_emit(
|
|||
})
|
||||
}
|
||||
|
||||
pub enum BundleType {
|
||||
/// Return the emitted contents of the program as a single "flattened" ES
|
||||
/// module.
|
||||
Module,
|
||||
/// Return the emitted contents of the program as a single script that
|
||||
/// executes the program using an immediately invoked function execution
|
||||
/// (IIFE).
|
||||
Classic,
|
||||
}
|
||||
|
||||
impl From<BundleType> for swc::bundler::ModuleType {
|
||||
fn from(bundle_type: BundleType) -> Self {
|
||||
match bundle_type {
|
||||
BundleType::Classic => Self::Iife,
|
||||
BundleType::Module => Self::Es,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BundleOptions {
|
||||
pub bundle_type: BundleType,
|
||||
pub ts_config: TsConfig,
|
||||
pub emit_ignore_directives: bool,
|
||||
}
|
||||
|
||||
/// A module loader for swc which does the appropriate retrieval and transpiling
|
||||
/// of modules from the graph.
|
||||
struct BundleLoader<'a> {
|
||||
cm: Rc<swc::common::SourceMap>,
|
||||
emit_options: &'a deno_ast::EmitOptions,
|
||||
graph: &'a ModuleGraph,
|
||||
}
|
||||
|
||||
impl swc::bundler::Load for BundleLoader<'_> {
|
||||
fn load(
|
||||
&self,
|
||||
file_name: &swc::common::FileName,
|
||||
) -> Result<swc::bundler::ModuleData, AnyError> {
|
||||
match file_name {
|
||||
swc::common::FileName::Url(specifier) => {
|
||||
if let Some(m) = self.graph.get(specifier) {
|
||||
let (fm, module) = transpile_module(
|
||||
specifier,
|
||||
m.maybe_source.as_ref().map(|s| s as &str).unwrap_or(""),
|
||||
m.media_type,
|
||||
self.emit_options,
|
||||
self.cm.clone(),
|
||||
)?;
|
||||
Ok(swc::bundler::ModuleData {
|
||||
fm,
|
||||
module,
|
||||
helpers: Default::default(),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"Module \"{}\" unexpectedly missing when bundling.",
|
||||
specifier
|
||||
))
|
||||
}
|
||||
}
|
||||
_ => unreachable!(
|
||||
"Received a request for unsupported filename {:?}",
|
||||
file_name
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Transpiles a source module into an swc SourceFile.
|
||||
fn transpile_module(
|
||||
specifier: &ModuleSpecifier,
|
||||
source: &str,
|
||||
media_type: MediaType,
|
||||
options: &deno_ast::EmitOptions,
|
||||
cm: Rc<swc::common::SourceMap>,
|
||||
) -> Result<(Rc<swc::common::SourceFile>, swc::ast::Module), AnyError> {
|
||||
let source = strip_bom(source);
|
||||
let source = if media_type == MediaType::Json {
|
||||
format!(
|
||||
"export default JSON.parse(`{}`);",
|
||||
source.replace("${", "\\${").replace('`', "\\`")
|
||||
)
|
||||
} else {
|
||||
source.to_string()
|
||||
};
|
||||
let source_file =
|
||||
cm.new_source_file(FileName::Url(specifier.clone()), source);
|
||||
let input = StringInput::from(&*source_file);
|
||||
let comments = SingleThreadedComments::default();
|
||||
let syntax = if media_type == MediaType::Json {
|
||||
get_syntax(MediaType::JavaScript)
|
||||
} else {
|
||||
get_syntax(media_type)
|
||||
};
|
||||
let lexer = Lexer::new(syntax, deno_ast::ES_VERSION, input, Some(&comments));
|
||||
let mut parser = swc::parser::Parser::new_from(lexer);
|
||||
let module = parser
|
||||
.parse_module()
|
||||
.map_err(|e| swc_err_to_diagnostic(&cm, specifier, e))?;
|
||||
let diagnostics = parser
|
||||
.take_errors()
|
||||
.into_iter()
|
||||
.map(|e| swc_err_to_diagnostic(&cm, specifier, e))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let top_level_mark = Mark::fresh(Mark::root());
|
||||
let program = deno_ast::fold_program(
|
||||
swc::ast::Program::Module(module),
|
||||
options,
|
||||
cm,
|
||||
&comments,
|
||||
top_level_mark,
|
||||
&diagnostics,
|
||||
)?;
|
||||
let module = match program {
|
||||
swc::ast::Program::Module(module) => module,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
Ok((source_file, module))
|
||||
}
|
||||
|
||||
fn swc_err_to_diagnostic(
|
||||
source_map: &SourceMap,
|
||||
specifier: &ModuleSpecifier,
|
||||
err: SwcError,
|
||||
) -> Diagnostic {
|
||||
let location = source_map.lookup_char_pos(err.span().lo);
|
||||
Diagnostic {
|
||||
specifier: specifier.to_string(),
|
||||
range: err.range(),
|
||||
display_position: LineAndColumnDisplay {
|
||||
line_number: location.line,
|
||||
column_number: location.col_display + 1,
|
||||
},
|
||||
kind: err.into_kind(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A resolver implementation for swc that resolves specifiers from the graph.
|
||||
struct BundleResolver<'a>(&'a ModuleGraph);
|
||||
|
||||
impl swc::bundler::Resolve for BundleResolver<'_> {
|
||||
fn resolve(
|
||||
&self,
|
||||
referrer: &swc::common::FileName,
|
||||
specifier: &str,
|
||||
) -> Result<swc::common::FileName, AnyError> {
|
||||
let referrer = if let swc::common::FileName::Url(referrer) = referrer {
|
||||
referrer
|
||||
} else {
|
||||
unreachable!(
|
||||
"An unexpected referrer was passed when bundling: {:?}",
|
||||
referrer
|
||||
);
|
||||
};
|
||||
if let Some(specifier) =
|
||||
self.0.resolve_dependency(specifier, referrer, false)
|
||||
{
|
||||
Ok(deno_ast::swc::common::FileName::Url(specifier.clone()))
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"Cannot resolve \"{}\" from \"{}\".",
|
||||
specifier,
|
||||
referrer
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a module graph, generate and return a bundle of the graph and
|
||||
/// optionally its source map. Unlike emitting with `check_and_maybe_emit` and
|
||||
/// `emit`, which store the emitted modules in the cache, this function simply
|
||||
/// returns the output.
|
||||
pub fn bundle(
|
||||
graph: &ModuleGraph,
|
||||
options: BundleOptions,
|
||||
) -> Result<(String, Option<String>), AnyError> {
|
||||
let globals = swc::common::Globals::new();
|
||||
deno_ast::swc::common::GLOBALS.set(&globals, || {
|
||||
let emit_options: deno_ast::EmitOptions = options.ts_config.into();
|
||||
let source_map_config = deno_ast::SourceMapConfig {
|
||||
inline_sources: emit_options.inline_sources,
|
||||
};
|
||||
|
||||
let cm = Rc::new(swc::common::SourceMap::new(
|
||||
swc::common::FilePathMapping::empty(),
|
||||
));
|
||||
let loader = BundleLoader {
|
||||
graph,
|
||||
emit_options: &emit_options,
|
||||
cm: cm.clone(),
|
||||
};
|
||||
let resolver = BundleResolver(graph);
|
||||
let config = swc::bundler::Config {
|
||||
module: options.bundle_type.into(),
|
||||
..Default::default()
|
||||
};
|
||||
// This hook will rewrite the `import.meta` when bundling to give a consistent
|
||||
// behavior between bundled and unbundled code.
|
||||
let hook = Box::new(BundleHook);
|
||||
let mut bundler = swc::bundler::Bundler::new(
|
||||
&globals,
|
||||
cm.clone(),
|
||||
loader,
|
||||
resolver,
|
||||
config,
|
||||
hook,
|
||||
);
|
||||
let mut entries = HashMap::new();
|
||||
entries.insert(
|
||||
"bundle".to_string(),
|
||||
swc::common::FileName::Url(graph.roots[0].0.clone()),
|
||||
);
|
||||
let output = bundler
|
||||
.bundle(entries)
|
||||
.context("Unable to output during bundling.")?;
|
||||
let mut buf = Vec::new();
|
||||
let mut srcmap = Vec::new();
|
||||
{
|
||||
let cfg = swc::codegen::Config {
|
||||
minify: false,
|
||||
ascii_only: false,
|
||||
target: deno_ast::ES_VERSION,
|
||||
};
|
||||
let mut wr = Box::new(swc::codegen::text_writer::JsWriter::new(
|
||||
cm.clone(),
|
||||
"\n",
|
||||
&mut buf,
|
||||
Some(&mut srcmap),
|
||||
));
|
||||
|
||||
if options.emit_ignore_directives {
|
||||
// write leading comments in bundled file
|
||||
use swc::codegen::text_writer::WriteJs;
|
||||
let cmt = IGNORE_DIRECTIVES.join("\n") + "\n";
|
||||
wr.write_comment(&cmt)?;
|
||||
}
|
||||
|
||||
let mut emitter = swc::codegen::Emitter {
|
||||
cfg,
|
||||
cm: cm.clone(),
|
||||
comments: None,
|
||||
wr,
|
||||
};
|
||||
emitter
|
||||
.emit_module(&output[0].module)
|
||||
.context("Unable to emit during bundling.")?;
|
||||
}
|
||||
let mut code =
|
||||
String::from_utf8(buf).context("Emitted code is an invalid string.")?;
|
||||
let mut maybe_map: Option<String> = None;
|
||||
{
|
||||
let mut buf = Vec::new();
|
||||
cm.build_source_map_with_config(&mut srcmap, None, source_map_config)
|
||||
.to_writer(&mut buf)?;
|
||||
if emit_options.inline_source_map {
|
||||
let encoded_map = format!(
|
||||
"//# sourceMappingURL=data:application/json;base64,{}\n",
|
||||
base64::encode(buf)
|
||||
);
|
||||
code.push_str(&encoded_map);
|
||||
} else if emit_options.source_map {
|
||||
maybe_map = Some(String::from_utf8(buf)?);
|
||||
}
|
||||
}
|
||||
|
||||
Ok((code, maybe_map))
|
||||
})
|
||||
}
|
||||
|
||||
pub struct EmitOptions {
|
||||
pub ts_config: TsConfig,
|
||||
pub reload: bool,
|
||||
|
|
19
cli/main.rs
19
cli/main.rs
|
@ -758,7 +758,7 @@ fn bundle_module_graph(
|
|||
graph: &deno_graph::ModuleGraph,
|
||||
ps: &ProcState,
|
||||
flags: &Flags,
|
||||
) -> Result<(String, Option<String>), AnyError> {
|
||||
) -> Result<deno_emit::BundleEmit, AnyError> {
|
||||
info!("{} {}", colors::green("Bundle"), graph.roots[0].0);
|
||||
|
||||
let (ts_config, maybe_ignored_options) = emit::get_ts_config(
|
||||
|
@ -772,11 +772,11 @@ fn bundle_module_graph(
|
|||
}
|
||||
}
|
||||
|
||||
emit::bundle(
|
||||
deno_emit::bundle_graph(
|
||||
graph,
|
||||
emit::BundleOptions {
|
||||
bundle_type: emit::BundleType::Module,
|
||||
ts_config,
|
||||
deno_emit::BundleOptions {
|
||||
bundle_type: deno_emit::BundleType::Module,
|
||||
emit_options: ts_config.into(),
|
||||
emit_ignore_directives: true,
|
||||
},
|
||||
)
|
||||
|
@ -836,12 +836,11 @@ async fn bundle_command(
|
|||
let operation = |(ps, graph): (ProcState, Arc<deno_graph::ModuleGraph>)| {
|
||||
let out_file = bundle_flags.out_file.clone();
|
||||
async move {
|
||||
let (bundle_emit, maybe_bundle_map) =
|
||||
bundle_module_graph(graph.as_ref(), &ps, &ps.flags)?;
|
||||
let bundle_output = bundle_module_graph(graph.as_ref(), &ps, &ps.flags)?;
|
||||
debug!(">>>>> bundle END");
|
||||
|
||||
if let Some(out_file) = out_file.as_ref() {
|
||||
let output_bytes = bundle_emit.as_bytes();
|
||||
let output_bytes = bundle_output.code.as_bytes();
|
||||
let output_len = output_bytes.len();
|
||||
fs_util::write_file(out_file, output_bytes, 0o644)?;
|
||||
info!(
|
||||
|
@ -850,7 +849,7 @@ async fn bundle_command(
|
|||
out_file,
|
||||
colors::gray(display::human_size(output_len as f64))
|
||||
);
|
||||
if let Some(bundle_map) = maybe_bundle_map {
|
||||
if let Some(bundle_map) = bundle_output.maybe_map {
|
||||
let map_bytes = bundle_map.as_bytes();
|
||||
let map_len = map_bytes.len();
|
||||
let ext = if let Some(curr_ext) = out_file.extension() {
|
||||
|
@ -868,7 +867,7 @@ async fn bundle_command(
|
|||
);
|
||||
}
|
||||
} else {
|
||||
println!("{}", bundle_emit);
|
||||
println!("{}", bundle_output.code);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
Loading…
Reference in a new issue