From 759ef9c9f315c440d9fdf6cfa3c9275638eb4dca Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Sat, 25 May 2024 19:57:02 +0200 Subject: [PATCH] Add option for inlining typeof window (vercel/turbo#8211) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Description In Next.js with webpack we have a feature to inline `typeof window` in server/client environments. That allows e.g. ``` "use client" if(typeof window === 'undefined') { import('server-only-package') } ``` Today with Turbopack above code causes `server-only-package` to still be loaded in the browser compilation. Which causes issues like https://github.com/vercel/next.js/issues/66058 where the `if`/`else` condition is used to use different modules for server and client. This change is required to pass the test in https://github.com/vercel/next.js/issues/66058 Pushing up the Next.js changes in that PR. ### Testing Instructions --------- Co-authored-by: Donny/강동윤 --- crates/turbopack-ecmascript/Cargo.toml | 1 + .../turbopack-ecmascript/src/transform/mod.rs | 18 +++++++++++++++++- crates/turbopack/src/module_options/mod.rs | 10 ++++++++++ .../module_options/module_options_context.rs | 8 ++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/crates/turbopack-ecmascript/Cargo.toml b/crates/turbopack-ecmascript/Cargo.toml index 105d36b868d8d..8142da8f863bb 100644 --- a/crates/turbopack-ecmascript/Cargo.toml +++ b/crates/turbopack-ecmascript/Cargo.toml @@ -55,6 +55,7 @@ swc_core = { workspace = true, features = [ "ecma_transforms", "ecma_transforms_module", "ecma_transforms_react", + "ecma_transforms_optimization", "ecma_transforms_typescript", "ecma_transforms_proposal", "ecma_quote", diff --git a/crates/turbopack-ecmascript/src/transform/mod.rs b/crates/turbopack-ecmascript/src/transform/mod.rs index 24e325751bb4b..b7383e529d565 100644 --- a/crates/turbopack-ecmascript/src/transform/mod.rs +++ b/crates/turbopack-ecmascript/src/transform/mod.rs @@ -3,13 +3,15 @@ use std::{fmt::Debug, hash::Hash, sync::Arc}; use anyhow::Result; use async_trait::async_trait; use swc_core::{ + atoms::JsWord, base::SwcComments, - common::{chain, comments::Comments, util::take::Take, Mark, SourceMap}, + common::{chain, collections::AHashMap, comments::Comments, util::take::Take, Mark, SourceMap}, ecma::{ ast::{Module, ModuleItem, Program, Script}, preset_env::{self, Targets}, transforms::{ base::{feature::FeatureFlag, helpers::inject_helpers, Assumptions}, + optimization::inline_globals2, react::react, }, visit::{FoldWith, VisitMutWith}, @@ -39,6 +41,9 @@ pub enum EcmascriptInputTransform { // swc.jsc.transform.react.runtime, runtime: Vc>, }, + GlobalTypeofs { + window_value: String, + }, // These options are subset of swc_core::ecma::transforms::typescript::Config, but // it doesn't derive `Copy` so repeating values in here TypeScript { @@ -134,6 +139,17 @@ impl EcmascriptInputTransform { .. } = ctx; match self { + EcmascriptInputTransform::GlobalTypeofs { window_value } => { + let mut typeofs: AHashMap = Default::default(); + typeofs.insert("window".into(), JsWord::from(&**window_value)); + + program.visit_mut_with(&mut inline_globals2( + Default::default(), + Default::default(), + Default::default(), + Arc::new(typeofs), + )); + } EcmascriptInputTransform::React { development, refresh, diff --git a/crates/turbopack/src/module_options/mod.rs b/crates/turbopack/src/module_options/mod.rs index fb01248ae7a2a..f2d7bc83e3b30 100644 --- a/crates/turbopack/src/module_options/mod.rs +++ b/crates/turbopack/src/module_options/mod.rs @@ -81,6 +81,7 @@ impl ModuleOptions { import_externals, ignore_dynamic_requests, use_swc_css, + ref enable_typeof_window_inlining, .. } = *module_options_context.await?; if !rules.is_empty() { @@ -130,6 +131,15 @@ impl ModuleOptions { transforms.push(EcmascriptInputTransform::PresetEnv(env)); } + if let Some(enable_typeof_window_inlining) = enable_typeof_window_inlining { + transforms.push(EcmascriptInputTransform::GlobalTypeofs { + window_value: match enable_typeof_window_inlining { + TypeofWindow::Object => "object".to_string(), + TypeofWindow::Undefined => "undefined".to_string(), + }, + }); + } + let ts_transform = if let Some(options) = enable_typescript_transform { let options = options.await?; Some(EcmascriptInputTransform::TypeScript { diff --git a/crates/turbopack/src/module_options/module_options_context.rs b/crates/turbopack/src/module_options/module_options_context.rs index 1e9dd365c216b..ac068c1496ba7 100644 --- a/crates/turbopack/src/module_options/module_options_context.rs +++ b/crates/turbopack/src/module_options/module_options_context.rs @@ -46,6 +46,13 @@ pub enum DecoratorsKind { Ecma, } +/// The types when replacing `typeof window` with a constant. +#[derive(Clone, PartialEq, Eq, Debug, TraceRawVcs, Serialize, Deserialize)] +pub enum TypeofWindow { + Object, + Undefined, +} + /// Configuration options for the decorators transform. /// This is not part of Typescript transform: while there are typescript /// specific transforms (legay decorators), there is an ecma decorator transform @@ -105,6 +112,7 @@ pub struct JsxTransformOptions { #[derive(Default, Clone)] #[serde(default)] pub struct ModuleOptionsContext { + pub enable_typeof_window_inlining: Option, pub enable_jsx: Option>, pub enable_postcss_transform: Option>, pub enable_webpack_loaders: Option>,