diff --git a/.changeset/moody-houses-judge.md b/.changeset/moody-houses-judge.md new file mode 100644 index 000000000000..e95555e19e96 --- /dev/null +++ b/.changeset/moody-houses-judge.md @@ -0,0 +1,6 @@ +--- +swc_core: minor +swc: minor +--- + +feat(es): Add Rust plugin host part for analysis API diff --git a/.vscode/settings.json b/.vscode/settings.json index 76c8e2a55894..0303457075ae 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -23,5 +23,8 @@ "crates/swc_ecma_transforms_proposal/tests/decorator-tests" ], "eslint.enable": false, - "rust-analyzer.check.command": "clippy" -} + "rust-analyzer.check.command": "clippy", + "rust-analyzer.cargo.features": [ + "plugin" + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 0e60a950ab6e..b40b93270ab1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4593,9 +4593,9 @@ dependencies = [ "napi-derive", "once_cell", "par-core", + "par-iter", "parking_lot", "pathdiff", - "rayon", "regex", "rustc-hash 2.1.0", "serde", diff --git a/crates/swc/Cargo.toml b/crates/swc/Cargo.toml index 25adc04d7453..e8c015a66841 100644 --- a/crates/swc/Cargo.toml +++ b/crates/swc/Cargo.toml @@ -60,6 +60,7 @@ jsonc-parser = { workspace = true, features = ["serde"] } lru = { workspace = true } once_cell = { workspace = true } par-core = { workspace = true } +par-iter = { workspace = true } parking_lot = { workspace = true } pathdiff = { workspace = true } regex = { workspace = true } @@ -70,6 +71,7 @@ sourcemap = { workspace = true } tracing = { workspace = true } url = { workspace = true } + swc_atoms = { version = "5.0.0", path = "../swc_atoms" } swc_cached = { version = "2.0.0", path = "../swc_cached" } swc_common = { version = "8.0.1", path = "../swc_common", features = [ @@ -133,11 +135,12 @@ ansi_term = { workspace = true } criterion = { workspace = true } flate2 = { workspace = true } humansize = { workspace = true } -rayon = { workspace = true } walkdir = { workspace = true } codspeed-criterion-compat = { workspace = true } +par-core = { workspace = true, features = ["chili"] } + swc_ecma_ast = { version = "8.1.0", path = "../swc_ecma_ast", features = [ "serde-impl", ] } diff --git a/crates/swc/src/config/mod.rs b/crates/swc/src/config/mod.rs index 2aedabeef30d..c9c134b3ede5 100644 --- a/crates/swc/src/config/mod.rs +++ b/crates/swc/src/config/mod.rs @@ -624,10 +624,10 @@ impl Options { for plugin_config in plugins.iter() { let plugin_name = &plugin_config.0; - if !inner_cache.contains(&plugin_name) { + if !inner_cache.contains(plugin_name) { let resolved_path = plugin_resolver.resolve( - &FileName::Real(PathBuf::from(&plugin_name)), - &plugin_name, + &FileName::Real(PathBuf::from(plugin_name)), + plugin_name, )?; let path = if let FileName::Real(value) = resolved_path.filename { @@ -636,7 +636,7 @@ impl Options { anyhow::bail!("Failed to resolve plugin path: {:?}", resolved_path); }; - inner_cache.store_bytes_from_path(&path, &plugin_name)?; + inner_cache.store_bytes_from_path(&path, plugin_name)?; tracing::debug!("Initialized WASM plugin {plugin_name}"); } } @@ -1731,7 +1731,7 @@ impl GlobalPassOption { } } -fn default_env_name() -> String { +pub(crate) fn default_env_name() -> String { if let Ok(v) = env::var("SWC_ENV") { return v; } diff --git a/crates/swc/src/lib.rs b/crates/swc/src/lib.rs index 2b8ec7014b24..922216509935 100644 --- a/crates/swc/src/lib.rs +++ b/crates/swc/src/lib.rs @@ -170,6 +170,7 @@ mod builder; pub mod config; mod dropped_comments_preserver; mod plugin; +mod wasm_analysis; pub mod resolver { use std::path::PathBuf; diff --git a/crates/swc/src/plugin.rs b/crates/swc/src/plugin.rs index ce9ef9930489..34fc1386b031 100644 --- a/crates/swc/src/plugin.rs +++ b/crates/swc/src/plugin.rs @@ -115,7 +115,7 @@ impl RustPlugins { .unwrap() .lock() .get_fs_cache_root() - .map(|v| std::path::PathBuf::from(v)), + .map(std::path::PathBuf::from), ); let mut transform_plugin_executor = swc_plugin_runner::create_plugin_transform_executor( diff --git a/crates/swc/src/wasm_analysis.rs b/crates/swc/src/wasm_analysis.rs new file mode 100644 index 000000000000..4c6681e2699e --- /dev/null +++ b/crates/swc/src/wasm_analysis.rs @@ -0,0 +1,136 @@ +#![cfg(feature = "plugin")] + +use std::sync::Arc; + +use anyhow::{Context, Result}; +use common::{ + comments::SingleThreadedComments, + errors::Handler, + plugin::{metadata::TransformPluginMetadataContext, serialized::PluginSerializedBytes}, + Mark, SourceFile, GLOBALS, +}; +use par_iter::iter::{IntoParallelRefIterator, ParallelIterator}; +use serde::Deserialize; +use swc_config::IsModule; +use swc_ecma_ast::EsVersion; +use swc_ecma_parser::Syntax; +use swc_ecma_transforms::resolver; + +use crate::{plugin::PluginConfig, Compiler}; + +impl Compiler { + /// Run analysis using Wasm plugins. + pub fn run_wasm_analysis( + &self, + fm: Arc, + handler: &Handler, + opts: &WasmAnalysisOptions, + comments: SingleThreadedComments, + ) -> Result { + self.run(|| { + GLOBALS.with(|globals| { + let unresolved_mark = Mark::new(); + let top_level_mark = Mark::new(); + + let mut program = self.parse_js( + fm.clone(), + handler, + EsVersion::latest(), + opts.parser, + opts.module, + Some(&comments), + )?; + + program.mutate(resolver( + unresolved_mark, + top_level_mark, + opts.parser.typescript(), + )); + + let serialized = { + let _span = tracing::span!(tracing::Level::INFO, "serialize_program").entered(); + let program = + swc_common::plugin::serialized::VersionedSerializable::new(program); + PluginSerializedBytes::try_serialize(&program)? + }; + + let transform_metadata_context = Arc::new(TransformPluginMetadataContext::new( + Some(fm.name.to_string()), + crate::config::default_env_name(), + None, + )); + + let result = opts + .plugins + .par_iter() + .map(|p| { + GLOBALS.set(globals, || { + let plugin_module_bytes = crate::config::PLUGIN_MODULE_CACHE + .inner + .get() + .unwrap() + .lock() + .get(&p.0) + .expect("plugin module should be loaded"); + + let plugin_name = plugin_module_bytes.get_module_name().to_string(); + let runtime = swc_plugin_runner::wasix_runtime::build_wasi_runtime( + crate::config::PLUGIN_MODULE_CACHE + .inner + .get() + .unwrap() + .lock() + .get_fs_cache_root() + .map(std::path::PathBuf::from), + ); + let mut transform_plugin_executor = + swc_plugin_runner::create_plugin_transform_executor( + &self.cm, + &unresolved_mark, + &transform_metadata_context, + plugin_module_bytes, + Some(p.1.clone()), + runtime, + ); + + let span = tracing::span!( + tracing::Level::INFO, + "execute_plugin_runner", + plugin_module = p.0.as_str() + ) + .entered(); + + let (result, output) = swc_transform_common::output::capture(|| { + transform_plugin_executor + .transform(&serialized, Some(true)) + .with_context(|| { + format!( + "failed to invoke `{}` as js analysis plugin at {}", + &p.0, plugin_name + ) + }) + }); + result?; + drop(span); + + Ok(output) + }) + }) + .collect::>>()?; + + serde_json::to_string(&result) + .map_err(|e| anyhow::anyhow!("Failed to serialize output: {e}")) + }) + }) + } +} + +#[derive(Debug, Deserialize)] +pub struct WasmAnalysisOptions { + #[serde(default)] + pub parser: Syntax, + #[serde(default)] + pub module: IsModule, + + pub plugins: Vec, +} diff --git a/crates/swc/tests/projects.rs b/crates/swc/tests/projects.rs index 55a50e7cba7d..a8226fde6443 100644 --- a/crates/swc/tests/projects.rs +++ b/crates/swc/tests/projects.rs @@ -5,7 +5,7 @@ use std::{ }; use anyhow::Context; -use rayon::prelude::*; +use par_iter::prelude::*; use swc::{ config::{ Config, FileMatcher, JsMinifyOptions, JscConfig, ModuleConfig, Options, Paths, diff --git a/crates/swc_plugin_runner/benches/ecma_invoke.rs b/crates/swc_plugin_runner/benches/ecma_invoke.rs index 8608b583b04f..d47d00e40cff 100644 --- a/crates/swc_plugin_runner/benches/ecma_invoke.rs +++ b/crates/swc_plugin_runner/benches/ecma_invoke.rs @@ -10,6 +10,7 @@ use std::{ }; use codspeed_criterion_compat::{black_box, criterion_group, criterion_main, Bencher, Criterion}; +use rustc_hash::FxHashMap; #[cfg(feature = "__rkyv")] use swc_common::plugin::serialized::{PluginSerializedBytes, VersionedSerializable}; use swc_common::{ diff --git a/crates/swc_plugin_runner/tests/css_rkyv.rs b/crates/swc_plugin_runner/tests/css_rkyv.rs index c4bbaa9adfd8..e0e2dfbf482d 100644 --- a/crates/swc_plugin_runner/tests/css_rkyv.rs +++ b/crates/swc_plugin_runner/tests/css_rkyv.rs @@ -80,7 +80,7 @@ fn invoke(input: PathBuf) { tokio::runtime::Runtime::new().unwrap().block_on(async { // run single plugin testing::run_test(false, |cm, _handler| { - let fm = cm.new_source_file(FileName::Anon.into(), "console.log(foo)".into()); + let fm = cm.load_file(&input).unwrap(); let parsed: Stylesheet = swc_css_parser::parse_file(&fm, None, Default::default(), &mut Vec::new()).unwrap();