refactor: Add lightningcss mode for turbopack-css
(#58471)
### What? We are experimenting with `lightningcss`. This is about replacing `swc_css` with `lightningcss` in turbopack, and the main reason for this is to reduce the maintenance burden. But when I tried, it introduced several regressions, so I'm putting it behind an experimental flag. You can enable `lightningcss` mode for **turbopack** by adding a flag to the next config file. ```js /** * @type {import('next').NextConfig} */ const nextConfig = { experimental: { useLightningcss: true, }, } module.exports = nextConfig ``` Note that this is only for turbopack because we were not using `swc_css` for non-turbopack mode of next.js x-ref: https://vercel.slack.com/archives/C03EWR7LGEN/p1700025496732229?thread_ts=1700019629.866549&cid=C03EWR7LGEN ### Why? We should avoid regressions. ### How? --- turbopack PR: https://github.com/vercel/turbo/pull/6456 Closes PACK-1966 --------- Co-authored-by: Leah <github.leah@hrmny.sh>
This commit is contained in:
parent
32c9ce6805
commit
94771bf3cc
19 changed files with 347 additions and 215 deletions
405
Cargo.lock
generated
405
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
11
Cargo.toml
11
Cargo.toml
|
@ -36,18 +36,18 @@ next-transform-dynamic = { path = "packages/next-swc/crates/next-transform-dynam
|
||||||
next-transform-strip-page-exports = { path = "packages/next-swc/crates/next-transform-strip-page-exports" }
|
next-transform-strip-page-exports = { path = "packages/next-swc/crates/next-transform-strip-page-exports" }
|
||||||
|
|
||||||
# SWC crates
|
# SWC crates
|
||||||
swc_core = { version = "0.86.40", features = [
|
swc_core = { version = "0.86.60", features = [
|
||||||
"ecma_loader_lru",
|
"ecma_loader_lru",
|
||||||
"ecma_loader_parking_lot",
|
"ecma_loader_parking_lot",
|
||||||
] }
|
] }
|
||||||
testing = { version = "0.35.7" }
|
testing = { version = "0.35.10" }
|
||||||
|
|
||||||
# Turbo crates
|
# Turbo crates
|
||||||
turbopack-binding = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-231117.4" }
|
turbopack-binding = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-231120.2" }
|
||||||
# [TODO]: need to refactor embed_directory! macro usages, as well as resolving turbo_tasks::function, macros..
|
# [TODO]: need to refactor embed_directory! macro usages, as well as resolving turbo_tasks::function, macros..
|
||||||
turbo-tasks = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-231117.4" }
|
turbo-tasks = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-231120.2" }
|
||||||
# [TODO]: need to refactor embed_directory! macro usage in next-core
|
# [TODO]: need to refactor embed_directory! macro usage in next-core
|
||||||
turbo-tasks-fs = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-231117.4" }
|
turbo-tasks-fs = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-231120.2" }
|
||||||
|
|
||||||
# General Deps
|
# General Deps
|
||||||
|
|
||||||
|
@ -135,3 +135,4 @@ url = "2.2.2"
|
||||||
urlencoding = "2.1.2"
|
urlencoding = "2.1.2"
|
||||||
webbrowser = "0.8.7"
|
webbrowser = "0.8.7"
|
||||||
dhat = { version = "0.3.2" }
|
dhat = { version = "0.3.2" }
|
||||||
|
|
||||||
|
|
|
@ -38,8 +38,8 @@ turbopack-binding = { workspace = true, features = [
|
||||||
"__swc_transform_modularize_imports",
|
"__swc_transform_modularize_imports",
|
||||||
"__swc_transform_relay",
|
"__swc_transform_relay",
|
||||||
] }
|
] }
|
||||||
react_remove_properties = "0.10.0"
|
react_remove_properties = "0.11.0"
|
||||||
remove_console = "0.11.0"
|
remove_console = "0.12.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
turbopack-binding = { workspace = true, features = [
|
turbopack-binding = { workspace = true, features = [
|
||||||
|
|
|
@ -237,12 +237,14 @@ pub async fn get_client_module_options_context(
|
||||||
.cell()
|
.cell()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let use_lightningcss = *next_config.use_lightningcss().await?;
|
||||||
|
|
||||||
let source_transforms = vec![
|
let source_transforms = vec![
|
||||||
*get_swc_ecma_transform_plugin(project_path, next_config).await?,
|
*get_swc_ecma_transform_plugin(project_path, next_config).await?,
|
||||||
*get_relay_transform_plugin(next_config).await?,
|
*get_relay_transform_plugin(next_config).await?,
|
||||||
*get_emotion_transform_plugin(next_config).await?,
|
*get_emotion_transform_plugin(next_config).await?,
|
||||||
*get_styled_components_transform_plugin(next_config).await?,
|
*get_styled_components_transform_plugin(next_config).await?,
|
||||||
*get_styled_jsx_transform_plugin().await?,
|
*get_styled_jsx_transform_plugin(use_lightningcss).await?,
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
|
@ -302,6 +304,7 @@ pub async fn get_client_module_options_context(
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
custom_rules,
|
custom_rules,
|
||||||
|
use_lightningcss,
|
||||||
..module_options_context
|
..module_options_context
|
||||||
}
|
}
|
||||||
.cell();
|
.cell();
|
||||||
|
|
|
@ -3,10 +3,14 @@ use turbo_tasks::Vc;
|
||||||
use turbopack_binding::turbopack::{
|
use turbopack_binding::turbopack::{
|
||||||
core::{
|
core::{
|
||||||
asset::{Asset, AssetContent},
|
asset::{Asset, AssetContent},
|
||||||
|
chunk::ChunkingContext,
|
||||||
ident::AssetIdent,
|
ident::AssetIdent,
|
||||||
module::Module,
|
module::Module,
|
||||||
},
|
},
|
||||||
turbopack::css::{chunk::CssChunkPlaceable, ParseCss, ParseCssResult},
|
turbopack::css::{
|
||||||
|
chunk::CssChunkPlaceable, CssWithPlaceholderResult, FinalCssResult, ParseCss,
|
||||||
|
ParseCssResult, ProcessCss,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A [`CssClientReferenceModule`] is a marker module used to indicate which
|
/// A [`CssClientReferenceModule`] is a marker module used to indicate which
|
||||||
|
@ -63,3 +67,29 @@ impl ParseCss for CssClientReferenceModule {
|
||||||
Ok(parse_css.parse_css())
|
Ok(parse_css.parse_css())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[turbo_tasks::value_impl]
|
||||||
|
impl ProcessCss for CssClientReferenceModule {
|
||||||
|
#[turbo_tasks::function]
|
||||||
|
async fn get_css_with_placeholder(&self) -> Result<Vc<CssWithPlaceholderResult>> {
|
||||||
|
let Some(imp) = Vc::try_resolve_sidecast::<Box<dyn ProcessCss>>(self.client_module).await?
|
||||||
|
else {
|
||||||
|
bail!("CSS client reference client module must be CSS processable");
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(imp.get_css_with_placeholder())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[turbo_tasks::function]
|
||||||
|
async fn finalize_css(
|
||||||
|
&self,
|
||||||
|
chunking_context: Vc<Box<dyn ChunkingContext>>,
|
||||||
|
) -> Result<Vc<FinalCssResult>> {
|
||||||
|
let Some(imp) = Vc::try_resolve_sidecast::<Box<dyn ProcessCss>>(self.client_module).await?
|
||||||
|
else {
|
||||||
|
bail!("CSS client reference client module must be CSS processable");
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(imp.finalize_css(chunking_context))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -503,6 +503,8 @@ pub struct ExperimentalConfig {
|
||||||
/// (doesn't apply to Turbopack).
|
/// (doesn't apply to Turbopack).
|
||||||
webpack_build_worker: Option<bool>,
|
webpack_build_worker: Option<bool>,
|
||||||
worker_threads: Option<bool>,
|
worker_threads: Option<bool>,
|
||||||
|
|
||||||
|
use_lightningcss: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs)]
|
||||||
|
@ -765,6 +767,13 @@ impl NextConfig {
|
||||||
pub async fn enable_taint(self: Vc<Self>) -> Result<Vc<bool>> {
|
pub async fn enable_taint(self: Vc<Self>) -> Result<Vc<bool>> {
|
||||||
Ok(Vc::cell(self.await?.experimental.taint.unwrap_or(false)))
|
Ok(Vc::cell(self.await?.experimental.taint.unwrap_or(false)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[turbo_tasks::function]
|
||||||
|
pub async fn use_lightningcss(self: Vc<Self>) -> Result<Vc<bool>> {
|
||||||
|
Ok(Vc::cell(
|
||||||
|
self.await?.experimental.use_lightningcss.unwrap_or(false),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_configs() -> Vc<Vec<String>> {
|
fn next_configs() -> Vc<Vec<String>> {
|
||||||
|
|
|
@ -291,10 +291,12 @@ pub async fn get_server_module_options_context(
|
||||||
.cell()
|
.cell()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let use_lightningcss = *next_config.use_lightningcss().await?;
|
||||||
|
|
||||||
// EcmascriptTransformPlugins for custom transforms
|
// EcmascriptTransformPlugins for custom transforms
|
||||||
let styled_components_transform_plugin =
|
let styled_components_transform_plugin =
|
||||||
*get_styled_components_transform_plugin(next_config).await?;
|
*get_styled_components_transform_plugin(next_config).await?;
|
||||||
let styled_jsx_transform_plugin = *get_styled_jsx_transform_plugin().await?;
|
let styled_jsx_transform_plugin = *get_styled_jsx_transform_plugin(use_lightningcss).await?;
|
||||||
|
|
||||||
// ModuleOptionsContext related options
|
// ModuleOptionsContext related options
|
||||||
let tsconfig = get_typescript_transform_options(project_path);
|
let tsconfig = get_typescript_transform_options(project_path);
|
||||||
|
@ -363,6 +365,7 @@ pub async fn get_server_module_options_context(
|
||||||
let module_options_context = ModuleOptionsContext {
|
let module_options_context = ModuleOptionsContext {
|
||||||
execution_context: Some(execution_context),
|
execution_context: Some(execution_context),
|
||||||
esm_url_rewrite_behavior: url_rewrite_behavior,
|
esm_url_rewrite_behavior: url_rewrite_behavior,
|
||||||
|
use_lightningcss,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -431,6 +434,7 @@ pub async fn get_server_module_options_context(
|
||||||
let module_options_context = ModuleOptionsContext {
|
let module_options_context = ModuleOptionsContext {
|
||||||
custom_ecma_transform_plugins: base_ecma_transform_plugins,
|
custom_ecma_transform_plugins: base_ecma_transform_plugins,
|
||||||
execution_context: Some(execution_context),
|
execution_context: Some(execution_context),
|
||||||
|
use_lightningcss,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let foreign_code_module_options_context = ModuleOptionsContext {
|
let foreign_code_module_options_context = ModuleOptionsContext {
|
||||||
|
@ -515,6 +519,7 @@ pub async fn get_server_module_options_context(
|
||||||
let module_options_context = ModuleOptionsContext {
|
let module_options_context = ModuleOptionsContext {
|
||||||
custom_ecma_transform_plugins: base_ecma_transform_plugins,
|
custom_ecma_transform_plugins: base_ecma_transform_plugins,
|
||||||
execution_context: Some(execution_context),
|
execution_context: Some(execution_context),
|
||||||
|
use_lightningcss,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let foreign_code_module_options_context = ModuleOptionsContext {
|
let foreign_code_module_options_context = ModuleOptionsContext {
|
||||||
|
|
|
@ -7,8 +7,10 @@ use turbopack_binding::turbopack::{
|
||||||
|
|
||||||
/// Returns a transform plugin for the relay graphql transform.
|
/// Returns a transform plugin for the relay graphql transform.
|
||||||
#[turbo_tasks::function]
|
#[turbo_tasks::function]
|
||||||
pub async fn get_styled_jsx_transform_plugin() -> Result<Vc<OptionTransformPlugin>> {
|
pub async fn get_styled_jsx_transform_plugin(
|
||||||
|
use_lightningcss: bool,
|
||||||
|
) -> Result<Vc<OptionTransformPlugin>> {
|
||||||
Ok(Vc::cell(Some(Vc::cell(
|
Ok(Vc::cell(Some(Vc::cell(
|
||||||
Box::new(StyledJsxTransformer::new()) as _,
|
Box::new(StyledJsxTransformer::new(use_lightningcss)) as _,
|
||||||
))))
|
))))
|
||||||
}
|
}
|
||||||
|
|
|
@ -193,7 +193,7 @@
|
||||||
"@types/ws": "8.2.0",
|
"@types/ws": "8.2.0",
|
||||||
"@vercel/ncc": "0.34.0",
|
"@vercel/ncc": "0.34.0",
|
||||||
"@vercel/nft": "0.22.6",
|
"@vercel/nft": "0.22.6",
|
||||||
"@vercel/turbopack-ecmascript-runtime": "https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-231113.3",
|
"@vercel/turbopack-ecmascript-runtime": "https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-231120.2",
|
||||||
"acorn": "8.5.0",
|
"acorn": "8.5.0",
|
||||||
"amphtml-validator": "1.0.35",
|
"amphtml-validator": "1.0.35",
|
||||||
"anser": "1.4.9",
|
"anser": "1.4.9",
|
||||||
|
|
|
@ -66,6 +66,7 @@ const supportedTurbopackNextConfigOptions = [
|
||||||
'experimental.useDeploymentId',
|
'experimental.useDeploymentId',
|
||||||
'experimental.useDeploymentIdServerActions',
|
'experimental.useDeploymentIdServerActions',
|
||||||
'experimental.deploymentId',
|
'experimental.deploymentId',
|
||||||
|
'experimental.useLightningcss',
|
||||||
|
|
||||||
// Experimental options that don't affect compilation
|
// Experimental options that don't affect compilation
|
||||||
'experimental.ppr',
|
'experimental.ppr',
|
||||||
|
|
|
@ -368,6 +368,7 @@ export const configSchema: zod.ZodType<NextConfig> = z.lazy(() =>
|
||||||
bundlePagesExternals: z.boolean().optional(),
|
bundlePagesExternals: z.boolean().optional(),
|
||||||
staticWorkerRequestDeduping: z.boolean().optional(),
|
staticWorkerRequestDeduping: z.boolean().optional(),
|
||||||
useWasmBinary: z.boolean().optional(),
|
useWasmBinary: z.boolean().optional(),
|
||||||
|
useLightningcss: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
exportPathMap: z
|
exportPathMap: z
|
||||||
|
|
|
@ -342,6 +342,11 @@ export interface ExperimentalConfig {
|
||||||
staticWorkerRequestDeduping?: boolean
|
staticWorkerRequestDeduping?: boolean
|
||||||
|
|
||||||
useWasmBinary?: boolean
|
useWasmBinary?: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use lightningcss instead of swc_css
|
||||||
|
*/
|
||||||
|
useLightningcss?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ExportPathMap = {
|
export type ExportPathMap = {
|
||||||
|
|
|
@ -1065,8 +1065,8 @@ importers:
|
||||||
specifier: 0.22.6
|
specifier: 0.22.6
|
||||||
version: 0.22.6
|
version: 0.22.6
|
||||||
'@vercel/turbopack-ecmascript-runtime':
|
'@vercel/turbopack-ecmascript-runtime':
|
||||||
specifier: https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-231113.3
|
specifier: https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-231120.2
|
||||||
version: '@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-231113.3(react-refresh@0.12.0)(webpack@5.86.0)'
|
version: '@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-231120.2(react-refresh@0.12.0)(webpack@5.86.0)'
|
||||||
acorn:
|
acorn:
|
||||||
specifier: 8.5.0
|
specifier: 8.5.0
|
||||||
version: 8.5.0
|
version: 8.5.0
|
||||||
|
@ -24646,9 +24646,9 @@ packages:
|
||||||
/zwitch@2.0.4:
|
/zwitch@2.0.4:
|
||||||
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
||||||
|
|
||||||
'@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-231113.3(react-refresh@0.12.0)(webpack@5.86.0)':
|
'@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-231120.2(react-refresh@0.12.0)(webpack@5.86.0)':
|
||||||
resolution: {registry: https://registry.npmjs.org/, tarball: https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-231113.3}
|
resolution: {tarball: https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-231120.2}
|
||||||
id: '@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-231113.3'
|
id: '@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-231120.2'
|
||||||
name: '@vercel/turbopack-ecmascript-runtime'
|
name: '@vercel/turbopack-ecmascript-runtime'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
export default function Root({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<html>
|
||||||
|
<body>{children}</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
import styles from './style.module.css'
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <p className={styles.blue}>hello world</p>
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
.blue {
|
||||||
|
color: blue;
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { nextTestSetup } from 'e2e-utils'
|
||||||
|
import { describeVariants as describe } from 'next-test-utils'
|
||||||
|
|
||||||
|
describe.each(['turbo'])('experimental-lightningcss', () => {
|
||||||
|
const { next } = nextTestSetup({
|
||||||
|
files: __dirname,
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should support css modules', async () => {
|
||||||
|
// Recommended for tests that check HTML. Cheerio is a HTML parser that has a jQuery like API.
|
||||||
|
const $ = await next.render$('/')
|
||||||
|
expect($('p').text()).toBe('hello world')
|
||||||
|
// swc_css does not include `-module` in the class name, while lightningcss does.
|
||||||
|
expect($('p').attr('class')).toBe('style-module__hlQ3RG__blue')
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,10 @@
|
||||||
|
/**
|
||||||
|
* @type {import('next').NextConfig}
|
||||||
|
*/
|
||||||
|
const nextConfig = {
|
||||||
|
experimental: {
|
||||||
|
useLightningcss: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = nextConfig
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": false,
|
||||||
|
"noEmit": true,
|
||||||
|
"incremental": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
Loading…
Reference in a new issue