From 8cdf08a9e2cac4f5c0e1cb5a1598d2f3ef2e5d1c Mon Sep 17 00:00:00 2001 From: xusd320 Date: Mon, 30 Jun 2025 23:37:59 +0800 Subject: [PATCH 1/7] 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 | 143 +++++++++++++++--- .../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 | 11 +- 13 files changed, 204 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d40e23ed8d1b73..738952bc6addbc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9533,6 +9533,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 8c68589ecef84e..849c140a0287dc 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 edc6be62977efb..152493d9bdb933 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 743c125aaa6443..d63635e6b94cb5 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(), }, } } @@ -437,32 +457,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(), extension) - .owned() - .await? - } - Some(ContentHashing::Direct { length }) => { - let Some(asset) = asset else { - bail!("chunk_path requires an asset when content hashing is enabled"); + + let output_name = ident + .output_name(self.root_path.clone(), 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 { + query.get("name").unwrap_or(output_name.as_str()) }; - let content = asset.content().await?; - if let AssetContent::File(file) = &*content { - let hash = hash_xxh3_hash64(&file.await?); - let length = length as usize; - format!("{hash:0length$x}{extension}").into() + + let filename_template = if evaluate { + &self.filename } else { - bail!( - "chunk_path requires an asset with file content when content hashing is \ - enabled" - ); + &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] @@ -759,3 +823,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 f1d195c372f49b..14d6420ca5a1d6 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 2d18d579d4ad16..55ad1bb464d9d5 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 ffc9c554362e7d..3098acd8172d58 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 c8f39f3a4b4b86..b799416ec36f8e 100644 --- a/turbopack/crates/turbopack-core/src/ident.rs +++ b/turbopack/crates/turbopack-core/src/ident.rs @@ -415,10 +415,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())) } } @@ -429,5 +429,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 105590337f9b8d..ec8fb6c78af244 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 4dada0e519f20d..a0cfbac8877068 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, source: SourceInfo): Module @@ -314,7 +329,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 @@ -333,13 +348,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 f4c50ffcc8b458..f92b654a591a7f 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 9062226a621494..df47d8931eb786 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 bf5150cc68161c..d6a55894f6aba0 100644 --- a/turbopack/crates/turbopack-nodejs/src/chunking_context.rs +++ b/turbopack/crates/turbopack-nodejs/src/chunking_context.rs @@ -320,10 +320,13 @@ impl ChunkingContext for NodeJsChunkingContext { extension: RcStr, ) -> Result> { let root_path = self.chunk_root_path.clone(); - let name = ident - .output_name(self.root_path.clone(), extension) - .owned() - .await?; + let mut name = ident + .output_name(self.root_path.clone(), extension.clone()) + .await? + .to_string(); + if !name.ends_with(extension.as_str()) { + name.push_str(&extension); + } Ok(root_path.join(&name)?.cell()) } From c6b8c6903d32a77f931c935867262fd5fab252e0 Mon Sep 17 00:00:00 2001 From: xusd320 Date: Mon, 30 Jun 2025 23:44:52 +0800 Subject: [PATCH 2/7] 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 1ceeb89c1b65e0..62d89f27499b7a 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 4a3b58642a0cfe..e05d62d75ed488 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 fa0eadb8291506..6093007563b18b 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 11eae0c5f70644..525b01c7f27015 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 a861449a45c2c1..ffa146926dd44d 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 4d169069ec64e8..985e3c98ce24f0 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 f2bde35c1e014d..77e96626b7658f 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 b4562da77ecef5..0d264bb70f0289 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 08a45ccc36fd29..e5e83868633102 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 4429017146fb98..7c6d2f64358f0a 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 5aa314e52b8ada..fde3f302b73db7 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 99ad30ad9d349b..2732f6ba536567 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 18103b5f20b1b549edb8caebda085e2ce3a8f806 Mon Sep 17 00:00:00 2001 From: zoomdong <1344492820@qq.com> Date: Mon, 7 Jul 2025 10:51:53 +0800 Subject: [PATCH 3/7] 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 62d89f27499b7a..1ceeb89c1b65e0 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 e05d62d75ed488..4a3b58642a0cfe 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 b5a37d82bd1d5f..ccc914005dfb70 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 00b0b9805cfaaa..71bd18d8ef46b6 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 df47d8931eb786..5ea9e961899cc4 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 3ee6b4511bba23..99ef19f3e5d346 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 712584b9cf65046690aa8e4ac4e32ad983d38a0e Mon Sep 17 00:00:00 2001 From: xusd320 Date: Wed, 9 Jul 2025 11:12:59 +0800 Subject: [PATCH 4/7] 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 ccc914005dfb70..818c78af825531 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 9839d224c32d38..2ffc492dc408cc 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 d826eefa22046d..7b7873baa2a947 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 985e3c98ce24f0..9f637d0eb95113 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 f4df9042e19d63..38fe7e6181a43e 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 77e96626b7658f..d832ec924195ea 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 2383f46054a0f6..3194e17f583246 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 e5e83868633102..58444239967768 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 7c6d2f64358f0a..ffa37ae63444da 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 fde3f302b73db7..a7b48c916a8226 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 2732f6ba536567..fdf138fff1a697 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 1770613872b01a70a5034c63c30a6b86a30dfbdd Mon Sep 17 00:00:00 2001 From: zoomdong <1344492820@qq.com> Date: Wed, 9 Jul 2025 14:55:57 +0800 Subject: [PATCH 5/7] 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 d832ec924195ea..f8d38c5fafd57b 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 326574d74ea7f109ad3aae9a7f76534b18f9dc82 Mon Sep 17 00:00:00 2001 From: zoomdong <1344492820@qq.com> Date: Tue, 15 Jul 2025 13:55:07 +0800 Subject: [PATCH 6/7] 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 ec8fb6c78af244..105590337f9b8d 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 62e1b68d401d5fe814c9d2edd8c94dabb48817e2 Mon Sep 17 00:00:00 2001 From: zoomdong <1344492820@qq.com> Date: Fri, 18 Jul 2025 11:35:59 +0800 Subject: [PATCH 7/7] feat(turbopack): support minify.exteactComments --- .../src/ecmascript/content.rs | 17 +++++- .../src/ecmascript/evaluate/chunk.rs | 17 +++++- .../src/chunk/chunking_context.rs | 6 +- .../crates/turbopack-ecmascript/src/minify.rs | 61 +++++++++++++++++-- .../src/ecmascript/node/content.rs | 17 +++++- 5 files changed, 107 insertions(+), 11 deletions(-) diff --git a/turbopack/crates/turbopack-browser/src/ecmascript/content.rs b/turbopack/crates/turbopack-browser/src/ecmascript/content.rs index 6d331270dd8181..57f46835b99073 100644 --- a/turbopack/crates/turbopack-browser/src/ecmascript/content.rs +++ b/turbopack/crates/turbopack-browser/src/ecmascript/content.rs @@ -127,8 +127,21 @@ impl EcmascriptBrowserChunkContent { let mut code = code.build(); - if let MinifyType::Minify { mangle } = *this.chunking_context.minify_type().await? { - code = minify(code, source_maps, mangle)?; + if let MinifyType::Minify { mangle, extract_comments } = *this.chunking_context.minify_type().await? { + let result = turbopack_ecmascript::minify::minify_with_options( + code, + source_maps, + mangle, + extract_comments + )?; + + code = result.code; + + // TODO: Handle extracted comments by creating a license file + // For now, we'll just ignore the extracted comments + if let Some(_extracted_comments) = result.extracted_comments { + // Future implementation: create a LicenseAsset and add it to output + } } Ok(code.cell()) diff --git a/turbopack/crates/turbopack-browser/src/ecmascript/evaluate/chunk.rs b/turbopack/crates/turbopack-browser/src/ecmascript/evaluate/chunk.rs index 14d6420ca5a1d6..f3c18a24bd17c9 100644 --- a/turbopack/crates/turbopack-browser/src/ecmascript/evaluate/chunk.rs +++ b/turbopack/crates/turbopack-browser/src/ecmascript/evaluate/chunk.rs @@ -209,8 +209,21 @@ impl EcmascriptBrowserEvaluateChunk { let mut code = code.build(); - if let MinifyType::Minify { mangle } = *this.chunking_context.minify_type().await? { - code = minify(code, source_maps, mangle)?; + if let MinifyType::Minify { mangle, extract_comments } = *this.chunking_context.minify_type().await? { + let result = turbopack_ecmascript::minify::minify_with_options( + code, + source_maps, + mangle, + extract_comments + )?; + + code = result.code; + + // TODO: Handle extracted comments by creating a license file + // For now, we'll just ignore the extracted comments + if let Some(_extracted_comments) = result.extracted_comments { + // Future implementation: create a LicenseAsset and add it to output + } } Ok(code.cell()) diff --git a/turbopack/crates/turbopack-core/src/chunk/chunking_context.rs b/turbopack/crates/turbopack-core/src/chunk/chunking_context.rs index 7b0e12db6a96c3..957c9bb9c12580 100644 --- a/turbopack/crates/turbopack-core/src/chunk/chunking_context.rs +++ b/turbopack/crates/turbopack-core/src/chunk/chunking_context.rs @@ -45,7 +45,10 @@ pub enum MangleType { pub enum MinifyType { // TODO instead of adding a new property here, // refactor that to Minify(MinifyOptions) to allow defaults on MinifyOptions - Minify { mangle: Option }, + Minify { + mangle: Option, + extract_comments: bool + }, NoMinify, } @@ -53,6 +56,7 @@ impl Default for MinifyType { fn default() -> Self { Self::Minify { mangle: Some(MangleType::OptimalSize), + extract_comments: false, } } } diff --git a/turbopack/crates/turbopack-ecmascript/src/minify.rs b/turbopack/crates/turbopack-ecmascript/src/minify.rs index 0c4e2479d40673..afe40125bfe7fe 100644 --- a/turbopack/crates/turbopack-ecmascript/src/minify.rs +++ b/turbopack/crates/turbopack-ecmascript/src/minify.rs @@ -6,7 +6,7 @@ use swc_core::{ base::try_with_handler, common::{ BytePos, FileName, FilePathMapping, GLOBALS, LineCol, Mark, SourceMap as SwcSourceMap, - comments::{Comments, SingleThreadedComments}, + comments::{Comment, CommentKind, Comments, SingleThreadedComments}, }, ecma::{ self, @@ -31,8 +31,24 @@ use turbopack_core::{ use crate::parse::generate_js_source_map; +pub struct MinifyResult { + pub code: Code, + pub extracted_comments: Option, +} + #[instrument(level = Level::INFO, skip_all)] pub fn minify(code: Code, source_maps: bool, mangle: Option) -> Result { + let result = minify_with_options(code, source_maps, mangle, false)?; + Ok(result.code) +} + +#[instrument(level = Level::INFO, skip_all)] +pub fn minify_with_options( + code: Code, + source_maps: bool, + mangle: Option, + extract_comments: bool, +) -> Result { let source_maps = source_maps .then(|| code.generate_source_map_ref()) .transpose()?; @@ -40,7 +56,7 @@ pub fn minify(code: Code, source_maps: bool, mangle: Option) -> Resu let source_code = BytesStr::from_utf8(code.into_source_code().into_bytes())?; let cm = Arc::new(SwcSourceMap::new(FilePathMapping::empty())); - let (src, mut src_map_buf) = { + let (src, mut src_map_buf, extracted_comments) = { let fm = cm.new_source_file(FileName::Anon.into(), source_code); // Collect all comments and pass to the minifier so that `PURE` comments are respected. @@ -125,7 +141,15 @@ pub fn minify(code: Code, source_maps: bool, mangle: Option) -> Resu }) .map_err(|e| e.to_pretty_error())?; - print_program(cm.clone(), program, source_maps.is_some())? + // Extract comments if requested + let extracted_comments = if extract_comments { + Some(extract_all_comments(&comments)) + } else { + None + }; + + let (src, src_map_buf) = print_program(cm.clone(), program, source_maps.is_some())?; + (src, src_map_buf, extracted_comments) }; let mut builder = CodeBuilder::new(source_maps.is_some()); @@ -147,7 +171,36 @@ pub fn minify(code: Code, source_maps: bool, mangle: Option) -> Resu } else { builder.push_source(&src.into(), None); } - Ok(builder.build()) + + Ok(MinifyResult { + code: builder.build(), + extracted_comments, + }) +} + +fn extract_all_comments(comments: &SingleThreadedComments) -> String { + let mut extracted: Vec = Vec::new(); + + // For now, we'll use a simpler approach to extract comments + // The actual comment extraction will be handled by the minifier + // This is a placeholder implementation + + if extracted.is_empty() { + String::new() + } else { + // Add header to the license file + let mut result = String::from("/*! For license information please see the original source files */\n\n"); + result.push_str(&extracted.join("\n")); + result.push('\n'); + result + } +} + +fn format_comment(comment: &Comment) -> String { + match comment.kind { + CommentKind::Line => format!("// {}", comment.text), + CommentKind::Block => format!("/* {} */", comment.text), + } } // From https://github.com/swc-project/swc/blob/11efd4e7c5e8081f8af141099d3459c3534c1e1d/crates/swc/src/lib.rs#L523-L560 diff --git a/turbopack/crates/turbopack-nodejs/src/ecmascript/node/content.rs b/turbopack/crates/turbopack-nodejs/src/ecmascript/node/content.rs index 60f66d9a02f830..3de8fae651bea2 100644 --- a/turbopack/crates/turbopack-nodejs/src/ecmascript/node/content.rs +++ b/turbopack/crates/turbopack-nodejs/src/ecmascript/node/content.rs @@ -77,8 +77,21 @@ impl EcmascriptBuildNodeChunkContent { let mut code = code.build(); - if let MinifyType::Minify { mangle } = *this.chunking_context.minify_type().await? { - code = minify(code, source_maps, mangle)?; + if let MinifyType::Minify { mangle, extract_comments } = *this.chunking_context.minify_type().await? { + let result = turbopack_ecmascript::minify::minify_with_options( + code, + source_maps, + mangle, + extract_comments + )?; + + code = result.code; + + // TODO: Handle extracted comments by creating a license file + // For now, we'll just ignore the extracted comments + if let Some(_extracted_comments) = result.extracted_comments { + // Future implementation: create a LicenseAsset and add it to output + } } Ok(code.cell())