Skip to content

Commit 4ada314

Browse files
authored
Add auto-commonjs and update swc (#30661)
Closes #30596 ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `yarn lint` - This patch contains several patches from swc. This includes swc-project/swc#2581, which allows customizing the import path for regenerator. - This adds auto-detection of common js. If `module.exports` is found and module config is not set, module config becomes common js. - As bonus, this includes some performance improvements The logic for analyzing the input source file and parsing options as json is moved from the js thread to a background worker thread.
1 parent d8cb8c5 commit 4ada314

File tree

9 files changed

+191
-62
lines changed

9 files changed

+191
-62
lines changed

packages/next/build/swc/Cargo.lock

Lines changed: 32 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/next/build/swc/Cargo.toml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ path-clean = "0.1"
1717
regex = "1.5"
1818
serde = "1"
1919
serde_json = "1"
20-
swc = "0.79.0"
20+
swc = "0.80.0"
2121
swc_atoms = "0.2.7"
2222
swc_common = { version = "0.14.2", features = ["concurrent", "sourcemap"] }
2323
swc_css = "0.20.0"
24-
swc_ecmascript = { version = "0.82.0", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] }
25-
swc_ecma_preset_env = "0.61.0"
24+
swc_ecmascript = { version = "0.83.0", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] }
25+
swc_ecma_preset_env = "0.62.0"
2626
swc_node_base = "0.5.1"
2727
swc_stylis = "0.17.0"
2828
fxhash = "0.2.1"
@@ -36,9 +36,9 @@ tracing = { version = "0.1.28", features = ["release_max_level_off"] }
3636
napi-build = "1"
3737

3838
[dev-dependencies]
39-
swc_ecma_transforms_testing = "0.42.0"
40-
testing = "0.15.0"
39+
swc_ecma_transforms_testing = "0.42.1"
40+
testing = "0.15.1"
4141
walkdir = "2.3.2"
4242

4343
[profile.release]
44-
# lto = true
44+
lto = true
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
use swc_common::DUMMY_SP;
2+
use swc_ecmascript::{
3+
ast::*,
4+
visit::{Node, Visit, VisitWith},
5+
};
6+
7+
pub(crate) fn contains_cjs(m: &Module) -> bool {
8+
let mut v = CjsFinder::default();
9+
m.visit_with(&Invalid { span: DUMMY_SP }, &mut v);
10+
v.found
11+
}
12+
13+
#[derive(Copy, Clone, Default)]
14+
struct CjsFinder {
15+
found: bool,
16+
}
17+
18+
/// This visitor implementation supports typescript, because the api of `swc`
19+
/// does not support changing configuration based on content of the file.
20+
impl Visit for CjsFinder {
21+
fn visit_member_expr(&mut self, e: &MemberExpr, _: &dyn Node) {
22+
if !e.computed {
23+
match &e.obj {
24+
ExprOrSuper::Super(_) => {}
25+
ExprOrSuper::Expr(obj) => match &**obj {
26+
Expr::Ident(obj) => match &*e.prop {
27+
Expr::Ident(prop) => {
28+
if &*obj.sym == "module" && &*prop.sym == "exports" {
29+
self.found = true;
30+
return;
31+
}
32+
}
33+
_ => {}
34+
},
35+
_ => {}
36+
},
37+
}
38+
}
39+
40+
e.obj.visit_with(e, self);
41+
42+
if e.computed {
43+
e.prop.visit_with(e, self);
44+
}
45+
}
46+
}

packages/next/build/swc/src/lib.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,22 @@ extern crate napi_derive;
3434
/// Explicit extern crate to use allocator.
3535
extern crate swc_node_base;
3636

37+
use auto_cjs::contains_cjs;
3738
use backtrace::Backtrace;
3839
use napi::{CallContext, Env, JsObject, JsUndefined};
3940
use serde::Deserialize;
4041
use std::{env, panic::set_hook, path::PathBuf, sync::Arc};
41-
use swc::{Compiler, TransformOutput};
42+
use swc::{config::ModuleConfig, Compiler, TransformOutput};
43+
use swc_common::SourceFile;
4244
use swc_common::{self, chain, pass::Optional, sync::Lazy, FileName, FilePathMapping, SourceMap};
43-
use swc_ecmascript::visit::Fold;
45+
use swc_ecmascript::ast::EsVersion;
46+
use swc_ecmascript::{
47+
parser::{lexer::Lexer, Parser, StringInput},
48+
visit::Fold,
49+
};
4450

4551
pub mod amp_attributes;
52+
mod auto_cjs;
4653
pub mod hook_optimizer;
4754
pub mod minify;
4855
pub mod next_dynamic;
@@ -52,7 +59,7 @@ pub mod styled_jsx;
5259
mod transform;
5360
mod util;
5461

55-
#[derive(Debug, Deserialize)]
62+
#[derive(Clone, Debug, Deserialize)]
5663
#[serde(rename_all = "camelCase")]
5764
pub struct TransformOptions {
5865
#[serde(flatten)]
@@ -126,4 +133,27 @@ pub fn complete_output(env: &Env, output: TransformOutput) -> napi::Result<JsObj
126133
env.to_js_value(&output)?.coerce_to_object()
127134
}
128135

136+
impl TransformOptions {
137+
pub fn patch(mut self, fm: &SourceFile) -> Self {
138+
self.swc.swcrc = false;
139+
140+
let should_enable_commonjs =
141+
self.swc.config.module.is_none() && fm.src.contains("module.exports") && {
142+
let syntax = self.swc.config.jsc.syntax.unwrap_or_default();
143+
let target = self.swc.config.jsc.target.unwrap_or(EsVersion::latest());
144+
let lexer = Lexer::new(syntax, target, StringInput::from(&*fm), None);
145+
let mut p = Parser::new_from(lexer);
146+
p.parse_module()
147+
.map(|m| contains_cjs(&m))
148+
.unwrap_or_default()
149+
};
150+
151+
if should_enable_commonjs {
152+
self.swc.config.module = Some(ModuleConfig::CommonJs(Default::default()));
153+
}
154+
155+
self
156+
}
157+
}
158+
129159
pub type ArcCompiler = Arc<Compiler>;

packages/next/build/swc/src/transform.rs

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ DEALINGS IN THE SOFTWARE.
2828

2929
use crate::{
3030
complete_output, custom_before_pass, get_compiler,
31-
util::{CtxtExt, MapErr},
31+
util::{deserialize_json, CtxtExt, MapErr},
3232
TransformOptions,
3333
};
3434
use anyhow::{anyhow, Context as _, Error};
@@ -46,13 +46,13 @@ use swc_ecmascript::transforms::pass::noop;
4646
#[derive(Debug)]
4747
pub enum Input {
4848
/// Raw source code.
49-
Source(Arc<SourceFile>),
49+
Source { src: String },
5050
}
5151

5252
pub struct TransformTask {
5353
pub c: Arc<Compiler>,
5454
pub input: Input,
55-
pub options: TransformOptions,
55+
pub options: String,
5656
}
5757

5858
impl Task for TransformTask {
@@ -62,13 +62,25 @@ impl Task for TransformTask {
6262
fn compute(&mut self) -> napi::Result<Self::Output> {
6363
let res = catch_unwind(AssertUnwindSafe(|| {
6464
try_with_handler(self.c.cm.clone(), true, |handler| {
65-
self.c.run(|| match self.input {
66-
Input::Source(ref s) => {
67-
let before_pass = custom_before_pass(&s.name, &self.options);
65+
self.c.run(|| match &self.input {
66+
Input::Source { src } => {
67+
let options: TransformOptions = deserialize_json(&self.options)?;
68+
69+
let filename = if options.swc.filename.is_empty() {
70+
FileName::Anon
71+
} else {
72+
FileName::Real(options.swc.filename.clone().into())
73+
};
74+
75+
let fm = self.c.cm.new_source_file(filename, src.to_string());
76+
77+
let options = options.patch(&fm);
78+
79+
let before_pass = custom_before_pass(&fm.name, &options);
6880
self.c.process_js_with_custom_pass(
69-
s.clone(),
81+
fm.clone(),
7082
&handler,
71-
&self.options.swc,
83+
&options.swc,
7284
before_pass,
7385
noop(),
7486
)
@@ -101,15 +113,15 @@ impl Task for TransformTask {
101113
/// returns `compiler, (src / path), options, plugin, callback`
102114
pub fn schedule_transform<F>(cx: CallContext, op: F) -> napi::Result<JsObject>
103115
where
104-
F: FnOnce(&Arc<Compiler>, String, bool, TransformOptions) -> TransformTask,
116+
F: FnOnce(&Arc<Compiler>, String, bool, String) -> TransformTask,
105117
{
106118
let c = get_compiler(&cx);
107119

108-
let s = cx.get::<JsString>(0)?.into_utf8()?.as_str()?.to_owned();
120+
let src = cx.get::<JsString>(0)?.into_utf8()?.as_str()?.to_owned();
109121
let is_module = cx.get::<JsBoolean>(1)?;
110-
let options: TransformOptions = cx.get_deserialized(2)?;
122+
let options = cx.get_buffer_as_string(2)?;
111123

112-
let task = op(&c, s, is_module.get_value()?, options);
124+
let task = op(&c, src, is_module.get_value()?, options);
113125

114126
cx.env.spawn(task).map(|t| t.promise_object())
115127
}
@@ -145,17 +157,8 @@ where
145157

146158
#[js_function(4)]
147159
pub fn transform(cx: CallContext) -> napi::Result<JsObject> {
148-
schedule_transform(cx, |c, src, _, mut options| {
149-
options.swc.swcrc = false;
150-
151-
let input = Input::Source(c.cm.new_source_file(
152-
if options.swc.filename.is_empty() {
153-
FileName::Anon
154-
} else {
155-
FileName::Real(options.swc.filename.clone().into())
156-
},
157-
src,
158-
));
160+
schedule_transform(cx, |c, src, _, options| {
161+
let input = Input::Source { src };
159162

160163
TransformTask {
161164
c: c.clone(),

0 commit comments

Comments
 (0)