Skip to main content
Module

x/deno/cli/emit.rs

A modern runtime for JavaScript and TypeScript.
Go to Latest
File
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
//! The collection of APIs to be able to take `deno_graph` module graphs and//! populate a cache, emit files, and transform a graph into the structures for//! loading into an isolate.
use crate::cache::CacheType;use crate::cache::Cacher;use crate::colors;use crate::config_file;use crate::config_file::ConfigFile;use crate::config_file::IgnoredCompilerOptions;use crate::config_file::TsConfig;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_core::anyhow::anyhow;use deno_core::anyhow::Context;use deno_core::error::AnyError;use deno_core::parking_lot::RwLock;use deno_core::serde::Deserialize;use deno_core::serde::Deserializer;use deno_core::serde::Serialize;use deno_core::serde::Serializer;use deno_core::serde_json;use deno_core::serde_json::json;use deno_core::serde_json::Value;use deno_core::ModuleSpecifier;use deno_graph::MediaType;use deno_graph::ModuleGraph;use deno_graph::ModuleGraphError;use deno_graph::ModuleKind;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.#[derive(Debug, Clone, Eq, Hash, PartialEq)]pub enum TypeLib { DenoWindow, DenoWorker, UnstableDenoWindow, UnstableDenoWorker,}
impl Default for TypeLib { fn default() -> Self { Self::DenoWindow }}
impl Serialize for TypeLib { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { let value = match self { Self::DenoWindow => vec!["deno.window".to_string()], Self::DenoWorker => vec!["deno.worker".to_string()], Self::UnstableDenoWindow => { vec!["deno.window".to_string(), "deno.unstable".to_string()] } Self::UnstableDenoWorker => { vec!["deno.worker".to_string(), "deno.unstable".to_string()] } }; Serialize::serialize(&value, serializer) }}
/// A structure representing stats from an emit operation for a graph.#[derive(Clone, Debug, Default, Eq, PartialEq)]pub struct Stats(pub Vec<(String, u32)>);
impl<'de> Deserialize<'de> for Stats { fn deserialize<D>(deserializer: D) -> result::Result<Self, D::Error> where D: Deserializer<'de>, { let items: Vec<(String, u32)> = Deserialize::deserialize(deserializer)?; Ok(Stats(items)) }}
impl Serialize for Stats { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { Serialize::serialize(&self.0, serializer) }}
impl fmt::Display for Stats { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!(f, "Compilation statistics:")?; for (key, value) in self.0.clone() { writeln!(f, " {}: {}", key, value)?; }
Ok(()) }}
/// An enum that represents the base tsc configuration to return.pub enum ConfigType { /// Return a configuration for bundling, using swc to emit the bundle. This is /// independent of type checking. Bundle, /// Return a configuration to use tsc to type check and optionally emit. This /// is independent of either bundling or just emitting via swc Check { lib: TypeLib, tsc_emit: bool }, /// Return a configuration to use swc to emit single module files. Emit, /// Return a configuration as a base for the runtime `Deno.emit()` API. RuntimeEmit { tsc_emit: bool },}
/// For a given configuration type and optionally a configuration file, return a/// tuple of the resulting `TsConfig` struct and optionally any user/// configuration options that were ignored.pub fn get_ts_config( config_type: ConfigType, maybe_config_file: Option<&ConfigFile>, maybe_user_config: Option<&HashMap<String, Value>>,) -> Result<(TsConfig, Option<IgnoredCompilerOptions>), AnyError> { let mut ts_config = match config_type { ConfigType::Bundle => TsConfig::new(json!({ "checkJs": false, "emitDecoratorMetadata": false, "importsNotUsedAsValues": "remove", "inlineSourceMap": false, "inlineSources": false, "sourceMap": false, "jsx": "react", "jsxFactory": "React.createElement", "jsxFragmentFactory": "React.Fragment", })), ConfigType::Check { tsc_emit, lib } => { let mut ts_config = TsConfig::new(json!({ "allowJs": true, "allowSyntheticDefaultImports": true, "experimentalDecorators": true, "incremental": true, "jsx": "react", "isolatedModules": true, "lib": lib, "module": "esnext", "resolveJsonModule": true, "strict": true, "target": "esnext", "tsBuildInfoFile": "deno:///.tsbuildinfo", "useDefineForClassFields": true, // TODO(@kitsonk) remove for Deno 2.0 "useUnknownInCatchVariables": false, })); if tsc_emit { ts_config.merge(&json!({ "emitDecoratorMetadata": false, "importsNotUsedAsValues": "remove", "inlineSourceMap": true, "inlineSources": true, "outDir": "deno://", "removeComments": true, })); } else { ts_config.merge(&json!({ "noEmit": true, })); } ts_config } ConfigType::Emit => TsConfig::new(json!({ "checkJs": false, "emitDecoratorMetadata": false, "importsNotUsedAsValues": "remove", "inlineSourceMap": true, "inlineSources": true, "sourceMap": false, "jsx": "react", "jsxFactory": "React.createElement", "jsxFragmentFactory": "React.Fragment", "resolveJsonModule": true, })), ConfigType::RuntimeEmit { tsc_emit } => { let mut ts_config = TsConfig::new(json!({ "allowJs": true, "allowSyntheticDefaultImports": true, "checkJs": false, "emitDecoratorMetadata": false, "experimentalDecorators": true, "importsNotUsedAsValues": "remove", "incremental": true, "isolatedModules": true, "jsx": "react", "jsxFactory": "React.createElement", "jsxFragmentFactory": "React.Fragment", "lib": TypeLib::DenoWindow, "module": "esnext", "removeComments": true, "inlineSourceMap": false, "inlineSources": false, "sourceMap": true, "strict": true, "target": "esnext", "tsBuildInfoFile": "deno:///.tsbuildinfo", "useDefineForClassFields": true, // TODO(@kitsonk) remove for Deno 2.0 "useUnknownInCatchVariables": false, })); if tsc_emit { ts_config.merge(&json!({ "importsNotUsedAsValues": "remove", "outDir": "deno://", })); } else { ts_config.merge(&json!({ "noEmit": true, })); } ts_config } }; let maybe_ignored_options = if let Some(user_options) = maybe_user_config { ts_config.merge_user_config(user_options)? } else { ts_config.merge_tsconfig_from_config_file(maybe_config_file)? }; Ok((ts_config, maybe_ignored_options))}
/// Transform the graph into root specifiers that we can feed `tsc`. We have to/// provide the media type for root modules because `tsc` does not "resolve" the/// media type like other modules, as well as a root specifier needs any/// redirects resolved. If we aren't checking JavaScript, we need to include all/// the emittable files in the roots, so they get type checked and optionally/// emitted, otherwise they would be ignored if only imported into JavaScript.fn get_tsc_roots( roots: &[(ModuleSpecifier, ModuleKind)], graph_data: &GraphData, check_js: bool,) -> Vec<(ModuleSpecifier, MediaType)> { if !check_js { graph_data .entries() .into_iter() .filter_map(|(specifier, module_entry)| match module_entry { ModuleEntry::Module { media_type, ts_check, .. } => match &media_type { MediaType::TypeScript | MediaType::Tsx | MediaType::Mts | MediaType::Cts | MediaType::Jsx => Some((specifier.clone(), *media_type)), MediaType::JavaScript | MediaType::Mjs | MediaType::Cjs if check_js || *ts_check => { Some((specifier.clone(), *media_type)) } _ => None, }, _ => None, }) .collect() } else { roots .iter() .filter_map(|(specifier, _)| match graph_data.get(specifier) { Some(ModuleEntry::Module { media_type, .. }) => { Some((specifier.clone(), *media_type)) } _ => None, }) .collect() }}
/// A hashing function that takes the source code, version and optionally a/// user provided config and generates a string hash which can be stored to/// determine if the cached emit is valid or not.fn get_version(source_bytes: &[u8], config_bytes: &[u8]) -> String { crate::checksum::gen(&[ source_bytes, version::deno().as_bytes(), config_bytes, ])}
/// Determine if a given module kind and media type is emittable or not.pub fn is_emittable( kind: &ModuleKind, media_type: &MediaType, include_js: bool,) -> bool { if matches!(kind, ModuleKind::Synthetic) { return false; } match &media_type { MediaType::TypeScript | MediaType::Mts | MediaType::Cts | MediaType::Tsx | MediaType::Jsx => true, MediaType::JavaScript | MediaType::Mjs | MediaType::Cjs => include_js, _ => false, }}
/// Options for performing a check of a module graph. Note that the decision to/// emit or not is determined by the `ts_config` settings.pub struct CheckOptions { /// The check flag from the option which can effect the filtering of /// diagnostics in the emit result. pub type_check_mode: flags::TypeCheckMode, /// Set the debug flag on the TypeScript type checker. pub debug: bool, /// If true, any files emitted will be cached, even if there are diagnostics /// produced. If false, if there are diagnostics, caching emitted files will /// be skipped. pub emit_with_diagnostics: bool, /// The module specifier to the configuration file, passed to tsc so that /// configuration related diagnostics are properly formed. pub maybe_config_specifier: Option<ModuleSpecifier>, /// The derived tsconfig that should be used when checking. pub ts_config: TsConfig, /// If true, `Check <specifier>` will be written to stdout for each root. pub log_checks: bool, /// If true, valid existing emits and `.tsbuildinfo` files will be ignored. pub reload: bool, pub reload_exclusions: HashSet<ModuleSpecifier>,}
/// The result of a check or emit of a module graph. Note that the actual/// emitted sources are stored in the cache and are not returned in the result.#[derive(Debug, Default)]pub struct CheckEmitResult { pub diagnostics: Diagnostics, pub stats: Stats,}
/// Given a set of roots and graph data, type check the module graph and/// optionally emit modules, updating the cache as appropriate. Emitting is/// determined by the `ts_config` supplied in the options, and if emitting, the/// files are stored in the cache.////// It is expected that it is determined if a check and/or emit is validated/// before the function is called.pub fn check_and_maybe_emit( roots: &[(ModuleSpecifier, ModuleKind)], graph_data: Arc<RwLock<GraphData>>, cache: &mut dyn Cacher, options: CheckOptions,) -> Result<CheckEmitResult, AnyError> { let check_js = options.ts_config.get_check_js(); let segment_graph_data = { let graph_data = graph_data.read(); graph_data.graph_segment(roots).unwrap() }; if valid_emit( &segment_graph_data, cache, &options.ts_config, options.reload, &options.reload_exclusions, ) { return Ok(Default::default()); } let root_names = get_tsc_roots(roots, &segment_graph_data, check_js); if options.log_checks { for (root, _) in roots { let root_str = root.to_string(); // `$deno` specifiers are internal, don't print them. if !root_str.contains("$deno") { log::info!("{} {}", colors::green("Check"), root); } } } // while there might be multiple roots, we can't "merge" the build info, so we // try to retrieve the build info for first root, which is the most common use // case. let maybe_tsbuildinfo = if options.reload { None } else { cache.get(CacheType::TypeScriptBuildInfo, &roots[0].0) }; // to make tsc build info work, we need to consistently hash modules, so that // tsc can better determine if an emit is still valid or not, so we provide // that data here. let hash_data = vec![ options.ts_config.as_bytes(), version::deno().as_bytes().to_owned(), ]; let config_bytes = options.ts_config.as_bytes();
let response = tsc::exec(tsc::Request { config: options.ts_config, debug: options.debug, graph_data: graph_data.clone(), hash_data, maybe_config_specifier: options.maybe_config_specifier, maybe_tsbuildinfo, root_names, })?;
let diagnostics = if options.type_check_mode == flags::TypeCheckMode::Local { response.diagnostics.filter(|d| { if let Some(file_name) = &d.file_name { !file_name.starts_with("http") } else { true } }) } else { response.diagnostics };
// sometimes we want to emit when there are diagnostics, and sometimes we // don't. tsc will always return an emit if there are diagnostics if (diagnostics.is_empty() || options.emit_with_diagnostics) && !response.emitted_files.is_empty() { if let Some(info) = &response.maybe_tsbuildinfo { // while we retrieve the build info for just the first module, it can be // used for all the roots in the graph, so we will cache it for all roots for (root, _) in roots { cache.set(CacheType::TypeScriptBuildInfo, root, info.clone())?; } } for emit in response.emitted_files.into_iter() { if let Some(specifiers) = emit.maybe_specifiers { assert!(specifiers.len() == 1); // The emitted specifier might not be the file specifier we want, so we // resolve it via the graph. let graph_data = graph_data.read(); let specifier = graph_data.follow_redirect(&specifiers[0]); let (source_bytes, media_type, ts_check) = match graph_data.get(&specifier) { Some(ModuleEntry::Module { code, media_type, ts_check, .. }) => (code.as_bytes(), *media_type, *ts_check), _ => { log::debug!("skipping emit for {}", specifier); continue; } }; // Sometimes if `tsc` sees a CommonJS file or a JSON module, it will // _helpfully_ output it, which we don't really want to do unless // someone has enabled check_js. if matches!(media_type, MediaType::Json) || (!check_js && !ts_check && matches!( media_type, MediaType::JavaScript | MediaType::Cjs | MediaType::Mjs )) { log::debug!("skipping emit for {}", specifier); continue; } match emit.media_type { MediaType::JavaScript | MediaType::Mjs | MediaType::Cjs => { let version = get_version(source_bytes, &config_bytes); cache.set(CacheType::Version, &specifier, version)?; cache.set(CacheType::Emit, &specifier, emit.data)?; } MediaType::SourceMap => { cache.set(CacheType::SourceMap, &specifier, emit.data)?; } // this only occurs with the runtime emit, but we are using the same // code paths, so we handle it here. MediaType::Dts | MediaType::Dcts | MediaType::Dmts => { cache.set(CacheType::Declaration, &specifier, emit.data)?; } _ => unreachable!( "unexpected media_type {} {}", emit.media_type, specifier ), } } } }
Ok(CheckEmitResult { diagnostics, stats: response.stats, })}
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(), span: err.span(), 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 }; 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, pub reload_exclusions: HashSet<ModuleSpecifier>,}
/// Given a module graph, emit any appropriate modules and cache them.// TODO(nayeemrmn): This would ideally take `GraphData` like// `check_and_maybe_emit()`, but the AST isn't stored in that. Cleanup.pub fn emit( graph: &ModuleGraph, cache: &mut dyn Cacher, options: EmitOptions,) -> Result<CheckEmitResult, AnyError> { let start = Instant::now(); let config_bytes = options.ts_config.as_bytes(); let include_js = options.ts_config.get_check_js(); let emit_options = options.ts_config.into();
let mut emit_count = 0_u32; let mut file_count = 0_u32; for module in graph.modules() { file_count += 1; if !is_emittable(&module.kind, &module.media_type, include_js) { continue; } let needs_reload = options.reload && !options.reload_exclusions.contains(&module.specifier); let version = get_version( module.maybe_source.as_ref().map(|s| s.as_bytes()).unwrap(), &config_bytes, ); let is_valid = cache .get(CacheType::Version, &module.specifier) .map_or(false, |v| { v == get_version( module.maybe_source.as_ref().map(|s| s.as_bytes()).unwrap(), &config_bytes, ) }); if is_valid && !needs_reload { continue; } let transpiled_source = module .maybe_parsed_source .as_ref() .map(|ps| ps.transpile(&emit_options)) .unwrap()?; emit_count += 1; cache.set(CacheType::Emit, &module.specifier, transpiled_source.text)?; if let Some(map) = transpiled_source.source_map { cache.set(CacheType::SourceMap, &module.specifier, map)?; } if !is_valid { cache.set(CacheType::Version, &module.specifier, version)?; } }
let stats = Stats(vec![ ("Files".to_string(), file_count), ("Emitted".to_string(), emit_count), ("Total time".to_string(), start.elapsed().as_millis() as u32), ]);
Ok(CheckEmitResult { diagnostics: Diagnostics::default(), stats, })}
/// Check a module graph to determine if the graph contains anything that/// is required to be emitted to be valid. It determines what modules in the/// graph are emittable and for those that are emittable, if there is currently/// a valid emit in the cache.fn valid_emit( graph_data: &GraphData, cache: &dyn Cacher, ts_config: &TsConfig, reload: bool, reload_exclusions: &HashSet<ModuleSpecifier>,) -> bool { let config_bytes = ts_config.as_bytes(); let check_js = ts_config.get_check_js(); for (specifier, module_entry) in graph_data.entries() { if let ModuleEntry::Module { code, media_type, ts_check, .. } = module_entry { match media_type { MediaType::TypeScript | MediaType::Mts | MediaType::Cts | MediaType::Tsx | MediaType::Jsx => {} MediaType::JavaScript | MediaType::Mjs | MediaType::Cjs => { if !check_js && !ts_check { continue; } } _ => continue, } if reload && !reload_exclusions.contains(specifier) { return false; } if let Some(version) = cache.get(CacheType::Version, specifier) { if version != get_version(code.as_bytes(), &config_bytes) { return false; } } else { return false; } } } true}
/// An adapter struct to make a deno_graph::ModuleGraphError display as expected/// in the Deno CLI.#[derive(Debug)]pub struct GraphError(pub ModuleGraphError);
impl std::error::Error for GraphError {}
impl From<ModuleGraphError> for GraphError { fn from(err: ModuleGraphError) -> Self { Self(err) }}
impl fmt::Display for GraphError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.0 { ModuleGraphError::ResolutionError(err) => { if matches!( err, ResolutionError::InvalidDowngrade { .. } | ResolutionError::InvalidLocalImport { .. } ) { write!(f, "{}", err.to_string_with_range()) } else { self.0.fmt(f) } } _ => self.0.fmt(f), } }}
/// Convert a module graph to a map of "files", which are used by the runtime/// emit to be passed back to the caller.pub fn to_file_map( graph: &ModuleGraph, cache: &dyn Cacher,) -> HashMap<String, String> { let mut files = HashMap::new(); for (_, result) in graph.specifiers().into_iter() { if let Ok((specifier, _, media_type)) = result { if let Some(emit) = cache.get(CacheType::Emit, &specifier) { files.insert(format!("{}.js", specifier), emit); if let Some(map) = cache.get(CacheType::SourceMap, &specifier) { files.insert(format!("{}.js.map", specifier), map); } } else if matches!( media_type, MediaType::JavaScript | MediaType::Mjs | MediaType::Cjs | MediaType::Json | MediaType::Unknown ) { if let Some(module) = graph.get(&specifier) { files.insert( specifier.to_string(), module .maybe_source .as_ref() .map(|s| s.to_string()) .unwrap_or_else(|| "".to_string()), ); } } if let Some(declaration) = cache.get(CacheType::Declaration, &specifier) { files.insert(format!("{}.d.ts", specifier), declaration); } } } files}
/// This contains the logic for Deno to rewrite the `import.meta` when bundling.pub struct BundleHook;
impl Hook for BundleHook { fn get_import_meta_props( &self, span: Span, module_record: &ModuleRecord, ) -> Result<Vec<deno_ast::swc::ast::KeyValueProp>, AnyError> { use deno_ast::swc::ast;
Ok(vec![ ast::KeyValueProp { key: ast::PropName::Ident(ast::Ident::new("url".into(), span)), value: Box::new(ast::Expr::Lit(ast::Lit::Str(ast::Str { span, value: module_record.file_name.to_string().into(), raw: None, }))), }, ast::KeyValueProp { key: ast::PropName::Ident(ast::Ident::new("main".into(), span)), value: Box::new(if module_record.is_entry { ast::Expr::Member(ast::MemberExpr { span, obj: Box::new(ast::Expr::MetaProp(ast::MetaPropExpr { span, kind: ast::MetaPropKind::ImportMeta, })), prop: ast::MemberProp::Ident(ast::Ident::new("main".into(), span)), }) } else { ast::Expr::Lit(ast::Lit::Bool(ast::Bool { span, value: false })) }), }, ]) }}
impl From<config_file::TsConfig> for deno_ast::EmitOptions { fn from(config: config_file::TsConfig) -> Self { let options: config_file::EmitConfigOptions = serde_json::from_value(config.0).unwrap(); let imports_not_used_as_values = match options.imports_not_used_as_values.as_str() { "preserve" => deno_ast::ImportsNotUsedAsValues::Preserve, "error" => deno_ast::ImportsNotUsedAsValues::Error, _ => deno_ast::ImportsNotUsedAsValues::Remove, }; let (transform_jsx, jsx_automatic, jsx_development) = match options.jsx.as_str() { "react" => (true, false, false), "react-jsx" => (true, true, false), "react-jsxdev" => (true, true, true), _ => (false, false, false), }; deno_ast::EmitOptions { emit_metadata: options.emit_decorator_metadata, imports_not_used_as_values, inline_source_map: options.inline_source_map, inline_sources: options.inline_sources, source_map: options.source_map, jsx_automatic, jsx_development, jsx_factory: options.jsx_factory, jsx_fragment_factory: options.jsx_fragment_factory, jsx_import_source: options.jsx_import_source, transform_jsx, var_decl_imports: false, } }}
#[cfg(test)]mod tests { use super::*;
#[test] fn test_is_emittable() { assert!(is_emittable( &ModuleKind::Esm, &MediaType::TypeScript, false )); assert!(!is_emittable( &ModuleKind::Synthetic, &MediaType::TypeScript, false )); assert!(!is_emittable(&ModuleKind::Esm, &MediaType::Dts, false)); assert!(!is_emittable(&ModuleKind::Esm, &MediaType::Dcts, false)); assert!(!is_emittable(&ModuleKind::Esm, &MediaType::Dmts, false)); assert!(is_emittable(&ModuleKind::Esm, &MediaType::Tsx, false)); assert!(!is_emittable( &ModuleKind::Esm, &MediaType::JavaScript, false )); assert!(!is_emittable(&ModuleKind::Esm, &MediaType::Cjs, false)); assert!(!is_emittable(&ModuleKind::Esm, &MediaType::Mjs, false)); assert!(is_emittable(&ModuleKind::Esm, &MediaType::JavaScript, true)); assert!(is_emittable(&ModuleKind::Esm, &MediaType::Jsx, false)); assert!(!is_emittable(&ModuleKind::Esm, &MediaType::Json, false)); }}