diff --git a/bench/module-cost/.gitignore b/bench/module-cost/.gitignore index fd0a8a0a91b50..b1d3827455c44 100644 --- a/bench/module-cost/.gitignore +++ b/bench/module-cost/.gitignore @@ -1,3 +1,4 @@ commonjs/* esm/* -CPU* \ No newline at end of file +CPU* +benchmark-results-*.json \ No newline at end of file diff --git a/bench/module-cost/components/client.js b/bench/module-cost/components/client.js index 967e20a312fa9..f8118beb353a2 100644 --- a/bench/module-cost/components/client.js +++ b/bench/module-cost/components/client.js @@ -4,6 +4,11 @@ import { useEffect, useRef, useState } from 'react' import { format, measure } from '../lib/measure' function report(result, element, textarea) { + if (!globalThis.BENCHMARK_RESULTS) { + globalThis.BENCHMARK_RESULTS = [] + } + globalThis.BENCHMARK_RESULTS.push(result) + const formattedResult = format(result) element.textContent += `: ${formattedResult}` textarea.current.value += `\n ${formattedResult}` diff --git a/bench/module-cost/package.json b/bench/module-cost/package.json index d50bd133aaf55..eb6e8a7478d92 100644 --- a/bench/module-cost/package.json +++ b/bench/module-cost/package.json @@ -2,6 +2,7 @@ "name": "module-cost", "scripts": { "prepare-bench": "node scripts/prepare-bench.mjs", + "benchmark": "node scripts/benchmark-runner.mjs", "dev-webpack": "next dev", "dev-turbopack": "next dev --turbo", "build-webpack": "next build", @@ -10,6 +11,7 @@ }, "devDependencies": { "rimraf": "6.0.1", - "next": "workspace:*" + "next": "workspace:*", + "playwright": "^1.40.0" } } diff --git a/bench/module-cost/scripts/benchmark-runner.mjs b/bench/module-cost/scripts/benchmark-runner.mjs new file mode 100644 index 0000000000000..eb96b27ce3571 --- /dev/null +++ b/bench/module-cost/scripts/benchmark-runner.mjs @@ -0,0 +1,255 @@ +import { spawn } from 'node:child_process' +import { writeFileSync } from 'node:fs' +import { chromium } from 'playwright' + +/// To use: +/// - Install Playwright: `npx playwright install chromium` +/// - Install dependencies: `pnpm install` +/// - Build the application: `pnpm build-webpack` or pnpm build-turbopack` +/// - Run the benchmark: `pnpm benchmark` + +class BenchmarkRunner { + constructor(options) { + this.name = options.name + this.samples = options.samples ?? 50 + this.buttonClickDelay = options.buttonClickDelay ?? 500 + this.results = [] + } + + async runBenchmark() { + for (let i = 1; i <= this.samples; i++) { + console.log(`\n--- Running sample ${i}/${this.samples} ---`) + + const result = await this.runSingleSample() + this.results.push(...result) + } + + this.saveResults() + console.log('\nBenchmark completed!') + } + + async runSingleSample() { + let server + let browser + + try { + // 1. Launch the server + server = await this.startServer() + + // 2. Launch Chrome incognito + console.log('Launching browser...') + browser = await chromium.launch({ + headless: true, // Set to true if you don't want to see the browser + args: ['--incognito'], + }) + + const context = await browser.newContext() + const page = await context.newPage() + + // 3. Navigate to localhost:3000 + await page.goto('http://localhost:3000', { waitUntil: 'load' }) + + // 4. Find and click all buttons + const buttons = await page.locator('button').all() + + for (let j = 0; j < buttons.length; j++) { + await buttons[j].click() + await this.sleep(this.buttonClickDelay) + } + + // 5. Capture data from textbox + console.log('Capturing data from the page...') + const textboxData = await this.capturePageData(page) + console.log('Captured data from the page:', textboxData) + + // 6. Close browser + console.log('Closing browser...') + await browser.close() + browser = null + + // 7. Shut down server + console.log('Shutting down server...') + await this.stopServer(server) + server = null + + return textboxData + } catch (error) { + // Cleanup in case of error + if (browser) { + try { + await browser.close() + } catch (e) { + console.error('Error closing browser:', e.message) + } + } + if (server) { + try { + await this.stopServer(server) + } catch (e) { + console.error('Error stopping server:', e.message) + } + } + throw error + } + } + + async startServer() { + return new Promise((resolve, reject) => { + const server = spawn('pnpm', ['start'], { + stdio: ['pipe', 'pipe', 'pipe'], + shell: true, + }) + + let serverReady = false + + server.stdout.on('data', (data) => { + const output = data.toString() + console.log('Server:', output.trim()) + + // Look for common Next.js ready indicators + if ( + output.includes('Ready') || + output.includes('started server') || + output.includes('Local:') + ) { + if (!serverReady) { + serverReady = true + resolve(server) + } + } + }) + + server.stderr.on('data', (data) => { + console.error('Server Error:', data.toString().trim()) + }) + + server.on('error', (error) => { + reject(new Error(`Failed to start server: ${error.message}`)) + }) + + server.on('close', (code) => { + if (!serverReady) { + reject( + new Error(`Server exited with code ${code} before becoming ready`) + ) + } + }) + + // Timeout after 30 seconds + setTimeout(() => { + if (!serverReady) { + server.kill() + reject(new Error('Server startup timeout')) + } + }, 30000) + }) + } + + async stopServer(server) { + return new Promise((resolve) => { + if (!server || server.killed) { + resolve() + return + } + + server.on('close', () => { + resolve() + }) + + // Try graceful shutdown first + server.kill('SIGTERM') + + // Force kill after 5 seconds + setTimeout(() => { + if (!server.killed) { + server.kill('SIGKILL') + } + resolve() + }, 5000) + }) + } + + async capturePageData(page) { + return await page.evaluate(() => globalThis.BENCHMARK_RESULTS) + } + + async sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)) + } + + saveResults() { + const timestamp = new Date().toISOString().replace(/[:.]/g, '-') + const filename = `benchmark-results-${this.name}-${timestamp}.json` + + writeFileSync( + filename, + JSON.stringify(summarizeDurations(this.results), null, 2) + ) + console.log(`Results saved to ${filename}`) + } +} + +const summarizeDurations = (data) => { + if (!Array.isArray(data) || data.length === 0) { + throw new Error('No data to summarize') + } + + const byName = new Map() + for (const item of data) { + const name = item.name + if (!byName.has(name)) { + byName.set(name, []) + } + byName.get(name).push(item) + } + const results = [] + for (const [name, data] of byName) { + const loadDurations = data + .map((item) => item.loadDuration) + .sort((a, b) => a - b) + const executeDurations = data + .map((item) => item.executeDuration) + .sort((a, b) => a - b) + + const getSummary = (durations) => { + const sum = durations.reduce((acc, val) => acc + val, 0) + const average = sum / durations.length + + const middle = Math.floor(durations.length / 2) + const median = + durations.length % 2 === 0 + ? (durations[middle - 1] + durations[middle]) / 2 + : durations[middle] + + const percentile75Index = Math.floor(durations.length * 0.75) + const percentile75 = durations[percentile75Index] + + return { + average, + median, + percentile75, + } + } + + results.push({ + name, + totalSamples: data.length, + loadDuration: getSummary(loadDurations), + executeDuration: getSummary(executeDurations), + }) + } + + return results +} + +// CLI usage +const args = process.argv.slice(2) +const samples = args.length > 0 ? Number.parseInt(args[0]) : undefined +const name = args.length > 1 ? args[1] : undefined + +const runner = new BenchmarkRunner({ + name, + samples, +}) + +runner.runBenchmark().catch(console.error) diff --git a/crates/next-core/src/next_server/context.rs b/crates/next-core/src/next_server/context.rs index b29e3945b334c..99d607e7d53c7 100644 --- a/crates/next-core/src/next_server/context.rs +++ b/crates/next-core/src/next_server/context.rs @@ -58,7 +58,7 @@ use crate::{ get_invalid_styled_jsx_resolve_plugin, }, transforms::{ - emotion::get_emotion_transform_rule, get_ecma_transform_rule, + EcmascriptTransformStage, emotion::get_emotion_transform_rule, get_ecma_transform_rule, next_react_server_components::get_next_react_server_components_transform_rule, react_remove_properties::get_react_remove_properties_transform_rule, relay::get_relay_transform_rule, remove_console::get_remove_console_transform_rule, @@ -768,7 +768,7 @@ pub async fn get_server_module_options_context( ecmascript_client_reference_transition_name, )), enable_mdx_rs.is_some(), - true, + EcmascriptTransformStage::Preprocess, )); } @@ -844,7 +844,7 @@ pub async fn get_server_module_options_context( ecmascript_client_reference_transition_name, )), enable_mdx_rs.is_some(), - true, + EcmascriptTransformStage::Preprocess, )); } @@ -922,7 +922,7 @@ pub async fn get_server_module_options_context( ecmascript_client_reference_transition_name, )), enable_mdx_rs.is_some(), - true, + EcmascriptTransformStage::Preprocess, )); } else { custom_source_transform_rules.push(get_ecma_transform_rule( @@ -930,7 +930,7 @@ pub async fn get_server_module_options_context( "next/dist/client/use-client-disallowed.js".to_string(), )), enable_mdx_rs.is_some(), - true, + EcmascriptTransformStage::Preprocess, )); } diff --git a/crates/next-core/src/next_shared/transforms/debug_fn_name.rs b/crates/next-core/src/next_shared/transforms/debug_fn_name.rs index a95e10ed6034f..7664fd190da3b 100644 --- a/crates/next-core/src/next_shared/transforms/debug_fn_name.rs +++ b/crates/next-core/src/next_shared/transforms/debug_fn_name.rs @@ -16,8 +16,9 @@ pub fn get_debug_fn_name_rule(enable_mdx_rs: bool) -> ModuleRule { ModuleRule::new( module_rule_match_js_no_url(enable_mdx_rs), vec![ModuleRuleEffect::ExtendEcmascriptTransforms { - prepend: ResolvedVc::cell(vec![]), - append: ResolvedVc::cell(vec![debug_fn_name_transform]), + preprocess: ResolvedVc::cell(vec![]), + main: ResolvedVc::cell(vec![]), + postprocess: ResolvedVc::cell(vec![debug_fn_name_transform]), }], ) } diff --git a/crates/next-core/src/next_shared/transforms/emotion.rs b/crates/next-core/src/next_shared/transforms/emotion.rs index d8997df8695ee..6fd4d324b7b72 100644 --- a/crates/next-core/src/next_shared/transforms/emotion.rs +++ b/crates/next-core/src/next_shared/transforms/emotion.rs @@ -4,7 +4,10 @@ use turbopack::module_options::ModuleRule; use turbopack_ecmascript_plugins::transform::emotion::EmotionTransformer; use super::get_ecma_transform_rule; -use crate::next_config::{EmotionTransformOptionsOrBoolean, NextConfig}; +use crate::{ + next_config::{EmotionTransformOptionsOrBoolean, NextConfig}, + next_shared::transforms::EcmascriptTransformStage, +}; pub async fn get_emotion_transform_rule(next_config: Vc) -> Result> { let enable_mdx_rs = next_config.mdx_rs().await?.is_some(); @@ -20,7 +23,13 @@ pub async fn get_emotion_transform_rule(next_config: Vc) -> Result EmotionTransformer::new(value), _ => None, }) - .map(|transformer| get_ecma_transform_rule(Box::new(transformer), enable_mdx_rs, true)); + .map(|transformer| { + get_ecma_transform_rule( + Box::new(transformer), + enable_mdx_rs, + EcmascriptTransformStage::Main, + ) + }); Ok(module_rule) } diff --git a/crates/next-core/src/next_shared/transforms/mod.rs b/crates/next-core/src/next_shared/transforms/mod.rs index 4491f6aa86b15..071e89f33cb22 100644 --- a/crates/next-core/src/next_shared/transforms/mod.rs +++ b/crates/next-core/src/next_shared/transforms/mod.rs @@ -134,28 +134,32 @@ pub(crate) fn module_rule_match_pages_page_file( ]) } +pub(crate) enum EcmascriptTransformStage { + Preprocess, + Main, + Postprocess, +} + /// Create a new module rule for the given ecmatransform, runs against /// any ecmascript (with mdx if enabled) except url reference type pub(crate) fn get_ecma_transform_rule( transformer: Box, enable_mdx_rs: bool, - prepend: bool, + stage: EcmascriptTransformStage, ) -> ModuleRule { let transformer = EcmascriptInputTransform::Plugin(ResolvedVc::cell(transformer as _)); - let (prepend, append) = if prepend { - ( - ResolvedVc::cell(vec![transformer]), - ResolvedVc::cell(vec![]), - ) - } else { - ( - ResolvedVc::cell(vec![]), - ResolvedVc::cell(vec![transformer]), - ) + let (preprocess, main, postprocess) = match stage { + EcmascriptTransformStage::Preprocess => (vec![transformer], vec![], vec![]), + EcmascriptTransformStage::Main => (vec![], vec![transformer], vec![]), + EcmascriptTransformStage::Postprocess => (vec![], vec![], vec![transformer]), }; ModuleRule::new( module_rule_match_js_no_url(enable_mdx_rs), - vec![ModuleRuleEffect::ExtendEcmascriptTransforms { prepend, append }], + vec![ModuleRuleEffect::ExtendEcmascriptTransforms { + preprocess: ResolvedVc::cell(preprocess), + main: ResolvedVc::cell(main), + postprocess: ResolvedVc::cell(postprocess), + }], ) } diff --git a/crates/next-core/src/next_shared/transforms/modularize_imports.rs b/crates/next-core/src/next_shared/transforms/modularize_imports.rs index d963108dfb707..50e9298a42a0c 100644 --- a/crates/next-core/src/next_shared/transforms/modularize_imports.rs +++ b/crates/next-core/src/next_shared/transforms/modularize_imports.rs @@ -63,8 +63,9 @@ pub fn get_next_modularize_imports_rule( ModuleRule::new( module_rule_match_js_no_url(enable_mdx_rs), vec![ModuleRuleEffect::ExtendEcmascriptTransforms { - prepend: ResolvedVc::cell(vec![]), - append: ResolvedVc::cell(vec![transformer]), + preprocess: ResolvedVc::cell(vec![]), + main: ResolvedVc::cell(vec![]), + postprocess: ResolvedVc::cell(vec![transformer]), }], ) } diff --git a/crates/next-core/src/next_shared/transforms/next_amp_attributes.rs b/crates/next-core/src/next_shared/transforms/next_amp_attributes.rs index e3a78f105c422..3d0128750d4ff 100644 --- a/crates/next-core/src/next_shared/transforms/next_amp_attributes.rs +++ b/crates/next-core/src/next_shared/transforms/next_amp_attributes.rs @@ -14,8 +14,9 @@ pub fn get_next_amp_attr_rule(enable_mdx_rs: bool) -> ModuleRule { ModuleRule::new( module_rule_match_js_no_url(enable_mdx_rs), vec![ModuleRuleEffect::ExtendEcmascriptTransforms { - prepend: ResolvedVc::cell(vec![]), - append: ResolvedVc::cell(vec![transformer]), + preprocess: ResolvedVc::cell(vec![]), + main: ResolvedVc::cell(vec![transformer]), + postprocess: ResolvedVc::cell(vec![]), }], ) } diff --git a/crates/next-core/src/next_shared/transforms/next_cjs_optimizer.rs b/crates/next-core/src/next_shared/transforms/next_cjs_optimizer.rs index cf09fbcd7e1c5..6ad13957c82db 100644 --- a/crates/next-core/src/next_shared/transforms/next_cjs_optimizer.rs +++ b/crates/next-core/src/next_shared/transforms/next_cjs_optimizer.rs @@ -54,8 +54,9 @@ pub fn get_next_cjs_optimizer_rule(enable_mdx_rs: bool) -> ModuleRule { ModuleRule::new( module_rule_match_js_no_url(enable_mdx_rs), vec![ModuleRuleEffect::ExtendEcmascriptTransforms { - prepend: ResolvedVc::cell(vec![]), - append: ResolvedVc::cell(vec![transformer]), + preprocess: ResolvedVc::cell(vec![]), + main: ResolvedVc::cell(vec![]), + postprocess: ResolvedVc::cell(vec![transformer]), }], ) } diff --git a/crates/next-core/src/next_shared/transforms/next_disallow_re_export_all_in_page.rs b/crates/next-core/src/next_shared/transforms/next_disallow_re_export_all_in_page.rs index 855ae62c15061..a581a16f8c6ee 100644 --- a/crates/next-core/src/next_shared/transforms/next_disallow_re_export_all_in_page.rs +++ b/crates/next-core/src/next_shared/transforms/next_disallow_re_export_all_in_page.rs @@ -19,8 +19,9 @@ pub fn get_next_disallow_export_all_in_page_rule( ModuleRule::new( module_rule_match_pages_page_file(enable_mdx_rs, pages_dir), vec![ModuleRuleEffect::ExtendEcmascriptTransforms { - prepend: ResolvedVc::cell(vec![]), - append: ResolvedVc::cell(vec![transformer]), + preprocess: ResolvedVc::cell(vec![]), + main: ResolvedVc::cell(vec![]), + postprocess: ResolvedVc::cell(vec![transformer]), }], ) } diff --git a/crates/next-core/src/next_shared/transforms/next_dynamic.rs b/crates/next-core/src/next_shared/transforms/next_dynamic.rs index 0fb60d6bad27a..ceb173230be76 100644 --- a/crates/next-core/src/next_shared/transforms/next_dynamic.rs +++ b/crates/next-core/src/next_shared/transforms/next_dynamic.rs @@ -27,8 +27,9 @@ pub async fn get_next_dynamic_transform_rule( Ok(ModuleRule::new( module_rule_match_js_no_url(enable_mdx_rs), vec![ModuleRuleEffect::ExtendEcmascriptTransforms { - prepend: ResolvedVc::cell(vec![]), - append: ResolvedVc::cell(vec![dynamic_transform]), + preprocess: ResolvedVc::cell(vec![]), + main: ResolvedVc::cell(vec![]), + postprocess: ResolvedVc::cell(vec![dynamic_transform]), }], )) } diff --git a/crates/next-core/src/next_shared/transforms/next_edge_node_api_assert.rs b/crates/next-core/src/next_shared/transforms/next_edge_node_api_assert.rs index 657cf64efca70..5ebd7ceb70c1f 100644 --- a/crates/next-core/src/next_shared/transforms/next_edge_node_api_assert.rs +++ b/crates/next-core/src/next_shared/transforms/next_edge_node_api_assert.rs @@ -24,8 +24,9 @@ pub fn next_edge_node_api_assert( ModuleRule::new( module_rule_match_js_no_url(enable_mdx_rs), vec![ModuleRuleEffect::ExtendEcmascriptTransforms { - prepend: ResolvedVc::cell(vec![]), - append: ResolvedVc::cell(vec![transformer]), + preprocess: ResolvedVc::cell(vec![]), + main: ResolvedVc::cell(vec![]), + postprocess: ResolvedVc::cell(vec![transformer]), }], ) } diff --git a/crates/next-core/src/next_shared/transforms/next_font.rs b/crates/next-core/src/next_shared/transforms/next_font.rs index 0b81722d400aa..b40bcf080ff1c 100644 --- a/crates/next-core/src/next_shared/transforms/next_font.rs +++ b/crates/next-core/src/next_shared/transforms/next_font.rs @@ -28,8 +28,9 @@ pub fn get_next_font_transform_rule(enable_mdx_rs: bool) -> ModuleRule { // TODO: Only match in pages (not pages/api), app/, etc. module_rule_match_js_no_url(enable_mdx_rs), vec![ModuleRuleEffect::ExtendEcmascriptTransforms { - prepend: ResolvedVc::cell(vec![]), - append: ResolvedVc::cell(vec![transformer]), + preprocess: ResolvedVc::cell(vec![]), + main: ResolvedVc::cell(vec![]), + postprocess: ResolvedVc::cell(vec![transformer]), }], ) } diff --git a/crates/next-core/src/next_shared/transforms/next_lint.rs b/crates/next-core/src/next_shared/transforms/next_lint.rs index 3009dc7f217fd..453e962683837 100644 --- a/crates/next-core/src/next_shared/transforms/next_lint.rs +++ b/crates/next-core/src/next_shared/transforms/next_lint.rs @@ -6,9 +6,14 @@ use turbopack::module_options::ModuleRule; use turbopack_ecmascript::{CustomTransformer, TransformContext}; use super::get_ecma_transform_rule; +use crate::next_shared::transforms::EcmascriptTransformStage; pub fn get_next_lint_transform_rule(enable_mdx_rs: bool) -> ModuleRule { - get_ecma_transform_rule(Box::new(LintTransformer {}), enable_mdx_rs, true) + get_ecma_transform_rule( + Box::new(LintTransformer {}), + enable_mdx_rs, + EcmascriptTransformStage::Preprocess, + ) } #[derive(Debug)] diff --git a/crates/next-core/src/next_shared/transforms/next_middleware_dynamic_assert.rs b/crates/next-core/src/next_shared/transforms/next_middleware_dynamic_assert.rs index c23635607b19c..cc6eadc459329 100644 --- a/crates/next-core/src/next_shared/transforms/next_middleware_dynamic_assert.rs +++ b/crates/next-core/src/next_shared/transforms/next_middleware_dynamic_assert.rs @@ -15,8 +15,9 @@ pub fn get_middleware_dynamic_assert_rule(enable_mdx_rs: bool) -> ModuleRule { ModuleRule::new( module_rule_match_js_no_url(enable_mdx_rs), vec![ModuleRuleEffect::ExtendEcmascriptTransforms { - prepend: ResolvedVc::cell(vec![]), - append: ResolvedVc::cell(vec![transformer]), + preprocess: ResolvedVc::cell(vec![]), + main: ResolvedVc::cell(vec![]), + postprocess: ResolvedVc::cell(vec![transformer]), }], ) } diff --git a/crates/next-core/src/next_shared/transforms/next_optimize_server_react.rs b/crates/next-core/src/next_shared/transforms/next_optimize_server_react.rs index acbe54ce12714..5e5340e15e7be 100644 --- a/crates/next-core/src/next_shared/transforms/next_optimize_server_react.rs +++ b/crates/next-core/src/next_shared/transforms/next_optimize_server_react.rs @@ -20,8 +20,9 @@ pub fn get_next_optimize_server_react_rule( ModuleRule::new( module_rule_match_js_no_url(enable_mdx_rs), vec![ModuleRuleEffect::ExtendEcmascriptTransforms { - prepend: ResolvedVc::cell(vec![]), - append: ResolvedVc::cell(vec![transformer]), + preprocess: ResolvedVc::cell(vec![]), + main: ResolvedVc::cell(vec![]), + postprocess: ResolvedVc::cell(vec![transformer]), }], ) } diff --git a/crates/next-core/src/next_shared/transforms/next_page_config.rs b/crates/next-core/src/next_shared/transforms/next_page_config.rs index c95fe13a92685..a2914c96674bb 100644 --- a/crates/next-core/src/next_shared/transforms/next_page_config.rs +++ b/crates/next-core/src/next_shared/transforms/next_page_config.rs @@ -17,8 +17,9 @@ pub fn get_next_page_config_rule(enable_mdx_rs: bool, pages_dir: FileSystemPath) ModuleRule::new( module_rule_match_pages_page_file(enable_mdx_rs, pages_dir), vec![ModuleRuleEffect::ExtendEcmascriptTransforms { - prepend: ResolvedVc::cell(vec![]), - append: ResolvedVc::cell(vec![transformer]), + preprocess: ResolvedVc::cell(vec![]), + main: ResolvedVc::cell(vec![]), + postprocess: ResolvedVc::cell(vec![transformer]), }], ) } diff --git a/crates/next-core/src/next_shared/transforms/next_page_static_info.rs b/crates/next-core/src/next_shared/transforms/next_page_static_info.rs index 658c044adb33d..3217f8847b163 100644 --- a/crates/next-core/src/next_shared/transforms/next_page_static_info.rs +++ b/crates/next-core/src/next_shared/transforms/next_page_static_info.rs @@ -39,8 +39,9 @@ pub fn get_next_page_static_info_assert_rule( ModuleRule::new( module_rule_match_js_no_url(enable_mdx_rs), vec![ModuleRuleEffect::ExtendEcmascriptTransforms { - prepend: ResolvedVc::cell(vec![transformer]), - append: ResolvedVc::cell(vec![]), + preprocess: ResolvedVc::cell(vec![transformer]), + main: ResolvedVc::cell(vec![]), + postprocess: ResolvedVc::cell(vec![]), }], ) } diff --git a/crates/next-core/src/next_shared/transforms/next_pure.rs b/crates/next-core/src/next_shared/transforms/next_pure.rs index 8226499ecd47e..31baeca2947d4 100644 --- a/crates/next-core/src/next_shared/transforms/next_pure.rs +++ b/crates/next-core/src/next_shared/transforms/next_pure.rs @@ -14,8 +14,9 @@ pub fn get_next_pure_rule(enable_mdx_rs: bool) -> ModuleRule { ModuleRule::new( module_rule_match_js_no_url(enable_mdx_rs), vec![ModuleRuleEffect::ExtendEcmascriptTransforms { - prepend: ResolvedVc::cell(vec![]), - append: ResolvedVc::cell(vec![transformer]), + preprocess: ResolvedVc::cell(vec![]), + main: ResolvedVc::cell(vec![]), + postprocess: ResolvedVc::cell(vec![transformer]), }], ) } diff --git a/crates/next-core/src/next_shared/transforms/next_react_server_components.rs b/crates/next-core/src/next_shared/transforms/next_react_server_components.rs index cd0b1fe71e640..89a3e3ba984e5 100644 --- a/crates/next-core/src/next_shared/transforms/next_react_server_components.rs +++ b/crates/next-core/src/next_shared/transforms/next_react_server_components.rs @@ -11,7 +11,7 @@ use turbopack::module_options::ModuleRule; use turbopack_ecmascript::{CustomTransformer, TransformContext}; use super::get_ecma_transform_rule; -use crate::next_config::NextConfig; +use crate::{next_config::NextConfig, next_shared::transforms::EcmascriptTransformStage}; /// Returns a rule which applies the Next.js react server components transform. /// This transform owns responsibility to assert various import / usage @@ -44,7 +44,7 @@ pub async fn get_next_react_server_components_transform_rule( app_dir, )), enable_mdx_rs, - true, + EcmascriptTransformStage::Preprocess, )) } diff --git a/crates/next-core/src/next_shared/transforms/next_shake_exports.rs b/crates/next-core/src/next_shared/transforms/next_shake_exports.rs index d8cc453ff975b..9a1bc958bfb94 100644 --- a/crates/next-core/src/next_shared/transforms/next_shake_exports.rs +++ b/crates/next-core/src/next_shared/transforms/next_shake_exports.rs @@ -16,8 +16,9 @@ pub fn get_next_shake_exports_rule(enable_mdx_rs: bool, ignore: Vec) -> ModuleRule::new( module_rule_match_js_no_url(enable_mdx_rs), vec![ModuleRuleEffect::ExtendEcmascriptTransforms { - prepend: ResolvedVc::cell(vec![]), - append: ResolvedVc::cell(vec![transformer]), + preprocess: ResolvedVc::cell(vec![]), + main: ResolvedVc::cell(vec![]), + postprocess: ResolvedVc::cell(vec![transformer]), }], ) } diff --git a/crates/next-core/src/next_shared/transforms/next_strip_page_exports.rs b/crates/next-core/src/next_shared/transforms/next_strip_page_exports.rs index a7df0711325b3..94c150bcf307f 100644 --- a/crates/next-core/src/next_shared/transforms/next_strip_page_exports.rs +++ b/crates/next-core/src/next_shared/transforms/next_strip_page_exports.rs @@ -40,8 +40,9 @@ pub async fn get_next_pages_transforms_rule( module_rule_match_js_no_url(enable_mdx_rs), ]), vec![ModuleRuleEffect::ExtendEcmascriptTransforms { - prepend: ResolvedVc::cell(vec![]), - append: ResolvedVc::cell(vec![strip_transform]), + preprocess: ResolvedVc::cell(vec![]), + main: ResolvedVc::cell(vec![]), + postprocess: ResolvedVc::cell(vec![strip_transform]), }], )) } diff --git a/crates/next-core/src/next_shared/transforms/next_track_dynamic_imports.rs b/crates/next-core/src/next_shared/transforms/next_track_dynamic_imports.rs index 653df152afce8..abcec227dfc54 100644 --- a/crates/next-core/src/next_shared/transforms/next_track_dynamic_imports.rs +++ b/crates/next-core/src/next_shared/transforms/next_track_dynamic_imports.rs @@ -6,9 +6,14 @@ use turbopack::module_options::ModuleRule; use turbopack_ecmascript::{CustomTransformer, TransformContext}; use super::get_ecma_transform_rule; +use crate::next_shared::transforms::EcmascriptTransformStage; pub fn get_next_track_dynamic_imports_transform_rule(mdx_rs: bool) -> ModuleRule { - get_ecma_transform_rule(Box::new(NextTrackDynamicImports {}), mdx_rs, false) + get_ecma_transform_rule( + Box::new(NextTrackDynamicImports {}), + mdx_rs, + EcmascriptTransformStage::Postprocess, + ) } #[derive(Debug)] diff --git a/crates/next-core/src/next_shared/transforms/react_remove_properties.rs b/crates/next-core/src/next_shared/transforms/react_remove_properties.rs index 7b0c62bcaa537..47a783211fd63 100644 --- a/crates/next-core/src/next_shared/transforms/react_remove_properties.rs +++ b/crates/next-core/src/next_shared/transforms/react_remove_properties.rs @@ -6,7 +6,10 @@ use turbopack::module_options::ModuleRule; use turbopack_ecmascript::{CustomTransformer, TransformContext}; use super::get_ecma_transform_rule; -use crate::next_config::{NextConfig, ReactRemoveProperties}; +use crate::{ + next_config::{NextConfig, ReactRemoveProperties}, + next_shared::transforms::EcmascriptTransformStage, +}; /// Returns a rule which applies the react_remove_properties transform. pub async fn get_react_remove_properties_transform_rule( @@ -34,7 +37,7 @@ pub async fn get_react_remove_properties_transform_rule( get_ecma_transform_rule( Box::new(ReactRemovePropertiesTransformer { config }), enable_mdx_rs, - true, + EcmascriptTransformStage::Preprocess, ) }); diff --git a/crates/next-core/src/next_shared/transforms/relay.rs b/crates/next-core/src/next_shared/transforms/relay.rs index 6e2d9b27bb7de..51d22a30b3b72 100644 --- a/crates/next-core/src/next_shared/transforms/relay.rs +++ b/crates/next-core/src/next_shared/transforms/relay.rs @@ -5,7 +5,7 @@ use turbopack::module_options::ModuleRule; use turbopack_ecmascript_plugins::transform::relay::RelayTransformer; use super::get_ecma_transform_rule; -use crate::next_config::NextConfig; +use crate::{next_config::NextConfig, next_shared::transforms::EcmascriptTransformStage}; /// Returns a transform rule for the relay graphql transform. pub async fn get_relay_transform_rule( @@ -17,7 +17,7 @@ pub async fn get_relay_transform_rule( get_ecma_transform_rule( Box::new(RelayTransformer::new(config, &project_path)), enable_mdx_rs, - true, + EcmascriptTransformStage::Preprocess, ) }); diff --git a/crates/next-core/src/next_shared/transforms/remove_console.rs b/crates/next-core/src/next_shared/transforms/remove_console.rs index b730a75f2a331..f454e86e46168 100644 --- a/crates/next-core/src/next_shared/transforms/remove_console.rs +++ b/crates/next-core/src/next_shared/transforms/remove_console.rs @@ -6,7 +6,10 @@ use turbopack::module_options::ModuleRule; use turbopack_ecmascript::{CustomTransformer, TransformContext}; use super::get_ecma_transform_rule; -use crate::next_config::{NextConfig, RemoveConsoleConfig}; +use crate::{ + next_config::{NextConfig, RemoveConsoleConfig}, + next_shared::transforms::EcmascriptTransformStage, +}; /// Returns a rule which applies the remove_console transform. pub async fn get_remove_console_transform_rule( @@ -37,7 +40,7 @@ pub async fn get_remove_console_transform_rule( get_ecma_transform_rule( Box::new(RemoveConsoleTransformer { config }), enable_mdx_rs, - true, + EcmascriptTransformStage::Preprocess, ) }); diff --git a/crates/next-core/src/next_shared/transforms/server_actions.rs b/crates/next-core/src/next_shared/transforms/server_actions.rs index 93ece0814ab76..12ba811184ed9 100644 --- a/crates/next-core/src/next_shared/transforms/server_actions.rs +++ b/crates/next-core/src/next_shared/transforms/server_actions.rs @@ -40,8 +40,9 @@ pub async fn get_server_actions_transform_rule( Ok(ModuleRule::new( module_rule_match_js_no_url(enable_mdx_rs), vec![ModuleRuleEffect::ExtendEcmascriptTransforms { - prepend: ResolvedVc::cell(vec![transformer]), - append: ResolvedVc::cell(vec![]), + preprocess: ResolvedVc::cell(vec![transformer]), + main: ResolvedVc::cell(vec![]), + postprocess: ResolvedVc::cell(vec![]), }], )) } diff --git a/crates/next-core/src/next_shared/transforms/styled_components.rs b/crates/next-core/src/next_shared/transforms/styled_components.rs index 7b81974b1f8cc..ac3f5ef65d41f 100644 --- a/crates/next-core/src/next_shared/transforms/styled_components.rs +++ b/crates/next-core/src/next_shared/transforms/styled_components.rs @@ -5,7 +5,7 @@ use turbopack_ecmascript_plugins::transform::styled_components::StyledComponents use crate::{ next_config::{NextConfig, StyledComponentsTransformOptionsOrBoolean}, - next_shared::transforms::get_ecma_transform_rule, + next_shared::transforms::{EcmascriptTransformStage, get_ecma_transform_rule}, }; pub async fn get_styled_components_transform_rule( @@ -27,7 +27,13 @@ pub async fn get_styled_components_transform_rule( } _ => None, }) - .map(|transformer| get_ecma_transform_rule(Box::new(transformer), enable_mdx_rs, true)); + .map(|transformer| { + get_ecma_transform_rule( + Box::new(transformer), + enable_mdx_rs, + EcmascriptTransformStage::Main, + ) + }); Ok(module_rule) } diff --git a/crates/next-core/src/next_shared/transforms/styled_jsx.rs b/crates/next-core/src/next_shared/transforms/styled_jsx.rs index 04c4163bd59f1..28fdcf49fec3f 100644 --- a/crates/next-core/src/next_shared/transforms/styled_jsx.rs +++ b/crates/next-core/src/next_shared/transforms/styled_jsx.rs @@ -5,7 +5,7 @@ use turbopack_core::environment::RuntimeVersions; use turbopack_ecmascript_plugins::transform::styled_jsx::StyledJsxTransformer; use super::get_ecma_transform_rule; -use crate::next_config::NextConfig; +use crate::{next_config::NextConfig, next_shared::transforms::EcmascriptTransformStage}; /// Returns a transform rule for the styled jsx transform. pub async fn get_styled_jsx_transform_rule( @@ -19,6 +19,6 @@ pub async fn get_styled_jsx_transform_rule( Ok(Some(get_ecma_transform_rule( Box::new(transformer), enable_mdx_rs, - true, + EcmascriptTransformStage::Main, ))) } diff --git a/crates/next-core/src/next_shared/transforms/swc_ecma_transform_plugins.rs b/crates/next-core/src/next_shared/transforms/swc_ecma_transform_plugins.rs index f09a04f613caf..78fc1e0ca51f4 100644 --- a/crates/next-core/src/next_shared/transforms/swc_ecma_transform_plugins.rs +++ b/crates/next-core/src/next_shared/transforms/swc_ecma_transform_plugins.rs @@ -48,7 +48,7 @@ pub async fn get_swc_ecma_transform_rule_impl( SwcEcmaTransformPluginsTransformer, SwcPluginModule, }; - use crate::next_shared::transforms::get_ecma_transform_rule; + use crate::next_shared::transforms::{EcmascriptTransformStage, get_ecma_transform_rule}; let plugins = plugin_configs .iter() @@ -116,6 +116,6 @@ pub async fn get_swc_ecma_transform_rule_impl( Ok(Some(get_ecma_transform_rule( Box::new(SwcEcmaTransformPluginsTransformer::new(plugins)), enable_mdx_rs, - true, + EcmascriptTransformStage::Main, ))) } diff --git a/docs/01-app/03-api-reference/08-turbopack.mdx b/docs/01-app/03-api-reference/08-turbopack.mdx index 5aafcd4cd3b94..06bb270786246 100644 --- a/docs/01-app/03-api-reference/08-turbopack.mdx +++ b/docs/01-app/03-api-reference/08-turbopack.mdx @@ -132,8 +132,6 @@ Turbopack can be configured via `next.config.js` (or `next.config.ts`) under the Change or extend file extensions for module resolution. - **`moduleIds`** Set how module IDs are generated (`'named'` vs `'deterministic'`). -- **`treeShaking`** - Enable or disable tree shaking in dev and future production builds. - **`memoryLimit`** Set a memory limit (in bytes) for Turbopack. diff --git a/lerna.json b/lerna.json index be7dbf64cac68..08d35255a24d0 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "15.4.2-canary.29" + "version": "15.4.2-canary.30" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index eeadcb988eaec..76378709edf28 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "15.4.2-canary.29", + "version": "15.4.2-canary.30", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 15b7409185168..f20f03df276aa 100644 --- a/packages/eslint-config-next/package.json +++ b/packages/eslint-config-next/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-next", - "version": "15.4.2-canary.29", + "version": "15.4.2-canary.30", "description": "ESLint configuration used by Next.js.", "main": "index.js", "license": "MIT", @@ -10,7 +10,7 @@ }, "homepage": "https://nextjs.org/docs/app/api-reference/config/eslint", "dependencies": { - "@next/eslint-plugin-next": "15.4.2-canary.29", + "@next/eslint-plugin-next": "15.4.2-canary.30", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json index e4919e29f9bee..4e070d7e0461b 100644 --- a/packages/eslint-plugin-internal/package.json +++ b/packages/eslint-plugin-internal/package.json @@ -1,7 +1,7 @@ { "name": "@next/eslint-plugin-internal", "private": true, - "version": "15.4.2-canary.29", + "version": "15.4.2-canary.30", "description": "ESLint plugin for working on Next.js.", "exports": { ".": "./src/eslint-plugin-internal.js" diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index f3cd18c2d59da..ea002317f5fbe 100644 --- a/packages/eslint-plugin-next/package.json +++ b/packages/eslint-plugin-next/package.json @@ -1,6 +1,6 @@ { "name": "@next/eslint-plugin-next", - "version": "15.4.2-canary.29", + "version": "15.4.2-canary.30", "description": "ESLint plugin for Next.js.", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/font/package.json b/packages/font/package.json index 352ff38105d16..a5af286c37324 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,7 +1,7 @@ { "name": "@next/font", "private": true, - "version": "15.4.2-canary.29", + "version": "15.4.2-canary.30", "repository": { "url": "vercel/next.js", "directory": "packages/font" diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json index 6c39d178fcf64..d82160a91eb3b 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "15.4.2-canary.29", + "version": "15.4.2-canary.30", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 85cabc01e3472..aeac419254a82 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "15.4.2-canary.29", + "version": "15.4.2-canary.30", "license": "MIT", "repository": { "type": "git", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 8457e5915df23..724a3a50eb710 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "15.4.2-canary.29", + "version": "15.4.2-canary.30", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index dc78d9e59ceb5..37e025eefe211 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "15.4.2-canary.29", + "version": "15.4.2-canary.30", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 3a4b64d8fa63a..846c0e99d6ae3 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "15.4.2-canary.29", + "version": "15.4.2-canary.30", "repository": { "url": "vercel/next.js", "directory": "packages/next-plugin-storybook" diff --git a/packages/next-polyfill-module/package.json b/packages/next-polyfill-module/package.json index 90a1e49c271ec..3e1e03e03aed2 100644 --- a/packages/next-polyfill-module/package.json +++ b/packages/next-polyfill-module/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-module", - "version": "15.4.2-canary.29", + "version": "15.4.2-canary.30", "description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)", "main": "dist/polyfill-module.js", "license": "MIT", diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json index e43df0a08f540..d948a3e8cdd73 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "15.4.2-canary.29", + "version": "15.4.2-canary.30", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next-rspack/package.json b/packages/next-rspack/package.json index 713715173f337..b9cb7fc9da8ba 100644 --- a/packages/next-rspack/package.json +++ b/packages/next-rspack/package.json @@ -1,6 +1,6 @@ { "name": "next-rspack", - "version": "15.4.2-canary.29", + "version": "15.4.2-canary.30", "repository": { "url": "vercel/next.js", "directory": "packages/next-rspack" diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index ef5752f126e58..5753073e50f0f 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "15.4.2-canary.29", + "version": "15.4.2-canary.30", "private": true, "files": [ "native/" diff --git a/packages/next/package.json b/packages/next/package.json index 08663a4144981..3cb1085bd31f0 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "15.4.2-canary.29", + "version": "15.4.2-canary.30", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -102,7 +102,7 @@ ] }, "dependencies": { - "@next/env": "15.4.2-canary.29", + "@next/env": "15.4.2-canary.30", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", @@ -166,11 +166,11 @@ "@jest/types": "29.5.0", "@mswjs/interceptors": "0.23.0", "@napi-rs/triples": "1.2.0", - "@next/font": "15.4.2-canary.29", - "@next/polyfill-module": "15.4.2-canary.29", - "@next/polyfill-nomodule": "15.4.2-canary.29", - "@next/react-refresh-utils": "15.4.2-canary.29", - "@next/swc": "15.4.2-canary.29", + "@next/font": "15.4.2-canary.30", + "@next/polyfill-module": "15.4.2-canary.30", + "@next/polyfill-nomodule": "15.4.2-canary.30", + "@next/react-refresh-utils": "15.4.2-canary.30", + "@next/swc": "15.4.2-canary.30", "@opentelemetry/api": "1.6.0", "@playwright/test": "1.51.1", "@rspack/core": "1.4.5", diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json index 35f6a0ca70b1c..601a1f089f14c 100644 --- a/packages/react-refresh-utils/package.json +++ b/packages/react-refresh-utils/package.json @@ -1,6 +1,6 @@ { "name": "@next/react-refresh-utils", - "version": "15.4.2-canary.29", + "version": "15.4.2-canary.30", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", diff --git a/packages/third-parties/package.json b/packages/third-parties/package.json index 5f317e7a870b9..33aae304ec1f6 100644 --- a/packages/third-parties/package.json +++ b/packages/third-parties/package.json @@ -1,6 +1,6 @@ { "name": "@next/third-parties", - "version": "15.4.2-canary.29", + "version": "15.4.2-canary.30", "repository": { "url": "vercel/next.js", "directory": "packages/third-parties" @@ -26,7 +26,7 @@ "third-party-capital": "1.0.20" }, "devDependencies": { - "next": "15.4.2-canary.29", + "next": "15.4.2-canary.30", "outdent": "0.8.0", "prettier": "2.5.1", "typescript": "5.8.2" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5ee92e26da969..27d73760c6940 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -676,6 +676,9 @@ importers: next: specifier: workspace:* version: link:../../packages/next + playwright: + specifier: ^1.40.0 + version: 1.48.0 rimraf: specifier: 6.0.1 version: 6.0.1 @@ -851,7 +854,7 @@ importers: packages/eslint-config-next: dependencies: '@next/eslint-plugin-next': - specifier: 15.4.2-canary.29 + specifier: 15.4.2-canary.30 version: link:../eslint-plugin-next '@rushstack/eslint-patch': specifier: ^1.10.3 @@ -921,7 +924,7 @@ importers: packages/next: dependencies: '@next/env': - specifier: 15.4.2-canary.29 + specifier: 15.4.2-canary.30 version: link:../next-env '@swc/helpers': specifier: 0.5.15 @@ -1046,19 +1049,19 @@ importers: specifier: 1.2.0 version: 1.2.0 '@next/font': - specifier: 15.4.2-canary.29 + specifier: 15.4.2-canary.30 version: link:../font '@next/polyfill-module': - specifier: 15.4.2-canary.29 + specifier: 15.4.2-canary.30 version: link:../next-polyfill-module '@next/polyfill-nomodule': - specifier: 15.4.2-canary.29 + specifier: 15.4.2-canary.30 version: link:../next-polyfill-nomodule '@next/react-refresh-utils': - specifier: 15.4.2-canary.29 + specifier: 15.4.2-canary.30 version: link:../react-refresh-utils '@next/swc': - specifier: 15.4.2-canary.29 + specifier: 15.4.2-canary.30 version: link:../next-swc '@opentelemetry/api': specifier: 1.6.0 @@ -1761,7 +1764,7 @@ importers: version: 1.0.20 devDependencies: next: - specifier: 15.4.2-canary.29 + specifier: 15.4.2-canary.30 version: link:../next outdent: specifier: 0.8.0 diff --git a/test/e2e/styled-jsx/index.test.ts b/test/e2e/styled-jsx/index.test.ts index 3e4bebe999d7d..c3f39f2c72abe 100644 --- a/test/e2e/styled-jsx/index.test.ts +++ b/test/e2e/styled-jsx/index.test.ts @@ -41,4 +41,13 @@ describe('styled-jsx', () => { const html = await next.render('/amp') expect(html).toMatch(/color:.*?cyan/) }) + + it('should render styles inside TypeScript', async () => { + const browser = await next.browser('/typescript') + const color = await browser.eval( + `getComputedStyle(document.querySelector('button')).color` + ) + + expect(color).toMatch('255, 0, 0') + }) }) diff --git a/test/e2e/styled-jsx/pages/typescript.tsx b/test/e2e/styled-jsx/pages/typescript.tsx new file mode 100644 index 0000000000000..1dfbf4409eef2 --- /dev/null +++ b/test/e2e/styled-jsx/pages/typescript.tsx @@ -0,0 +1,20 @@ +interface Props { + color: string +} + +function Test(p: Props) { + return ( +
+ + +
+ ) +} + +export default function Page() { + return +} diff --git a/turbopack/crates/turbopack-cli/src/contexts.rs b/turbopack/crates/turbopack-cli/src/contexts.rs index 1a6c933deff70..a00803a23ac10 100644 --- a/turbopack/crates/turbopack-cli/src/contexts.rs +++ b/turbopack/crates/turbopack-cli/src/contexts.rs @@ -8,8 +8,8 @@ use turbopack::{ ModuleAssetContext, ecmascript::TreeShakingMode, module_options::{ - EcmascriptOptionsContext, JsxTransformOptions, ModuleOptionsContext, ModuleRule, - ModuleRuleEffect, RuleCondition, TypescriptTransformOptions, + EcmascriptOptionsContext, JsxTransformOptions, ModuleOptionsContext, + TypescriptTransformOptions, }, }; use turbopack_browser::react_refresh::assert_can_resolve_react_refresh; @@ -135,21 +135,6 @@ async fn get_client_module_options_context( .resolved_cell(), ); - let conditions = RuleCondition::any(vec![ - RuleCondition::ResourcePathEndsWith(".js".to_string()), - RuleCondition::ResourcePathEndsWith(".jsx".to_string()), - RuleCondition::ResourcePathEndsWith(".ts".to_string()), - RuleCondition::ResourcePathEndsWith(".tsx".to_string()), - ]); - - let module_rules = ModuleRule::new( - conditions, - vec![ModuleRuleEffect::ExtendEcmascriptTransforms { - prepend: ResolvedVc::cell(vec![]), - append: ResolvedVc::cell(vec![]), - }], - ); - let module_options_context = ModuleOptionsContext { ecmascript: EcmascriptOptionsContext { enable_jsx, @@ -164,7 +149,6 @@ async fn get_client_module_options_context( foreign_code_context_condition(), module_options_context.clone().resolved_cell(), )], - module_rules: vec![module_rules], ..module_options_context } .cell(); diff --git a/turbopack/crates/turbopack-tests/tests/snapshot.rs b/turbopack/crates/turbopack-tests/tests/snapshot.rs index ca2b7a478a530..bd58646e492f5 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot.rs +++ b/turbopack/crates/turbopack-tests/tests/snapshot.rs @@ -321,7 +321,8 @@ async fn run_test_operation(resource: RcStr) -> Result> { let module_rules = ModuleRule::new( conditions, vec![ModuleRuleEffect::ExtendEcmascriptTransforms { - prepend: ResolvedVc::cell(vec![ + preprocess: ResolvedVc::cell(vec![]), + main: ResolvedVc::cell(vec![ EcmascriptInputTransform::Plugin(ResolvedVc::cell(Box::new( EmotionTransformer::new(&EmotionTransformConfig::default()) .expect("Should be able to create emotion transformer"), @@ -330,7 +331,7 @@ async fn run_test_operation(resource: RcStr) -> Result> { StyledComponentsTransformer::new(&StyledComponentsTransformConfig::default()), ) as _)), ]), - append: ResolvedVc::cell(vec![]), + postprocess: ResolvedVc::cell(vec![]), }], ); let asset_context: Vc> = Vc::upcast(ModuleAssetContext::new( diff --git a/turbopack/crates/turbopack/src/lib.rs b/turbopack/crates/turbopack/src/lib.rs index eebe8672d570a..98130b4c42c86 100644 --- a/turbopack/crates/turbopack/src/lib.rs +++ b/turbopack/crates/turbopack/src/lib.rs @@ -80,17 +80,23 @@ async fn apply_module_type( let module_type = &*module_type.await?; Ok(ProcessResult::Module(match module_type { ModuleType::Ecmascript { - transforms, + preprocess, + main, + postprocess, options, } | ModuleType::Typescript { - transforms, + preprocess, + main, + postprocess, tsx: _, analyze_types: _, options, } | ModuleType::TypescriptDeclaration { - transforms, + preprocess, + main, + postprocess, options, } => { let context_for_module = match module_type { @@ -107,7 +113,11 @@ async fn apply_module_type( let mut builder = EcmascriptModuleAsset::builder( source, ResolvedVc::upcast(context_for_module), - *transforms, + preprocess + .extend(**main) + .extend(**postprocess) + .to_resolved() + .await?, *options, module_asset_context .compile_time_info() @@ -555,28 +565,44 @@ async fn process_default_internal( ModuleRuleEffect::ModuleType(module) => { current_module_type = Some(module.clone()); } - ModuleRuleEffect::ExtendEcmascriptTransforms { prepend, append } => { + ModuleRuleEffect::ExtendEcmascriptTransforms { + preprocess: extend_preprocess, + main: extend_main, + postprocess: extend_postprocess, + } => { current_module_type = match current_module_type { Some(ModuleType::Ecmascript { - transforms, + preprocess, + main, + postprocess, options, }) => Some(ModuleType::Ecmascript { - transforms: prepend - .extend(*transforms) - .extend(**append) + preprocess: extend_preprocess + .extend(*preprocess) + .to_resolved() + .await?, + main: extend_main.extend(*main).to_resolved().await?, + postprocess: postprocess + .extend(**extend_postprocess) .to_resolved() .await?, options, }), Some(ModuleType::Typescript { - transforms, + preprocess, + main, + postprocess, tsx, analyze_types, options, }) => Some(ModuleType::Typescript { - transforms: prepend - .extend(*transforms) - .extend(**append) + preprocess: extend_preprocess + .extend(*preprocess) + .to_resolved() + .await?, + main: extend_main.extend(*main).to_resolved().await?, + postprocess: postprocess + .extend(**extend_postprocess) .to_resolved() .await?, tsx, diff --git a/turbopack/crates/turbopack/src/module_options/mod.rs b/turbopack/crates/turbopack/src/module_options/mod.rs index 3d71f94998871..e946dfe47fffd 100644 --- a/turbopack/crates/turbopack/src/module_options/mod.rs +++ b/turbopack/crates/turbopack/src/module_options/mod.rs @@ -149,7 +149,9 @@ impl ModuleOptions { .. } = *module_options_context.await?; - let mut transforms = vec![]; + let mut ts_preprocess = vec![]; + let mut ecma_preprocess = vec![]; + let mut postprocess = vec![]; // Order of transforms is important. e.g. if the React transform occurs before // Styled JSX, there won't be JSX nodes for Styled JSX to transform. @@ -158,7 +160,7 @@ impl ModuleOptions { if let Some(enable_jsx) = enable_jsx { let jsx = enable_jsx.await?; - transforms.push(EcmascriptInputTransform::React { + postprocess.push(EcmascriptInputTransform::React { development: jsx.development, refresh: jsx.react_refresh, import_source: ResolvedVc::cell(jsx.import_source.clone()), @@ -178,11 +180,11 @@ impl ModuleOptions { let ecmascript_options_vc = ecmascript_options.resolved_cell(); if let Some(environment) = environment { - transforms.push(EcmascriptInputTransform::PresetEnv(environment)); + postprocess.push(EcmascriptInputTransform::PresetEnv(environment)); } if let Some(enable_typeof_window_inlining) = enable_typeof_window_inlining { - transforms.push(EcmascriptInputTransform::GlobalTypeofs { + postprocess.push(EcmascriptInputTransform::GlobalTypeofs { window_value: match enable_typeof_window_inlining { TypeofWindow::Object => rcstr!("object"), TypeofWindow::Undefined => rcstr!("undefined"), @@ -214,43 +216,30 @@ impl ModuleOptions { None }; - let vendor_transforms = Vc::::cell(vec![]); - let ts_app_transforms = if let Some(transform) = &ts_transform { - let base_transforms = if let Some(decorators_transform) = &decorators_transform { - vec![decorators_transform.clone(), transform.clone()] - } else { - vec![transform.clone()] - }; - Vc::::cell( - base_transforms - .iter() - .cloned() - .chain(transforms.iter().cloned()) - .collect(), - ) - } else { - Vc::cell(transforms.clone()) - }; - - // Apply decorators transform for the ModuleType::Ecmascript as well after - // constructing ts_app_transforms. Ecmascript can have decorators for - // the cases of 1. using jsconfig, to enable ts-specific runtime - // decorators (i.e legacy) 2. ecma spec decorators - // - // Since typescript transform (`ts_app_transforms`) needs to apply decorators - // _before_ stripping types, we create ts_app_transforms first in a - // specific order with typescript, then apply decorators to app_transforms. - let app_transforms = Vc::::cell( + if let Some(ts_transform) = &ts_transform { if let Some(decorators_transform) = &decorators_transform { - vec![decorators_transform.clone()] + ts_preprocess.splice(0..0, [decorators_transform.clone(), ts_transform.clone()]); } else { - vec![] + ts_preprocess.splice(0..0, [ts_transform.clone()]); } - .iter() - .cloned() - .chain(transforms.iter().cloned()) - .collect(), - ); + } + if let Some(decorators_transform) = &decorators_transform { + // Apply decorators transform for the ModuleType::Ecmascript as well after + // constructing ts_app_transforms. Ecmascript can have decorators for + // the cases of 1. using jsconfig, to enable ts-specific runtime + // decorators (i.e legacy) 2. ecma spec decorators + // + // Since typescript transform (`ts_app_transforms`) needs to apply decorators + // _before_ stripping types, we create ts_app_transforms first in a + // specific order with typescript, then apply decorators to app_transforms. + ecma_preprocess.splice(0..0, [decorators_transform.clone()]); + } + + let ts_preprocess = ResolvedVc::cell(ts_preprocess); + let ecma_preprocess = ResolvedVc::cell(ecma_preprocess); + let main = ResolvedVc::::cell(vec![]); + let postprocess = ResolvedVc::cell(postprocess); + let empty = ResolvedVc::::cell(vec![]); let mut rules = vec![ ModuleRule::new_all( @@ -268,14 +257,18 @@ impl ModuleOptions { RuleCondition::ContentTypeStartsWith("text/javascript".to_string()), ]), vec![ModuleRuleEffect::ModuleType(ModuleType::Ecmascript { - transforms: app_transforms.to_resolved().await?, + preprocess: ecma_preprocess, + main, + postprocess, options: ecmascript_options_vc, })], ), ModuleRule::new_all( RuleCondition::ResourcePathEndsWith(".mjs".to_string()), vec![ModuleRuleEffect::ModuleType(ModuleType::Ecmascript { - transforms: app_transforms.to_resolved().await?, + preprocess: ecma_preprocess, + main, + postprocess, options: EcmascriptOptions { specified_module_type: SpecifiedModuleType::EcmaScript, ..ecmascript_options @@ -286,7 +279,9 @@ impl ModuleOptions { ModuleRule::new_all( RuleCondition::ResourcePathEndsWith(".cjs".to_string()), vec![ModuleRuleEffect::ModuleType(ModuleType::Ecmascript { - transforms: app_transforms.to_resolved().await?, + preprocess: ecma_preprocess, + main, + postprocess, options: EcmascriptOptions { specified_module_type: SpecifiedModuleType::CommonJs, ..ecmascript_options @@ -297,7 +292,9 @@ impl ModuleOptions { ModuleRule::new_all( RuleCondition::ResourcePathEndsWith(".ts".to_string()), vec![ModuleRuleEffect::ModuleType(ModuleType::Typescript { - transforms: ts_app_transforms.to_resolved().await?, + preprocess: ts_preprocess, + main, + postprocess, tsx: false, analyze_types: enable_types, options: ecmascript_options_vc, @@ -306,7 +303,9 @@ impl ModuleOptions { ModuleRule::new_all( RuleCondition::ResourcePathEndsWith(".tsx".to_string()), vec![ModuleRuleEffect::ModuleType(ModuleType::Typescript { - transforms: ts_app_transforms.to_resolved().await?, + preprocess: ts_preprocess, + main, + postprocess, tsx: true, analyze_types: enable_types, options: ecmascript_options_vc, @@ -315,7 +314,9 @@ impl ModuleOptions { ModuleRule::new_all( RuleCondition::ResourcePathEndsWith(".mts".to_string()), vec![ModuleRuleEffect::ModuleType(ModuleType::Typescript { - transforms: ts_app_transforms.to_resolved().await?, + preprocess: ts_preprocess, + main, + postprocess, tsx: false, analyze_types: enable_types, options: EcmascriptOptions { @@ -328,7 +329,9 @@ impl ModuleOptions { ModuleRule::new_all( RuleCondition::ResourcePathEndsWith(".mtsx".to_string()), vec![ModuleRuleEffect::ModuleType(ModuleType::Typescript { - transforms: ts_app_transforms.to_resolved().await?, + preprocess: ts_preprocess, + main, + postprocess, tsx: true, analyze_types: enable_types, options: EcmascriptOptions { @@ -341,7 +344,9 @@ impl ModuleOptions { ModuleRule::new_all( RuleCondition::ResourcePathEndsWith(".cts".to_string()), vec![ModuleRuleEffect::ModuleType(ModuleType::Typescript { - transforms: ts_app_transforms.to_resolved().await?, + preprocess: ts_preprocess, + main, + postprocess, tsx: false, analyze_types: enable_types, options: EcmascriptOptions { @@ -354,7 +359,9 @@ impl ModuleOptions { ModuleRule::new_all( RuleCondition::ResourcePathEndsWith(".ctsx".to_string()), vec![ModuleRuleEffect::ModuleType(ModuleType::Typescript { - transforms: ts_app_transforms.to_resolved().await?, + preprocess: ts_preprocess, + main, + postprocess, tsx: true, analyze_types: enable_types, options: EcmascriptOptions { @@ -368,7 +375,9 @@ impl ModuleOptions { RuleCondition::ResourcePathEndsWith(".d.ts".to_string()), vec![ModuleRuleEffect::ModuleType( ModuleType::TypescriptDeclaration { - transforms: vendor_transforms.to_resolved().await?, + preprocess: empty, + main: empty, + postprocess: empty, options: ecmascript_options_vc, }, )], @@ -404,7 +413,9 @@ impl ModuleOptions { RuleCondition::ContentTypeEmpty, ]), vec![ModuleRuleEffect::ModuleType(ModuleType::Ecmascript { - transforms: vendor_transforms.to_resolved().await?, + preprocess: empty, + main: empty, + postprocess: empty, options: ecmascript_options_vc, })], ), diff --git a/turbopack/crates/turbopack/src/module_options/module_rule.rs b/turbopack/crates/turbopack/src/module_options/module_rule.rs index 66fb41f5736ce..9cf9f48fcb44c 100644 --- a/turbopack/crates/turbopack/src/module_options/module_rule.rs +++ b/turbopack/crates/turbopack/src/module_options/module_rule.rs @@ -67,11 +67,14 @@ impl ModuleRule { pub enum ModuleRuleEffect { ModuleType(ModuleType), /// Allow to extend an existing Ecmascript module rules for the additional - /// transforms. First argument will prepend the existing transforms, and - /// the second argument will append the new transforms. + /// transforms ExtendEcmascriptTransforms { - prepend: ResolvedVc, - append: ResolvedVc, + /// Transforms to run first: transpile TypeScript, decorators, ... + preprocess: ResolvedVc, + /// Transforms to execute on standard EcmaScript (plus JSX): styled-jsx, swc plugins, ... + main: ResolvedVc, + /// Transforms to run last: JSX, preset-env, scan for imports, ... + postprocess: ResolvedVc, }, SourceTransforms(ResolvedVc), Ignore, @@ -81,12 +84,22 @@ pub enum ModuleRuleEffect { #[derive(Hash, Debug, Clone)] pub enum ModuleType { Ecmascript { - transforms: ResolvedVc, + /// Transforms to run first: transpile TypeScript, decorators, ... + preprocess: ResolvedVc, + /// Transforms to execute on standard EcmaScript (plus JSX): styled-jsx, swc plugins, ... + main: ResolvedVc, + /// Transforms to run last: JSX, preset-env, scan for imports, ... + postprocess: ResolvedVc, #[turbo_tasks(trace_ignore)] options: ResolvedVc, }, Typescript { - transforms: ResolvedVc, + /// Transforms to run first: transpile TypeScript, decorators, ... + preprocess: ResolvedVc, + /// Transforms to execute on standard EcmaScript (plus JSX): styled-jsx, swc plugins, ... + main: ResolvedVc, + /// Transforms to run last: JSX, preset-env, scan for imports, ... + postprocess: ResolvedVc, // parse JSX syntax. tsx: bool, // follow references to imported types. @@ -95,7 +108,12 @@ pub enum ModuleType { options: ResolvedVc, }, TypescriptDeclaration { - transforms: ResolvedVc, + /// Transforms to run first: transpile TypeScript, decorators, ... + preprocess: ResolvedVc, + /// Transforms to execute on standard EcmaScript (plus JSX): styled-jsx, swc plugins, ... + main: ResolvedVc, + /// Transforms to run last: JSX, preset-env, scan for imports, ... + postprocess: ResolvedVc, #[turbo_tasks(trace_ignore)] options: ResolvedVc, },