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:
Donny/강동윤 2023-11-21 02:09:36 +09:00 committed by GitHub
parent 32c9ce6805
commit 94771bf3cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 347 additions and 215 deletions

405
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -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" }
# SWC crates
swc_core = { version = "0.86.40", features = [
swc_core = { version = "0.86.60", features = [
"ecma_loader_lru",
"ecma_loader_parking_lot",
] }
testing = { version = "0.35.7" }
testing = { version = "0.35.10" }
# 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..
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
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
@ -135,3 +135,4 @@ url = "2.2.2"
urlencoding = "2.1.2"
webbrowser = "0.8.7"
dhat = { version = "0.3.2" }

View file

@ -38,8 +38,8 @@ turbopack-binding = { workspace = true, features = [
"__swc_transform_modularize_imports",
"__swc_transform_relay",
] }
react_remove_properties = "0.10.0"
remove_console = "0.11.0"
react_remove_properties = "0.11.0"
remove_console = "0.12.0"
[dev-dependencies]
turbopack-binding = { workspace = true, features = [

View file

@ -237,12 +237,14 @@ pub async fn get_client_module_options_context(
.cell()
});
let use_lightningcss = *next_config.use_lightningcss().await?;
let source_transforms = vec![
*get_swc_ecma_transform_plugin(project_path, next_config).await?,
*get_relay_transform_plugin(next_config).await?,
*get_emotion_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()
.flatten()
@ -302,6 +304,7 @@ pub async fn get_client_module_options_context(
),
],
custom_rules,
use_lightningcss,
..module_options_context
}
.cell();

View file

@ -3,10 +3,14 @@ use turbo_tasks::Vc;
use turbopack_binding::turbopack::{
core::{
asset::{Asset, AssetContent},
chunk::ChunkingContext,
ident::AssetIdent,
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
@ -63,3 +67,29 @@ impl ParseCss for CssClientReferenceModule {
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))
}
}

View file

@ -503,6 +503,8 @@ pub struct ExperimentalConfig {
/// (doesn't apply to Turbopack).
webpack_build_worker: Option<bool>,
worker_threads: Option<bool>,
use_lightningcss: Option<bool>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs)]
@ -765,6 +767,13 @@ impl NextConfig {
pub async fn enable_taint(self: Vc<Self>) -> Result<Vc<bool>> {
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>> {

View file

@ -291,10 +291,12 @@ pub async fn get_server_module_options_context(
.cell()
});
let use_lightningcss = *next_config.use_lightningcss().await?;
// EcmascriptTransformPlugins for custom transforms
let styled_components_transform_plugin =
*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
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 {
execution_context: Some(execution_context),
esm_url_rewrite_behavior: url_rewrite_behavior,
use_lightningcss,
..Default::default()
};
@ -431,6 +434,7 @@ pub async fn get_server_module_options_context(
let module_options_context = ModuleOptionsContext {
custom_ecma_transform_plugins: base_ecma_transform_plugins,
execution_context: Some(execution_context),
use_lightningcss,
..Default::default()
};
let foreign_code_module_options_context = ModuleOptionsContext {
@ -515,6 +519,7 @@ pub async fn get_server_module_options_context(
let module_options_context = ModuleOptionsContext {
custom_ecma_transform_plugins: base_ecma_transform_plugins,
execution_context: Some(execution_context),
use_lightningcss,
..Default::default()
};
let foreign_code_module_options_context = ModuleOptionsContext {

View file

@ -7,8 +7,10 @@ use turbopack_binding::turbopack::{
/// Returns a transform plugin for the relay graphql transform.
#[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(
Box::new(StyledJsxTransformer::new()) as _,
Box::new(StyledJsxTransformer::new(use_lightningcss)) as _,
))))
}

View file

@ -193,7 +193,7 @@
"@types/ws": "8.2.0",
"@vercel/ncc": "0.34.0",
"@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",
"amphtml-validator": "1.0.35",
"anser": "1.4.9",

View file

@ -66,6 +66,7 @@ const supportedTurbopackNextConfigOptions = [
'experimental.useDeploymentId',
'experimental.useDeploymentIdServerActions',
'experimental.deploymentId',
'experimental.useLightningcss',
// Experimental options that don't affect compilation
'experimental.ppr',

View file

@ -368,6 +368,7 @@ export const configSchema: zod.ZodType<NextConfig> = z.lazy(() =>
bundlePagesExternals: z.boolean().optional(),
staticWorkerRequestDeduping: z.boolean().optional(),
useWasmBinary: z.boolean().optional(),
useLightningcss: z.boolean().optional(),
})
.optional(),
exportPathMap: z

View file

@ -342,6 +342,11 @@ export interface ExperimentalConfig {
staticWorkerRequestDeduping?: boolean
useWasmBinary?: boolean
/**
* Use lightningcss instead of swc_css
*/
useLightningcss?: boolean
}
export type ExportPathMap = {

View file

@ -1065,8 +1065,8 @@ importers:
specifier: 0.22.6
version: 0.22.6
'@vercel/turbopack-ecmascript-runtime':
specifier: https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-231113.3
version: '@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-231113.3(react-refresh@0.12.0)(webpack@5.86.0)'
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-231120.2(react-refresh@0.12.0)(webpack@5.86.0)'
acorn:
specifier: 8.5.0
version: 8.5.0
@ -24646,9 +24646,9 @@ packages:
/zwitch@2.0.4:
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)':
resolution: {registry: https://registry.npmjs.org/, tarball: https://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-231113.3'
'@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-231120.2(react-refresh@0.12.0)(webpack@5.86.0)':
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-231120.2'
name: '@vercel/turbopack-ecmascript-runtime'
version: 0.0.0
dependencies:

View file

@ -0,0 +1,7 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>{children}</body>
</html>
)
}

View file

@ -0,0 +1,5 @@
import styles from './style.module.css'
export default function Page() {
return <p className={styles.blue}>hello world</p>
}

View file

@ -0,0 +1,3 @@
.blue {
color: blue;
}

View file

@ -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')
})
})

View file

@ -0,0 +1,10 @@
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
experimental: {
useLightningcss: true,
},
}
module.exports = nextConfig

View file

@ -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"]
}