summaryrefslogtreecommitdiff
path: root/cli/module_graph2.rs
diff options
context:
space:
mode:
authorKitson Kelly <me@kitsonkelly.com>2020-10-20 14:10:42 +1100
committerGitHub <noreply@github.com>2020-10-20 14:10:42 +1100
commit57e95032c898cfdfe795e6924d402fdbe2ae59a8 (patch)
treee548b283d0129b5a1c00828f5a456cc094276bd1 /cli/module_graph2.rs
parent034ab48086557af00216ffe311c71ad4eb0ec4d5 (diff)
feat(cli): add support for bundle --no-check (#8023)
Fixes #6686
Diffstat (limited to 'cli/module_graph2.rs')
-rw-r--r--cli/module_graph2.rs224
1 files changed, 205 insertions, 19 deletions
diff --git a/cli/module_graph2.rs b/cli/module_graph2.rs
index 681cc3bb5..e2dcdfefc 100644
--- a/cli/module_graph2.rs
+++ b/cli/module_graph2.rs
@@ -1,7 +1,9 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
-use crate::ast;
use crate::ast::parse;
+use crate::ast::transpile_module;
+use crate::ast::BundleHook;
+use crate::ast::EmitOptions;
use crate::ast::Location;
use crate::ast::ParsedModule;
use crate::import_map::ImportMap;
@@ -21,6 +23,7 @@ use crate::tsc_config::TsConfig;
use crate::version;
use crate::AnyError;
+use deno_core::error::Context;
use deno_core::futures::stream::FuturesUnordered;
use deno_core::futures::stream::StreamExt;
use deno_core::serde_json::json;
@@ -38,7 +41,6 @@ use std::rc::Rc;
use std::result;
use std::sync::Mutex;
use std::time::Instant;
-use swc_ecmascript::dep_graph::DependencyKind;
lazy_static! {
/// Matched the `@deno-types` pragma.
@@ -108,6 +110,59 @@ impl fmt::Display for GraphError {
impl Error for GraphError {}
+/// A structure for handling bundle loading, which is implemented here, to
+/// avoid a circular dependency with `ast`.
+struct BundleLoader<'a> {
+ cm: Rc<swc_common::SourceMap>,
+ graph: &'a Graph2,
+ emit_options: &'a EmitOptions,
+}
+
+impl<'a> BundleLoader<'a> {
+ pub fn new(
+ graph: &'a Graph2,
+ emit_options: &'a EmitOptions,
+ cm: Rc<swc_common::SourceMap>,
+ ) -> Self {
+ BundleLoader {
+ cm,
+ graph,
+ emit_options,
+ }
+ }
+}
+
+impl swc_bundler::Load for BundleLoader<'_> {
+ fn load(
+ &self,
+ file: &swc_common::FileName,
+ ) -> Result<(Rc<swc_common::SourceFile>, swc_ecmascript::ast::Module), AnyError>
+ {
+ match file {
+ swc_common::FileName::Custom(filename) => {
+ let specifier = ModuleSpecifier::resolve_url_or_path(filename)
+ .context("Failed to convert swc FileName to ModuleSpecifier.")?;
+ if let Some(src) = self.graph.get_source(&specifier) {
+ let media_type = self
+ .graph
+ .get_media_type(&specifier)
+ .context("Looking up media type during bundling.")?;
+ transpile_module(
+ filename,
+ &src,
+ &media_type,
+ self.emit_options,
+ self.cm.clone(),
+ )
+ } else {
+ Err(MissingDependency(specifier, "<bundle>".to_string()).into())
+ }
+ }
+ _ => unreachable!("Received request for unsupported filename {:?}", file),
+ }
+ }
+}
+
/// An enum which represents the parsed out values of references in source code.
#[derive(Debug, Clone, Eq, PartialEq)]
enum TypeScriptReference {
@@ -273,10 +328,9 @@ impl Module {
// Parse out all the syntactical dependencies for a module
let dependencies = parsed_module.analyze_dependencies();
- for desc in dependencies
- .iter()
- .filter(|desc| desc.kind != DependencyKind::Require)
- {
+ for desc in dependencies.iter().filter(|desc| {
+ desc.kind != swc_ecmascript::dep_graph::DependencyKind::Require
+ }) {
let location = Location {
filename: self.specifier.to_string(),
col: desc.col,
@@ -301,8 +355,8 @@ impl Module {
.dependencies
.entry(desc.specifier.to_string())
.or_default();
- if desc.kind == DependencyKind::ExportType
- || desc.kind == DependencyKind::ImportType
+ if desc.kind == swc_ecmascript::dep_graph::DependencyKind::ExportType
+ || desc.kind == swc_ecmascript::dep_graph::DependencyKind::ImportType
{
dep.maybe_type = Some(specifier);
} else {
@@ -392,6 +446,12 @@ impl fmt::Display for Stats {
}
}
+#[derive(Debug, Default)]
+pub struct BundleOptions {
+ pub debug: bool,
+ pub maybe_config_path: Option<String>,
+}
+
/// A structure which provides options when transpiling modules.
#[derive(Debug, Default)]
pub struct TranspileOptions {
@@ -431,6 +491,76 @@ impl Graph2 {
}
}
+ /// Transform the module graph into a single JavaScript module which is
+ /// returned as a `String` in the result.
+ pub fn bundle(
+ &self,
+ options: BundleOptions,
+ ) -> Result<(String, Stats, Option<IgnoredCompilerOptions>), AnyError> {
+ if self.roots.is_empty() || self.roots.len() > 1 {
+ return Err(NotSupported(format!("Bundling is only supported when there is a single root module in the graph. Found: {}", self.roots.len())).into());
+ }
+
+ let start = Instant::now();
+ let root_specifier = self.roots[0].clone();
+ let mut ts_config = TsConfig::new(json!({
+ "checkJs": false,
+ "emitDecoratorMetadata": false,
+ "jsx": "react",
+ "jsxFactory": "React.createElement",
+ "jsxFragmentFactory": "React.Fragment",
+ }));
+ let maybe_ignored_options =
+ ts_config.merge_user_config(options.maybe_config_path)?;
+ let emit_options: EmitOptions = ts_config.into();
+ let cm = Rc::new(swc_common::SourceMap::new(
+ swc_common::FilePathMapping::empty(),
+ ));
+ let loader = BundleLoader::new(self, &emit_options, cm.clone());
+ let hook = Box::new(BundleHook);
+ let globals = swc_common::Globals::new();
+ let bundler = swc_bundler::Bundler::new(
+ &globals,
+ cm.clone(),
+ loader,
+ self,
+ swc_bundler::Config::default(),
+ hook,
+ );
+ let mut entries = HashMap::new();
+ entries.insert(
+ "bundle".to_string(),
+ swc_common::FileName::Custom(root_specifier.to_string()),
+ );
+ let output = bundler
+ .bundle(entries)
+ .context("Unable to output bundle during Graph2::bundle().")?;
+ let mut buf = Vec::new();
+ {
+ let mut emitter = swc_ecmascript::codegen::Emitter {
+ cfg: swc_ecmascript::codegen::Config { minify: false },
+ cm: cm.clone(),
+ comments: None,
+ wr: Box::new(swc_ecmascript::codegen::text_writer::JsWriter::new(
+ cm, "\n", &mut buf, None,
+ )),
+ };
+
+ emitter
+ .emit_module(&output[0].module)
+ .context("Unable to emit bundle during Graph2::bundle().")?;
+ }
+
+ let s = String::from_utf8(buf)
+ .context("Emitted bundle is an invalid utf-8 string.")?;
+ let stats = Stats(vec![
+ ("Files".to_string(), self.modules.len() as u128),
+ ("Total time".to_string(), start.elapsed().as_millis()),
+ ]);
+
+ Ok((s, stats, maybe_ignored_options))
+ }
+
fn contains_module(&self, specifier: &ModuleSpecifier) -> bool {
let s = self.resolve_specifier(specifier);
self.modules.contains_key(s)
@@ -725,16 +855,7 @@ impl Graph2 {
let maybe_ignored_options =
ts_config.merge_user_config(options.maybe_config_path)?;
- let compiler_options = ts_config.as_transpile_config()?;
- let check_js = compiler_options.check_js;
- let transform_jsx = compiler_options.jsx == "react";
- let emit_options = ast::TranspileOptions {
- emit_metadata: compiler_options.emit_decorator_metadata,
- inline_source_map: true,
- jsx_factory: compiler_options.jsx_factory,
- jsx_fragment_factory: compiler_options.jsx_fragment_factory,
- transform_jsx,
- };
+ let emit_options: EmitOptions = ts_config.clone().into();
let mut emit_count: u128 = 0;
for (_, module) in self.modules.iter_mut() {
@@ -748,7 +869,7 @@ impl Graph2 {
}
// if we don't have check_js enabled, we won't touch non TypeScript
// modules
- if !(check_js
+ if !(emit_options.check_js
|| module.media_type == MediaType::TSX
|| module.media_type == MediaType::TypeScript)
{
@@ -781,6 +902,27 @@ impl Graph2 {
}
}
+impl swc_bundler::Resolve for Graph2 {
+ fn resolve(
+ &self,
+ referrer: &swc_common::FileName,
+ specifier: &str,
+ ) -> Result<swc_common::FileName, AnyError> {
+ let referrer = if let swc_common::FileName::Custom(referrer) = referrer {
+ ModuleSpecifier::resolve_url_or_path(referrer)
+ .context("Cannot resolve swc FileName to a module specifier")?
+ } else {
+ unreachable!(
+ "An unexpected referrer was passed when bundling: {:?}",
+ referrer
+ )
+ };
+ let specifier = self.resolve(specifier, &referrer)?;
+
+ Ok(swc_common::FileName::Custom(specifier.to_string()))
+ }
+}
+
/// A structure for building a dependency graph of modules.
pub struct GraphBuilder2 {
fetched: HashSet<ModuleSpecifier>,
@@ -1095,6 +1237,50 @@ pub mod tests {
}
#[tokio::test]
+ async fn test_graph_bundle() {
+ let tests = vec![
+ ("file:///tests/fixture01.ts", "fixture01.out"),
+ ("file:///tests/fixture02.ts", "fixture02.out"),
+ ("file:///tests/fixture03.ts", "fixture03.out"),
+ ("file:///tests/fixture04.ts", "fixture04.out"),
+ ("file:///tests/fixture05.ts", "fixture05.out"),
+ ("file:///tests/fixture06.ts", "fixture06.out"),
+ ("file:///tests/fixture07.ts", "fixture07.out"),
+ ("file:///tests/fixture08.ts", "fixture08.out"),
+ ("file:///tests/fixture09.ts", "fixture09.out"),
+ ("file:///tests/fixture10.ts", "fixture10.out"),
+ ("file:///tests/fixture11.ts", "fixture11.out"),
+ ("file:///tests/fixture12.ts", "fixture12.out"),
+ ("file:///tests/fixture13.ts", "fixture13.out"),
+ ("file:///tests/fixture14.ts", "fixture14.out"),
+ ];
+ let c = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
+ let fixtures = c.join("tests/bundle");
+
+ for (specifier, expected_str) in tests {
+ let specifier = ModuleSpecifier::resolve_url_or_path(specifier).unwrap();
+ let handler = Rc::new(RefCell::new(MockSpecifierHandler {
+ fixtures: fixtures.clone(),
+ ..MockSpecifierHandler::default()
+ }));
+ let mut builder = GraphBuilder2::new(handler.clone(), None);
+ builder
+ .insert(&specifier)
+ .await
+ .expect("module not inserted");
+ let graph = builder.get_graph(&None).expect("could not get graph");
+ let (actual, stats, maybe_ignored_options) = graph
+ .bundle(BundleOptions::default())
+ .expect("could not bundle");
+ assert_eq!(stats.0.len(), 2);
+ assert_eq!(maybe_ignored_options, None);
+ let expected_path = fixtures.join(expected_str);
+ let expected = fs::read_to_string(expected_path).unwrap();
+ assert_eq!(actual, expected, "fixture: {}", specifier);
+ }
+ }
+
+ #[tokio::test]
async fn test_graph_info() {
let c = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
let fixtures = c.join("tests/module_graph");