next-swc: Add .bundle() (#30935)

This commit is contained in:
Donny/강동윤 2021-11-04 21:23:25 +09:00 committed by GitHub
parent 5684edb36f
commit e8e4210f9f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 297 additions and 9 deletions

View file

@ -159,6 +159,12 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "build_const"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.4.3" version = "1.4.3"
@ -214,6 +220,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "crc"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb"
dependencies = [
"build_const",
]
[[package]] [[package]]
name = "crossbeam-channel" name = "crossbeam-channel"
version = "0.5.1" version = "0.5.1"
@ -364,6 +379,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "fixedbitset"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@ -506,6 +527,7 @@ checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
dependencies = [ dependencies = [
"autocfg 1.0.1", "autocfg 1.0.1",
"hashbrown", "hashbrown",
"rayon",
"serde", "serde",
] ]
@ -728,6 +750,7 @@ dependencies = [
"napi", "napi",
"napi-build", "napi-build",
"napi-derive", "napi-derive",
"once_cell",
"path-clean", "path-clean",
"pathdiff", "pathdiff",
"regex", "regex",
@ -737,8 +760,10 @@ dependencies = [
"serde_json", "serde_json",
"swc", "swc",
"swc_atoms", "swc_atoms",
"swc_bundler",
"swc_common", "swc_common",
"swc_css", "swc_css",
"swc_ecma_loader",
"swc_ecma_preset_env", "swc_ecma_preset_env",
"swc_ecma_transforms_testing", "swc_ecma_transforms_testing",
"swc_ecmascript", "swc_ecmascript",
@ -913,6 +938,16 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "petgraph"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7"
dependencies = [
"fixedbitset",
"indexmap",
]
[[package]] [[package]]
name = "phf" name = "phf"
version = "0.8.0" version = "0.8.0"
@ -1046,6 +1081,12 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "radix_fmt"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce082a9940a7ace2ad4a8b7d0b1eac6aa378895f18be598230c5f2284ac05426"
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.6.5" version = "0.6.5"
@ -1631,6 +1672,38 @@ dependencies = [
"string_cache_codegen", "string_cache_codegen",
] ]
[[package]]
name = "swc_bundler"
version = "0.79.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52993195a4365d22ec362b26b6d5f64d9f51ffe1a2bb93a6b68e64648b1193c6"
dependencies = [
"ahash",
"anyhow",
"crc",
"dashmap",
"indexmap",
"is-macro",
"once_cell",
"parking_lot 0.11.2",
"petgraph",
"radix_fmt",
"rayon",
"relative-path",
"retain_mut",
"swc_atoms",
"swc_common",
"swc_ecma_ast",
"swc_ecma_codegen",
"swc_ecma_loader",
"swc_ecma_parser",
"swc_ecma_transforms_base",
"swc_ecma_transforms_optimization",
"swc_ecma_utils",
"swc_ecma_visit",
"tracing",
]
[[package]] [[package]]
name = "swc_common" name = "swc_common"
version = "0.14.3" version = "0.14.3"
@ -1938,6 +2011,7 @@ checksum = "6d47323456ee73ecffa584a3964cc82dbf3daee2c7c72221d1f2130d3e42312d"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"phf", "phf",
"rayon",
"scoped-tls", "scoped-tls",
"smallvec 1.7.0", "smallvec 1.7.0",
"swc_atoms", "swc_atoms",
@ -2032,6 +2106,7 @@ dependencies = [
"dashmap", "dashmap",
"indexmap", "indexmap",
"once_cell", "once_cell",
"rayon",
"retain_mut", "retain_mut",
"serde_json", "serde_json",
"swc_atoms", "swc_atoms",
@ -2137,6 +2212,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8762b5fccb1bbb5f4bcb83eb6668a78b3861ae8df692c76e75cc33605d611480" checksum = "8762b5fccb1bbb5f4bcb83eb6668a78b3861ae8df692c76e75cc33605d611480"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"rayon",
"swc_atoms", "swc_atoms",
"swc_common", "swc_common",
"swc_ecma_ast", "swc_ecma_ast",

View file

@ -11,26 +11,28 @@ anyhow = "1.0"
backtrace = "0.3" backtrace = "0.3"
chrono = "0.4" chrono = "0.4"
easy-error = "1.0.0" easy-error = "1.0.0"
napi = { version = "1", features = ["serde-json"] } fxhash = "0.2.1"
napi = {version = "1", features = ["serde-json"]}
napi-derive = "1" napi-derive = "1"
once_cell = "1.8.0"
path-clean = "0.1" path-clean = "0.1"
pathdiff = "0.2.0"
regex = "1.5" regex = "1.5"
retain_mut = "0.1.3"
rustc-hash = "1.1.0"
serde = "1" serde = "1"
serde_json = "1" serde_json = "1"
swc = "0.81.1" swc = "0.81.1"
swc_atoms = "0.2.7" swc_atoms = "0.2.7"
swc_common = { version = "0.14.2", features = ["concurrent", "sourcemap"] } swc_bundler = {version = "0.79.0", features = ["concurrent"]}
swc_common = {version = "0.14.2", features = ["concurrent", "sourcemap"]}
swc_css = "0.20.0" swc_css = "0.20.0"
swc_ecmascript = { version = "0.84.1", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] } swc_ecma_loader = {version = "0.23.0", features = ["node", "lru"]}
swc_ecma_preset_env = "0.63.1" swc_ecma_preset_env = "0.63.1"
swc_ecmascript = {version = "0.84.1", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"]}
swc_node_base = "0.5.1" swc_node_base = "0.5.1"
swc_stylis = "0.17.0" swc_stylis = "0.17.0"
fxhash = "0.2.1" tracing = {version = "0.1.28", features = ["release_max_level_off"]}
retain_mut = "0.1.3"
pathdiff = "0.2.0"
rustc-hash = "1.1.0"
tracing = { version = "0.1.28", features = ["release_max_level_off"] }
[build-dependencies] [build-dependencies]
napi-build = "1" napi-build = "1"

View file

@ -91,6 +91,10 @@ export function minifySync(src, opts) {
return bindings.minifySync(toBuffer(src), toBuffer(opts ?? {})) return bindings.minifySync(toBuffer(src), toBuffer(opts ?? {}))
} }
export async function bundle(options) {
return bindings.bundle(toBuffer(options))
}
module.exports.transform = transform module.exports.transform = transform
module.exports.transformSync = transformSync module.exports.transformSync = transformSync
module.exports.minify = minify module.exports.minify = minify

View file

@ -0,0 +1,203 @@
use crate::{
complete_output, get_compiler,
util::{CtxtExt, MapErr},
};
use anyhow::{anyhow, bail, Context, Error};
use napi::{CallContext, JsObject, Task};
use once_cell::sync::Lazy;
use serde::Deserialize;
use std::{collections::HashMap, path::PathBuf, sync::Arc};
use swc::{
config::{util::BoolOrObject, SourceMapsConfig},
try_with_handler, TransformOutput,
};
use swc_atoms::JsWord;
use swc_bundler::{Bundler, ModuleData, ModuleRecord};
use swc_common::{
collections::AHashMap, errors::Handler, BytePos, FileName, SourceMap, Span, DUMMY_SP,
};
use swc_ecma_loader::{
resolvers::{lru::CachingResolver, node::NodeModulesResolver},
NODE_BUILTINS,
};
use swc_ecmascript::{
ast::*,
parser::{lexer::Lexer, EsConfig, Parser, StringInput, Syntax},
visit::{noop_visit_type, Node, Visit, VisitWith},
};
#[js_function(1)]
pub fn bundle(cx: CallContext) -> napi::Result<JsObject> {
let option = cx.get_buffer_as_string(0)?;
let task = BundleTask {
c: get_compiler(&cx),
config: option,
};
cx.env.spawn(task).map(|t| t.promise_object())
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
struct BundleOption {
entry: PathBuf,
}
struct BundleTask {
c: Arc<swc::Compiler>,
config: String,
}
impl Task for BundleTask {
type Output = TransformOutput;
type JsValue = JsObject;
fn compute(&mut self) -> napi::Result<Self::Output> {
let option: BundleOption = crate::util::deserialize_json(&self.config).convert_err()?;
try_with_handler(self.c.cm.clone(), true, |handler| {
let builtins = NODE_BUILTINS
.to_vec()
.into_iter()
.map(JsWord::from)
.collect::<Vec<_>>();
//
let mut bundler = Bundler::new(
&self.c.globals(),
self.c.cm.clone(),
CustomLoader {
cm: self.c.cm.clone(),
handler: &handler,
},
make_resolver(),
swc_bundler::Config {
require: false,
disable_inliner: false,
external_modules: builtins,
module: swc_bundler::ModuleType::Es,
},
Box::new(CustomHook),
);
let mut entries = HashMap::default();
let path: PathBuf = option.entry.into();
let path = path
.canonicalize()
.context("failed to canonicalize entry file")?;
entries.insert("main".to_string(), FileName::Real(path));
let outputs = bundler.bundle(entries)?;
let output = outputs
.into_iter()
.next()
.ok_or_else(|| anyhow!("swc_bundler::Bundle::bundle returned empty result"))?;
let source_map_names = {
let mut v = SourceMapIdentCollector {
names: Default::default(),
};
output
.module
.visit_with(&Invalid { span: DUMMY_SP }, &mut v);
v.names
};
let code = self.c.print(
&output.module,
None,
None,
true,
EsVersion::Es5,
SourceMapsConfig::Bool(true),
&source_map_names,
None,
false,
Some(BoolOrObject::Bool(true)),
)?;
Ok(code)
})
.convert_err()
}
fn resolve(self, env: napi::Env, output: Self::Output) -> napi::Result<Self::JsValue> {
complete_output(&env, output)
}
}
type Resolver = Arc<CachingResolver<NodeModulesResolver>>;
fn make_resolver() -> Resolver {
static CACHE: Lazy<Resolver> = Lazy::new(|| {
// TODO: Make target env and alias configurable
let r = NodeModulesResolver::new(TargetEnv::Node, Default::default());
let r = CachingResolver::new(256, r);
Arc::new(r)
});
(*CACHE).clone()
}
struct CustomLoader<'a> {
handler: &'a Handler,
cm: Arc<SourceMap>,
}
impl swc_bundler::Load for CustomLoader<'_> {
fn load(&self, f: &FileName) -> Result<ModuleData, Error> {
let fm = match f {
FileName::Real(path) => self.cm.load_file(&path)?,
_ => unreachable!(),
};
let lexer = Lexer::new(
Syntax::Es(EsConfig {
..Default::default()
}),
EsVersion::Es2020,
StringInput::from(&*fm),
None,
);
let mut parser = Parser::new_from(lexer);
let module = parser.parse_module().map_err(|err| {
err.into_diagnostic(&self.handler).emit();
anyhow!("failed to parse")
})?;
Ok(ModuleData {
fm,
module,
helpers: Default::default(),
})
}
}
struct CustomHook;
impl swc_bundler::Hook for CustomHook {
fn get_import_meta_props(
&self,
_span: Span,
_module_record: &ModuleRecord,
) -> Result<Vec<KeyValueProp>, Error> {
bail!("`import.meta` is not supported yet")
}
}
pub struct SourceMapIdentCollector {
names: AHashMap<BytePos, JsWord>,
}
impl Visit for SourceMapIdentCollector {
noop_visit_type!();
fn visit_ident(&mut self, ident: &Ident, _: &dyn Node) {
self.names.insert(ident.span.lo, ident.sym.clone());
}
}

View file

@ -50,6 +50,7 @@ use swc_ecmascript::{
pub mod amp_attributes; pub mod amp_attributes;
mod auto_cjs; mod auto_cjs;
mod bundle;
pub mod hook_optimizer; pub mod hook_optimizer;
pub mod minify; pub mod minify;
pub mod next_dynamic; pub mod next_dynamic;
@ -110,6 +111,8 @@ fn init(mut exports: JsObject) -> napi::Result<()> {
})); }));
} }
exports.create_named_method("bundle", bundle::bundle)?;
exports.create_named_method("transform", transform::transform)?; exports.create_named_method("transform", transform::transform)?;
exports.create_named_method("transformSync", transform::transform_sync)?; exports.create_named_method("transformSync", transform::transform_sync)?;