Skip to content

Commit 2dc9e7c

Browse files
authored
feat(ecma-plugins): extract custom emotion transform (vercel/turborepo#4662)
### Description First step for WEB-940. Context: https://vercel.slack.com/archives/C03EWR7LGEN/p1681789689115509 Currently all of the ecma transforms are explicitly listed under EcmaInputTransform in turbopack-ecmascript. This makes enum verbose, we have to manually expand it each time adding new transform, as well as turbopack-ecmascript gets larger to contain all of the 3rd party transforms by default. PR extracts non-core transforms into a new crate, named as ecmascript-plugins then utilize EcmaInputTransform::Custom to invoke transforms instead. `EcmaInputTransform::Custom` is renamed to `EcmaInputTransform::Plugin` as well. Goal is extracting all of 3rd party / non-core transforms. This also reduces multiple steps to construct option value between caller (next-*) to actual transform (swcOptions). #48671 have corresponding next.js changes.
1 parent 796a39f commit 2dc9e7c

File tree

17 files changed

+207
-107
lines changed

17 files changed

+207
-107
lines changed

crates/turbo-binding/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ __turbopack_dev_dynamic_embed_contents = [
108108
]
109109
__turbopack_dev_server = ["__turbopack", "turbopack-dev-server"]
110110
__turbopack_ecmascript = ["__turbopack", "turbopack-ecmascript"]
111+
# [Note]: currently all of the transform features are enabled by default
112+
__turbopack_ecmascript_plugin = [
113+
"__turbopack",
114+
"turbopack-ecmascript-plugins",
115+
"turbopack-ecmascript-plugins/transform_emotion",
116+
]
111117
__turbopack_env = ["__turbopack", "turbopack-env"]
112118
__turbopack_image = ["__turbopack", "turbopack-image"]
113119
__turbopack_image_avif = ["turbopack-image/avif"]
@@ -185,6 +191,7 @@ turbopack-css = { optional = true, workspace = true }
185191
turbopack-dev = { optional = true, workspace = true }
186192
turbopack-dev-server = { optional = true, workspace = true }
187193
turbopack-ecmascript = { optional = true, workspace = true }
194+
turbopack-ecmascript-plugins = { optional = true, workspace = true }
188195
turbopack-env = { optional = true, workspace = true }
189196
turbopack-image = { optional = true, workspace = true }
190197
turbopack-json = { optional = true, workspace = true }

crates/turbo-binding/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ pub mod turbopack {
7070
pub use turbopack_dev_server as dev_server;
7171
#[cfg(feature = "__turbopack_ecmascript")]
7272
pub use turbopack_ecmascript as ecmascript;
73+
#[cfg(feature = "__turbopack_ecmascript_plugin")]
74+
pub use turbopack_ecmascript_plugins as ecmascript_plugin;
7375
#[cfg(feature = "__turbopack_env")]
7476
pub use turbopack_env as env;
7577
#[cfg(feature = "__turbopack_image")]

crates/turbopack-cli/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ turbopack-cli-utils = { workspace = true }
5757
turbopack-core = { workspace = true }
5858
turbopack-dev = { workspace = true }
5959
turbopack-dev-server = { workspace = true }
60+
turbopack-ecmascript-plugins = { workspace = true, features = [
61+
"transform_emotion",
62+
] }
6063
turbopack-env = { workspace = true }
6164
turbopack-node = { workspace = true }
6265
webbrowser = { workspace = true }

crates/turbopack-cli/src/dev/web_entry_source.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use turbopack::{
88
condition::ContextCondition,
99
ecmascript::EcmascriptModuleAssetVc,
1010
module_options::{
11-
EmotionTransformConfigVc, JsxTransformOptions, ModuleOptionsContext,
12-
ModuleOptionsContextVc, StyledComponentsTransformConfigVc,
11+
JsxTransformOptions, ModuleOptionsContext, ModuleOptionsContextVc,
12+
StyledComponentsTransformConfigVc,
1313
},
1414
resolve_options_context::{ResolveOptionsContext, ResolveOptionsContextVc},
1515
transition::TransitionsByNameVc,
@@ -35,6 +35,7 @@ use turbopack_dev_server::{
3535
html::DevHtmlAssetVc,
3636
source::{asset_graph::AssetGraphContentSourceVc, ContentSourceVc},
3737
};
38+
use turbopack_ecmascript_plugins::transform::emotion::EmotionTransformConfigVc;
3839
use turbopack_node::execution_context::ExecutionContextVc;
3940

4041
use crate::embed_js::embed_file_path;

crates/turbopack-cli/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ pub(crate) mod embed_js;
88
pub fn register() {
99
turbopack::register();
1010
turbopack_dev::register();
11+
turbopack_ecmascript_plugins::register();
1112
include!(concat!(env!("OUT_DIR"), "/register.rs"));
1213
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[package]
2+
name = "turbopack-ecmascript-plugins"
3+
version = "0.1.0"
4+
description = "TBD"
5+
license = "MPL-2.0"
6+
edition = "2021"
7+
autobenches = false
8+
9+
[lib]
10+
bench = false
11+
12+
[features]
13+
transform_emotion = []
14+
15+
[dependencies]
16+
anyhow = { workspace = true }
17+
serde = { workspace = true }
18+
19+
turbo-tasks = { workspace = true }
20+
turbopack-ecmascript = { workspace = true }
21+
22+
swc_core = { workspace = true, features = ["ecma_ast", "ecma_visit", "common"] }
23+
swc_emotion = { workspace = true }
24+
25+
[build-dependencies]
26+
turbo-tasks-build = { workspace = true }
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
use turbo_tasks_build::generate_register;
2+
3+
fn main() {
4+
generate_register();
5+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pub mod transform;
2+
3+
pub fn register() {
4+
turbo_tasks::register();
5+
turbopack_ecmascript::register();
6+
include!(concat!(env!("OUT_DIR"), "/register.rs"));
7+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#![allow(unused)]
2+
use std::{
3+
hash::{Hash, Hasher},
4+
path::Path,
5+
};
6+
7+
use anyhow::Result;
8+
use serde::{Deserialize, Serialize};
9+
use swc_core::{
10+
common::util::take::Take,
11+
ecma::{
12+
ast::{Module, Program},
13+
visit::FoldWith,
14+
},
15+
};
16+
use turbo_tasks::trace::TraceRawVcs;
17+
use turbopack_ecmascript::{CustomTransformer, TransformContext};
18+
19+
#[derive(Clone, PartialEq, Eq, Debug, TraceRawVcs, Serialize, Deserialize)]
20+
#[serde(rename_all = "kebab-case")]
21+
pub enum EmotionLabelKind {
22+
DevOnly,
23+
Always,
24+
Never,
25+
}
26+
27+
#[turbo_tasks::value(transparent)]
28+
pub struct OptionEmotionTransformConfig(Option<EmotionTransformConfigVc>);
29+
30+
//[TODO]: need to support importmap, there are type mismatch between
31+
//next.config.js to swc's emotion options
32+
#[turbo_tasks::value(shared)]
33+
#[derive(Default, Clone, Debug)]
34+
#[serde(rename_all = "camelCase")]
35+
pub struct EmotionTransformConfig {
36+
pub sourcemap: Option<bool>,
37+
pub label_format: Option<String>,
38+
pub auto_label: Option<EmotionLabelKind>,
39+
}
40+
41+
#[turbo_tasks::value_impl]
42+
impl EmotionTransformConfigVc {
43+
#[turbo_tasks::function]
44+
pub fn default() -> Self {
45+
Self::cell(Default::default())
46+
}
47+
}
48+
49+
impl Default for EmotionTransformConfigVc {
50+
fn default() -> Self {
51+
Self::default()
52+
}
53+
}
54+
55+
#[derive(Debug)]
56+
pub struct EmotionTransformer {
57+
#[cfg(feature = "transform_emotion")]
58+
config: swc_emotion::EmotionOptions,
59+
}
60+
61+
#[cfg(feature = "transform_emotion")]
62+
impl EmotionTransformer {
63+
pub fn new(config: &EmotionTransformConfig) -> Option<Self> {
64+
let config = swc_emotion::EmotionOptions {
65+
// When you create a transformer structure, it is assumed that you are performing an
66+
// emotion transform.
67+
enabled: Some(true),
68+
sourcemap: config.sourcemap,
69+
label_format: config.label_format.clone(),
70+
auto_label: if let Some(auto_label) = config.auto_label.as_ref() {
71+
match auto_label {
72+
EmotionLabelKind::Always => Some(true),
73+
EmotionLabelKind::Never => Some(false),
74+
// [TODO]: this is not correct coerece, need to be fixed
75+
EmotionLabelKind::DevOnly => None,
76+
}
77+
} else {
78+
None
79+
},
80+
..Default::default()
81+
};
82+
83+
Some(EmotionTransformer { config })
84+
}
85+
}
86+
87+
#[cfg(not(feature = "transform_emotion"))]
88+
impl EmotionTransformer {
89+
pub fn new(_config: &EmotionTransformConfig) -> Option<Self> {
90+
None
91+
}
92+
}
93+
94+
impl CustomTransformer for EmotionTransformer {
95+
fn transform(&self, program: &mut Program, ctx: &TransformContext<'_>) -> Option<Program> {
96+
#[cfg(feature = "transform_emotion")]
97+
{
98+
let p = std::mem::replace(program, Program::Module(Module::dummy()));
99+
let hash = {
100+
#[allow(clippy::disallowed_types)]
101+
let mut hasher = std::collections::hash_map::DefaultHasher::new();
102+
p.hash(&mut hasher);
103+
hasher.finish()
104+
};
105+
*program = p.fold_with(&mut swc_emotion::emotion(
106+
self.config.clone(),
107+
Path::new(ctx.file_name_str),
108+
hash as u32,
109+
ctx.source_map.clone(),
110+
ctx.comments.clone(),
111+
));
112+
}
113+
114+
None
115+
}
116+
}
117+
118+
pub async fn build_emotion_transformer(
119+
config: &Option<EmotionTransformConfigVc>,
120+
) -> Result<Option<Box<EmotionTransformer>>> {
121+
Ok(if let Some(config) = config {
122+
EmotionTransformer::new(&*config.await?).map(Box::new)
123+
} else {
124+
None
125+
})
126+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod emotion;

crates/turbopack-ecmascript/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ use swc_core::{
4343
},
4444
};
4545
pub use transform::{
46-
CustomTransform, CustomTransformVc, CustomTransformer, EcmascriptInputTransform,
47-
EcmascriptInputTransformsVc, TransformContext,
46+
CustomTransformer, EcmascriptInputTransform, EcmascriptInputTransformsVc, TransformContext,
47+
TransformPlugin, TransformPluginVc,
4848
};
4949
use turbo_tasks::{
5050
primitives::StringVc, trace::TraceRawVcs, RawVc, ReadRef, TryJoinIterExt, Value, ValueToString,

crates/turbopack-ecmascript/src/transform/mod.rs

Lines changed: 8 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
mod server_to_client_proxy;
22
mod util;
33

4-
use std::{
5-
collections::hash_map::DefaultHasher,
6-
fmt::Debug,
7-
hash::{Hash, Hasher},
8-
path::{Path, PathBuf},
9-
sync::Arc,
10-
};
4+
use std::{fmt::Debug, hash::Hash, path::PathBuf, sync::Arc};
115

126
use anyhow::Result;
137
use swc_core::{
@@ -43,13 +37,7 @@ pub enum EcmascriptInputTransform {
4337
ClientDirective(StringVc),
4438
ServerDirective(StringVc),
4539
CommonJs,
46-
Custom(CustomTransformVc),
47-
Emotion {
48-
#[serde(default)]
49-
sourcemap: bool,
50-
label_format: OptionStringVc,
51-
auto_label: Option<bool>,
52-
},
40+
Plugin(TransformPluginVc),
5341
PresetEnv(EnvironmentVc),
5442
React {
5543
#[serde(default)]
@@ -93,7 +81,7 @@ pub trait CustomTransformer: Debug {
9381
fn transform(&self, program: &mut Program, ctx: &TransformContext<'_>) -> Option<Program>;
9482
}
9583

96-
/// A wrapper around a CustomTransformer instance, allowing it to operate with
84+
/// A wrapper around a TransformPlugin instance, allowing it to operate with
9785
/// the turbo_task caching requirements.
9886
#[turbo_tasks::value(
9987
transparent,
@@ -103,9 +91,9 @@ pub trait CustomTransformer: Debug {
10391
cell = "new"
10492
)]
10593
#[derive(Debug)]
106-
pub struct CustomTransform(#[turbo_tasks(trace_ignore)] Box<dyn CustomTransformer + Send + Sync>);
94+
pub struct TransformPlugin(#[turbo_tasks(trace_ignore)] Box<dyn CustomTransformer + Send + Sync>);
10795

108-
impl CustomTransformer for CustomTransform {
96+
impl CustomTransformer for TransformPlugin {
10997
fn transform(&self, program: &mut Program, ctx: &TransformContext<'_>) -> Option<Program> {
11098
self.0.transform(program, ctx)
11199
}
@@ -211,35 +199,6 @@ impl EcmascriptInputTransform {
211199
Some(comments.clone()),
212200
));
213201
}
214-
EcmascriptInputTransform::Emotion {
215-
sourcemap,
216-
label_format,
217-
auto_label,
218-
} => {
219-
let options = swc_emotion::EmotionOptions {
220-
// this should be always enabled if match arrives here:
221-
// since moduleoptions expect to push emotion transform only if
222-
// there are valid, enabled config values.
223-
enabled: Some(true),
224-
sourcemap: Some(*sourcemap),
225-
label_format: label_format.await?.clone_value(),
226-
auto_label: *auto_label,
227-
..Default::default()
228-
};
229-
let p = std::mem::replace(program, Program::Module(Module::dummy()));
230-
let hash = {
231-
let mut hasher = DefaultHasher::new();
232-
p.hash(&mut hasher);
233-
hasher.finish()
234-
};
235-
*program = p.fold_with(&mut swc_emotion::emotion(
236-
options,
237-
Path::new(file_name_str),
238-
hash as u32,
239-
source_map.clone(),
240-
comments.clone(),
241-
))
242-
}
243202
EcmascriptInputTransform::PresetEnv(env) => {
244203
let versions = env.runtime_versions().await?;
245204
let config = swc_core::ecma::preset_env::Config {
@@ -283,14 +242,14 @@ impl EcmascriptInputTransform {
283242
}
284243

285244
let top_level_import_paths = &*top_level_import_paths.await?;
286-
if top_level_import_paths.len() > 0 {
245+
if !top_level_import_paths.is_empty() {
287246
options.top_level_import_paths = top_level_import_paths
288247
.iter()
289248
.map(|s| JsWord::from(s.clone()))
290249
.collect();
291250
}
292251
let meaningless_file_names = &*meaningless_file_names.await?;
293-
if meaningless_file_names.len() > 0 {
252+
if !meaningless_file_names.is_empty() {
294253
options.meaningless_file_names = meaningless_file_names.clone();
295254
}
296255

@@ -361,7 +320,7 @@ impl EcmascriptInputTransform {
361320
.emit();
362321
}
363322
}
364-
EcmascriptInputTransform::Custom(transform) => {
323+
EcmascriptInputTransform::Plugin(transform) => {
365324
if let Some(output) = transform.await?.transform(program, ctx) {
366325
*program = output;
367326
}

crates/turbopack-tests/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ turbo-tasks-fs = { workspace = true }
2626
turbo-tasks-memory = { workspace = true }
2727
turbopack-core = { workspace = true, features = ["issue_path"] }
2828
turbopack-dev = { workspace = true }
29+
turbopack-ecmascript-plugins = { workspace = true, features = [
30+
"transform_emotion",
31+
] }
2932
turbopack-env = { workspace = true }
3033
turbopack-test-utils = { workspace = true }
3134

crates/turbopack-tests/tests/snapshot.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use turbopack::{
2121
condition::ContextCondition,
2222
ecmascript::EcmascriptModuleAssetVc,
2323
module_options::{
24-
EmotionTransformConfig, JsxTransformOptions, JsxTransformOptionsVc, ModuleOptionsContext,
24+
JsxTransformOptions, JsxTransformOptionsVc, ModuleOptionsContext,
2525
StyledComponentsTransformConfigVc,
2626
},
2727
resolve_options_context::ResolveOptionsContext,
@@ -43,12 +43,14 @@ use turbopack_core::{
4343
source_asset::SourceAssetVc,
4444
};
4545
use turbopack_dev::DevChunkingContextVc;
46+
use turbopack_ecmascript_plugins::transform::emotion::EmotionTransformConfig;
4647
use turbopack_env::ProcessEnvAssetVc;
4748
use turbopack_test_utils::snapshot::{diff, expected, matches_expected, snapshot_issues};
4849

4950
fn register() {
5051
turbopack::register();
5152
turbopack_dev::register();
53+
turbopack_ecmascript_plugins::register();
5254
include!(concat!(env!("OUT_DIR"), "/register_test_snapshot.rs"));
5355
}
5456

0 commit comments

Comments
 (0)