diff --git a/Cargo.lock b/Cargo.lock index a7ebf1f8b58..c6e0ec75991 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2315,6 +2315,7 @@ dependencies = [ "parcel-js-swc-core", "parcel_core", "parcel_filesystem", + "serde", "swc_core", ] diff --git a/crates/node-bindings/src/transformer.rs b/crates/node-bindings/src/transformer.rs index 2592c73502f..44b9f32a8d1 100644 --- a/crates/node-bindings/src/transformer.rs +++ b/crates/node-bindings/src/transformer.rs @@ -3,31 +3,6 @@ use napi::JsObject; use napi::JsUnknown; use napi_derive::napi; -use parcel_core::plugin::{ - InitialAsset, RunTransformContext, TransformationInput, TransformerPlugin, -}; -use parcel_plugin_transformer_js::ParcelJsTransformerPlugin; - -use parcel_napi_helpers::anyhow_to_napi; - -#[napi] -pub fn _testing_run_parcel_js_transformer_plugin( - target_path: String, - env: Env, -) -> napi::Result { - let mut transformer = ParcelJsTransformerPlugin::new(); - let mut context = RunTransformContext::default(); - let input = TransformationInput::InitialAsset(InitialAsset { - file_path: target_path.into(), - ..Default::default() - }); - let result = transformer - .transform(&mut context, input) - .map_err(anyhow_to_napi)?; - let result = env.to_js_value(&result)?; - Ok(result) -} - #[napi] pub fn transform(opts: JsObject, env: Env) -> napi::Result { let config: parcel_js_swc_core::Config = env.from_js_value(opts)?; diff --git a/crates/parcel/src/parcel.rs b/crates/parcel/src/parcel.rs index a2cf8a3d783..690401fd286 100644 --- a/crates/parcel/src/parcel.rs +++ b/crates/parcel/src/parcel.rs @@ -97,7 +97,11 @@ impl Parcel { config, PluginContext { config: Arc::clone(&config_loader), + file_system: self.fs.clone(), options: Arc::new(PluginOptions { + core_path: self.options.core_path.clone(), + env: self.options.env.clone(), + log_level: self.options.log_level.clone(), mode: self.options.mode.clone(), project_root: self.project_root.clone(), }), diff --git a/crates/parcel/src/plugins/config_plugins.rs b/crates/parcel/src/plugins/config_plugins.rs index 300ecf490fb..bdc2551688f 100644 --- a/crates/parcel/src/plugins/config_plugins.rs +++ b/crates/parcel/src/plugins/config_plugins.rs @@ -198,7 +198,7 @@ impl Plugins for ConfigPlugins { } if transformer.package_name == "@parcel/transformer-js" { - transformers.push(Box::new(ParcelJsTransformerPlugin::new())); + transformers.push(Box::new(ParcelJsTransformerPlugin::new(&self.ctx)?)); continue; } @@ -305,13 +305,21 @@ mod tests { #[test] fn returns_transformers() { - let transformers = config_plugins(make_test_plugin_context()) + let pipeline = config_plugins(make_test_plugin_context()) .transformers(Path::new("a.ts"), None) .expect("Not to panic"); assert_eq!( - format!("{:?}", transformers), - r"TransformerPipeline { transformers: [ParcelJsTransformerPlugin] }" - ) + format!("{:?}", pipeline), + format!( + "{:?}", + TransformerPipeline { + transformers: vec![Box::new( + ParcelJsTransformerPlugin::new(&make_test_plugin_context()).unwrap() + )], + hash: 1 + } + ) + ); } } diff --git a/crates/parcel/src/requests/asset_graph_request.rs b/crates/parcel/src/requests/asset_graph_request.rs index b3f5c5c6999..b019a132fcf 100644 --- a/crates/parcel/src/requests/asset_graph_request.rs +++ b/crates/parcel/src/requests/asset_graph_request.rs @@ -348,7 +348,7 @@ mod test { use tracing::Level; - use parcel_core::types::Code; + use parcel_core::types::{Code, ParcelOptions}; use parcel_filesystem::in_memory_file_system::InMemoryFileSystem; use parcel_filesystem::FileSystem; @@ -449,55 +449,59 @@ console.log('hello world'); #[test] fn test_asset_graph_request_with_a_couple_of_entries() { - let _ = tracing_subscriber::FmtSubscriber::builder() - .with_max_level(Level::TRACE) - .try_init(); - - let mut options = RequestTrackerTestOptions::default(); - let fs = InMemoryFileSystem::default(); #[cfg(not(target_os = "windows"))] let temporary_dir = PathBuf::from("/parcel_tests"); #[cfg(target_os = "windows")] let temporary_dir = PathBuf::from("C:\\windows\\parcel_tests"); + + let core_path = temporary_dir.join("parcel_core"); + let fs = InMemoryFileSystem::default(); + fs.create_directory(&temporary_dir).unwrap(); - fs.set_current_working_directory(&temporary_dir); // <- resolver is broken without this - options - .parcel_options - .entries - .push(temporary_dir.join("entry.js").to_str().unwrap().to_string()); - options.project_root = temporary_dir.clone(); - options.search_path = temporary_dir.clone(); - options.parcel_options.core_path = temporary_dir.clone().join("parcel_core"); + fs.set_current_working_directory(&temporary_dir); + fs.write_file( &temporary_dir.join("entry.js"), String::from( r#" -import {x} from './a'; -import {y} from './b'; -console.log(x + y); + import {x} from './a'; + import {y} from './b'; + console.log(x + y); "#, ), ); + fs.write_file( &temporary_dir.join("a.js"), String::from( r#" -export const x = 15; + export const x = 15; "#, ), ); + fs.write_file( &temporary_dir.join("b.js"), String::from( r#" -export const y = 27; + export const y = 27; "#, ), ); - setup_core_modules(&fs, &options.parcel_options.core_path); - options.fs = Arc::new(fs); - let mut request_tracker = request_tracker(options); + setup_core_modules(&fs, &core_path); + + let mut request_tracker = request_tracker(RequestTrackerTestOptions { + fs: Arc::new(fs), + parcel_options: ParcelOptions { + core_path, + entries: vec![temporary_dir.join("entry.js").to_str().unwrap().to_string()], + ..ParcelOptions::default() + }, + project_root: temporary_dir.clone(), + search_path: temporary_dir.clone(), + ..RequestTrackerTestOptions::default() + }); let asset_graph_request = AssetGraphRequest {}; let RequestResult::AssetGraph(asset_graph_request_result) = request_tracker @@ -529,11 +533,10 @@ export const y = 27; let transformer_path = core_path .join("node_modules") .join("@parcel/transformer-js"); - let source_path = transformer_path.join("src"); - fs.create_directory(&source_path).unwrap(); + fs.write_file(&transformer_path.join("package.json"), String::from("{}")); fs.write_file( - &source_path.join("esmodule-helpers.js"), + &transformer_path.join("src").join("esmodule-helpers.js"), String::from("/* helpers */"), ); } diff --git a/crates/parcel/src/requests/asset_request.rs b/crates/parcel/src/requests/asset_request.rs index 8ec9bbe6e8f..69f2c01a4ca 100644 --- a/crates/parcel/src/requests/asset_request.rs +++ b/crates/parcel/src/requests/asset_request.rs @@ -7,7 +7,6 @@ use parcel_core::plugin::AssetBuildEvent; use parcel_core::plugin::BuildProgressEvent; use parcel_core::plugin::InitialAsset; use parcel_core::plugin::ReporterEvent; -use parcel_core::plugin::RunTransformContext; use parcel_core::plugin::TransformResult; use parcel_core::plugin::TransformationInput; use parcel_core::types::Asset; @@ -65,11 +64,6 @@ impl Request for AssetRequest { .and_then(|s| s.to_str()) .unwrap_or(""), ); - let mut transform_ctx = RunTransformContext::new( - request_context.file_system().clone(), - request_context.options.clone(), - request_context.project_root.clone(), - ); let result = run_pipeline( pipeline, @@ -82,7 +76,6 @@ impl Request for AssetRequest { }), asset_type, request_context.plugins().clone(), - &mut transform_ctx, )?; Ok(ResultAndInvalidations { @@ -105,9 +98,8 @@ impl Request for AssetRequest { fn run_pipeline( mut pipeline: TransformerPipeline, input: TransformationInput, - asset_type: FileType, + file_type: FileType, plugins: PluginsRef, - transform_ctx: &mut RunTransformContext, ) -> anyhow::Result { let mut dependencies = vec![]; let mut invalidations = vec![]; @@ -116,8 +108,8 @@ fn run_pipeline( let pipeline_hash = pipeline.hash(); for transformer in &mut pipeline.transformers { - let transform_result = transformer.transform(transform_ctx, transform_input)?; - let is_different_asset_type = transform_result.asset.asset_type != asset_type; + let transform_result = transformer.transform(transform_input)?; + let is_different_asset_type = transform_result.asset.file_type != file_type; transform_input = TransformationInput::Asset(transform_result.asset); @@ -126,13 +118,7 @@ fn run_pipeline( let next_pipeline = plugins.transformers(transform_input.file_path(), None)?; if next_pipeline.hash() != pipeline_hash { - return run_pipeline( - next_pipeline, - transform_input, - asset_type, - plugins, - transform_ctx, - ); + return run_pipeline(next_pipeline, transform_input, file_type, plugins); }; } diff --git a/crates/parcel/src/test_utils.rs b/crates/parcel/src/test_utils.rs index 40ba28644c2..4642fc0a42b 100644 --- a/crates/parcel/src/test_utils.rs +++ b/crates/parcel/src/test_utils.rs @@ -2,7 +2,6 @@ use std::path::PathBuf; use std::sync::Arc; use parcel_config::parcel_config_fixtures::default_config; -use parcel_core::types::BuildMode; use parcel_core::{ config_loader::ConfigLoader, plugin::{PluginContext, PluginLogger, PluginOptions}, @@ -16,12 +15,15 @@ use crate::{ }; pub(crate) fn make_test_plugin_context() -> PluginContext { + let fs = Arc::new(InMemoryFileSystem::default()); + PluginContext { config: Arc::new(ConfigLoader { - fs: Arc::new(InMemoryFileSystem::default()), + fs: fs.clone(), project_root: PathBuf::default(), search_path: PathBuf::default(), }), + file_system: fs.clone(), options: Arc::new(PluginOptions::default()), logger: PluginLogger::default(), } @@ -71,8 +73,12 @@ pub(crate) fn request_tracker(options: RequestTrackerTestOptions) -> RequestTrac let plugins = plugins.unwrap_or_else(|| { config_plugins(PluginContext { config: Arc::clone(&config_loader), + file_system: fs.clone(), options: Arc::new(PluginOptions { - mode: BuildMode::default(), + core_path: parcel_options.core_path.clone(), + env: parcel_options.env.clone(), + log_level: parcel_options.log_level.clone(), + mode: parcel_options.mode.clone(), project_root: project_root.clone(), }), logger: PluginLogger::default(), diff --git a/crates/parcel_core/src/plugin.rs b/crates/parcel_core/src/plugin.rs index b37704bc3a0..6e8129feba8 100644 --- a/crates/parcel_core/src/plugin.rs +++ b/crates/parcel_core/src/plugin.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; @@ -17,6 +18,7 @@ mod packager_plugin; pub use packager_plugin::*; mod reporter_plugin; +use parcel_filesystem::FileSystemRef; pub use reporter_plugin::*; mod resolver_plugin; @@ -31,13 +33,14 @@ pub use transformer_plugin::*; mod validator_plugin; pub use validator_plugin::*; -use crate::config_loader::ConfigLoader; -use crate::types::BuildMode; +use crate::config_loader::{ConfigLoader, ConfigLoaderRef}; +use crate::types::{BuildMode, LogLevel}; pub struct PluginContext { - pub config: Arc, - pub options: Arc, + pub config: ConfigLoaderRef, + pub file_system: FileSystemRef, pub logger: PluginLogger, + pub options: Arc, } #[derive(Default)] @@ -45,6 +48,9 @@ pub struct PluginLogger {} #[derive(Debug, Default)] pub struct PluginOptions { + pub core_path: PathBuf, + pub env: Option>, + pub log_level: LogLevel, pub mode: BuildMode, pub project_root: PathBuf, } diff --git a/crates/parcel_core/src/plugin/transformer_plugin.rs b/crates/parcel_core/src/plugin/transformer_plugin.rs index 57d223efc5a..ed2c06c555a 100644 --- a/crates/parcel_core/src/plugin/transformer_plugin.rs +++ b/crates/parcel_core/src/plugin/transformer_plugin.rs @@ -4,10 +4,9 @@ use std::sync::Arc; use serde::Serialize; -use parcel_filesystem::os_file_system::OsFileSystem; use parcel_filesystem::FileSystemRef; -use crate::types::{Asset, Code, Dependency, Environment, ParcelOptions, SpecifierType}; +use crate::types::{Asset, Code, Dependency, Environment, FileType, SpecifierType}; pub struct ResolveOptions { /// A list of custom conditions to use when resolving package.json "exports" and "imports" @@ -43,6 +42,19 @@ pub enum TransformationInput { } impl TransformationInput { + pub fn file_type(&self) -> FileType { + match self { + TransformationInput::InitialAsset(raw_asset) => FileType::from_extension( + raw_asset + .file_path + .extension() + .and_then(|s| s.to_str()) + .unwrap_or_default(), + ), + TransformationInput::Asset(asset) => asset.file_type.clone(), + } + } + pub fn env(&self) -> Arc { match self { TransformationInput::InitialAsset(raw_asset) => raw_asset.env.clone(), @@ -80,49 +92,6 @@ impl TransformationInput { } } -/// Context parameters for the transformer, other than the input. -pub struct RunTransformContext { - file_system: FileSystemRef, - options: Arc, - project_root: PathBuf, -} - -impl Default for RunTransformContext { - fn default() -> Self { - Self { - file_system: Arc::new(OsFileSystem::default()), - options: Arc::new(ParcelOptions::default()), - project_root: PathBuf::default(), - } - } -} - -impl RunTransformContext { - pub fn new( - file_system: FileSystemRef, - options: Arc, - project_root: PathBuf, - ) -> Self { - Self { - file_system, - options, - project_root, - } - } - - pub fn file_system(&self) -> FileSystemRef { - self.file_system.clone() - } - - pub fn options(&self) -> &Arc { - &self.options - } - - pub fn project_root(&self) -> &Path { - &self.project_root - } -} - #[derive(Debug, Serialize, PartialEq)] pub struct TransformResult { pub asset: Asset, @@ -139,9 +108,5 @@ pub struct TransformResult { /// pub trait TransformerPlugin: Debug + Send + Sync { /// Transform the asset and/or add new assets - fn transform( - &mut self, - context: &mut RunTransformContext, - input: TransformationInput, - ) -> Result; + fn transform(&mut self, input: TransformationInput) -> Result; } diff --git a/crates/parcel_core/src/types/asset.rs b/crates/parcel_core/src/types/asset.rs index 23d0f3e8b14..6d143b7313b 100644 --- a/crates/parcel_core/src/types/asset.rs +++ b/crates/parcel_core/src/types/asset.rs @@ -52,10 +52,6 @@ impl From for Code { #[derive(Default, PartialEq, Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct Asset { - /// The file type of the asset, which may change during transformation - #[serde(rename = "type")] - pub asset_type: FileType, - /// Controls which bundle the asset is placed into pub bundle_behavior: BundleBehavior, @@ -65,6 +61,10 @@ pub struct Asset { /// The file path to the asset pub file_path: PathBuf, + /// The file type of the asset, which may change during transformation + #[serde(rename = "type")] + pub file_type: FileType, + /// The code of this asset, initially read from disk, then becoming the /// transformed output pub code: Arc, @@ -146,9 +146,9 @@ impl Asset { pub fn id(&self) -> u64 { let mut hasher = crate::hash::IdentifierHasher::default(); - self.asset_type.hash(&mut hasher); self.env.hash(&mut hasher); self.file_path.hash(&mut hasher); + self.file_type.hash(&mut hasher); self.pipeline.hash(&mut hasher); self.query.hash(&mut hasher); self.unique_key.hash(&mut hasher); diff --git a/crates/parcel_core/src/types/environment.rs b/crates/parcel_core/src/types/environment.rs index bd55e135dae..1e7b206cde2 100644 --- a/crates/parcel_core/src/types/environment.rs +++ b/crates/parcel_core/src/types/environment.rs @@ -136,6 +136,11 @@ impl EnvironmentContext { matches!(self, WebWorker | ServiceWorker) } + pub fn is_worklet(&self) -> bool { + use EnvironmentContext::*; + matches!(self, Worklet) + } + pub fn is_electron(&self) -> bool { use EnvironmentContext::*; matches!(self, ElectronMain | ElectronRenderer) diff --git a/crates/parcel_core/src/types/environment/browsers.rs b/crates/parcel_core/src/types/environment/browsers.rs index c53366b6085..c42a8311830 100644 --- a/crates/parcel_core/src/types/environment/browsers.rs +++ b/crates/parcel_core/src/types/environment/browsers.rs @@ -21,15 +21,44 @@ pub struct Browsers { impl Browsers { pub fn is_empty(&self) -> bool { - self.android.is_none() - && self.chrome.is_none() - && self.edge.is_none() - && self.firefox.is_none() - && self.ie.is_none() - && self.ios_saf.is_none() - && self.opera.is_none() - && self.safari.is_none() - && self.samsung.is_none() + self.iter().all(|(_browser, version)| version.is_none()) + } + + pub fn iter(&self) -> BrowsersIterator { + BrowsersIterator { + browsers: self, + index: 0, + } + } +} + +pub struct BrowsersIterator<'a> { + browsers: &'a Browsers, + index: usize, +} + +impl<'a> Iterator for BrowsersIterator<'a> { + type Item = (&'a str, Option); + + fn next(&mut self) -> Option { + let browser = match self.index { + 0 => Some(("android", self.browsers.android)), + 1 => Some(("chrome", self.browsers.chrome)), + 2 => Some(("edge", self.browsers.edge)), + 3 => Some(("firefox", self.browsers.firefox)), + 4 => Some(("ie", self.browsers.ie)), + 5 => Some(("ios_saf", self.browsers.ios_saf)), + 6 => Some(("opera", self.browsers.opera)), + 7 => Some(("safari", self.browsers.safari)), + 8 => Some(("samsung", self.browsers.samsung)), + _ => None, + }; + + if self.index < 9 { + self.index += 1; + } + + browser } } diff --git a/crates/parcel_plugin_resolver/src/parcel_resolver.rs b/crates/parcel_plugin_resolver/src/parcel_resolver.rs index 93bea1fd632..a18b688e05d 100644 --- a/crates/parcel_plugin_resolver/src/parcel_resolver.rs +++ b/crates/parcel_plugin_resolver/src/parcel_resolver.rs @@ -418,6 +418,7 @@ mod test { project_root: PathBuf::default(), search_path: PathBuf::default(), }), + file_system: Arc::new(InMemoryFileSystem::default()), logger: PluginLogger::default(), options: Arc::new(PluginOptions::default()), } @@ -505,6 +506,7 @@ mod test { project_root: PathBuf::default(), search_path: PathBuf::from("/foo"), }), + file_system: Arc::new(InMemoryFileSystem::default()), logger: PluginLogger::default(), options: Arc::new(PluginOptions::default()), }; diff --git a/crates/parcel_plugin_rpc/src/plugin/transformer.rs b/crates/parcel_plugin_rpc/src/plugin/transformer.rs index 9226c9a8ecc..7cf289476da 100644 --- a/crates/parcel_plugin_rpc/src/plugin/transformer.rs +++ b/crates/parcel_plugin_rpc/src/plugin/transformer.rs @@ -6,7 +6,7 @@ use anyhow::Error; use parcel_config::PluginNode; use parcel_core::plugin::PluginContext; use parcel_core::plugin::TransformerPlugin; -use parcel_core::plugin::{RunTransformContext, TransformResult, TransformationInput}; +use parcel_core::plugin::{TransformResult, TransformationInput}; pub struct RpcTransformerPlugin { _name: String, @@ -27,11 +27,7 @@ impl RpcTransformerPlugin { } impl TransformerPlugin for RpcTransformerPlugin { - fn transform( - &mut self, - _context: &mut RunTransformContext, - _asset: TransformationInput, - ) -> Result { + fn transform(&mut self, _asset: TransformationInput) -> Result { todo!() } } diff --git a/crates/parcel_plugin_transformer_js/Cargo.toml b/crates/parcel_plugin_transformer_js/Cargo.toml index 9cd10331d27..dcb5ab7e939 100644 --- a/crates/parcel_plugin_transformer_js/Cargo.toml +++ b/crates/parcel_plugin_transformer_js/Cargo.toml @@ -6,10 +6,10 @@ description = "JavaScript Transformer Plugin for the Parcel Bundler" [dependencies] parcel_core = { path = "../parcel_core" } +parcel_filesystem = { path = "../parcel_filesystem" } + anyhow = "1" +indexmap = "2.2.6" parcel-js-swc-core = { path = "../../packages/transformers/js/core" } +serde = { version = "1.0.200", features = ["derive"] } swc_core = { version = "0.96", features = ["ecma_ast"] } -indexmap = "2.2.6" - -[dev-dependencies] -parcel_filesystem = { path = "../parcel_filesystem" } diff --git a/crates/parcel_plugin_transformer_js/src/lib.rs b/crates/parcel_plugin_transformer_js/src/lib.rs index 45820a5a96f..818b4a49868 100644 --- a/crates/parcel_plugin_transformer_js/src/lib.rs +++ b/crates/parcel_plugin_transformer_js/src/lib.rs @@ -3,3 +3,4 @@ pub use transformer::ParcelJsTransformerPlugin; mod transformer; +mod ts_config; diff --git a/crates/parcel_plugin_transformer_js/src/transformer.rs b/crates/parcel_plugin_transformer_js/src/transformer.rs index 5495a3026f7..4037c192211 100644 --- a/crates/parcel_plugin_transformer_js/src/transformer.rs +++ b/crates/parcel_plugin_transformer_js/src/transformer.rs @@ -1,9 +1,18 @@ +use std::collections::HashMap; +use std::fmt; +use std::sync::Arc; + use anyhow::{anyhow, Error}; -use parcel_core::plugin::TransformerPlugin; -use parcel_core::plugin::{RunTransformContext, TransformResult, TransformationInput}; +use parcel_core::plugin::{PluginContext, PluginOptions, TransformerPlugin}; +use parcel_core::plugin::{TransformResult, TransformationInput}; use parcel_core::types::engines::EnvironmentFeature; -use parcel_core::types::{Asset, BuildMode, FileType, LogLevel, OutputFormat, SourceType}; +use parcel_core::types::{ + Asset, BuildMode, Diagnostic, ErrorKind, FileType, LogLevel, OutputFormat, SourceType, +}; +use parcel_filesystem::FileSystemRef; + +use crate::ts_config::{Jsx, Target, TsConfig}; mod conversion; #[cfg(test)] @@ -20,34 +29,106 @@ mod test_helpers; /// `Dependency` as well as exported, imported and re-exported symbols (as `Symbol`, usually /// mapping to a mangled name that the SWC transformer replaced in the source file + the source /// module and the source name that has been imported) -#[derive(Debug)] -pub struct ParcelJsTransformerPlugin {} +pub struct ParcelJsTransformerPlugin { + file_system: FileSystemRef, + options: Arc, + ts_config: Option, +} impl ParcelJsTransformerPlugin { - pub fn new() -> Self { - Self {} + pub fn new(ctx: &PluginContext) -> Result { + let ts_config = ctx + .config + .load_json_config::("tsconfig.json") + .map(|config| config.contents) + .map_err(|err| { + let diagnostic = err.downcast_ref::(); + + if diagnostic.is_some_and(|d| d.kind != ErrorKind::NotFound) { + return Err(err); + } + + Ok(None::) + }) + .ok(); + + Ok(Self { + file_system: ctx.file_system.clone(), + options: ctx.options.clone(), + ts_config, + }) + } +} + +impl fmt::Debug for ParcelJsTransformerPlugin { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ParcelJsTransformerPlugin") + .field("options", &self.options) + .finish() } } impl TransformerPlugin for ParcelJsTransformerPlugin { /// This does a lot of equivalent work to `JSTransformer::transform` in /// `packages/transformers/js` - fn transform( - &mut self, - context: &mut RunTransformContext, - input: TransformationInput, - ) -> Result { + fn transform(&mut self, input: TransformationInput) -> Result { + let compiler_options = self + .ts_config + .as_ref() + .and_then(|ts| ts.compiler_options.as_ref()); + let env = input.env(); - let file_system = context.file_system(); + let file_type = input.file_type(); let is_node = env.context.is_node(); - let source_code = input.read_code(file_system)?; + let source_code = input.read_code(self.file_system.clone())?; + + let mut targets: HashMap = HashMap::new(); + if env.context.is_browser() { + for (name, version) in env.engines.browsers.iter() { + if let Some(version) = version { + targets.insert( + String::from(name), + format!("{}.{}", version.major(), version.minor()), + ); + } + } + } + + if env.context.is_electron() { + if let Some(version) = env.engines.electron { + targets.insert( + String::from("electron"), + format!("{}.{}", version.major(), version.minor()), + ); + } + } + + if env.context.is_node() { + if let Some(version) = env.engines.node { + targets.insert( + String::from("node"), + format!("{}.{}", version.major(), version.minor()), + ); + } + } let transformation_result = parcel_js_swc_core::transform( parcel_js_swc_core::Config { + // TODO: Infer from package.json + automatic_jsx_runtime: compiler_options + .map(|co| { + co.jsx + .as_ref() + .is_some_and(|jsx| matches!(jsx, Jsx::ReactJsx | Jsx::ReactJsxDev)) + || co.jsx_import_source.is_some() + }) + .unwrap_or_default(), code: source_code.bytes().to_vec(), - // TODO Lift context up into constructor to improve performance? - env: context - .options() + decorators: compiler_options + .and_then(|co| co.experimental_decorators) + .unwrap_or_default(), + env: self + .options .env .clone() .unwrap_or_default() @@ -61,12 +142,29 @@ impl TransformerPlugin for ParcelJsTransformerPlugin { .to_string(), insert_node_globals: !is_node && env.source_type != SourceType::Script, is_browser: env.context.is_browser(), - is_development: context.options().mode == BuildMode::Development, + is_development: self.options.mode == BuildMode::Development, is_esm_output: env.output_format == OutputFormat::EsModule, + is_jsx: matches!(file_type, FileType::Jsx | FileType::Tsx), is_library: env.is_library, + is_type_script: matches!(file_type, FileType::Ts | FileType::Tsx), is_worker: env.context.is_worker(), + // TODO Infer from package.json + jsx_import_source: compiler_options.and_then(|co| co.jsx_import_source.clone()), + jsx_pragma: compiler_options.and_then(|co| co.jsx_factory.clone()), + jsx_pragma_frag: compiler_options.and_then(|co| co.jsx_fragment_factory.clone()), node_replacer: is_node, - project_root: context.project_root().to_string_lossy().into_owned(), + project_root: self.options.project_root.to_string_lossy().into_owned(), + // TODO: Boolean( + // pkg?.dependencies?.react || + // pkg?.devDependencies?.react || + // pkg?.peerDependencies?.react, + // ); + react_refresh: self.options.mode == BuildMode::Development + // && TODO: self.options.hmr_options + && env.context.is_browser() + && !env.is_library + && !env.context.is_worker() + && !env.context.is_worklet(), replace_env: !is_node, scope_hoist: env.should_scope_hoist && env.source_type != SourceType::Script, source_maps: env.source_map.is_some(), @@ -76,7 +174,19 @@ impl TransformerPlugin for ParcelJsTransformerPlugin { }, supports_module_workers: env.should_scope_hoist && env.engines.supports(EnvironmentFeature::WorkerModule), - trace_bailouts: context.options().log_level == LogLevel::Verbose, + // TODO: Update transformer to use engines directly + targets: Some(targets), + trace_bailouts: self.options.log_level == LogLevel::Verbose, + use_define_for_class_fields: compiler_options + .map(|co| { + co.use_define_for_class_fields.unwrap_or_else(|| { + // Default useDefineForClassFields to true if target is ES2022 or higher (including ESNext) + co.target.as_ref().is_some_and(|target| { + matches!(target, Target::ES2022 | Target::ES2023 | Target::ESNext) + }) + }) + }) + .unwrap_or_default(), ..parcel_js_swc_core::Config::default() }, None, @@ -88,7 +198,7 @@ impl TransformerPlugin for ParcelJsTransformerPlugin { } let file_path = input.file_path(); - let asset_type = FileType::from_extension( + let file_type = FileType::from_extension( file_path .extension() .and_then(|s| s.to_str()) @@ -96,16 +206,15 @@ impl TransformerPlugin for ParcelJsTransformerPlugin { ); let asset = Asset { - asset_type, code: source_code, env: env.clone(), file_path: file_path.to_path_buf(), + file_type, ..Asset::default() }; let config = parcel_js_swc_core::Config::default(); - let options = context.options(); - let result = conversion::convert_result(asset, &config, transformation_result, &options) + let result = conversion::convert_result(asset, &config, transformation_result, &self.options) // TODO handle errors properly .map_err(|_err| anyhow!("Failed to transform"))?; @@ -116,22 +225,19 @@ impl TransformerPlugin for ParcelJsTransformerPlugin { #[cfg(test)] mod test { use std::path::PathBuf; - use std::sync::Arc; - use parcel_core::plugin::{ - RunTransformContext, TransformResult, TransformationInput, TransformerPlugin, - }; - use parcel_core::types::{ - Asset, Code, Dependency, FileType, Location, ParcelOptions, SourceLocation, SpecifierType, - Symbol, + use parcel_core::{ + config_loader::ConfigLoader, + plugin::PluginLogger, + types::{Code, Dependency, Location, SourceLocation, SpecifierType, Symbol}, }; use parcel_filesystem::in_memory_file_system::InMemoryFileSystem; - use crate::ParcelJsTransformerPlugin; + use super::*; fn empty_asset() -> Asset { Asset { - asset_type: FileType::Js, + file_type: FileType::Js, ..Default::default() } } @@ -153,7 +259,7 @@ mod test { }; // This nÂș should not change across runs / compilation - assert_eq!(asset_1.id(), 5787511958692361102); + assert_eq!(asset_1.id(), 12098957784286304761); assert_eq!(asset_1.id(), asset_2.id()); } @@ -173,7 +279,7 @@ mod test { TransformResult { asset: Asset { file_path: "mock_path.js".into(), - asset_type: FileType::Js, + file_type: FileType::Js, // SWC inserts a newline here code: Arc::new(Code::from(String::from("function hello() {}\n"))), symbols: vec![], @@ -237,10 +343,10 @@ exports.hello = function() {}; TransformResult { asset: Asset { file_path: "mock_path.js".into(), - asset_type: FileType::Js, + file_type: FileType::Js, // SWC inserts a newline here code: Arc::new(Code::from(String::from( - "const x = require(\"e83f3db3d6f57ea6\");\nexports.hello = function() {};\n" + "var x = require(\"e83f3db3d6f57ea6\");\nexports.hello = function() {};\n" ))), symbols: vec![ Symbol { @@ -285,12 +391,21 @@ exports.hello = function() {}; fn run_test(asset: Asset) -> anyhow::Result { let file_system = Arc::new(InMemoryFileSystem::default()); - let options = Arc::new(ParcelOptions::default()); - let mut context = RunTransformContext::new(file_system, options, PathBuf::default()); - let mut transformer = ParcelJsTransformerPlugin::new(); - let input = TransformationInput::Asset(asset); - let result = transformer.transform(&mut context, input)?; + let ctx = PluginContext { + config: Arc::new(ConfigLoader { + fs: file_system.clone(), + project_root: PathBuf::default(), + search_path: PathBuf::default(), + }), + file_system, + logger: PluginLogger::default(), + options: Arc::new(PluginOptions::default()), + }; + + let mut transformer = ParcelJsTransformerPlugin::new(&ctx).expect("Expected transformer"); + + let result = transformer.transform(TransformationInput::Asset(asset))?; Ok(result) } } diff --git a/crates/parcel_plugin_transformer_js/src/transformer/conversion.rs b/crates/parcel_plugin_transformer_js/src/transformer/conversion.rs index 21287478a9a..a19c6697ab8 100644 --- a/crates/parcel_plugin_transformer_js/src/transformer/conversion.rs +++ b/crates/parcel_plugin_transformer_js/src/transformer/conversion.rs @@ -5,11 +5,11 @@ use indexmap::IndexMap; use parcel_core::diagnostic; use swc_core::atoms::Atom; -use parcel_core::plugin::TransformResult; +use parcel_core::plugin::{PluginOptions, TransformResult}; use parcel_core::types::engines::EnvironmentFeature; use parcel_core::types::{ Asset, BundleBehavior, Code, CodeFrame, CodeHighlight, Dependency, Diagnostic, DiagnosticBuilder, - Environment, EnvironmentContext, File, FileType, IncludeNodeModules, OutputFormat, ParcelOptions, + Environment, EnvironmentContext, File, FileType, IncludeNodeModules, OutputFormat, SourceLocation, SourceType, SpecifierType, Symbol, }; @@ -29,7 +29,7 @@ pub(crate) fn convert_result( mut asset: Asset, transformer_config: &parcel_js_swc_core::Config, result: parcel_js_swc_core::TransformResult, - options: &ParcelOptions, + options: &PluginOptions, ) -> Result> { let asset_file_path = asset.file_path.to_path_buf(); let asset_environment = asset.env.clone(); @@ -252,7 +252,7 @@ pub(crate) fn convert_result( if asset.unique_key.is_none() { asset.unique_key = Some(format!("{:016x}", asset_id)); } - asset.asset_type = FileType::Js; + asset.file_type = FileType::Js; // Overwrite the source-code with SWC output let result_source_code_string = String::from_utf8(result.code) @@ -333,7 +333,7 @@ fn make_export_star_symbol(asset_id: u64) -> Symbol { } fn make_esm_helpers_dependency( - options: &ParcelOptions, + options: &PluginOptions, asset_file_path: &PathBuf, asset_environment: Environment, has_symbols: bool, diff --git a/crates/parcel_plugin_transformer_js/src/ts_config.rs b/crates/parcel_plugin_transformer_js/src/ts_config.rs new file mode 100644 index 00000000000..023a4b0d142 --- /dev/null +++ b/crates/parcel_plugin_transformer_js/src/ts_config.rs @@ -0,0 +1,75 @@ +use serde::{Deserialize, Deserializer}; + +#[derive(Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum Jsx { + Preserve, + React, + ReactJsx, + #[serde(rename = "react-jsxdev")] + ReactJsxDev, + ReactNative, +} + +pub enum Target { + ES3, + ES5, + ES6, + ES2015, + ES2016, + ES2017, + ES2018, + ES2019, + ES2020, + ES2021, + ES2022, + ES2023, + ESNext, + #[allow(dead_code)] + Other(String), +} + +impl<'de> Deserialize<'de> for Target { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let target = String::deserialize(deserializer)?.to_lowercase(); + + Ok(match target.as_str() { + "es3" => Target::ES3, + "es5" => Target::ES5, + "es6" => Target::ES6, + "es2015" => Target::ES2015, + "es2016" => Target::ES2016, + "es2017" => Target::ES2017, + "es2018" => Target::ES2018, + "es2019" => Target::ES2019, + "es2020" => Target::ES2020, + "es2021" => Target::ES2021, + "es2022" => Target::ES2022, + "es2023" => Target::ES2023, + "esnext" => Target::ESNext, + other => Target::Other(other.to_string()), + }) + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CompilerOptions { + pub experimental_decorators: Option, + pub jsx: Option, + pub jsx_factory: Option, + pub jsx_import_source: Option, + pub jsx_fragment_factory: Option, + pub target: Option, + pub use_define_for_class_fields: Option, +} + +/// Refer to https://www.typescriptlang.org/tsconfig +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TsConfig { + pub compiler_options: Option, +} diff --git a/packages/core/core/src/CommittedAsset.js b/packages/core/core/src/CommittedAsset.js index ea84ca2b1fc..8a9d7c420ec 100644 --- a/packages/core/core/src/CommittedAsset.js +++ b/packages/core/core/src/CommittedAsset.js @@ -6,7 +6,6 @@ import type {Asset, Dependency, ParcelOptions} from './types'; import {Readable} from 'stream'; import SourceMap from '@parcel/source-map'; import {bufferStream, blobToStream, streamFromPromise} from '@parcel/utils'; -import {getFeatureFlag} from '@parcel/feature-flags'; import {generateFromAST} from './assetUtils'; import {deserializeRaw} from './serializer'; @@ -23,9 +22,7 @@ export default class CommittedAsset { constructor(value: Asset, options: ParcelOptions) { this.value = value; - this.key = getFeatureFlag('parcelV3') - ? this.value.id - : this.value.contentKey; + this.key = this.value.contentKey; this.options = options; } diff --git a/packages/core/core/src/requests/AssetGraphRequestRust.js b/packages/core/core/src/requests/AssetGraphRequestRust.js index 160acde9627..5b0b0664b06 100644 --- a/packages/core/core/src/requests/AssetGraphRequestRust.js +++ b/packages/core/core/src/requests/AssetGraphRequestRust.js @@ -123,6 +123,7 @@ function getAssetGraph(serializedGraph, options) { ...asset, id, committed: true, + contentKey: id, filePath: toProjectPath(options.projectRoot, asset.filePath), symbols: asset.hasSymbols ? new Map( @@ -149,6 +150,7 @@ function getAssetGraph(serializedGraph, options) { dependency = { ...dependency, id, + contentKey: id, sourcePath: dependency.sourcePath ? toProjectPath(options.projectRoot, dependency.sourcePath) : null, diff --git a/packages/core/integration-tests/test/javascript.js b/packages/core/integration-tests/test/javascript.js index 74319abb849..11677462687 100644 --- a/packages/core/integration-tests/test/javascript.js +++ b/packages/core/integration-tests/test/javascript.js @@ -481,74 +481,65 @@ describe('javascript', function () { assert(!mainBundleContent.includes('foo:')); }); - it.v2( - 'should split bundles when a dynamic import is used with a node environment', - async function () { - let b = await bundle( - path.join(__dirname, '/integration/dynamic-node/index.js'), - ); + it('should split bundles when a dynamic import is used with a node environment', async function () { + let b = await bundle( + path.join(__dirname, '/integration/dynamic-node/index.js'), + ); - assertBundles(b, [ - { - name: 'index.js', - assets: ['index.js'], - }, - { - assets: ['local.js'], - }, - ]); + assertBundles(b, [ + { + name: 'index.js', + assets: ['index.js'], + }, + { + assets: ['local.js'], + }, + ]); - let output = await run(b); - assert.equal(typeof output, 'function'); - assert.equal(await output(), 3); - }, - ); + let output = await run(b); + assert.equal(typeof output, 'function'); + assert.equal(await output(), 3); + }); - it.v2( - 'should split bundles when a dynamic import is used with an electron-main environment', - async function () { - let b = await bundle( - path.join(__dirname, '/integration/dynamic-electron-main/index.js'), - ); + it('should split bundles when a dynamic import is used with an electron-main environment', async function () { + let b = await bundle( + path.join(__dirname, '/integration/dynamic-electron-main/index.js'), + ); - assertBundles(b, [ - { - name: 'index.js', - assets: ['index.js'], - }, - { - assets: ['local.js'], - }, - ]); + assertBundles(b, [ + { + name: 'index.js', + assets: ['index.js'], + }, + { + assets: ['local.js'], + }, + ]); - let output = await run(b); - assert.equal(typeof output, 'function'); - assert.equal(await output(), 3); - }, - ); + let output = await run(b); + assert.equal(typeof output, 'function'); + assert.equal(await output(), 3); + }); - it.v2( - 'should split bundles when a dynamic import is used with an electron-renderer environment', - async function () { - let b = await bundle( - path.join(__dirname, '/integration/dynamic-electron-renderer/index.js'), - ); + it('should split bundles when a dynamic import is used with an electron-renderer environment', async function () { + let b = await bundle( + path.join(__dirname, '/integration/dynamic-electron-renderer/index.js'), + ); - assertBundles(b, [ - { - name: 'index.js', - assets: ['index.js'], - }, - { - assets: ['local.js'], - }, - ]); + assertBundles(b, [ + { + name: 'index.js', + assets: ['index.js'], + }, + { + assets: ['local.js'], + }, + ]); - let output = await run(b); - assert.equal(typeof output, 'function'); - assert.equal(await output(), 3); - }, - ); + let output = await run(b); + assert.equal(typeof output, 'function'); + assert.equal(await output(), 3); + }); it.skip('should load dynamic bundle when entry is in a subdirectory', async function () { let bu = await bundler( @@ -1183,7 +1174,7 @@ describe('javascript', function () { assert(!js.includes('local.a')); }); - it.v2('should use terser config', async function () { + it('should use terser config', async function () { await bundle(path.join(__dirname, '/integration/terser-config/index.js'), { defaultTargetOptions: { shouldOptimize: true, @@ -1389,35 +1380,29 @@ describe('javascript', function () { }, ); - it.v2( - 'should not insert global variables in dead branches', - async function () { - let b = await bundle( - path.join(__dirname, '/integration/globals-unused/a.js'), - ); + it('should not insert global variables in dead branches', async function () { + let b = await bundle( + path.join(__dirname, '/integration/globals-unused/a.js'), + ); - assertBundles(b, [ - { - assets: ['a.js'], - }, - ]); + assertBundles(b, [ + { + assets: ['a.js'], + }, + ]); - let output = await run(b); - assert.deepEqual(output, 'foo'); - }, - ); + let output = await run(b); + assert.deepEqual(output, 'foo'); + }); - it.v2( - 'should handle re-declaration of the global constant', - async function () { - let b = await bundle( - path.join(__dirname, '/integration/global-redeclare/index.js'), - ); + it('should handle re-declaration of the global constant', async function () { + let b = await bundle( + path.join(__dirname, '/integration/global-redeclare/index.js'), + ); - let output = await run(b); - assert.deepEqual(output(), false); - }, - ); + let output = await run(b); + assert.deepEqual(output(), false); + }); it.v2( 'should insert environment variables inserted by a prior transform', @@ -1434,18 +1419,15 @@ describe('javascript', function () { }, ); - it.v2( - 'should not insert environment variables in node environment', - async function () { - let b = await bundle( - path.join(__dirname, '/integration/env-node/index.js'), - ); + it('should not insert environment variables in node environment', async function () { + let b = await bundle( + path.join(__dirname, '/integration/env-node/index.js'), + ); - let output = await run(b); - assert.ok(output.toString().includes('process.env')); - assert.equal(output(), 'test:test'); - }, - ); + let output = await run(b); + assert.ok(output.toString().includes('process.env')); + assert.equal(output(), 'test:test'); + }); it.v2( 'should not replace process.env.hasOwnProperty with undefined', diff --git a/packages/transformers/js/test/JSTransformer.test.js b/packages/transformers/js/test/JSTransformer.test.js deleted file mode 100644 index eb3654f08ba..00000000000 --- a/packages/transformers/js/test/JSTransformer.test.js +++ /dev/null @@ -1,9 +0,0 @@ -import {testingRunParcelJsTransformerPlugin} from '@parcel/rust'; -import assert from 'node:assert'; - -describe('rust transformer', () => { - it('runs', async () => { - const result = await testingRunParcelJsTransformerPlugin(__filename); - assert(result != null); - }); -});