diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index aeb75fe21061..ee453f323315 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -168,8 +168,7 @@ jobs: abi: msvc arch: x86_64: {} - aarch64: - build_task: build-native-no-plugin-release + aarch64: {} linux: host: "['self-hosted', 'linux', 'x64', 'metal']" docker: next-swc-builder:latest diff --git a/.github/workflows/setup-nextjs-build.yml b/.github/workflows/setup-nextjs-build.yml index 5e8e81d8a44c..fed51f73766a 100644 --- a/.github/workflows/setup-nextjs-build.yml +++ b/.github/workflows/setup-nextjs-build.yml @@ -88,7 +88,7 @@ jobs: - name: Build next-swc run: | - hyperfine --min-runs 1 --show-output 'pnpm run --filter=@next/swc build-native --features plugin --release' + hyperfine --min-runs 1 --show-output 'pnpm run --filter=@next/swc build-native --release' echo "Successfully built next-swc with published turbopack" - name: Build next.js diff --git a/Cargo.lock b/Cargo.lock index 17d2308153c2..4f6ec6c7942d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4989,7 +4989,6 @@ dependencies = [ "turbo-unix-path", "turbopack-core", "turbopack-ecmascript-hmr-protocol", - "turbopack-ecmascript-plugins", "turbopack-node", "turbopack-trace-server", "turbopack-trace-utils", diff --git a/Cargo.toml b/Cargo.toml index 209c0ed6f3d8..98f22314d045 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,7 +87,7 @@ inherits = "release" debug = true # Size-optimized crates: determined via `cargo bloat --release --crates -p next-napi-bindings`. -# Wasmtime/cranelift crates are used by the plugin feature (wasm builds). +# Wasmtime/cranelift crates are used by the SWC plugin transform system. [profile.release.package.browserslist-rs] opt-level = "s" diff --git a/crates/next-core/Cargo.toml b/crates/next-core/Cargo.toml index 8c1642278414..2f91b8133c2d 100644 --- a/crates/next-core/Cargo.toml +++ b/crates/next-core/Cargo.toml @@ -92,11 +92,6 @@ default = ["process_pool"] process_pool = ["turbopack-node/process_pool"] worker_pool = ["turbopack-node/worker_pool"] next-font-local = [] -plugin = [ - "swc_core/__plugin_transform_host", - "swc_core/__plugin_transform_host_schema_v1", - "turbopack-ecmascript-plugins/swc_ecma_transform_plugin", -] image-webp = ["turbopack-image/webp"] image-avif = ["turbopack-image/avif"] diff --git a/crates/next-core/src/next_shared/transforms/swc_ecma_transform_plugins.rs b/crates/next-core/src/next_shared/transforms/swc_ecma_transform_plugins.rs index 93f023917f69..bd33a478ad0b 100644 --- a/crates/next-core/src/next_shared/transforms/swc_ecma_transform_plugins.rs +++ b/crates/next-core/src/next_shared/transforms/swc_ecma_transform_plugins.rs @@ -1,10 +1,8 @@ use anyhow::Result; -#[allow(unused_imports)] use turbo_rcstr::RcStr; use turbo_tasks::Vc; use turbo_tasks_fs::FileSystemPath; use turbopack::module_options::ModuleRule; -#[allow(unused_imports)] use turbopack_core::{context::AssetContext, resolve::origin::ResolveOrigin}; use crate::next_config::NextConfig; @@ -15,30 +13,19 @@ pub async fn get_swc_ecma_transform_plugin_rule( ) -> Result> { let plugin_configs = next_config.experimental_swc_plugins().await?; if !plugin_configs.is_empty() { - #[cfg(feature = "plugin")] - { - let enable_mdx_rs = next_config.mdx_rs().await?.is_some(); - get_swc_ecma_transform_rule_impl(project_path, &plugin_configs, enable_mdx_rs).await - } - - #[cfg(not(feature = "plugin"))] - { - let _ = project_path; // To satisfy lint - Ok(None) - } + let enable_mdx_rs = next_config.mdx_rs().await?.is_some(); + get_swc_ecma_transform_rule_impl(project_path, &plugin_configs, enable_mdx_rs).await } else { Ok(None) } } /// A resolve origin without any asset_context, intended for handle_resolve_error -#[cfg(feature = "plugin")] #[turbo_tasks::value] pub struct DummyResolveOrigin { origin_path: FileSystemPath, } -#[cfg(feature = "plugin")] #[turbo_tasks::value_impl] impl DummyResolveOrigin { #[turbo_tasks::function] @@ -47,7 +34,6 @@ impl DummyResolveOrigin { } } -#[cfg(feature = "plugin")] #[turbo_tasks::value_impl] impl ResolveOrigin for DummyResolveOrigin { #[turbo_tasks::function] @@ -61,7 +47,6 @@ impl ResolveOrigin for DummyResolveOrigin { } } -#[cfg(feature = "plugin")] pub async fn get_swc_ecma_transform_rule_impl( project_path: FileSystemPath, plugin_configs: &[(RcStr, serde_json::Value)], diff --git a/crates/next-custom-transforms/Cargo.toml b/crates/next-custom-transforms/Cargo.toml index b9ef422401f8..a6db59521da8 100644 --- a/crates/next-custom-transforms/Cargo.toml +++ b/crates/next-custom-transforms/Cargo.toml @@ -4,20 +4,6 @@ name = "next-custom-transforms" version = "0.0.0" publish = false -[features] -plugin = [ - "swc_core/__plugin_transform_host", - "swc_core/__plugin_transform_host_schema_v1", - "turbopack-ecmascript-plugins/swc_ecma_transform_plugin", -] - -[package.metadata.cargo-shear] -ignored = [ - # when using the `plugin` feature, we need to set a feature flag on `turbopack-ecmascript-plugins` - # so we must list it as a dependency even though we don't directly use it - "turbopack-ecmascript-plugins", -] - [lints] workspace = true @@ -64,7 +50,6 @@ styled_components = { workspace = true } styled_jsx = { workspace = true } swc_emotion = { workspace = true } swc_relay = { workspace = true } -turbopack-ecmascript-plugins = { workspace = true, optional = true } turbo-rcstr = { workspace = true } urlencoding = { workspace = true } next-taskless = { workspace = true } @@ -75,7 +60,6 @@ preset_env_base = { workspace = true } [dev-dependencies] testing = { workspace = true } -# effectively enable the "plugin" feature for tests swc_core = { workspace = true, features = ["testing_transform", "__plugin_transform_host", "__plugin_transform_host_schema_v1"] } swc_plugin_backend_wasmtime = { workspace = true } -turbopack-ecmascript-plugins = { workspace = true, features = ["swc_ecma_transform_plugin"] } +turbopack-ecmascript-plugins = { workspace = true } diff --git a/crates/next-napi-bindings/Cargo.toml b/crates/next-napi-bindings/Cargo.toml index 07cfff4f06d2..4652ed2d0b8b 100644 --- a/crates/next-napi-bindings/Cargo.toml +++ b/crates/next-napi-bindings/Cargo.toml @@ -8,23 +8,6 @@ publish = false crate-type = ["cdylib"] [features] -# Instead of enabling all the plugin-related functionality by default, make it -# overridable when built (i.e napi --build --features plugin). This is due to -# some of transitive dependencies have features cannot be enabled at the same -# time (i.e wasmer/default vs wasmer/js-default) while cargo merges all the -# features at once. -plugin = [ - # Use granular plugin features to avoid pulling in wasmer via __plugin_transform_env_native. - "swc_core/__plugin_transform_host", - "swc_core/__plugin_transform_host_schema_v1", - "swc_core/plugin_transform_host_native_filesystem_cache", - "dep:swc_plugin_backend_wasmtime", - "next-custom-transforms/plugin", - "next-core/plugin", - "turbopack-ecmascript-plugins", - "turbopack-ecmascript-plugins/swc_ecma_transform_plugin", -] - image-webp = ["next-core/image-webp"] image-avif = ["next-core/image-avif"] # Enable all the available image codec support. @@ -50,11 +33,6 @@ ignored = [ # we need to set features on these packages when building for WASM, but we don't directly use them "getrandom", "iana-time-zone", - # the plugins feature needs to set a feature on this transitively depended-on package, we never - # directly import it - "turbopack-ecmascript-plugins", - # used conditionally behind the `plugin` feature flag - "swc_plugin_backend_wasmtime", ] [dependencies] @@ -105,13 +83,16 @@ swc_core = { workspace = true, features = [ "ecma_transforms_typescript", "ecma_utils", "ecma_visit", + "__plugin_transform_host", + "__plugin_transform_host_schema_v1", + "plugin_transform_host_native_filesystem_cache", ] } # Dependencies for the native, non-wasm32 build. [target.'cfg(not(target_arch = "wasm32"))'.dependencies] lightningcss-napi = { workspace = true } -swc_plugin_backend_wasmtime = { workspace = true, optional = true } lightningcss = { workspace = true } +swc_plugin_backend_wasmtime = { workspace = true } tokio = { workspace = true, features = ["full"] } turbo-rcstr = { workspace = true, features = ["napi"] } turbo-tasks = { workspace = true } @@ -132,7 +113,6 @@ turbopack-core = { workspace = true } turbopack-ecmascript-hmr-protocol = { workspace = true } turbopack-trace-utils = { workspace = true } turbopack-trace-server = { workspace = true } -turbopack-ecmascript-plugins = { workspace = true, optional = true } turbopack-node = { workspace = true, default-features = false, features = ["worker_pool"] } [target.'cfg(windows)'.dependencies] diff --git a/crates/next-napi-bindings/src/transform.rs b/crates/next-napi-bindings/src/transform.rs index 74e6d93304ac..fba21f54b140 100644 --- a/crates/next-napi-bindings/src/transform.rs +++ b/crates/next-napi-bindings/src/transform.rs @@ -31,6 +31,7 @@ use std::{ fs::read_to_string, panic::{AssertUnwindSafe, catch_unwind}, rc::Rc, + sync::Arc, }; use anyhow::{Context as _, anyhow, bail}; @@ -41,10 +42,11 @@ use once_cell::sync::Lazy; use rustc_hash::{FxHashMap, FxHashSet}; use swc_core::{ atoms::Atom, - base::{Compiler, TransformOutput, try_with_handler}, + base::{Compiler, TransformOutput, config::RuntimeOptions, try_with_handler}, common::{FileName, GLOBALS, Mark, comments::SingleThreadedComments, errors::ColorConfig}, ecma::ast::noop_pass, }; +use swc_plugin_backend_wasmtime::WasmtimeRuntime; use crate::{complete_output, get_compiler, util::MapErr}; @@ -128,15 +130,8 @@ impl Task for TransformTask { let unresolved_mark = Mark::new(); let mut options = options.patch(&fm); options.swc.unresolved_mark = Some(unresolved_mark); - - #[cfg(feature = "plugin")] - { - options.swc.runtime_options = - swc_core::base::config::RuntimeOptions::default() - .plugin_runtime(std::sync::Arc::new( - swc_plugin_backend_wasmtime::WasmtimeRuntime, - )); - } + options.swc.runtime_options = + RuntimeOptions::default().plugin_runtime(Arc::new(WasmtimeRuntime)); let cm = self.c.cm.clone(); let file = fm.clone(); diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index cffe25b53062..0a61e64f1aac 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -8,11 +8,9 @@ "scripts": { "build-native-auto": "node maybe-build-native.mjs", "clean": "node ../../scripts/rm.mjs native", - "build-native": "napi build --platform -p next-napi-bindings --cargo-cwd ../../ --cargo-name next_napi_bindings --features plugin,image-extended --js false native", - "build-native-release": "napi build --platform -p next-napi-bindings --cargo-cwd ../../ --cargo-name next_napi_bindings --release --features plugin,image-extended,tracing/release_max_level_trace --js false native", - "build-native-release-with-assertions": "napi build --platform -p next-napi-bindings --cargo-cwd ../../ --cargo-name next_napi_bindings --profile release-with-assertions --features plugin,image-extended,tracing/release_max_level_trace --js false native", - "build-native-no-plugin": "napi build --platform -p next-napi-bindings --cargo-cwd ../../ --cargo-name next_napi_bindings --features image-webp --js false native", - "build-native-no-plugin-release": "napi build --platform -p next-napi-bindings --cargo-cwd ../../ --cargo-name next_napi_bindings --release --features image-webp,tracing/release_max_level_trace --js false native", + "build-native": "napi build --platform -p next-napi-bindings --cargo-cwd ../../ --cargo-name next_napi_bindings --features image-extended --js false native", + "build-native-release": "napi build --platform -p next-napi-bindings --cargo-cwd ../../ --cargo-name next_napi_bindings --release --features image-extended,tracing/release_max_level_trace --js false native", + "build-native-release-with-assertions": "napi build --platform -p next-napi-bindings --cargo-cwd ../../ --cargo-name next_napi_bindings --profile release-with-assertions --features image-extended,tracing/release_max_level_trace --js false native", "build-native-wasi": "npx --package=@napi-rs/cli@3.0.0-alpha.45 napi build --platform --target wasm32-wasip1-threads -p next-napi-bindings --cwd ../../ --output-dir packages/next-swc/native --no-default-features", "build-sccache": "node ../../scripts/build-sccache.js", "build-wasm": "wasm-pack build ../../crates/wasm --scope=next", diff --git a/packages/next-swc/turbo.jsonc b/packages/next-swc/turbo.jsonc index 84c2d247e643..f2ee87244207 100644 --- a/packages/next-swc/turbo.jsonc +++ b/packages/next-swc/turbo.jsonc @@ -46,16 +46,6 @@ "inputs": ["../../target/.rust-fingerprint"], "outputs": ["native/*.node", "native/index.d.ts"], }, - "build-native-no-plugin": { - "dependsOn": ["rust-fingerprint"], - "inputs": ["../../target/.rust-fingerprint"], - "outputs": ["native/*.node", "native/index.d.ts"], - }, - "build-native-no-plugin-release": { - "dependsOn": ["rust-fingerprint"], - "inputs": ["../../target/.rust-fingerprint"], - "outputs": ["native/*.node", "native/index.d.ts"], - }, "build-wasm": { "dependsOn": ["rust-fingerprint"], "inputs": ["../../target/.rust-fingerprint"], diff --git a/turbopack/crates/turbopack-ecmascript-plugins/Cargo.toml b/turbopack/crates/turbopack-ecmascript-plugins/Cargo.toml index 0aaaf9f72f43..66621d88574a 100644 --- a/turbopack/crates/turbopack-ecmascript-plugins/Cargo.toml +++ b/turbopack/crates/turbopack-ecmascript-plugins/Cargo.toml @@ -10,18 +10,7 @@ autobenches = false bench = false [features] -default = ["swc_ecma_transform_plugin"] transform_emotion = [] -# [NOTE]: Be careful to explicitly enable this only for the supported platform / targets. -swc_ecma_transform_plugin = [ - # Use granular plugin features to avoid pulling in wasmer via __plugin_transform_env_native. - # __plugin_transform_host provides the plugin runner types and proxy re-exports. - # __plugin_transform_host_schema_v1 provides the schema version support. - # We provide WasmtimeRuntime explicitly, so we don't need swc's env-native backend wiring. - "swc_core/__plugin_transform_host", - "swc_core/__plugin_transform_host_schema_v1", - "swc_plugin_backend_wasmtime" -] [lints] workspace = true @@ -45,8 +34,14 @@ turbopack-ecmascript = { workspace = true } styled_components = { workspace = true } styled_jsx = { workspace = true } -swc_core = { workspace = true, features = ["ecma_ast", "ecma_visit", "common"] } -swc_plugin_backend_wasmtime = { workspace = true, optional = true } +swc_core = { workspace = true, features = [ + "ecma_ast", + "ecma_visit", + "common", + "__plugin_transform_host", + "__plugin_transform_host_schema_v1", +] } +swc_plugin_backend_wasmtime = { workspace = true } swc_emotion = { workspace = true } swc_relay = { workspace = true } diff --git a/turbopack/crates/turbopack-ecmascript-plugins/src/transform/swc_ecma_transform_plugins.rs b/turbopack/crates/turbopack-ecmascript-plugins/src/transform/swc_ecma_transform_plugins.rs index 1188a69f63e8..a62a3a3a43cb 100644 --- a/turbopack/crates/turbopack-ecmascript-plugins/src/transform/swc_ecma_transform_plugins.rs +++ b/turbopack/crates/turbopack-ecmascript-plugins/src/transform/swc_ecma_transform_plugins.rs @@ -1,6 +1,10 @@ use anyhow::Result; use async_trait::async_trait; -use swc_core::ecma::ast::Program; +use swc_core::{ + ecma::ast::Program, + plugin_runner::plugin_module_bytes::{CompiledPluginModuleBytes, RawPluginModuleBytes}, +}; +use swc_plugin_backend_wasmtime::WasmtimeRuntime; use turbo_rcstr::{RcStr, rcstr}; use turbo_tasks_fs::FileSystemPath; use turbopack_core::issue::{Issue, IssueSeverity, IssueStage, StyledString}; @@ -16,70 +20,21 @@ use turbopack_ecmascript::{CustomTransformer, TransformContext}; pub struct SwcPluginModule { pub name: RcStr, #[turbo_tasks(trace_ignore, debug_ignore)] - #[cfg(feature = "swc_ecma_transform_plugin")] pub plugin: swc_core::plugin_runner::plugin_module_bytes::CompiledPluginModuleBytes, } impl SwcPluginModule { pub fn new(plugin_name: RcStr, plugin_bytes: Vec) -> Self { - #[cfg(feature = "swc_ecma_transform_plugin")] - { - use swc_core::plugin_runner::plugin_module_bytes::{ - CompiledPluginModuleBytes, RawPluginModuleBytes, - }; - use swc_plugin_backend_wasmtime::WasmtimeRuntime; - - Self { - plugin: CompiledPluginModuleBytes::from_raw_module( - &WasmtimeRuntime, - RawPluginModuleBytes::new(plugin_name.to_string(), plugin_bytes), - ), - name: plugin_name, - } - } - - #[cfg(not(feature = "swc_ecma_transform_plugin"))] - { - let _ = plugin_bytes; - Self { name: plugin_name } + Self { + plugin: CompiledPluginModuleBytes::from_raw_module( + &WasmtimeRuntime, + RawPluginModuleBytes::new(plugin_name.to_string(), plugin_bytes), + ), + name: plugin_name, } } } -#[turbo_tasks::value(shared)] -struct UnsupportedSwcEcmaTransformPluginsIssue { - pub file_path: FileSystemPath, -} - -#[async_trait] -#[turbo_tasks::value_impl] -impl Issue for UnsupportedSwcEcmaTransformPluginsIssue { - fn severity(&self) -> IssueSeverity { - IssueSeverity::Warning - } - - fn stage(&self) -> IssueStage { - IssueStage::Transform - } - - async fn title(&self) -> Result { - Ok(StyledString::Text(rcstr!( - "Unsupported SWC EcmaScript transform plugins on this platform." - ))) - } - - async fn file_path(&self) -> Result { - Ok(self.file_path.clone()) - } - - async fn description(&self) -> Result> { - Ok(Some(StyledString::Text(rcstr!( - "Turbopack does not yet support running SWC EcmaScript transform plugins on this \ - platform." - )))) - } -} - #[turbo_tasks::value(shared)] struct SwcEcmaTransformFailureIssue { pub file_path: FileSystemPath, @@ -123,206 +78,176 @@ impl Issue for SwcEcmaTransformFailureIssue { /// A custom transformer plugin to execute SWC's transform plugins. #[derive(Debug)] pub struct SwcEcmaTransformPluginsTransformer { - #[cfg(feature = "swc_ecma_transform_plugin")] plugins: Vec<(turbo_tasks::ResolvedVc, serde_json::Value)>, } impl SwcEcmaTransformPluginsTransformer { - #[cfg(feature = "swc_ecma_transform_plugin")] pub fn new( plugins: Vec<(turbo_tasks::ResolvedVc, serde_json::Value)>, ) -> Self { Self { plugins } } - - // [TODO] Due to WEB-1102 putting this module itself behind compile time feature - // doesn't work. Instead allow to instantiate dummy instance. - #[cfg(not(feature = "swc_ecma_transform_plugin"))] - #[allow(clippy::new_without_default)] - pub fn new() -> Self { - Self {} - } } #[async_trait] impl CustomTransformer for SwcEcmaTransformPluginsTransformer { - #[cfg_attr(not(feature = "swc_ecma_transform_plugin"), allow(unused))] #[tracing::instrument(level = tracing::Level::TRACE, name = "swc_ecma_transform_plugin", skip_all)] async fn transform(&self, program: &mut Program, ctx: &TransformContext<'_>) -> Result<()> { - #[cfg(feature = "swc_ecma_transform_plugin")] - { - use std::{cell::RefCell, rc::Rc, sync::Arc}; - - use anyhow::Context; - use swc_core::{ - common::{ - comments::SingleThreadedComments, - plugin::{ - metadata::TransformPluginMetadataContext, serialized::PluginSerializedBytes, - }, - util::take::Take, + use std::{cell::RefCell, rc::Rc, sync::Arc}; + + use anyhow::Context; + use swc_core::{ + common::{ + comments::SingleThreadedComments, + plugin::{ + metadata::TransformPluginMetadataContext, serialized::PluginSerializedBytes, }, - ecma::ast::Module, - plugin::proxies::{COMMENTS, HostCommentsStorage}, - plugin_runner::plugin_module_bytes::CompiledPluginModuleBytes, - }; - use swc_plugin_backend_wasmtime::WasmtimeRuntime; - use turbo_tasks::TryJoinIterExt; - - let plugins = self - .plugins - .iter() - .map(async |(plugin_module, config)| { - let plugin_module = plugin_module.await?; - Ok(( - plugin_module.name.clone(), - config.clone(), - Box::new(plugin_module.plugin.clone_module(&WasmtimeRuntime)), - )) - }) - .try_join() - .await?; - - let should_enable_comments_proxy = - !ctx.comments.leading.is_empty() && !ctx.comments.trailing.is_empty(); - - //[TODO]: as same as swc/core does, we should set should_enable_comments_proxy - // depends on the src's comments availability. For now, check naively if leading - // / trailing comments are empty. - let comments = if should_enable_comments_proxy { - // Plugin only able to accept singlethreaded comments, interop from - // multithreaded comments. - let mut leading = - swc_core::common::comments::SingleThreadedCommentsMapInner::default(); - ctx.comments.leading.as_ref().into_iter().for_each(|c| { - leading.insert(*c.key(), c.value().clone()); - }); - - let mut trailing = - swc_core::common::comments::SingleThreadedCommentsMapInner::default(); - ctx.comments.trailing.as_ref().into_iter().for_each(|c| { - trailing.insert(*c.key(), c.value().clone()); - }); - - Some(SingleThreadedComments::from_leading_and_trailing( - Rc::new(RefCell::new(leading)), - Rc::new(RefCell::new(trailing)), + util::take::Take, + }, + ecma::ast::Module, + plugin::proxies::{COMMENTS, HostCommentsStorage}, + plugin_runner::plugin_module_bytes::CompiledPluginModuleBytes, + }; + use swc_plugin_backend_wasmtime::WasmtimeRuntime; + use turbo_tasks::TryJoinIterExt; + + let plugins = self + .plugins + .iter() + .map(async |(plugin_module, config)| { + let plugin_module = plugin_module.await?; + Ok(( + plugin_module.name.clone(), + config.clone(), + Box::new(plugin_module.plugin.clone_module(&WasmtimeRuntime)), )) - } else { - None - }; - - fn transform( - original_serialized_program: &PluginSerializedBytes, - ctx: &TransformContext<'_>, - plugins: Vec<(RcStr, serde_json::Value, Box)>, - should_enable_comments_proxy: bool, - ) -> Result { - use either::Either; - - let transform_metadata_context = Arc::new(TransformPluginMetadataContext::new( - Some(ctx.file_path_str.to_string()), - //[TODO]: Support env-related variable injection, i.e process.env.NODE_ENV - "development".to_string(), - None, - )); - - let mut serialized_program = Either::Left(original_serialized_program); - - // Run plugin transformation against current program. - // We do not serialize / deserialize between each plugin execution but - // copies raw transformed bytes directly into plugin's memory space. - // Note: This doesn't mean plugin won't perform any se/deserialization: it - // still have to construct from raw bytes internally to perform actual - // transform. - for (plugin_name, plugin_config, plugin_module) in plugins { - let mut transform_plugin_executor = - swc_core::plugin_runner::create_plugin_transform_executor( - ctx.source_map, - &ctx.unresolved_mark, - &transform_metadata_context, - None, - plugin_module, - Some(plugin_config), - Arc::new(WasmtimeRuntime), - ); - - serialized_program = Either::Right( - transform_plugin_executor - .transform( - serialized_program.as_ref().either(|p| *p, |p| p), - Some(should_enable_comments_proxy), - ) - .with_context(|| format!("Failed to execute {plugin_name}"))?, + }) + .try_join() + .await?; + + let should_enable_comments_proxy = + !ctx.comments.leading.is_empty() && !ctx.comments.trailing.is_empty(); + + //[TODO]: as same as swc/core does, we should set should_enable_comments_proxy + // depends on the src's comments availability. For now, check naively if leading + // / trailing comments are empty. + let comments = if should_enable_comments_proxy { + // Plugin only able to accept singlethreaded comments, interop from + // multithreaded comments. + let mut leading = swc_core::common::comments::SingleThreadedCommentsMapInner::default(); + ctx.comments.leading.as_ref().into_iter().for_each(|c| { + leading.insert(*c.key(), c.value().clone()); + }); + + let mut trailing = + swc_core::common::comments::SingleThreadedCommentsMapInner::default(); + ctx.comments.trailing.as_ref().into_iter().for_each(|c| { + trailing.insert(*c.key(), c.value().clone()); + }); + + Some(SingleThreadedComments::from_leading_and_trailing( + Rc::new(RefCell::new(leading)), + Rc::new(RefCell::new(trailing)), + )) + } else { + None + }; + + fn transform( + original_serialized_program: &PluginSerializedBytes, + ctx: &TransformContext<'_>, + plugins: Vec<(RcStr, serde_json::Value, Box)>, + should_enable_comments_proxy: bool, + ) -> Result { + use either::Either; + + let transform_metadata_context = Arc::new(TransformPluginMetadataContext::new( + Some(ctx.file_path_str.to_string()), + //[TODO]: Support env-related variable injection, i.e process.env.NODE_ENV + "development".to_string(), + None, + )); + + let mut serialized_program = Either::Left(original_serialized_program); + + // Run plugin transformation against current program. + // We do not serialize / deserialize between each plugin execution but + // copies raw transformed bytes directly into plugin's memory space. + // Note: This doesn't mean plugin won't perform any se/deserialization: it + // still have to construct from raw bytes internally to perform actual + // transform. + for (plugin_name, plugin_config, plugin_module) in plugins { + let mut transform_plugin_executor = + swc_core::plugin_runner::create_plugin_transform_executor( + ctx.source_map, + &ctx.unresolved_mark, + &transform_metadata_context, + None, + plugin_module, + Some(plugin_config), + Arc::new(WasmtimeRuntime), ); - } - serialized_program - .as_ref() - .either(|p| *p, |p| p) - .deserialize() - .map(|v| v.into_inner()) + serialized_program = Either::Right( + transform_plugin_executor + .transform( + serialized_program.as_ref().either(|p| *p, |p| p), + Some(should_enable_comments_proxy), + ) + .with_context(|| format!("Failed to execute {plugin_name}"))?, + ); } - let transformed_program = - COMMENTS.set(&HostCommentsStorage { inner: comments }, || { - let module_program = - std::mem::replace(program, Program::Module(Module::dummy())); - let module_program = - swc_core::common::plugin::serialized::VersionedSerializable::new( - module_program, - ); - let serialized_program = PluginSerializedBytes::try_serialize(&module_program)?; - - match transform( - &serialized_program, - ctx, - plugins, - should_enable_comments_proxy, - ) { - Ok(program) => anyhow::Ok(program), - Err(e) => { - use turbopack_core::issue::IssueExt; - - // Format the error chain without backtrace. - // Using `{:?}` would include the backtrace when - // RUST_BACKTRACE=1, which is not useful in - // user-facing error messages. - let mut description = e.to_string(); - let mut causes = e.chain().skip(1).peekable(); - if causes.peek().is_some() { - description.push_str("\n\nCaused by:"); - for (i, cause) in causes.enumerate() { - description.push_str(&format!("\n {i}: {cause}")); - } - } - - SwcEcmaTransformFailureIssue { - file_path: ctx.file_path.clone(), - description: StyledString::Text(description.into()), - } - .resolved_cell() - .emit(); + serialized_program + .as_ref() + .either(|p| *p, |p| p) + .deserialize() + .map(|v| v.into_inner()) + } - // On failure, return the original program. - Ok(module_program.into_inner()) + let transformed_program = COMMENTS.set(&HostCommentsStorage { inner: comments }, || { + let module_program = std::mem::replace(program, Program::Module(Module::dummy())); + let module_program = + swc_core::common::plugin::serialized::VersionedSerializable::new(module_program); + let serialized_program = PluginSerializedBytes::try_serialize(&module_program)?; + + match transform( + &serialized_program, + ctx, + plugins, + should_enable_comments_proxy, + ) { + Ok(program) => anyhow::Ok(program), + Err(e) => { + use turbopack_core::issue::IssueExt; + + // Format the error chain without backtrace. + // Using `{:?}` would include the backtrace when + // RUST_BACKTRACE=1, which is not useful in + // user-facing error messages. + let mut description = e.to_string(); + let mut causes = e.chain().skip(1).peekable(); + if causes.peek().is_some() { + description.push_str("\n\nCaused by:"); + for (i, cause) in causes.enumerate() { + description.push_str(&format!("\n {i}: {cause}")); } } - })?; - *program = transformed_program; - } - - #[cfg(not(feature = "swc_ecma_transform_plugin"))] - { - use turbopack_core::issue::IssueExt; + SwcEcmaTransformFailureIssue { + file_path: ctx.file_path.clone(), + description: StyledString::Text(description.into()), + } + .resolved_cell() + .emit(); - UnsupportedSwcEcmaTransformPluginsIssue { - file_path: ctx.file_path.clone(), + // On failure, return the original program. + Ok(module_program.into_inner()) + } } - .resolved_cell() - .emit(); - } + })?; + + *program = transformed_program; Ok(()) }