From 9aec3f7ccdde01143b94897bf4fc2c1c298d7f44 Mon Sep 17 00:00:00 2001 From: xusd320 Date: Mon, 30 Jun 2025 23:37:59 +0800 Subject: [PATCH 1/8] feat(turbopack): apply utoo patches to canary (#27) * feat(turbopack): apply utoo patches to canary * chore: update external code --------- Co-authored-by: zoomdong <1344492820@qq.com> --- Cargo.lock | 2 + .../next/src/build/swc/generated-native.d.ts | 2 +- turbopack/crates/turbopack-browser/Cargo.toml | 3 +- .../turbopack-browser/src/chunking_context.rs | 147 +++++++++++++++--- .../src/ecmascript/evaluate/chunk.rs | 24 ++- .../turbopack-browser/src/ecmascript/mod.rs | 1 + turbopack/crates/turbopack-browser/src/lib.rs | 2 +- turbopack/crates/turbopack-core/src/ident.rs | 10 +- .../crates/turbopack-css/src/chunk/mod.rs | 9 +- .../src/browser/runtime/base/runtime-base.ts | 28 +++- .../browser/runtime/dom/dev-backend-dom.ts | 5 +- .../src/references/external_module.rs | 10 ++ .../turbopack-nodejs/src/chunking_context.rs | 3 + 13 files changed, 200 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b275e9d74999a..aad4db83f3c20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9539,6 +9539,8 @@ dependencies = [ "anyhow", "either", "indoc", + "qstring", + "regex", "serde", "serde_json", "serde_qs", diff --git a/packages/next/src/build/swc/generated-native.d.ts b/packages/next/src/build/swc/generated-native.d.ts index 34bf982be9d21..b824cc8151cc9 100644 --- a/packages/next/src/build/swc/generated-native.d.ts +++ b/packages/next/src/build/swc/generated-native.d.ts @@ -28,7 +28,7 @@ export function lightningCssTransformStyleAttribute( /* auto-generated by NAPI-RS */ -export declare class ExternalObject { +export class ExternalObject { readonly '': { readonly '': unique symbol [K: symbol]: T diff --git a/turbopack/crates/turbopack-browser/Cargo.toml b/turbopack/crates/turbopack-browser/Cargo.toml index edc6be62977ef..152493d9bdb93 100644 --- a/turbopack/crates/turbopack-browser/Cargo.toml +++ b/turbopack/crates/turbopack-browser/Cargo.toml @@ -27,7 +27,8 @@ serde_json = { workspace = true } serde_qs = { workspace = true } tracing = { workspace = true } urlencoding = { workspace = true } - +regex = { workspace = true } +qstring = { workspace = true } turbo-rcstr = { workspace = true } turbo-tasks = { workspace = true } turbo-tasks-fs = { workspace = true } diff --git a/turbopack/crates/turbopack-browser/src/chunking_context.rs b/turbopack/crates/turbopack-browser/src/chunking_context.rs index 0b70e283cd3e0..406140a2ca424 100644 --- a/turbopack/crates/turbopack-browser/src/chunking_context.rs +++ b/turbopack/crates/turbopack-browser/src/chunking_context.rs @@ -1,4 +1,8 @@ +use std::{cmp::min, sync::LazyLock}; + use anyhow::{Context, Result, bail}; +use qstring::QString; +use regex::Regex; use serde::{Deserialize, Serialize}; use tracing::Instrument; use turbo_rcstr::{RcStr, rcstr}; @@ -183,6 +187,16 @@ impl BrowserChunkingContextBuilder { self } + pub fn filename(mut self, filename: RcStr) -> Self { + self.chunking_context.filename = Some(filename); + self + } + + pub fn chunk_filename(mut self, chunk_filename: RcStr) -> Self { + self.chunking_context.chunk_filename = Some(chunk_filename); + self + } + pub fn build(self) -> Vc { BrowserChunkingContext::cell(self.chunking_context) } @@ -249,6 +263,10 @@ pub struct BrowserChunkingContext { export_usage: Option>, /// The chunking configs chunking_configs: Vec<(ResolvedVc>, ChunkingConfig)>, + /// Evaluate chunk filename template + filename: Option, + /// Non evaluate chunk filename template + chunk_filename: Option, } impl BrowserChunkingContext { @@ -289,6 +307,8 @@ impl BrowserChunkingContext { module_id_strategy: ResolvedVc::upcast(DevModuleIdStrategy::new_resolved()), export_usage: None, chunking_configs: Default::default(), + filename: Default::default(), + chunk_filename: Default::default(), }, } } @@ -438,36 +458,76 @@ impl ChunkingContext for BrowserChunkingContext { extension.starts_with("."), "`extension` should include the leading '.', got '{extension}'" ); - let root_path = self.chunk_root_path.clone(); - let name = match self.content_hashing { - None => { - ident - .output_name(self.root_path.clone(), prefix, extension) - .owned() - .await? - } - Some(ContentHashing::Direct { length }) => { - let Some(asset) = asset else { - bail!("chunk_path requires an asset when content hashing is enabled"); - }; - let content = asset.content().await?; - if let AssetContent::File(file) = &*content { - let hash = hash_xxh3_hash64(&file.await?); - let length = length as usize; - if let Some(prefix) = prefix { - format!("{prefix}-{hash:0length$x}{extension}").into() - } else { - format!("{hash:0length$x}{extension}").into() + + let output_name = ident + .output_name(self.root_path.clone(), prefix, extension.clone()) + .owned() + .await?; + + let mut filename = match asset { + Some(asset) => { + let ident = ident.await?; + + let mut evaluate = false; + let mut dev_chunk_list = false; + ident.modifiers.iter().for_each(|m| { + if m.contains("evaluate") { + evaluate = true; } + if m.contains("dev chunk list") { + dev_chunk_list = true; + } + }); + let query = QString::from(ident.query.as_str()); + let name = if dev_chunk_list { + output_name.as_str() } else { - bail!( - "chunk_path requires an asset with file content when content hashing is \ - enabled" - ); + query.get("name").unwrap_or(output_name.as_str()) + }; + + let filename_template = if evaluate { + &self.filename + } else { + &self.chunk_filename + }; + + match filename_template { + Some(filename) => { + let mut filename = filename.to_string(); + + if match_name_placeholder(&filename) { + filename = replace_name_placeholder(&filename, name); + } + + if match_content_hash_placeholder(&filename) { + let content = asset.content().await?; + if let AssetContent::File(file) = &*content { + let content_hash = hash_xxh3_hash64(&file.await?); + filename = replace_content_hash_placeholder( + &filename, + &format!("{content_hash:016x}"), + ); + } else { + bail!( + "chunk_path requires an asset with file content when content \ + hashing is enabled" + ); + } + }; + + filename + } + None => name.to_string(), } } + None => output_name.to_string(), }; - Ok(root_path.join(&name)?.cell()) + + if !filename.ends_with(extension.as_str()) { + filename.push_str(&extension); + } + + self.chunk_root_path.join(&filename).map(|p| p.cell()) } #[turbo_tasks::function] @@ -764,3 +824,40 @@ impl ChunkingContext for BrowserChunkingContext { } } } + +pub fn clean_separators(s: &str) -> String { + static SEPARATOR_REGEX: LazyLock = LazyLock::new(|| Regex::new(r".*[/#?]").unwrap()); + SEPARATOR_REGEX.replace_all(s, "").to_string() +} + +static NAME_PLACEHOLDER_REGEX: LazyLock = LazyLock::new(|| Regex::new(r"\[name\]").unwrap()); + +pub fn match_name_placeholder(s: &str) -> bool { + NAME_PLACEHOLDER_REGEX.is_match(s) +} + +pub fn replace_name_placeholder(s: &str, name: &str) -> String { + NAME_PLACEHOLDER_REGEX.replace_all(s, name).to_string() +} + +static CONTENT_HASH_PLACEHOLDER_REGEX: LazyLock = + LazyLock::new(|| Regex::new(r"\[contenthash(?::(?P\d+))?\]").unwrap()); + +pub fn match_content_hash_placeholder(s: &str) -> bool { + CONTENT_HASH_PLACEHOLDER_REGEX.is_match(s) +} + +pub fn replace_content_hash_placeholder(s: &str, hash: &str) -> String { + CONTENT_HASH_PLACEHOLDER_REGEX + .replace_all(s, |caps: ®ex::Captures| { + let len = caps.name("len").map(|m| m.as_str()).unwrap_or(""); + let len = if len.is_empty() { + hash.len() + } else { + len.parse().unwrap_or(hash.len()) + }; + let len = min(len, hash.len()); + hash[..len].to_string() + }) + .to_string() +} diff --git a/turbopack/crates/turbopack-browser/src/ecmascript/evaluate/chunk.rs b/turbopack/crates/turbopack-browser/src/ecmascript/evaluate/chunk.rs index ce7caa5ba9de4..d4a44585aa8ef 100644 --- a/turbopack/crates/turbopack-browser/src/ecmascript/evaluate/chunk.rs +++ b/turbopack/crates/turbopack-browser/src/ecmascript/evaluate/chunk.rs @@ -36,7 +36,7 @@ use crate::{ /// * Contains the Turbopack browser runtime code; and /// * Evaluates a list of runtime entries. #[turbo_tasks::value(shared)] -pub(crate) struct EcmascriptBrowserEvaluateChunk { +pub struct EcmascriptBrowserEvaluateChunk { chunking_context: ResolvedVc, ident: ResolvedVc, other_chunks: ResolvedVc, @@ -68,13 +68,33 @@ impl EcmascriptBrowserEvaluateChunk { } #[turbo_tasks::function] - async fn chunks_data(&self) -> Result> { + pub async fn chunks_data(&self) -> Result> { Ok(ChunkData::from_assets( self.chunking_context.output_root().owned().await?, *self.other_chunks, )) } + #[turbo_tasks::function] + pub fn ident(&self) -> Vc { + *self.ident + } + + #[turbo_tasks::function] + pub fn evaluatable_assets(&self) -> Vc { + *self.evaluatable_assets + } + + #[turbo_tasks::function] + pub fn module_graph(&self) -> Vc { + *self.module_graph + } + + #[turbo_tasks::function] + pub fn chunking_context(&self) -> Vc> { + Vc::upcast(*self.chunking_context) + } + #[turbo_tasks::function] async fn code(self: Vc) -> Result> { let this = self.await?; diff --git a/turbopack/crates/turbopack-browser/src/ecmascript/mod.rs b/turbopack/crates/turbopack-browser/src/ecmascript/mod.rs index 2d18d579d4ad1..55ad1bb464d9d 100644 --- a/turbopack/crates/turbopack-browser/src/ecmascript/mod.rs +++ b/turbopack/crates/turbopack-browser/src/ecmascript/mod.rs @@ -9,3 +9,4 @@ pub(crate) mod version; pub use chunk::EcmascriptBrowserChunk; pub use content::EcmascriptBrowserChunkContent; +pub use evaluate::chunk::EcmascriptBrowserEvaluateChunk; diff --git a/turbopack/crates/turbopack-browser/src/lib.rs b/turbopack/crates/turbopack-browser/src/lib.rs index ffc9c554362e7..3098acd8172d5 100644 --- a/turbopack/crates/turbopack-browser/src/lib.rs +++ b/turbopack/crates/turbopack-browser/src/lib.rs @@ -3,7 +3,7 @@ #![feature(arbitrary_self_types)] #![feature(arbitrary_self_types_pointers)] -pub(crate) mod chunking_context; +pub mod chunking_context; pub mod ecmascript; pub mod react_refresh; diff --git a/turbopack/crates/turbopack-core/src/ident.rs b/turbopack/crates/turbopack-core/src/ident.rs index ad1a42d22d6ed..108c237f48723 100644 --- a/turbopack/crates/turbopack-core/src/ident.rs +++ b/turbopack/crates/turbopack-core/src/ident.rs @@ -419,10 +419,10 @@ impl AssetIdent { // We need to make sure that `.json` and `.json.js` doesn't end up with the same // name. So when we add an extra extension when want to mark that with a "._" // suffix. - if !removed_extension { - name += "._"; - } - name += &expected_extension; + // if !removed_extension { + // name += "._"; + // } + // name += &expected_extension; Ok(Vc::cell(name.into())) } } @@ -433,5 +433,5 @@ fn clean_separators(s: &str) -> String { } fn clean_additional_extensions(s: &str) -> String { - s.replace('.', "_") + s.replace('.', "_").replace("[root-of-the-server]", "") } diff --git a/turbopack/crates/turbopack-css/src/chunk/mod.rs b/turbopack/crates/turbopack-css/src/chunk/mod.rs index c29d9bf68024f..9a8ffce8ef86b 100644 --- a/turbopack/crates/turbopack-css/src/chunk/mod.rs +++ b/turbopack/crates/turbopack-css/src/chunk/mod.rs @@ -4,11 +4,8 @@ pub mod source_map; use std::fmt::Write; use anyhow::{Result, bail}; -use swc_core::common::pass::Either; use turbo_rcstr::{RcStr, rcstr}; -use turbo_tasks::{ - FxIndexSet, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, ValueDefault, ValueToString, Vc, -}; +use turbo_tasks::{FxIndexSet, ResolvedVc, TryJoinIterExt, ValueDefault, ValueToString, Vc}; use turbo_tasks_fs::{ File, FileSystem, FileSystemPath, rope::{Rope, RopeBuilder}, @@ -35,7 +32,7 @@ use turbopack_core::{ source_map::{GenerateSourceMap, OptionStringifiedSourceMap, utils::fileify_source_map}, }; -use self::{single_item_chunk::chunk::SingleItemCssChunk, source_map::CssChunkSourceMapAsset}; +use self::source_map::CssChunkSourceMapAsset; use crate::{ImportAssetReference, util::stringify_js}; #[turbo_tasks::value] @@ -316,7 +313,7 @@ impl OutputChunk for CssChunk { }; Ok(OutputChunkRuntimeInfo { included_ids: Some(ResolvedVc::cell(included_ids)), - module_chunks: Some(ResolvedVc::cell(module_chunks)), + module_chunks: None, ..Default::default() } .cell()) diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/runtime-base.ts b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/runtime-base.ts index 8889fd84af06f..57871b867b586 100644 --- a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/runtime-base.ts +++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/runtime-base.ts @@ -21,6 +21,21 @@ declare var TURBOPACK_NEXT_CHUNK_URLS: ChunkUrl[] | undefined declare var CHUNK_BASE_PATH: string declare var CHUNK_SUFFIX_PATH: string +function normalizeChunkPath(path: string) { + if (path.startsWith('/')) { + path = path.substring(1) + } else if (path.startsWith('./')) { + path = path.substring(2) + } + + if (path.endsWith('/')) { + path = path.slice(0, -1) + } + return path +} + +const NORMALIZED_CHUNK_BASE_PATH = normalizeChunkPath(CHUNK_BASE_PATH) + // Provided by build or dev base declare function instantiateModule( id: ModuleId, @@ -340,7 +355,7 @@ function instantiateRuntimeModule( * Returns the URL relative to the origin where a chunk can be fetched from. */ function getChunkRelativeUrl(chunkPath: ChunkPath | ChunkListPath): ChunkUrl { - return `${CHUNK_BASE_PATH}${chunkPath + return `${NORMALIZED_CHUNK_BASE_PATH}${chunkPath .split('/') .map((p) => encodeURIComponent(p)) .join('/')}${CHUNK_SUFFIX_PATH}` as ChunkUrl @@ -359,13 +374,18 @@ function getPathFromScript( if (typeof chunkScript === 'string') { return chunkScript as ChunkPath | ChunkListPath } - const chunkUrl = + let chunkUrl = typeof TURBOPACK_NEXT_CHUNK_URLS !== 'undefined' ? TURBOPACK_NEXT_CHUNK_URLS.pop()! : chunkScript.getAttribute('src')! + if (chunkUrl.startsWith('/')) { + chunkUrl = chunkUrl.substring(1) + } else if (chunkUrl.startsWith('./')) { + chunkUrl = chunkUrl.substring(2) + } const src = decodeURIComponent(chunkUrl.replace(/[?#].*$/, '')) - const path = src.startsWith(CHUNK_BASE_PATH) - ? src.slice(CHUNK_BASE_PATH.length) + const path = src.startsWith(NORMALIZED_CHUNK_BASE_PATH) + ? src.slice(NORMALIZED_CHUNK_BASE_PATH.length) : src return path as ChunkPath | ChunkListPath } diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/dom/dev-backend-dom.ts b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/dom/dev-backend-dom.ts index f4c50ffcc8b45..f92b654a591a7 100644 --- a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/dom/dev-backend-dom.ts +++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/dom/dev-backend-dom.ts @@ -111,7 +111,10 @@ let DEV_BACKEND: DevRuntimeBackend function _eval({ code, url, map }: EcmascriptModuleEntry): ModuleFactory { code += `\n\n//# sourceURL=${encodeURI( - location.origin + CHUNK_BASE_PATH + url + CHUNK_SUFFIX_PATH + location.origin + + NORMALIZED_CHUNK_BASE_PATH + + normalizeChunkPath(url) + + CHUNK_SUFFIX_PATH )}` if (map) { code += `\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,${btoa( diff --git a/turbopack/crates/turbopack-ecmascript/src/references/external_module.rs b/turbopack/crates/turbopack-ecmascript/src/references/external_module.rs index 9062226a62149..df47d8931eb78 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/external_module.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/external_module.rs @@ -117,6 +117,16 @@ impl CachedExternalModule { CachedExternalType::Global => { if self.request.is_empty() { writeln!(code, "const mod = {{}};")?; + } else if self.request.contains('/') { + // Handle requests with '/' by splitting into nested global access + let global_access = self + .request + .split('/') + .fold("globalThis".to_string(), |acc, part| { + format!("{}[{}]", acc, StringifyJs(part)) + }); + + writeln!(code, "const mod = {global_access};")?; } else { writeln!( code, diff --git a/turbopack/crates/turbopack-nodejs/src/chunking_context.rs b/turbopack/crates/turbopack-nodejs/src/chunking_context.rs index 535fd67cd6fbf..6ff6fece5cd9e 100644 --- a/turbopack/crates/turbopack-nodejs/src/chunking_context.rs +++ b/turbopack/crates/turbopack-nodejs/src/chunking_context.rs @@ -325,6 +325,9 @@ impl ChunkingContext for NodeJsChunkingContext { .output_name(self.root_path.clone(), prefix, extension) .owned() .await?; + if !name.ends_with(extension.as_str()) { + name.push_str(&extension); + } Ok(root_path.join(&name)?.cell()) } From 62150bcb5f3710ddcaee1a8d7e62bcda00656e16 Mon Sep 17 00:00:00 2001 From: xusd320 Date: Mon, 30 Jun 2025 23:44:52 +0800 Subject: [PATCH 2/8] feat(turbopack): support full json types and basical evaluation of compile time define env (#28) * feat(turbopack): support more types of compile time define env * fix: should use Syntax::Es for evaluate define env parsing --- crates/next-core/src/next_client/context.rs | 2 +- crates/next-core/src/next_server/context.rs | 2 +- turbopack/crates/turbo-tasks/Cargo.toml | 9 +- .../crates/turbo-tasks/src/task/task_input.rs | 2 +- .../turbopack-core/src/compile_time_info.rs | 17 ++- .../turbopack-ecmascript/src/analyzer/mod.rs | 68 +++++++++--- .../src/references/constant_value.rs | 101 ++++++++++++++---- .../crates/turbopack-ecmascript/src/utils.rs | 1 + .../crates/turbopack-tests/tests/snapshot.rs | 22 +++- .../snapshot/comptime/define/input/index.js | 24 +++++ ...ot_comptime_define_input_index_4d74c0a3.js | 46 ++++++-- ...omptime_define_input_index_4d74c0a3.js.map | 2 +- 12 files changed, 246 insertions(+), 50 deletions(-) diff --git a/crates/next-core/src/next_client/context.rs b/crates/next-core/src/next_client/context.rs index 1ceeb89c1b65e..62d89f27499b7 100644 --- a/crates/next-core/src/next_client/context.rs +++ b/crates/next-core/src/next_client/context.rs @@ -1,4 +1,4 @@ -use std::iter::once; +use std::{iter::once, str::FromStr}; use anyhow::Result; use serde::{Deserialize, Serialize}; diff --git a/crates/next-core/src/next_server/context.rs b/crates/next-core/src/next_server/context.rs index 4a3b58642a0cf..e05d62d75ed48 100644 --- a/crates/next-core/src/next_server/context.rs +++ b/crates/next-core/src/next_server/context.rs @@ -1,4 +1,4 @@ -use std::iter::once; +use std::{iter::once, str::FromStr}; use anyhow::{Result, bail}; use serde::{Deserialize, Serialize}; diff --git a/turbopack/crates/turbo-tasks/Cargo.toml b/turbopack/crates/turbo-tasks/Cargo.toml index 3cdf94702e8bb..d18765b64e678 100644 --- a/turbopack/crates/turbo-tasks/Cargo.toml +++ b/turbopack/crates/turbo-tasks/Cargo.toml @@ -33,7 +33,7 @@ futures = { workspace = true } indexmap = { workspace = true, features = ["serde"] } mopa = "0.2.0" once_cell = { workspace = true } -parking_lot = { workspace = true, features = ["serde"]} +parking_lot = { workspace = true, features = ["serde"] } pin-project-lite = { workspace = true } rayon = { workspace = true } regex = { workspace = true } @@ -41,7 +41,12 @@ rustc-hash = { workspace = true } serde = { workspace = true, features = ["rc", "derive"] } serde_json = { workspace = true } serde_regex = "1.1.0" -shrink-to-fit = { workspace=true,features = ["indexmap", "serde_json", "smallvec", "nightly"] } +shrink-to-fit = { workspace = true, features = [ + "indexmap", + "serde_json", + "smallvec", + "nightly", +] } smallvec = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } diff --git a/turbopack/crates/turbo-tasks/src/task/task_input.rs b/turbopack/crates/turbo-tasks/src/task/task_input.rs index 11eae0c5f7064..525b01c7f2701 100644 --- a/turbopack/crates/turbo-tasks/src/task/task_input.rs +++ b/turbopack/crates/turbo-tasks/src/task/task_input.rs @@ -67,7 +67,7 @@ where async fn resolve_input(&self) -> Result { let mut resolved = Vec::with_capacity(self.len()); for value in self { - resolved.push(value.resolve_input().await?); + resolved.push(Box::pin(value.resolve_input()).await?); } Ok(resolved) } diff --git a/turbopack/crates/turbopack-core/src/compile_time_info.rs b/turbopack/crates/turbopack-core/src/compile_time_info.rs index a861449a45c2c..ffa146926dd44 100644 --- a/turbopack/crates/turbopack-core/src/compile_time_info.rs +++ b/turbopack/crates/turbopack-core/src/compile_time_info.rs @@ -104,10 +104,14 @@ macro_rules! free_var_references { #[turbo_tasks::value] #[derive(Debug, Clone, Hash, TaskInput)] pub enum CompileTimeDefineValue { + Null, Bool(bool), + Number(RcStr), String(RcStr), - JSON(RcStr), + Array(Vec), + Object(Vec<(RcStr, CompileTimeDefineValue)>), Undefined, + Evaluate(RcStr), } impl From for CompileTimeDefineValue { @@ -136,7 +140,16 @@ impl From<&str> for CompileTimeDefineValue { impl From for CompileTimeDefineValue { fn from(value: serde_json::Value) -> Self { - Self::JSON(value.to_string().into()) + match value { + serde_json::Value::Null => Self::Null, + serde_json::Value::Bool(b) => Self::Bool(b), + serde_json::Value::Number(n) => Self::Number(n.to_string().into()), + serde_json::Value::String(s) => Self::String(s.into()), + serde_json::Value::Array(a) => Self::Array(a.into_iter().map(|i| i.into()).collect()), + serde_json::Value::Object(m) => { + Self::Object(m.into_iter().map(|(k, v)| (k.into(), v.into())).collect()) + } + } } } diff --git a/turbopack/crates/turbopack-ecmascript/src/analyzer/mod.rs b/turbopack/crates/turbopack-ecmascript/src/analyzer/mod.rs index 4d169069ec64e..985e3c98ce24f 100644 --- a/turbopack/crates/turbopack-ecmascript/src/analyzer/mod.rs +++ b/turbopack/crates/turbopack-ecmascript/src/analyzer/mod.rs @@ -185,6 +185,7 @@ pub enum ConstantValue { Null, BigInt(Box), Regex(Box<(Atom, Atom)>), + Evaluate(RcStr), } impl ConstantValue { @@ -203,25 +204,27 @@ impl ConstantValue { } } - pub fn is_truthy(&self) -> bool { + pub fn is_truthy(&self) -> Option { match self { - Self::Undefined | Self::False | Self::Null => false, - Self::True | Self::Regex(..) => true, - Self::Str(s) => !s.is_empty(), - Self::Num(ConstantNumber(n)) => *n != 0.0, - Self::BigInt(n) => !n.is_zero(), + Self::Undefined | Self::False | Self::Null => Some(false), + Self::True | Self::Regex(..) => Some(true), + Self::Str(s) => Some(!s.is_empty()), + Self::Num(ConstantNumber(n)) => Some(*n != 0.0), + Self::BigInt(n) => Some(!n.is_zero()), + Self::Evaluate(_) => None, } } - pub fn is_nullish(&self) -> bool { + pub fn is_nullish(&self) -> Option { match self { - Self::Undefined | Self::Null => true, + Self::Undefined | Self::Null => Some(true), Self::Str(..) | Self::Num(..) | Self::True | Self::False | Self::BigInt(..) - | Self::Regex(..) => false, + | Self::Regex(..) => Some(false), + Self::Evaluate(_) => None, } } @@ -233,7 +236,16 @@ impl ConstantValue { } pub fn is_value_type(&self) -> bool { - !matches!(self, Self::Regex(..)) + match self { + ConstantValue::Undefined + | ConstantValue::Null + | ConstantValue::Str(_) + | ConstantValue::Num(_) + | ConstantValue::True + | ConstantValue::False + | ConstantValue::BigInt(_) => true, + ConstantValue::Regex(_) | ConstantValue::Evaluate(_) => false, + } } } @@ -283,6 +295,7 @@ impl Display for ConstantValue { ConstantValue::Num(ConstantNumber(n)) => write!(f, "{n}"), ConstantValue::BigInt(n) => write!(f, "{n}"), ConstantValue::Regex(regex) => write!(f, "/{}/{}", regex.0, regex.1), + ConstantValue::Evaluate(eval) => write!(f, "{eval}"), } } } @@ -584,12 +597,37 @@ impl From for JsValue { impl From<&CompileTimeDefineValue> for JsValue { fn from(v: &CompileTimeDefineValue) -> Self { match v { - CompileTimeDefineValue::String(s) => JsValue::Constant(s.as_str().into()), + CompileTimeDefineValue::Null => JsValue::Constant(ConstantValue::Null), CompileTimeDefineValue::Bool(b) => JsValue::Constant((*b).into()), - CompileTimeDefineValue::JSON(_) => { - JsValue::unknown_empty(false, "compile time injected JSON") + CompileTimeDefineValue::Number(n) => JsValue::Constant(ConstantValue::Num( + ConstantNumber(n.as_str().parse::().unwrap()), + )), + CompileTimeDefineValue::String(s) => JsValue::Constant(s.as_str().into()), + CompileTimeDefineValue::Array(a) => { + let mut js_value = JsValue::Array { + total_nodes: a.len() as u32, + items: a.iter().map(|i| i.into()).collect(), + mutable: false, + }; + js_value.update_total_nodes(); + js_value + } + CompileTimeDefineValue::Object(m) => { + let mut js_value = JsValue::Object { + total_nodes: m.len() as u32, + parts: m + .iter() + .map(|(k, v)| ObjectPart::KeyValue(k.clone().into(), v.into())) + .collect(), + mutable: false, + }; + js_value.update_total_nodes(); + js_value } CompileTimeDefineValue::Undefined => JsValue::Constant(ConstantValue::Undefined), + CompileTimeDefineValue::Evaluate(s) => { + JsValue::Constant(ConstantValue::Evaluate(s.clone())) + } } } } @@ -2192,7 +2230,7 @@ impl JsValue { /// Some if we know if or if not the value is truthy. pub fn is_truthy(&self) -> Option { match self { - JsValue::Constant(c) => Some(c.is_truthy()), + JsValue::Constant(c) => c.is_truthy(), JsValue::Concat(..) => self.is_empty_string().map(|x| !x), JsValue::Url(..) | JsValue::Array { .. } @@ -2273,7 +2311,7 @@ impl JsValue { /// don't know. Returns Some if we know if or if not the value is nullish. pub fn is_nullish(&self) -> Option { match self { - JsValue::Constant(c) => Some(c.is_nullish()), + JsValue::Constant(c) => c.is_nullish(), JsValue::Concat(..) | JsValue::Url(..) | JsValue::Array { .. } diff --git a/turbopack/crates/turbopack-ecmascript/src/references/constant_value.rs b/turbopack/crates/turbopack-ecmascript/src/references/constant_value.rs index f2bde35c1e014..77e96626b7658 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/constant_value.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/constant_value.rs @@ -1,6 +1,15 @@ +use std::{path::PathBuf, str::FromStr}; + use anyhow::Result; use serde::{Deserialize, Serialize}; -use swc_core::quote; +use swc_core::{ + common::{DUMMY_SP, SourceMap, sync::Lrc}, + ecma::{ + ast::{ArrayLit, EsVersion, Expr, KeyValueProp, ObjectLit, Prop, PropName, Str}, + parser::{Syntax, parse_file_as_expr}, + }, + quote, +}; use turbo_tasks::{NonLocalValue, TaskInput, Vc, debug::ValueDebugFormat, trace::TraceRawVcs}; use turbopack_core::{chunk::ChunkingContext, compile_time_info::CompileTimeDefineValue}; @@ -39,24 +48,8 @@ impl ConstantValueCodeGen { let value = self.value.clone(); let visitor = create_visitor!(self.path, visit_mut_expr, |expr: &mut Expr| { - *expr = match value { - CompileTimeDefineValue::Bool(true) => { - quote!("(\"TURBOPACK compile-time value\", true)" as Expr) - } - CompileTimeDefineValue::Bool(false) => { - quote!("(\"TURBOPACK compile-time value\", false)" as Expr) - } - CompileTimeDefineValue::String(ref s) => { - quote!("(\"TURBOPACK compile-time value\", $e)" as Expr, e: Expr = s.to_string().into()) - } - CompileTimeDefineValue::JSON(ref s) => { - quote!("(\"TURBOPACK compile-time value\", JSON.parse($e))" as Expr, e: Expr = s.to_string().into()) - } - // undefined can be re-bound, so use `void 0` to avoid any risks - CompileTimeDefineValue::Undefined => { - quote!("(\"TURBOPACK compile-time value\", void 0)" as Expr) - } - }; + // TODO: avoid this clone + *expr = define_env_to_expr((value).clone()); }); Ok(CodeGeneration::visitors(vec![visitor])) @@ -68,3 +61,73 @@ impl From for CodeGen { CodeGen::ConstantValueCodeGen(val) } } + +fn define_env_to_expr(value: CompileTimeDefineValue) -> Expr { + match value { + CompileTimeDefineValue::Null => { + quote!("(\"TURBOPACK compile-time value\", null)" as Expr) + } + CompileTimeDefineValue::Bool(true) => { + quote!("(\"TURBOPACK compile-time value\", true)" as Expr) + } + CompileTimeDefineValue::Bool(false) => { + quote!("(\"TURBOPACK compile-time value\", false)" as Expr) + } + CompileTimeDefineValue::Number(ref n) => { + quote!("(\"TURBOPACK compile-time value\", $e)" as Expr, e: Expr = n.parse::().unwrap().into()) + } + CompileTimeDefineValue::String(ref s) => { + quote!("(\"TURBOPACK compile-time value\", $e)" as Expr, e: Expr = s.to_string().into()) + } + CompileTimeDefineValue::Array(a) => { + quote!("(\"TURBOPACK compile-time value\", $e)" as Expr, e: Expr = Expr::Array(ArrayLit { + span: DUMMY_SP, + elems: a.into_iter().map(|i| Some(define_env_to_expr(i).into())).collect(), + })) + } + CompileTimeDefineValue::Object(m) => { + quote!("(\"TURBOPACK compile-time value\", $e)" as Expr, e: Expr = Expr::Object(ObjectLit { + span: DUMMY_SP, + props: m + .into_iter() + .map(|(k, v)| { + swc_core::ecma::ast::PropOrSpread::Prop( + Prop::KeyValue(KeyValueProp { + key: PropName::Str(Str::from(k.as_str())), + value: define_env_to_expr(v).into(), + }) + .into(), + ) + }) + .collect(), + })) + } + CompileTimeDefineValue::Undefined => { + quote!("(\"TURBOPACK compile-time value\", void 0)" as Expr) + } + CompileTimeDefineValue::Evaluate(ref s) => parse_code_to_expr(s.to_string()), + } +} + +fn parse_code_to_expr(code: String) -> Expr { + let cm = Lrc::new(SourceMap::default()); + let fm = cm.new_source_file( + Lrc::new( + PathBuf::from_str("__compile_time_define_value_internal__.js") + .unwrap() + .into(), + ), + code.clone(), + ); + parse_file_as_expr( + &fm, + Syntax::Es(Default::default()), + EsVersion::latest(), + None, + &mut vec![], + ) + .map_or( + quote!("$s" as Expr, s: Expr = code.into()), + |expr| quote!("(\"TURBOPACK compile-time value\", $e)" as Expr, e: Expr = *expr), + ) +} diff --git a/turbopack/crates/turbopack-ecmascript/src/utils.rs b/turbopack/crates/turbopack-ecmascript/src/utils.rs index b4562da77ecef..0d264bb70f028 100644 --- a/turbopack/crates/turbopack-ecmascript/src/utils.rs +++ b/turbopack/crates/turbopack-ecmascript/src/utils.rs @@ -47,6 +47,7 @@ pub fn js_value_to_pattern(value: &JsValue) -> Pattern { ConstantValue::BigInt(n) => n.to_string().into(), ConstantValue::Regex(box (exp, flags)) => format!("/{exp}/{flags}").into(), ConstantValue::Undefined => rcstr!("undefined"), + ConstantValue::Evaluate(eval) => eval.clone(), }), JsValue::Url(v, JsValueUrlKind::Relative) => Pattern::Constant(v.as_rcstr()), JsValue::Alternatives { diff --git a/turbopack/crates/turbopack-tests/tests/snapshot.rs b/turbopack/crates/turbopack-tests/tests/snapshot.rs index 08a45ccc36fd2..e5e8386863310 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot.rs +++ b/turbopack/crates/turbopack-tests/tests/snapshot.rs @@ -34,7 +34,7 @@ use turbopack_core::{ EvaluatableAssets, MinifyType, availability_info::AvailabilityInfo, }, compile_time_defines, - compile_time_info::CompileTimeInfo, + compile_time_info::{CompileTimeDefineValue, CompileTimeInfo, DefineableNameSegment}, condition::ContextCondition, context::AssetContext, environment::{BrowserEnvironment, Environment, ExecutionEnvironment, NodeJsEnvironment}, @@ -296,15 +296,33 @@ async fn run_test_operation(resource: RcStr) -> Result> { .to_resolved() .await?; - let defines = compile_time_defines!( + let mut defines = compile_time_defines!( process.turbopack = true, process.env.TURBOPACK = true, process.env.NODE_ENV = "development", DEFINED_VALUE = "value", DEFINED_TRUE = true, + DEFINED_NULL = json!(null), + DEFINED_INT = json!(1), + DEFINED_FLOAT = json!(0.01), + DEFINED_ARRAY = json!([ false, 0, "1", { "v": "v" }, null ]), A.VERY.LONG.DEFINED.VALUE = json!({ "test": true }), ); + defines.0.insert( + vec![DefineableNameSegment::from("DEFINED_EVALED")], + CompileTimeDefineValue::Evaluate("1 + 1".into()), + ); + + defines.0.insert( + vec![DefineableNameSegment::from("DEFINED_EVALED_NESTED")], + CompileTimeDefineValue::Array(vec![ + CompileTimeDefineValue::Bool(true), + CompileTimeDefineValue::Undefined, + CompileTimeDefineValue::Evaluate("() => 1".into()), + ]), + ); + let compile_time_info = CompileTimeInfo::builder(env) .defines(defines.clone().resolved_cell()) .free_var_references(free_var_references!(..defines.into_iter()).resolved_cell()) diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/input/index.js b/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/input/index.js index 4429017146fb9..7c6d2f64358f0 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/input/index.js +++ b/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/input/index.js @@ -6,6 +6,30 @@ if (DEFINED_TRUE) { console.log('DEFINED_VALUE') } +if (!DEFINED_NULL) { + console.log('DEFINED_NULL', DEFINED_NULL) +} + +if (DEFINED_INT) { + console.log('DEFINED_INT', DEFINED_INT) +} + +if (DEFINED_FLOAT) { + console.log('DEFINED_FLOAT', DEFINED_FLOAT) +} + +if (DEFINED_ARRAY) { + console.log('DEFINED_ARRAY', DEFINED_ARRAY) +} + +if (DEFINED_EVALED) { + console.log('DEFINED_EVALED', DEFINED_EVALED) +} + +if (DEFINED_EVALED_NESTED) { + console.log('DEFINED_EVALED_NESTED', DEFINED_EVALED_NESTED) +} + if (A.VERY.LONG.DEFINED.VALUE) { console.log('A.VERY.LONG.DEFINED.VALUE') } diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/output/4e721_crates_turbopack-tests_tests_snapshot_comptime_define_input_index_4d74c0a3.js b/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/output/4e721_crates_turbopack-tests_tests_snapshot_comptime_define_input_index_4d74c0a3.js index 5aa314e52b8ad..fde3f302b73db 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/output/4e721_crates_turbopack-tests_tests_snapshot_comptime_define_input_index_4d74c0a3.js +++ b/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/output/4e721_crates_turbopack-tests_tests_snapshot_comptime_define_input_index_4d74c0a3.js @@ -10,20 +10,54 @@ if ("TURBOPACK compile-time truthy", 1) { if ("TURBOPACK compile-time truthy", 1) { console.log('DEFINED_VALUE'); } -if ("TURBOPACK compile-time value", JSON.parse('{"test":true}')) { +if ("TURBOPACK compile-time truthy", 1) { + console.log('DEFINED_NULL', ("TURBOPACK compile-time value", null)); +} +if ("TURBOPACK compile-time truthy", 1) { + console.log('DEFINED_INT', ("TURBOPACK compile-time value", 1)); +} +if ("TURBOPACK compile-time truthy", 1) { + console.log('DEFINED_FLOAT', ("TURBOPACK compile-time value", 0.01)); +} +if ("TURBOPACK compile-time truthy", 1) { + console.log('DEFINED_ARRAY', ("TURBOPACK compile-time value", [ + ("TURBOPACK compile-time value", false), + ("TURBOPACK compile-time value", 0), + ("TURBOPACK compile-time value", "1"), + ("TURBOPACK compile-time value", { + "v": ("TURBOPACK compile-time value", "v") + }), + ("TURBOPACK compile-time value", null) + ])); +} +if ("TURBOPACK compile-time value", 1 + 1) { + console.log('DEFINED_EVALED', ("TURBOPACK compile-time value", 1 + 1)); +} +if ("TURBOPACK compile-time truthy", 1) { + console.log('DEFINED_EVALED_NESTED', ("TURBOPACK compile-time value", [ + ("TURBOPACK compile-time value", true), + ("TURBOPACK compile-time value", void 0), + ("TURBOPACK compile-time value", ()=>1) + ])); +} +if ("TURBOPACK compile-time truthy", 1) { console.log('A.VERY.LONG.DEFINED.VALUE'); } if ("TURBOPACK compile-time truthy", 1) { console.log('something'); } -if ("TURBOPACK compile-time falsy", 0) //TURBOPACK unreachable -; +if (("TURBOPACK compile-time value", "development") === 'production') { + console.log('production'); +} var p = process; -console.log(("TURBOPACK compile-time value", JSON.parse('{"test":true}'))); +console.log(("TURBOPACK compile-time value", { + "test": ("TURBOPACK compile-time value", true) +})); console.log(("TURBOPACK compile-time value", "value")); console.log(("TURBOPACK compile-time value", "development")); -if ("TURBOPACK compile-time falsy", 0) //TURBOPACK unreachable -; +if (("TURBOPACK compile-time value", "development") === 'production') { + console.log('production'); +} ("TURBOPACK compile-time falsy", 0) ? "TURBOPACK unreachable" : console.log('development'); // TODO short-circuit is not implemented yet ("TURBOPACK compile-time value", "development") != 'production' && console.log('development'); diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/output/4e721_crates_turbopack-tests_tests_snapshot_comptime_define_input_index_4d74c0a3.js.map b/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/output/4e721_crates_turbopack-tests_tests_snapshot_comptime_define_input_index_4d74c0a3.js.map index 99ad30ad9d349..2732f6ba53656 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/output/4e721_crates_turbopack-tests_tests_snapshot_comptime_define_input_index_4d74c0a3.js.map +++ b/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/output/4e721_crates_turbopack-tests_tests_snapshot_comptime_define_input_index_4d74c0a3.js.map @@ -2,5 +2,5 @@ "version": 3, "sources": [], "sections": [ - {"offset": {"line": 6, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/input/index.js"],"sourcesContent":["if (DEFINED_VALUE) {\n console.log('DEFINED_VALUE')\n}\n\nif (DEFINED_TRUE) {\n console.log('DEFINED_VALUE')\n}\n\nif (A.VERY.LONG.DEFINED.VALUE) {\n console.log('A.VERY.LONG.DEFINED.VALUE')\n}\n\nif (process.env.NODE_ENV) {\n console.log('something')\n}\n\nif (process.env.NODE_ENV === 'production') {\n console.log('production')\n}\n\nvar p = process\n\nconsole.log(A.VERY.LONG.DEFINED.VALUE)\nconsole.log(DEFINED_VALUE)\nconsole.log(p.env.NODE_ENV)\n\nif (p.env.NODE_ENV === 'production') {\n console.log('production')\n}\n\np.env.NODE_ENV == 'production'\n ? console.log('production')\n : console.log('development')\n\n// TODO short-circuit is not implemented yet\np.env.NODE_ENV != 'production' && console.log('development')\np.env.NODE_ENV == 'production' && console.log('production')\n\nconsole.log(__dirname)\n"],"names":[],"mappings":"AAAA,wCAAmB;IACjB,QAAQ,GAAG,CAAC;AACd;AAEA,wCAAkB;IAChB,QAAQ,GAAG,CAAC;AACd;AAEA,iEAA+B;IAC7B,QAAQ,GAAG,CAAC;AACd;AAEA,wCAA0B;IACxB,QAAQ,GAAG,CAAC;AACd;AAEA;;AAIA,IAAI,IAAI;AAER,QAAQ,GAAG;AACX,QAAQ,GAAG;AACX,QAAQ,GAAG;AAEX;;AAIA,sCACI,0BACA,QAAQ,GAAG,CAAC;AAEhB,4CAA4C;AAC5C,mDAAkB,gBAAgB,QAAQ,GAAG,CAAC;AAC9C,mDAAkB,gBAAgB,QAAQ,GAAG,CAAC;AAE9C,QAAQ,GAAG"}}] + {"offset": {"line": 6, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/input/index.js"],"sourcesContent":["if (DEFINED_VALUE) {\n console.log('DEFINED_VALUE')\n}\n\nif (DEFINED_TRUE) {\n console.log('DEFINED_VALUE')\n}\n\nif (!DEFINED_NULL) {\n console.log('DEFINED_NULL', DEFINED_NULL)\n}\n\nif (DEFINED_INT) {\n console.log('DEFINED_INT', DEFINED_INT)\n}\n\nif (DEFINED_FLOAT) {\n console.log('DEFINED_FLOAT', DEFINED_FLOAT)\n}\n\nif (DEFINED_ARRAY) {\n console.log('DEFINED_ARRAY', DEFINED_ARRAY)\n}\n\nif (DEFINED_EVALED) {\n console.log('DEFINED_EVALED', DEFINED_EVALED)\n}\n\nif (DEFINED_EVALED_NESTED) {\n console.log('DEFINED_EVALED_NESTED', DEFINED_EVALED_NESTED)\n}\n\nif (A.VERY.LONG.DEFINED.VALUE) {\n console.log('A.VERY.LONG.DEFINED.VALUE')\n}\n\nif (process.env.NODE_ENV) {\n console.log('something')\n}\n\nif (process.env.NODE_ENV === 'production') {\n console.log('production')\n}\n\nvar p = process\n\nconsole.log(A.VERY.LONG.DEFINED.VALUE)\nconsole.log(DEFINED_VALUE)\nconsole.log(p.env.NODE_ENV)\n\nif (p.env.NODE_ENV === 'production') {\n console.log('production')\n}\n\np.env.NODE_ENV == 'production'\n ? console.log('production')\n : console.log('development')\n\n// TODO short-circuit is not implemented yet\np.env.NODE_ENV != 'production' && console.log('development')\np.env.NODE_ENV == 'production' && console.log('production')\n\nconsole.log(__dirname)\n"],"names":[],"mappings":"AAAA,wCAAmB;IACjB,QAAQ,GAAG,CAAC;AACd;AAEA,wCAAkB;IAChB,QAAQ,GAAG,CAAC;AACd;AAEA,wCAAmB;IACjB,QAAQ,GAAG,CAAC;AACd;AAEA,wCAAiB;IACf,QAAQ,GAAG,CAAC;AACd;AAEA,wCAAmB;IACjB,QAAQ,GAAG,CAAC;AACd;AAEA,wCAAmB;IACjB,QAAQ,GAAG,CAAC;;;;;;;;;AACd;AAEA,oCAxBA,IAAI,GAwBgB;IAClB,QAAQ,GAAG,CAAC,mDAzBd,IAAI;AA0BJ;AAEA,wCAA2B;IACzB,QAAQ,GAAG,CAAC;;;yCA7Bd,IAAM;;AA8BN;AAEA,wCAA+B;IAC7B,QAAQ,GAAG,CAAC;AACd;AAEA,wCAA0B;IACxB,QAAQ,GAAG,CAAC;AACd;AAEA,IAAI,oDAAyB,cAAc;IACzC,QAAQ,GAAG,CAAC;AACd;AAEA,IAAI,IAAI;AAER,QAAQ,GAAG;;;AACX,QAAQ,GAAG;AACX,QAAQ,GAAG;AAEX,IAAI,oDAAmB,cAAc;IACnC,QAAQ,GAAG,CAAC;AACd;AAEA,sCACI,0BACA,QAAQ,GAAG,CAAC;AAEhB,4CAA4C;AAC5C,mDAAkB,gBAAgB,QAAQ,GAAG,CAAC;AAC9C,mDAAkB,gBAAgB,QAAQ,GAAG,CAAC;AAE9C,QAAQ,GAAG"}}] } \ No newline at end of file From 8f9c3539846b363a3c367fd837720f231059c459 Mon Sep 17 00:00:00 2001 From: zoomdong <1344492820@qq.com> Date: Mon, 7 Jul 2025 10:51:53 +0800 Subject: [PATCH 3/8] feat(turbopack): externalType support umd (#30) * feat(turbopack): externalType support umd * fix: clippy * fix: use CompileTimeDefineValue::Evaluate --------- Co-authored-by: xusd320 --- crates/next-core/src/next_client/context.rs | 2 +- crates/next-core/src/next_server/context.rs | 2 +- crates/next-core/src/util.rs | 2 +- .../crates/turbopack-core/src/resolve/mod.rs | 7 +++++- .../src/references/external_module.rs | 22 +++++++++++++++++-- turbopack/crates/turbopack/src/lib.rs | 5 ++++- 6 files changed, 33 insertions(+), 7 deletions(-) diff --git a/crates/next-core/src/next_client/context.rs b/crates/next-core/src/next_client/context.rs index 62d89f27499b7..1ceeb89c1b65e 100644 --- a/crates/next-core/src/next_client/context.rs +++ b/crates/next-core/src/next_client/context.rs @@ -1,4 +1,4 @@ -use std::{iter::once, str::FromStr}; +use std::iter::once; use anyhow::Result; use serde::{Deserialize, Serialize}; diff --git a/crates/next-core/src/next_server/context.rs b/crates/next-core/src/next_server/context.rs index e05d62d75ed48..4a3b58642a0cf 100644 --- a/crates/next-core/src/next_server/context.rs +++ b/crates/next-core/src/next_server/context.rs @@ -1,4 +1,4 @@ -use std::{iter::once, str::FromStr}; +use std::iter::once; use anyhow::{Result, bail}; use serde::{Deserialize, Serialize}; diff --git a/crates/next-core/src/util.rs b/crates/next-core/src/util.rs index b5a37d82bd1d5..ccc914005dfb7 100644 --- a/crates/next-core/src/util.rs +++ b/crates/next-core/src/util.rs @@ -66,7 +66,7 @@ pub fn defines(define_env: &FxIndexMap>) -> CompileTimeDefi Ok(serde_json::Value::String(v)) => { CompileTimeDefineValue::String(v.into()) } - _ => CompileTimeDefineValue::JSON(v.clone()), + _ => CompileTimeDefineValue::Evaluate(v.clone()), } } else { CompileTimeDefineValue::Undefined diff --git a/turbopack/crates/turbopack-core/src/resolve/mod.rs b/turbopack/crates/turbopack-core/src/resolve/mod.rs index ad3a1947df191..45ef3fdfa075d 100644 --- a/turbopack/crates/turbopack-core/src/resolve/mod.rs +++ b/turbopack/crates/turbopack-core/src/resolve/mod.rs @@ -485,6 +485,7 @@ pub enum ExternalType { EcmaScriptModule, Global, Script, + Umd, } impl Display for ExternalType { @@ -495,6 +496,7 @@ impl Display for ExternalType { ExternalType::Url => write!(f, "url"), ExternalType::Global => write!(f, "global"), ExternalType::Script => write!(f, "script"), + ExternalType::Umd => write!(f, "umd"), } } } @@ -2773,7 +2775,10 @@ async fn resolve_import_map_result( ExternalType::EcmaScriptModule => { node_esm_resolve_options(alias_lookup_path.root().owned().await?) } - ExternalType::Script | ExternalType::Url | ExternalType::Global => options, + ExternalType::Script + | ExternalType::Url + | ExternalType::Global + | ExternalType::Umd => options, }, ) .await? diff --git a/turbopack/crates/turbopack-ecmascript/src/references/external_module.rs b/turbopack/crates/turbopack-ecmascript/src/references/external_module.rs index df47d8931eb78..5ea9e961899cc 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/external_module.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/external_module.rs @@ -53,6 +53,7 @@ pub enum CachedExternalType { EcmaScriptViaImport, Global, Script, + Umd, } #[derive( @@ -76,6 +77,7 @@ impl Display for CachedExternalType { CachedExternalType::EcmaScriptViaImport => write!(f, "esm_import"), CachedExternalType::Global => write!(f, "global"), CachedExternalType::Script => write!(f, "script"), + CachedExternalType::Umd => write!(f, "umd"), } } } @@ -117,11 +119,11 @@ impl CachedExternalModule { CachedExternalType::Global => { if self.request.is_empty() { writeln!(code, "const mod = {{}};")?; - } else if self.request.contains('/') { + } else if self.request.contains(' ') { // Handle requests with '/' by splitting into nested global access let global_access = self .request - .split('/') + .split(' ') .fold("globalThis".to_string(), |acc, part| { format!("{}[{}]", acc, StringifyJs(part)) }); @@ -135,6 +137,22 @@ impl CachedExternalModule { )?; } } + CachedExternalType::Umd => { + // request format is: "root React commonjs react" + let parts = self.request.split(' ').collect::>(); + let global_name = parts[1]; + let module_name = parts[3]; + + writeln!( + code, + "let mod; if (typeof exports === 'object' && typeof module === 'object') {{ \ + mod = {TURBOPACK_EXTERNAL_REQUIRE}({}, () => require({})); }} else {{ mod = \ + globalThis[{}] }}", + StringifyJs(module_name), + StringifyJs(module_name), + StringifyJs(global_name), + )?; + } CachedExternalType::Script => { // Parse the request format: "variableName@url" // e.g., "foo@https://test.test.com" diff --git a/turbopack/crates/turbopack/src/lib.rs b/turbopack/crates/turbopack/src/lib.rs index 3ee6b4511bba2..99ef19f3e5d34 100644 --- a/turbopack/crates/turbopack/src/lib.rs +++ b/turbopack/crates/turbopack/src/lib.rs @@ -656,7 +656,9 @@ async fn externals_tracing_module_context(ty: ExternalType) -> Result vec!["require".into()], ExternalType::EcmaScriptModule => vec!["import".into()], - ExternalType::Url | ExternalType::Global | ExternalType::Script => vec![], + ExternalType::Url | ExternalType::Global | ExternalType::Script | ExternalType::Umd => { + vec![] + } }, ..Default::default() }; @@ -972,6 +974,7 @@ pub async fn replace_external( } ExternalType::Global => CachedExternalType::Global, ExternalType::Script => CachedExternalType::Script, + ExternalType::Umd => CachedExternalType::Umd, ExternalType::Url => { // we don't want to wrap url externals. return Ok(None); From d4191e6049aab74cc80db056d1d12213d8d05ae7 Mon Sep 17 00:00:00 2001 From: xusd320 Date: Wed, 9 Jul 2025 11:12:59 +0800 Subject: [PATCH 4/8] feat(turbopack): support more types of compile time define env (#33) Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> --- crates/next-core/src/util.rs | 9 +- turbopack/crates/turbo-rcstr/src/lib.rs | 6 ++ .../src/analyzer/graph.rs | 55 +++++++++++- .../turbopack-ecmascript/src/analyzer/mod.rs | 83 +++++++++++-------- .../crates/turbopack-ecmascript/src/lib.rs | 1 + .../src/references/constant_value.rs | 18 ++-- .../src/references/mod.rs | 4 +- .../crates/turbopack-tests/tests/snapshot.rs | 6 +- .../snapshot/comptime/define/input/index.js | 8 +- ...ot_comptime_define_input_index_4d74c0a3.js | 8 +- ...omptime_define_input_index_4d74c0a3.js.map | 4 +- 11 files changed, 133 insertions(+), 69 deletions(-) diff --git a/crates/next-core/src/util.rs b/crates/next-core/src/util.rs index ccc914005dfb7..818c78af82553 100644 --- a/crates/next-core/src/util.rs +++ b/crates/next-core/src/util.rs @@ -1,4 +1,4 @@ -use std::future::Future; +use std::{future::Future, str::FromStr}; use anyhow::{Context, Result, bail}; use serde::{Deserialize, Serialize, de::DeserializeOwned}; @@ -60,12 +60,9 @@ pub fn defines(define_env: &FxIndexMap>) -> CompileTimeDefi ) .or_insert_with(|| { if let Some(v) = v { - let val = serde_json::from_str(v); + let val = serde_json::Value::from_str(v); match val { - Ok(serde_json::Value::Bool(v)) => CompileTimeDefineValue::Bool(v), - Ok(serde_json::Value::String(v)) => { - CompileTimeDefineValue::String(v.into()) - } + Ok(v) => v.into(), _ => CompileTimeDefineValue::Evaluate(v.clone()), } } else { diff --git a/turbopack/crates/turbo-rcstr/src/lib.rs b/turbopack/crates/turbo-rcstr/src/lib.rs index 9839d224c32d3..2ffc492dc408c 100644 --- a/turbopack/crates/turbo-rcstr/src/lib.rs +++ b/turbopack/crates/turbo-rcstr/src/lib.rs @@ -219,6 +219,12 @@ impl AsRef<[u8]> for RcStr { } } +impl From for BytesStr { + fn from(value: RcStr) -> Self { + Self::from_str_slice(value.as_str()) + } +} + impl PartialEq for RcStr { fn eq(&self, other: &str) -> bool { self.as_str() == other diff --git a/turbopack/crates/turbopack-ecmascript/src/analyzer/graph.rs b/turbopack/crates/turbopack-ecmascript/src/analyzer/graph.rs index c6a03d9c8bf03..96794e90c2dbc 100644 --- a/turbopack/crates/turbopack-ecmascript/src/analyzer/graph.rs +++ b/turbopack/crates/turbopack-ecmascript/src/analyzer/graph.rs @@ -4,13 +4,19 @@ use std::{ sync::Arc, }; +use anyhow::{Ok, Result, bail}; use rustc_hash::{FxHashMap, FxHashSet}; use swc_core::{ atoms::Atom, - common::{GLOBALS, Mark, Span, Spanned, SyntaxContext, comments::Comments, pass::AstNodePath}, + base::try_with_handler, + common::{ + FileName, GLOBALS, Mark, SourceMap, Span, Spanned, SyntaxContext, comments::Comments, + pass::AstNodePath, sync::Lrc, + }, ecma::{ ast::*, atoms::atom, + parser::{Syntax, parse_file_as_program}, utils::contains_ident_ref, visit::{fields::*, *}, }, @@ -720,6 +726,53 @@ impl EvalContext { _ => JsValue::unknown_empty(true, "unsupported expression"), } } + + pub fn eval_single_expr_lit(expr_lit: RcStr) -> Result { + let cm = Lrc::new(SourceMap::default()); + let fm = cm.new_source_file(FileName::Anon.into(), expr_lit.clone()); + + let js_value = try_with_handler(cm, Default::default(), |handler| { + GLOBALS.set(&Default::default(), || { + let program = parse_file_as_program( + &fm, + Syntax::Es(Default::default()), + EsVersion::latest(), + None, + &mut vec![], + ) + .map_or_else( + |e| { + e.into_diagnostic(handler).emit(); + bail!(r#"Failed to evaluate compile-time defined value expression: "{expr_lit}""#) + }, + Ok, + )?; + + let eval_context = EvalContext::new( + &program, + Mark::new(),Mark::new(), + Default::default(), + None, + None, + ); + + if let Program::Script(script) = program { + if script.body.len() == 1 + && let Some(Stmt::Expr(expr_stmt)) = script.body.first() + { + Ok(eval_context.eval(&expr_stmt.expr)) + } else { + bail!(r#"Failed to parse compile-time defined value expression: "{expr_lit}""#) + } + } else { + bail!(r#"Failed to parse compile-time defined value: "{expr_lit}" in non-script context"#) + } + }) + }) + .map_err(|e| e.to_pretty_error())?; + + Ok(js_value) + } } enum EarlyReturn { diff --git a/turbopack/crates/turbopack-ecmascript/src/analyzer/mod.rs b/turbopack/crates/turbopack-ecmascript/src/analyzer/mod.rs index 985e3c98ce24f..9f637d0eb9511 100644 --- a/turbopack/crates/turbopack-ecmascript/src/analyzer/mod.rs +++ b/turbopack/crates/turbopack-ecmascript/src/analyzer/mod.rs @@ -33,7 +33,10 @@ use turbopack_core::compile_time_info::{ use self::imports::ImportAnnotations; pub(crate) use self::imports::ImportMap; -use crate::{references::require_context::RequireContextMap, utils::StringifyJs}; +use crate::{ + analyzer::graph::EvalContext, references::require_context::RequireContextMap, + utils::StringifyJs, +}; pub mod builtin; pub mod graph; @@ -594,63 +597,73 @@ impl From for JsValue { } } -impl From<&CompileTimeDefineValue> for JsValue { - fn from(v: &CompileTimeDefineValue) -> Self { - match v { - CompileTimeDefineValue::Null => JsValue::Constant(ConstantValue::Null), - CompileTimeDefineValue::Bool(b) => JsValue::Constant((*b).into()), - CompileTimeDefineValue::Number(n) => JsValue::Constant(ConstantValue::Num( - ConstantNumber(n.as_str().parse::().unwrap()), - )), - CompileTimeDefineValue::String(s) => JsValue::Constant(s.as_str().into()), +impl TryFrom<&CompileTimeDefineValue> for JsValue { + type Error = anyhow::Error; + + fn try_from(value: &CompileTimeDefineValue) -> Result { + match value { + CompileTimeDefineValue::Null => Ok(JsValue::Constant(ConstantValue::Null)), + CompileTimeDefineValue::Bool(b) => Ok(JsValue::Constant((*b).into())), + CompileTimeDefineValue::Number(n) => Ok(JsValue::Constant(ConstantValue::Num( + ConstantNumber(n.as_str().parse::()?), + ))), + CompileTimeDefineValue::String(s) => Ok(JsValue::Constant(s.as_str().into())), CompileTimeDefineValue::Array(a) => { let mut js_value = JsValue::Array { total_nodes: a.len() as u32, - items: a.iter().map(|i| i.into()).collect(), + items: a.iter().map(|i| i.try_into()).try_collect()?, mutable: false, }; js_value.update_total_nodes(); - js_value + Ok(js_value) } CompileTimeDefineValue::Object(m) => { let mut js_value = JsValue::Object { total_nodes: m.len() as u32, parts: m .iter() - .map(|(k, v)| ObjectPart::KeyValue(k.clone().into(), v.into())) - .collect(), + .map(|(k, v)| { + Ok::(ObjectPart::KeyValue( + k.clone().into(), + v.try_into()?, + )) + }) + .try_collect()?, mutable: false, }; js_value.update_total_nodes(); - js_value - } - CompileTimeDefineValue::Undefined => JsValue::Constant(ConstantValue::Undefined), - CompileTimeDefineValue::Evaluate(s) => { - JsValue::Constant(ConstantValue::Evaluate(s.clone())) + Ok(js_value) } + CompileTimeDefineValue::Undefined => Ok(JsValue::Constant(ConstantValue::Undefined)), + CompileTimeDefineValue::Evaluate(s) => EvalContext::eval_single_expr_lit(s.clone()), } } } -impl From<&FreeVarReference> for JsValue { - fn from(v: &FreeVarReference) -> Self { - match v { - FreeVarReference::Value(v) => v.into(), +impl TryFrom<&FreeVarReference> for JsValue { + type Error = anyhow::Error; + + fn try_from(value: &FreeVarReference) -> Result { + match value { + FreeVarReference::Value(v) => v.try_into(), FreeVarReference::Ident(_) => { - JsValue::unknown_empty(false, "compile time injected ident") - } - FreeVarReference::Member(_, _) => { - JsValue::unknown_empty(false, "compile time injected member") - } - FreeVarReference::EcmaScriptModule { .. } => { - JsValue::unknown_empty(false, "compile time injected free var module") - } - FreeVarReference::Error(_) => { - JsValue::unknown_empty(false, "compile time injected free var error") + Ok(JsValue::unknown_empty(false, "compile time injected ident")) } + FreeVarReference::Member(_, _) => Ok(JsValue::unknown_empty( + false, + "compile time injected member", + )), + FreeVarReference::EcmaScriptModule { .. } => Ok(JsValue::unknown_empty( + false, + "compile time injected free var module", + )), + FreeVarReference::Error(_) => Ok(JsValue::unknown_empty( + false, + "compile time injected free var error", + )), FreeVarReference::InputRelative(kind) => { use turbopack_core::compile_time_info::InputRelativeConstant; - JsValue::unknown_empty( + Ok(JsValue::unknown_empty( false, match kind { InputRelativeConstant::DirName => { @@ -660,7 +673,7 @@ impl From<&FreeVarReference> for JsValue { "compile time injected free var referencing the file name" } }, - ) + )) } } } diff --git a/turbopack/crates/turbopack-ecmascript/src/lib.rs b/turbopack/crates/turbopack-ecmascript/src/lib.rs index 498efdee64ead..aa4bcf9c2ae00 100644 --- a/turbopack/crates/turbopack-ecmascript/src/lib.rs +++ b/turbopack/crates/turbopack-ecmascript/src/lib.rs @@ -6,6 +6,7 @@ #![feature(int_roundings)] #![feature(arbitrary_self_types)] #![feature(arbitrary_self_types_pointers)] +#![feature(iterator_try_collect)] #![recursion_limit = "256"] pub mod analyzer; diff --git a/turbopack/crates/turbopack-ecmascript/src/references/constant_value.rs b/turbopack/crates/turbopack-ecmascript/src/references/constant_value.rs index 77e96626b7658..d832ec924195e 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/constant_value.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/constant_value.rs @@ -3,13 +3,14 @@ use std::{path::PathBuf, str::FromStr}; use anyhow::Result; use serde::{Deserialize, Serialize}; use swc_core::{ - common::{DUMMY_SP, SourceMap, sync::Lrc}, + common::{DUMMY_SP, FileName, SourceMap, sync::Lrc}, ecma::{ ast::{ArrayLit, EsVersion, Expr, KeyValueProp, ObjectLit, Prop, PropName, Str}, parser::{Syntax, parse_file_as_expr}, }, quote, }; +use turbo_rcstr::RcStr; use turbo_tasks::{NonLocalValue, TaskInput, Vc, debug::ValueDebugFormat, trace::TraceRawVcs}; use turbopack_core::{chunk::ChunkingContext, compile_time_info::CompileTimeDefineValue}; @@ -105,20 +106,13 @@ fn define_env_to_expr(value: CompileTimeDefineValue) -> Expr { CompileTimeDefineValue::Undefined => { quote!("(\"TURBOPACK compile-time value\", void 0)" as Expr) } - CompileTimeDefineValue::Evaluate(ref s) => parse_code_to_expr(s.to_string()), + CompileTimeDefineValue::Evaluate(ref s) => parse_single_expr_lit(s.clone()), } } -fn parse_code_to_expr(code: String) -> Expr { +fn parse_single_expr_lit(expr_lit: RcStr) -> Expr { let cm = Lrc::new(SourceMap::default()); - let fm = cm.new_source_file( - Lrc::new( - PathBuf::from_str("__compile_time_define_value_internal__.js") - .unwrap() - .into(), - ), - code.clone(), - ); + let fm = cm.new_source_file(FileName::Anon.into(), expr_lit.clone()); parse_file_as_expr( &fm, Syntax::Es(Default::default()), @@ -127,7 +121,7 @@ fn parse_code_to_expr(code: String) -> Expr { &mut vec![], ) .map_or( - quote!("$s" as Expr, s: Expr = code.into()), + quote!("(\"Failed parsed TURBOPACK compile-time value\", $s)" as Expr, s: Expr = expr_lit.as_str().into()), |expr| quote!("(\"TURBOPACK compile-time value\", $e)" as Expr, e: Expr = *expr), ) } diff --git a/turbopack/crates/turbopack-ecmascript/src/references/mod.rs b/turbopack/crates/turbopack-ecmascript/src/references/mod.rs index d24e50a94dfda..99660e96ea741 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/mod.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/mod.rs @@ -2906,11 +2906,11 @@ async fn value_visitor_inner( &DefinableNameSegment::TypeOf, ) { - return Ok(((&*value.await?).into(), true)); + return Ok(((&*value.await?).try_into()?, true)); } if let Some(value) = v.match_define(&*compile_time_info.defines.individual().await?) { - return Ok(((&*value.await?).into(), true)); + return Ok(((&*value.await?).try_into()?, true)); } } let value = match v { diff --git a/turbopack/crates/turbopack-tests/tests/snapshot.rs b/turbopack/crates/turbopack-tests/tests/snapshot.rs index e5e8386863310..5844423996776 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot.rs +++ b/turbopack/crates/turbopack-tests/tests/snapshot.rs @@ -34,7 +34,7 @@ use turbopack_core::{ EvaluatableAssets, MinifyType, availability_info::AvailabilityInfo, }, compile_time_defines, - compile_time_info::{CompileTimeDefineValue, CompileTimeInfo, DefineableNameSegment}, + compile_time_info::{CompileTimeDefineValue, CompileTimeInfo, DefinableNameSegment}, condition::ContextCondition, context::AssetContext, environment::{BrowserEnvironment, Environment, ExecutionEnvironment, NodeJsEnvironment}, @@ -310,12 +310,12 @@ async fn run_test_operation(resource: RcStr) -> Result> { ); defines.0.insert( - vec![DefineableNameSegment::from("DEFINED_EVALED")], + vec![DefinableNameSegment::from("DEFINED_EVALUATE")], CompileTimeDefineValue::Evaluate("1 + 1".into()), ); defines.0.insert( - vec![DefineableNameSegment::from("DEFINED_EVALED_NESTED")], + vec![DefinableNameSegment::from("DEFINED_EVALUATE_NESTED")], CompileTimeDefineValue::Array(vec![ CompileTimeDefineValue::Bool(true), CompileTimeDefineValue::Undefined, diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/input/index.js b/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/input/index.js index 7c6d2f64358f0..ffa37ae63444d 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/input/index.js +++ b/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/input/index.js @@ -22,12 +22,12 @@ if (DEFINED_ARRAY) { console.log('DEFINED_ARRAY', DEFINED_ARRAY) } -if (DEFINED_EVALED) { - console.log('DEFINED_EVALED', DEFINED_EVALED) +if (DEFINED_EVALUATE) { + console.log('DEFINED_EVALUATE', DEFINED_EVALUATE) } -if (DEFINED_EVALED_NESTED) { - console.log('DEFINED_EVALED_NESTED', DEFINED_EVALED_NESTED) +if (DEFINED_EVALUATE_NESTED) { + console.log('DEFINED_EVALUATE_NESTED', DEFINED_EVALUATE_NESTED) } if (A.VERY.LONG.DEFINED.VALUE) { diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/output/4e721_crates_turbopack-tests_tests_snapshot_comptime_define_input_index_4d74c0a3.js b/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/output/4e721_crates_turbopack-tests_tests_snapshot_comptime_define_input_index_4d74c0a3.js index fde3f302b73db..a7b48c916a822 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/output/4e721_crates_turbopack-tests_tests_snapshot_comptime_define_input_index_4d74c0a3.js +++ b/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/output/4e721_crates_turbopack-tests_tests_snapshot_comptime_define_input_index_4d74c0a3.js @@ -30,11 +30,11 @@ if ("TURBOPACK compile-time truthy", 1) { ("TURBOPACK compile-time value", null) ])); } -if ("TURBOPACK compile-time value", 1 + 1) { - console.log('DEFINED_EVALED', ("TURBOPACK compile-time value", 1 + 1)); +if ("TURBOPACK compile-time truthy", 1) { + console.log('DEFINED_EVALUATE', ("TURBOPACK compile-time value", 1 + 1)); } if ("TURBOPACK compile-time truthy", 1) { - console.log('DEFINED_EVALED_NESTED', ("TURBOPACK compile-time value", [ + console.log('DEFINED_EVALUATE_NESTED', ("TURBOPACK compile-time value", [ ("TURBOPACK compile-time value", true), ("TURBOPACK compile-time value", void 0), ("TURBOPACK compile-time value", ()=>1) @@ -66,4 +66,4 @@ console.log(("TURBOPACK compile-time value", "/ROOT/turbopack/crates/turbopack-t }}), }]); -//# sourceMappingURL=4e721_crates_turbopack-tests_tests_snapshot_comptime_define_input_index_4d74c0a3.js.map \ No newline at end of file +//# sourceMappingURL=4e721_crates_turbopack-tests_tests_snapshot_comptime_define_input_index_4d74c0a3.js.map diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/output/4e721_crates_turbopack-tests_tests_snapshot_comptime_define_input_index_4d74c0a3.js.map b/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/output/4e721_crates_turbopack-tests_tests_snapshot_comptime_define_input_index_4d74c0a3.js.map index 2732f6ba53656..fdf138fff1a69 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/output/4e721_crates_turbopack-tests_tests_snapshot_comptime_define_input_index_4d74c0a3.js.map +++ b/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/output/4e721_crates_turbopack-tests_tests_snapshot_comptime_define_input_index_4d74c0a3.js.map @@ -2,5 +2,5 @@ "version": 3, "sources": [], "sections": [ - {"offset": {"line": 6, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/input/index.js"],"sourcesContent":["if (DEFINED_VALUE) {\n console.log('DEFINED_VALUE')\n}\n\nif (DEFINED_TRUE) {\n console.log('DEFINED_VALUE')\n}\n\nif (!DEFINED_NULL) {\n console.log('DEFINED_NULL', DEFINED_NULL)\n}\n\nif (DEFINED_INT) {\n console.log('DEFINED_INT', DEFINED_INT)\n}\n\nif (DEFINED_FLOAT) {\n console.log('DEFINED_FLOAT', DEFINED_FLOAT)\n}\n\nif (DEFINED_ARRAY) {\n console.log('DEFINED_ARRAY', DEFINED_ARRAY)\n}\n\nif (DEFINED_EVALED) {\n console.log('DEFINED_EVALED', DEFINED_EVALED)\n}\n\nif (DEFINED_EVALED_NESTED) {\n console.log('DEFINED_EVALED_NESTED', DEFINED_EVALED_NESTED)\n}\n\nif (A.VERY.LONG.DEFINED.VALUE) {\n console.log('A.VERY.LONG.DEFINED.VALUE')\n}\n\nif (process.env.NODE_ENV) {\n console.log('something')\n}\n\nif (process.env.NODE_ENV === 'production') {\n console.log('production')\n}\n\nvar p = process\n\nconsole.log(A.VERY.LONG.DEFINED.VALUE)\nconsole.log(DEFINED_VALUE)\nconsole.log(p.env.NODE_ENV)\n\nif (p.env.NODE_ENV === 'production') {\n console.log('production')\n}\n\np.env.NODE_ENV == 'production'\n ? console.log('production')\n : console.log('development')\n\n// TODO short-circuit is not implemented yet\np.env.NODE_ENV != 'production' && console.log('development')\np.env.NODE_ENV == 'production' && console.log('production')\n\nconsole.log(__dirname)\n"],"names":[],"mappings":"AAAA,wCAAmB;IACjB,QAAQ,GAAG,CAAC;AACd;AAEA,wCAAkB;IAChB,QAAQ,GAAG,CAAC;AACd;AAEA,wCAAmB;IACjB,QAAQ,GAAG,CAAC;AACd;AAEA,wCAAiB;IACf,QAAQ,GAAG,CAAC;AACd;AAEA,wCAAmB;IACjB,QAAQ,GAAG,CAAC;AACd;AAEA,wCAAmB;IACjB,QAAQ,GAAG,CAAC;;;;;;;;;AACd;AAEA,oCAxBA,IAAI,GAwBgB;IAClB,QAAQ,GAAG,CAAC,mDAzBd,IAAI;AA0BJ;AAEA,wCAA2B;IACzB,QAAQ,GAAG,CAAC;;;yCA7Bd,IAAM;;AA8BN;AAEA,wCAA+B;IAC7B,QAAQ,GAAG,CAAC;AACd;AAEA,wCAA0B;IACxB,QAAQ,GAAG,CAAC;AACd;AAEA,IAAI,oDAAyB,cAAc;IACzC,QAAQ,GAAG,CAAC;AACd;AAEA,IAAI,IAAI;AAER,QAAQ,GAAG;;;AACX,QAAQ,GAAG;AACX,QAAQ,GAAG;AAEX,IAAI,oDAAmB,cAAc;IACnC,QAAQ,GAAG,CAAC;AACd;AAEA,sCACI,0BACA,QAAQ,GAAG,CAAC;AAEhB,4CAA4C;AAC5C,mDAAkB,gBAAgB,QAAQ,GAAG,CAAC;AAC9C,mDAAkB,gBAAgB,QAAQ,GAAG,CAAC;AAE9C,QAAQ,GAAG"}}] -} \ No newline at end of file + {"offset": {"line": 6, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/comptime/define/input/index.js"],"sourcesContent":["if (DEFINED_VALUE) {\n console.log('DEFINED_VALUE')\n}\n\nif (DEFINED_TRUE) {\n console.log('DEFINED_VALUE')\n}\n\nif (!DEFINED_NULL) {\n console.log('DEFINED_NULL', DEFINED_NULL)\n}\n\nif (DEFINED_INT) {\n console.log('DEFINED_INT', DEFINED_INT)\n}\n\nif (DEFINED_FLOAT) {\n console.log('DEFINED_FLOAT', DEFINED_FLOAT)\n}\n\nif (DEFINED_ARRAY) {\n console.log('DEFINED_ARRAY', DEFINED_ARRAY)\n}\n\nif (DEFINED_EVALUATE) {\n console.log('DEFINED_EVALUATE', DEFINED_EVALUATE)\n}\n\nif (DEFINED_EVALUATE_NESTED) {\n console.log('DEFINED_EVALUATE_NESTED', DEFINED_EVALUATE_NESTED)\n}\n\nif (A.VERY.LONG.DEFINED.VALUE) {\n console.log('A.VERY.LONG.DEFINED.VALUE')\n}\n\nif (process.env.NODE_ENV) {\n console.log('something')\n}\n\nif (process.env.NODE_ENV === 'production') {\n console.log('production')\n}\n\nvar p = process\n\nconsole.log(A.VERY.LONG.DEFINED.VALUE)\nconsole.log(DEFINED_VALUE)\nconsole.log(p.env.NODE_ENV)\n\nif (p.env.NODE_ENV === 'production') {\n console.log('production')\n}\n\np.env.NODE_ENV == 'production'\n ? console.log('production')\n : console.log('development')\n\n// TODO short-circuit is not implemented yet\np.env.NODE_ENV != 'production' && console.log('development')\np.env.NODE_ENV == 'production' && console.log('production')\n\nconsole.log(__dirname)\n"],"names":[],"mappings":"AAAA,wCAAmB;IACjB,QAAQ,GAAG,CAAC;AACd;AAEA,wCAAkB;IAChB,QAAQ,GAAG,CAAC;AACd;AAEA,wCAAmB;IACjB,QAAQ,GAAG,CAAC;AACd;AAEA,wCAAiB;IACf,QAAQ,GAAG,CAAC;AACd;AAEA,wCAAmB;IACjB,QAAQ,GAAG,CAAC;AACd;AAEA,wCAAmB;IACjB,QAAQ,GAAG,CAAC;;;;;;;;;AACd;AAEA,wCAAsB;IACpB,QAAQ,GAAG,CAAC,qDAzBd,IAAI;AA0BJ;AAEA,wCAA6B;IAC3B,QAAQ,GAAG,CAAC;;;yCA7Bd,IAAM;;AA8BN;AAEA,wCAA+B;IAC7B,QAAQ,GAAG,CAAC;AACd;AAEA,wCAA0B;IACxB,QAAQ,GAAG,CAAC;AACd;AAEA;;AAIA,IAAI,IAAI;AAER,QAAQ,GAAG;;;AACX,QAAQ,GAAG;AACX,QAAQ,GAAG;AAEX;;AAIA,sCACI,0BACA,QAAQ,GAAG,CAAC;AAEhB,4CAA4C;AAC5C,mDAAkB,gBAAgB,QAAQ,GAAG,CAAC;AAC9C,mDAAkB,gBAAgB,QAAQ,GAAG,CAAC;AAE9C,QAAQ,GAAG"}}] +} From 57b9b246ad369cab6ffdbf09e4395c4f9667d6c2 Mon Sep 17 00:00:00 2001 From: zoomdong <1344492820@qq.com> Date: Wed, 9 Jul 2025 14:55:57 +0800 Subject: [PATCH 5/8] fix(turbopack): clippy fix (#34) --- .../turbopack-ecmascript/src/references/constant_value.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/turbopack/crates/turbopack-ecmascript/src/references/constant_value.rs b/turbopack/crates/turbopack-ecmascript/src/references/constant_value.rs index d832ec924195e..f8d38c5fafd57 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/constant_value.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/constant_value.rs @@ -1,5 +1,3 @@ -use std::{path::PathBuf, str::FromStr}; - use anyhow::Result; use serde::{Deserialize, Serialize}; use swc_core::{ From 1b12b7e09c150c7d5531d371231acb8a4100f37d Mon Sep 17 00:00:00 2001 From: zoomdong <1344492820@qq.com> Date: Tue, 15 Jul 2025 13:55:07 +0800 Subject: [PATCH 6/8] fix(turbopack): compiler err --- turbopack/crates/turbopack-css/src/chunk/mod.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/turbopack/crates/turbopack-css/src/chunk/mod.rs b/turbopack/crates/turbopack-css/src/chunk/mod.rs index 9a8ffce8ef86b..c29d9bf68024f 100644 --- a/turbopack/crates/turbopack-css/src/chunk/mod.rs +++ b/turbopack/crates/turbopack-css/src/chunk/mod.rs @@ -4,8 +4,11 @@ pub mod source_map; use std::fmt::Write; use anyhow::{Result, bail}; +use swc_core::common::pass::Either; use turbo_rcstr::{RcStr, rcstr}; -use turbo_tasks::{FxIndexSet, ResolvedVc, TryJoinIterExt, ValueDefault, ValueToString, Vc}; +use turbo_tasks::{ + FxIndexSet, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, ValueDefault, ValueToString, Vc, +}; use turbo_tasks_fs::{ File, FileSystem, FileSystemPath, rope::{Rope, RopeBuilder}, @@ -32,7 +35,7 @@ use turbopack_core::{ source_map::{GenerateSourceMap, OptionStringifiedSourceMap, utils::fileify_source_map}, }; -use self::source_map::CssChunkSourceMapAsset; +use self::{single_item_chunk::chunk::SingleItemCssChunk, source_map::CssChunkSourceMapAsset}; use crate::{ImportAssetReference, util::stringify_js}; #[turbo_tasks::value] @@ -313,7 +316,7 @@ impl OutputChunk for CssChunk { }; Ok(OutputChunkRuntimeInfo { included_ids: Some(ResolvedVc::cell(included_ids)), - module_chunks: None, + module_chunks: Some(ResolvedVc::cell(module_chunks)), ..Default::default() } .cell()) From 10ee87f1c5690200b69d7ed58ef81ad84d06d966 Mon Sep 17 00:00:00 2001 From: xusd320 Date: Tue, 22 Jul 2025 22:49:49 +0800 Subject: [PATCH 7/8] perf(turbopack): avoid redundant Box::pin --- turbopack/crates/turbo-tasks/Cargo.toml | 9 ++------- turbopack/crates/turbo-tasks/src/task/task_input.rs | 2 +- turbopack/crates/turbopack-core/src/compile_time_info.rs | 4 ++-- .../src/references/constant_value.rs | 3 +-- .../crates/turbopack-nodejs/src/chunking_context.rs | 8 ++++---- 5 files changed, 10 insertions(+), 16 deletions(-) diff --git a/turbopack/crates/turbo-tasks/Cargo.toml b/turbopack/crates/turbo-tasks/Cargo.toml index d18765b64e678..3cdf94702e8bb 100644 --- a/turbopack/crates/turbo-tasks/Cargo.toml +++ b/turbopack/crates/turbo-tasks/Cargo.toml @@ -33,7 +33,7 @@ futures = { workspace = true } indexmap = { workspace = true, features = ["serde"] } mopa = "0.2.0" once_cell = { workspace = true } -parking_lot = { workspace = true, features = ["serde"] } +parking_lot = { workspace = true, features = ["serde"]} pin-project-lite = { workspace = true } rayon = { workspace = true } regex = { workspace = true } @@ -41,12 +41,7 @@ rustc-hash = { workspace = true } serde = { workspace = true, features = ["rc", "derive"] } serde_json = { workspace = true } serde_regex = "1.1.0" -shrink-to-fit = { workspace = true, features = [ - "indexmap", - "serde_json", - "smallvec", - "nightly", -] } +shrink-to-fit = { workspace=true,features = ["indexmap", "serde_json", "smallvec", "nightly"] } smallvec = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } diff --git a/turbopack/crates/turbo-tasks/src/task/task_input.rs b/turbopack/crates/turbo-tasks/src/task/task_input.rs index 525b01c7f2701..11eae0c5f7064 100644 --- a/turbopack/crates/turbo-tasks/src/task/task_input.rs +++ b/turbopack/crates/turbo-tasks/src/task/task_input.rs @@ -67,7 +67,7 @@ where async fn resolve_input(&self) -> Result { let mut resolved = Vec::with_capacity(self.len()); for value in self { - resolved.push(Box::pin(value.resolve_input()).await?); + resolved.push(value.resolve_input().await?); } Ok(resolved) } diff --git a/turbopack/crates/turbopack-core/src/compile_time_info.rs b/turbopack/crates/turbopack-core/src/compile_time_info.rs index ffa146926dd44..32ad84fe7c212 100644 --- a/turbopack/crates/turbopack-core/src/compile_time_info.rs +++ b/turbopack/crates/turbopack-core/src/compile_time_info.rs @@ -1,7 +1,7 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; use turbo_rcstr::RcStr; -use turbo_tasks::{FxIndexMap, NonLocalValue, ResolvedVc, TaskInput, Vc, trace::TraceRawVcs}; +use turbo_tasks::{FxIndexMap, NonLocalValue, ResolvedVc, Vc, trace::TraceRawVcs}; use turbo_tasks_fs::FileSystemPath; use crate::environment::Environment; @@ -102,7 +102,7 @@ macro_rules! free_var_references { // TODO: replace with just a `serde_json::Value` // https://linear.app/vercel/issue/WEB-1641/compiletimedefinevalue-should-just-use-serde-jsonvalue #[turbo_tasks::value] -#[derive(Debug, Clone, Hash, TaskInput)] +#[derive(Debug, Clone, Hash)] pub enum CompileTimeDefineValue { Null, Bool(bool), diff --git a/turbopack/crates/turbopack-ecmascript/src/references/constant_value.rs b/turbopack/crates/turbopack-ecmascript/src/references/constant_value.rs index f8d38c5fafd57..34173d1fb1c3b 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/constant_value.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/constant_value.rs @@ -9,7 +9,7 @@ use swc_core::{ quote, }; use turbo_rcstr::RcStr; -use turbo_tasks::{NonLocalValue, TaskInput, Vc, debug::ValueDebugFormat, trace::TraceRawVcs}; +use turbo_tasks::{NonLocalValue, Vc, debug::ValueDebugFormat, trace::TraceRawVcs}; use turbopack_core::{chunk::ChunkingContext, compile_time_info::CompileTimeDefineValue}; use super::AstPath; @@ -29,7 +29,6 @@ use crate::{ TraceRawVcs, ValueDebugFormat, NonLocalValue, - TaskInput, )] pub struct ConstantValueCodeGen { value: CompileTimeDefineValue, diff --git a/turbopack/crates/turbopack-nodejs/src/chunking_context.rs b/turbopack/crates/turbopack-nodejs/src/chunking_context.rs index 6ff6fece5cd9e..60b6a858c72e5 100644 --- a/turbopack/crates/turbopack-nodejs/src/chunking_context.rs +++ b/turbopack/crates/turbopack-nodejs/src/chunking_context.rs @@ -321,10 +321,10 @@ impl ChunkingContext for NodeJsChunkingContext { extension: RcStr, ) -> Result> { let root_path = self.chunk_root_path.clone(); - let name = ident - .output_name(self.root_path.clone(), prefix, extension) - .owned() - .await?; + let mut name = ident + .output_name(self.root_path.clone(), prefix, extension.clone()) + .await? + .to_string(); if !name.ends_with(extension.as_str()) { name.push_str(&extension); } From 1a1e35cc536ee878a618f81b26ec4af9d32efc36 Mon Sep 17 00:00:00 2001 From: zoomdong <1344492820@qq.com> Date: Fri, 25 Jul 2025 11:12:38 +0800 Subject: [PATCH 8/8] feat: POC for postcss under web-worker --- crates/next-core/src/next_client/context.rs | 13 + .../components/PostCssProcessor.tsx | 346 ++++++++++++++ .../lib/browser-loader-runner.js | 215 +++++++++ .../lib/browser-postcss-loader.js | 271 +++++++++++ .../lib/turbopack-browser-adapter.js | 270 +++++++++++ examples/web-worker-postcss/next-env.d.ts | 5 + examples/web-worker-postcss/next.config.js | 16 + examples/web-worker-postcss/package.json | 38 ++ examples/web-worker-postcss/pages/demo.tsx | 310 ++++++++++++ examples/web-worker-postcss/pages/index.tsx | 110 +++++ examples/web-worker-postcss/postcss.config.js | 12 + .../public/postcss-worker.js | 140 ++++++ .../public/turbopack-worker.js | 248 ++++++++++ examples/web-worker-postcss/run-example.sh | 73 +++ .../web-worker-postcss/styles/Demo.module.css | 308 ++++++++++++ .../web-worker-postcss/styles/Home.module.css | 232 +++++++++ .../styles/PostCssProcessor.module.css | 332 +++++++++++++ .../web-worker-postcss/tailwind.config.js | 20 + examples/web-worker-postcss/tsconfig.json | 28 ++ .../crates/turbopack-css/src/chunk/mod.rs | 11 +- .../js/src/transforms/browser-postcss.ts | 199 ++++++++ .../js/src/transforms/postcss-worker.js | 130 +++++ .../turbopack-node/src/execution_context.rs | 46 +- .../crates/turbopack-node/src/node_entry.rs | 2 + .../src/pool/web_worker_pool.rs | 135 ++++++ .../src/transforms/browser_postcss.rs | 446 ++++++++++++++++++ .../turbopack-node/src/transforms/mod.rs | 1 + .../turbopack-node/src/transforms/postcss.rs | 1 + .../turbopack-node/src/transforms/webpack.rs | 1 + .../turbopack/src/module_options/mod.rs | 140 ++++-- .../module_options/module_options_context.rs | 7 +- 31 files changed, 4059 insertions(+), 47 deletions(-) create mode 100644 examples/web-worker-postcss/components/PostCssProcessor.tsx create mode 100644 examples/web-worker-postcss/lib/browser-loader-runner.js create mode 100644 examples/web-worker-postcss/lib/browser-postcss-loader.js create mode 100644 examples/web-worker-postcss/lib/turbopack-browser-adapter.js create mode 100644 examples/web-worker-postcss/next-env.d.ts create mode 100644 examples/web-worker-postcss/next.config.js create mode 100644 examples/web-worker-postcss/package.json create mode 100644 examples/web-worker-postcss/pages/demo.tsx create mode 100644 examples/web-worker-postcss/pages/index.tsx create mode 100644 examples/web-worker-postcss/postcss.config.js create mode 100644 examples/web-worker-postcss/public/postcss-worker.js create mode 100644 examples/web-worker-postcss/public/turbopack-worker.js create mode 100755 examples/web-worker-postcss/run-example.sh create mode 100644 examples/web-worker-postcss/styles/Demo.module.css create mode 100644 examples/web-worker-postcss/styles/Home.module.css create mode 100644 examples/web-worker-postcss/styles/PostCssProcessor.module.css create mode 100644 examples/web-worker-postcss/tailwind.config.js create mode 100644 examples/web-worker-postcss/tsconfig.json create mode 100644 turbopack/crates/turbopack-node/js/src/transforms/browser-postcss.ts create mode 100644 turbopack/crates/turbopack-node/js/src/transforms/postcss-worker.js create mode 100644 turbopack/crates/turbopack-node/src/pool/web_worker_pool.rs create mode 100644 turbopack/crates/turbopack-node/src/transforms/browser_postcss.rs diff --git a/crates/next-core/src/next_client/context.rs b/crates/next-core/src/next_client/context.rs index 1ceeb89c1b65e..0ece93541af9f 100644 --- a/crates/next-core/src/next_client/context.rs +++ b/crates/next-core/src/next_client/context.rs @@ -301,6 +301,18 @@ pub async fn get_client_module_options_context( ..postcss_transform_options.clone() }; let enable_postcss_transform = Some(postcss_transform_options.resolved_cell()); + + // Postcss for browser environment + let browser_postcss_transform_options = PostCssTransformOptions { + postcss_package: Some( + get_postcss_package_mapping(project_path.clone()) + .to_resolved() + .await?, + ), + config_location: PostCssConfigLocation::ProjectPathOrLocalPath, + ..Default::default() + }; + let enable_browser_postcss_transform = Some(browser_postcss_transform_options.resolved_cell()); let enable_foreign_postcss_transform = Some(postcss_foreign_transform_options.resolved_cell()); let source_maps = if *next_config.client_source_maps(mode).await? { @@ -322,6 +334,7 @@ pub async fn get_client_module_options_context( execution_context: Some(execution_context), tree_shaking_mode: tree_shaking_mode_for_user_code, enable_postcss_transform, + enable_browser_postcss_transform, side_effect_free_packages: next_config.optimize_package_imports().owned().await?, keep_last_successful_parse: next_mode.is_development(), ..Default::default() diff --git a/examples/web-worker-postcss/components/PostCssProcessor.tsx b/examples/web-worker-postcss/components/PostCssProcessor.tsx new file mode 100644 index 0000000000000..649c7cad4d089 --- /dev/null +++ b/examples/web-worker-postcss/components/PostCssProcessor.tsx @@ -0,0 +1,346 @@ +import { useState, useEffect, useRef } from 'react' +import styles from '../styles/PostCssProcessor.module.css' + +interface PostCssProcessorProps { + mode: 'worker' | 'main-thread' +} + +interface ProcessingResult { + css: string + processingTime: number + mode: string + timestamp: number + sourceMap?: string + dependencies?: string[] +} + +export function PostCssProcessor({ mode }: PostCssProcessorProps) { + const [inputCss, setInputCss] = useState(`/* Example CSS with Tailwind and PostCSS features */ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer components { + .btn-primary { + @apply bg-primary-500 text-white px-4 py-2 rounded-lg hover:bg-primary-900 transition-colors; + } + + .card { + @apply bg-white shadow-lg rounded-lg p-6 border border-gray-200; + } +} + +/* Custom CSS with modern features */ +.example-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 1rem; + padding: 1rem; +} + +/* CSS with vendor prefixes that autoprefixer will handle */ +.flexbox-example { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.transform-example { + transform: translateX(10px) rotate(5deg); + transition: transform 0.3s ease; +} + +.transform-example:hover { + transform: translateX(20px) rotate(10deg); +}`) + + const [result, setResult] = useState(null) + const [isProcessing, setIsProcessing] = useState(false) + const [error, setError] = useState(null) + const [turbopackAdapter, setTurbopackAdapter] = useState(null) + const workerRef = useRef(null) + + // Initialize Turbopack adapter + useEffect(() => { + const initAdapter = async () => { + try { + // Load the Turbopack browser adapter + const { TurbopackBrowserAdapter } = await import('../lib/turbopack-browser-adapter') + + const adapter = new TurbopackBrowserAdapter({ + useWorkers: mode === 'worker', + workerPoolSize: 2 + }) + + setTurbopackAdapter(adapter) + } catch (error) { + console.error('Failed to initialize Turbopack adapter:', error) + setError('Failed to initialize Turbopack adapter') + } + } + + initAdapter() + + // Cleanup + return () => { + if (turbopackAdapter) { + turbopackAdapter.cleanup() + } + } + }, [mode]) + + // Cleanup Web Worker + useEffect(() => { + return () => { + if (workerRef.current) { + workerRef.current.terminate() + } + } + }, []) + + const processCss = async () => { + if (!turbopackAdapter) { + setError('Turbopack adapter not initialized') + return + } + + setIsProcessing(true) + setError(null) + const startTime = performance.now() + + try { + let processedResult: any + let processingMode: string + + if (mode === 'worker') { + // Use Turbopack adapter with Web Workers + processingMode = 'Turbopack Web Worker' + processedResult = await turbopackAdapter.processCssFile('example.css', inputCss) + } else { + // Use Turbopack adapter in main thread + processingMode = 'Turbopack Main Thread' + processedResult = await turbopackAdapter.processCssFile('example.css', inputCss) + } + + const endTime = performance.now() + const processingTime = endTime - startTime + + setResult({ + css: processedResult.source, + processingTime, + mode: processingMode, + timestamp: Date.now(), + sourceMap: processedResult.sourceMap, + dependencies: processedResult.dependencies + }) + } catch (err) { + setError(err instanceof Error ? err.message : 'Unknown error occurred') + } finally { + setIsProcessing(false) + } + } + + const processCssWithLegacyWorker = async () => { + setIsProcessing(true) + setError(null) + const startTime = performance.now() + + try { + let processedCss: string + let processingMode: string + + if (mode === 'worker' && typeof Worker !== 'undefined') { + // Use legacy Web Worker processing + processingMode = 'Legacy Web Worker' + processedCss = await processCssInWorker(inputCss) + } else { + // Use legacy main thread processing + processingMode = 'Legacy Main Thread' + processedCss = await processCssInMainThread(inputCss) + } + + const endTime = performance.now() + const processingTime = endTime - startTime + + setResult({ + css: processedCss, + processingTime, + mode: processingMode, + timestamp: Date.now() + }) + } catch (err) { + setError(err instanceof Error ? err.message : 'Unknown error occurred') + } finally { + setIsProcessing(false) + } + } + + const processCssInWorker = (css: string): Promise => { + return new Promise((resolve, reject) => { + // Create Web Worker + const worker = new Worker('/postcss-worker.js') + workerRef.current = worker + + const timeout = setTimeout(() => { + worker.terminate() + reject(new Error('Worker processing timeout')) + }, 10000) // 10秒超时 + + worker.onmessage = (event) => { + clearTimeout(timeout) + const { type, data, error } = event.data + + if (type === 'result') { + resolve(data.css) + } else if (type === 'error') { + reject(new Error(error)) + } + worker.terminate() + } + + worker.onerror = (error) => { + clearTimeout(timeout) + reject(error) + worker.terminate() + } + + // Send processing request + worker.postMessage({ + type: 'process', + css, + config: { + plugins: { + 'tailwindcss': {}, + 'autoprefixer': { + overrideBrowserslist: ['> 1%', 'last 2 versions', 'not dead'] + } + } + }, + sourceMap: false + }) + }) + } + + const processCssInMainThread = async (css: string): Promise => { + // 模拟主线程处理 + // 在实际实现中,这里会调用 PostCSS 的同步 API + await new Promise(resolve => setTimeout(resolve, 100)) // 模拟处理时间 + + // 简化的处理逻辑 + let processedCss = css + + // 模拟 Tailwind CSS 处理 + processedCss = processedCss.replace(/@tailwind\s+(\w+);/g, '/* Tailwind $1 styles */') + + // 模拟 Autoprefixer 处理 + processedCss = processedCss.replace(/display:\s*flex/g, 'display: -webkit-flex; display: -ms-flexbox; display: flex') + processedCss = processedCss.replace(/transform:\s*([^;]+);/g, 'transform: $1; -webkit-transform: $1; -ms-transform: $1;') + + return processedCss + } + + return ( +
+

PostCSS Processor with Turbopack Browser Adapter

+ +
+

Input CSS

+