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 https://github.com/swc-project/swc/pull/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.
This commit is contained in:
Donny/강동윤 2021-10-30 22:31:58 +09:00 committed by GitHub
parent d8cb8c5fbc
commit 4ada314663
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 191 additions and 62 deletions

View file

@ -469,6 +469,12 @@ dependencies = [
"libc",
]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "ident_case"
version = "1.0.1"
@ -1578,9 +1584,9 @@ checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
[[package]]
name = "swc"
version = "0.79.2"
version = "0.80.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c39e3a5230c137d91421ff1d71dae6a41c6a20934b19230dd586f15fbc6cffbb"
checksum = "bc6f880c108482b5ddbe859e4da2a50617ec3ae9984d2db9c9974cd37e8b515b"
dependencies = [
"ahash",
"anyhow",
@ -1831,9 +1837,9 @@ dependencies = [
[[package]]
name = "swc_ecma_minifier"
version = "0.46.7"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "352c358c3600520fd9d7f97523a3fed0e802219853d15acefbd2d0a7b5caaa66"
checksum = "2e4898ec9f84d9d9630d2dd2debf9d593824ddecc44c8f1e1a30988af8a78ee5"
dependencies = [
"ahash",
"indexmap",
@ -1880,9 +1886,9 @@ dependencies = [
[[package]]
name = "swc_ecma_preset_env"
version = "0.61.0"
version = "0.62.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d43efdeffd84fcd2edacbbf5abe6bbe5696d4098347d3796ed4d7ad2bfe2402c"
checksum = "fae8f62b754f17726a17339b08ead21121666a16bb916544f61837c6bba949d0"
dependencies = [
"ahash",
"dashmap",
@ -1904,9 +1910,9 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms"
version = "0.90.0"
version = "0.91.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6032d75bbca42aa6943867362c0136be74e4cf707f436358ca26bf5e1149bdc0"
checksum = "0cf60ed2f162385b466cd1d2238ae235d81d859d63024daf8fa157aadd35fdfe"
dependencies = [
"swc_atoms",
"swc_common",
@ -1959,9 +1965,9 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_compat"
version = "0.47.2"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "848a3df0611dd2c180a5c0265472b08583fae329dc6da2c79f4933be201940de"
checksum = "cbf954ba120a43c64062d92ce0a753b19e92817272a0cba12921041da105fa5c"
dependencies = [
"ahash",
"arrayvec",
@ -1996,9 +2002,9 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_module"
version = "0.53.0"
version = "0.54.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f3474868c30d28fdffe4ec9a009e775912076be7c8d5fd4b4c49c07e1e12786"
checksum = "a50df9d36d5622e563e1a6d8ecc4b264b5aeef031aa6cb9c631cb5495c3d0b16"
dependencies = [
"Inflector",
"ahash",
@ -2018,9 +2024,9 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_optimization"
version = "0.60.2"
version = "0.61.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5c1573805a8da8391c6d411a0485d345b1a9280236afc3663f392bab3d9a272"
checksum = "8ad9e812184d0dbef37e3600b5a599b6c9f2568ce67f048c961146dd33dd274d"
dependencies = [
"ahash",
"dashmap",
@ -2041,9 +2047,9 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_proposal"
version = "0.53.0"
version = "0.54.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc9e665a73f580b9ec31096a88660a1bf1c9df23bc6ae187d7ad3b0095bb9b73"
checksum = "742878426185a4365b8bcc8333116a6abf0d0417a98db1fd5817ff0a34276d14"
dependencies = [
"either",
"serde",
@ -2061,9 +2067,9 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_react"
version = "0.55.0"
version = "0.56.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fc5b294bcc167b2d72943ebcbb6a9d27d6a46840e6eb2b4f24295cc4254e259"
checksum = "5e9291e184940e23e17eb29d89899a95fa163f6f932b803f183932e5e80d3c07"
dependencies = [
"ahash",
"base64 0.13.0",
@ -2086,14 +2092,16 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_testing"
version = "0.42.0"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fead0df8b91a0f721790fa50104af1e6a262973090ace055adb6a9a962f2b0b1"
checksum = "4abe5a4d8f718627d2d5a9d96233d0292a2cd0a071efdfde0a14d7221f37a987"
dependencies = [
"ansi_term",
"anyhow",
"hex",
"serde",
"serde_json",
"sha-1",
"swc_common",
"swc_ecma_ast",
"swc_ecma_codegen",
@ -2107,9 +2115,9 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_typescript"
version = "0.56.1"
version = "0.57.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08e24e661be9fd9018ca10d6394db3e0f970c8c7fef6649740a1d96e54f8bc7d"
checksum = "d2698634fbfc7dbed01e516ef4610507e39dd81373de3b687d453e1b1e80f456"
dependencies = [
"serde",
"swc_atoms",
@ -2152,9 +2160,9 @@ dependencies = [
[[package]]
name = "swc_ecmascript"
version = "0.82.0"
version = "0.83.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf11dc8be7e4c7e33702fa92c1c2db4f505aa98b1995702f13fce7d05a74a18e"
checksum = "79dca2ffcbed1a4a5a0b90265bb4b484c7540437549dcf0c807c3f30d180a79f"
dependencies = [
"swc_ecma_ast",
"swc_ecma_codegen",

View file

@ -17,12 +17,12 @@ path-clean = "0.1"
regex = "1.5"
serde = "1"
serde_json = "1"
swc = "0.79.0"
swc = "0.80.0"
swc_atoms = "0.2.7"
swc_common = { version = "0.14.2", features = ["concurrent", "sourcemap"] }
swc_css = "0.20.0"
swc_ecmascript = { version = "0.82.0", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] }
swc_ecma_preset_env = "0.61.0"
swc_ecmascript = { version = "0.83.0", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] }
swc_ecma_preset_env = "0.62.0"
swc_node_base = "0.5.1"
swc_stylis = "0.17.0"
fxhash = "0.2.1"
@ -36,9 +36,9 @@ tracing = { version = "0.1.28", features = ["release_max_level_off"] }
napi-build = "1"
[dev-dependencies]
swc_ecma_transforms_testing = "0.42.0"
testing = "0.15.0"
swc_ecma_transforms_testing = "0.42.1"
testing = "0.15.1"
walkdir = "2.3.2"
[profile.release]
# lto = true
lto = true

View file

@ -0,0 +1,46 @@
use swc_common::DUMMY_SP;
use swc_ecmascript::{
ast::*,
visit::{Node, Visit, VisitWith},
};
pub(crate) fn contains_cjs(m: &Module) -> bool {
let mut v = CjsFinder::default();
m.visit_with(&Invalid { span: DUMMY_SP }, &mut v);
v.found
}
#[derive(Copy, Clone, Default)]
struct CjsFinder {
found: bool,
}
/// This visitor implementation supports typescript, because the api of `swc`
/// does not support changing configuration based on content of the file.
impl Visit for CjsFinder {
fn visit_member_expr(&mut self, e: &MemberExpr, _: &dyn Node) {
if !e.computed {
match &e.obj {
ExprOrSuper::Super(_) => {}
ExprOrSuper::Expr(obj) => match &**obj {
Expr::Ident(obj) => match &*e.prop {
Expr::Ident(prop) => {
if &*obj.sym == "module" && &*prop.sym == "exports" {
self.found = true;
return;
}
}
_ => {}
},
_ => {}
},
}
}
e.obj.visit_with(e, self);
if e.computed {
e.prop.visit_with(e, self);
}
}
}

View file

@ -34,15 +34,22 @@ extern crate napi_derive;
/// Explicit extern crate to use allocator.
extern crate swc_node_base;
use auto_cjs::contains_cjs;
use backtrace::Backtrace;
use napi::{CallContext, Env, JsObject, JsUndefined};
use serde::Deserialize;
use std::{env, panic::set_hook, path::PathBuf, sync::Arc};
use swc::{Compiler, TransformOutput};
use swc::{config::ModuleConfig, Compiler, TransformOutput};
use swc_common::SourceFile;
use swc_common::{self, chain, pass::Optional, sync::Lazy, FileName, FilePathMapping, SourceMap};
use swc_ecmascript::visit::Fold;
use swc_ecmascript::ast::EsVersion;
use swc_ecmascript::{
parser::{lexer::Lexer, Parser, StringInput},
visit::Fold,
};
pub mod amp_attributes;
mod auto_cjs;
pub mod hook_optimizer;
pub mod minify;
pub mod next_dynamic;
@ -52,7 +59,7 @@ pub mod styled_jsx;
mod transform;
mod util;
#[derive(Debug, Deserialize)]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransformOptions {
#[serde(flatten)]
@ -126,4 +133,27 @@ pub fn complete_output(env: &Env, output: TransformOutput) -> napi::Result<JsObj
env.to_js_value(&output)?.coerce_to_object()
}
impl TransformOptions {
pub fn patch(mut self, fm: &SourceFile) -> Self {
self.swc.swcrc = false;
let should_enable_commonjs =
self.swc.config.module.is_none() && fm.src.contains("module.exports") && {
let syntax = self.swc.config.jsc.syntax.unwrap_or_default();
let target = self.swc.config.jsc.target.unwrap_or(EsVersion::latest());
let lexer = Lexer::new(syntax, target, StringInput::from(&*fm), None);
let mut p = Parser::new_from(lexer);
p.parse_module()
.map(|m| contains_cjs(&m))
.unwrap_or_default()
};
if should_enable_commonjs {
self.swc.config.module = Some(ModuleConfig::CommonJs(Default::default()));
}
self
}
}
pub type ArcCompiler = Arc<Compiler>;

View file

@ -28,7 +28,7 @@ DEALINGS IN THE SOFTWARE.
use crate::{
complete_output, custom_before_pass, get_compiler,
util::{CtxtExt, MapErr},
util::{deserialize_json, CtxtExt, MapErr},
TransformOptions,
};
use anyhow::{anyhow, Context as _, Error};
@ -46,13 +46,13 @@ use swc_ecmascript::transforms::pass::noop;
#[derive(Debug)]
pub enum Input {
/// Raw source code.
Source(Arc<SourceFile>),
Source { src: String },
}
pub struct TransformTask {
pub c: Arc<Compiler>,
pub input: Input,
pub options: TransformOptions,
pub options: String,
}
impl Task for TransformTask {
@ -62,13 +62,25 @@ impl Task for TransformTask {
fn compute(&mut self) -> napi::Result<Self::Output> {
let res = catch_unwind(AssertUnwindSafe(|| {
try_with_handler(self.c.cm.clone(), true, |handler| {
self.c.run(|| match self.input {
Input::Source(ref s) => {
let before_pass = custom_before_pass(&s.name, &self.options);
self.c.run(|| match &self.input {
Input::Source { src } => {
let options: TransformOptions = deserialize_json(&self.options)?;
let filename = if options.swc.filename.is_empty() {
FileName::Anon
} else {
FileName::Real(options.swc.filename.clone().into())
};
let fm = self.c.cm.new_source_file(filename, src.to_string());
let options = options.patch(&fm);
let before_pass = custom_before_pass(&fm.name, &options);
self.c.process_js_with_custom_pass(
s.clone(),
fm.clone(),
&handler,
&self.options.swc,
&options.swc,
before_pass,
noop(),
)
@ -101,15 +113,15 @@ impl Task for TransformTask {
/// returns `compiler, (src / path), options, plugin, callback`
pub fn schedule_transform<F>(cx: CallContext, op: F) -> napi::Result<JsObject>
where
F: FnOnce(&Arc<Compiler>, String, bool, TransformOptions) -> TransformTask,
F: FnOnce(&Arc<Compiler>, String, bool, String) -> TransformTask,
{
let c = get_compiler(&cx);
let s = cx.get::<JsString>(0)?.into_utf8()?.as_str()?.to_owned();
let src = cx.get::<JsString>(0)?.into_utf8()?.as_str()?.to_owned();
let is_module = cx.get::<JsBoolean>(1)?;
let options: TransformOptions = cx.get_deserialized(2)?;
let options = cx.get_buffer_as_string(2)?;
let task = op(&c, s, is_module.get_value()?, options);
let task = op(&c, src, is_module.get_value()?, options);
cx.env.spawn(task).map(|t| t.promise_object())
}
@ -145,17 +157,8 @@ where
#[js_function(4)]
pub fn transform(cx: CallContext) -> napi::Result<JsObject> {
schedule_transform(cx, |c, src, _, mut options| {
options.swc.swcrc = false;
let input = Input::Source(c.cm.new_source_file(
if options.swc.filename.is_empty() {
FileName::Anon
} else {
FileName::Real(options.swc.filename.clone().into())
},
src,
));
schedule_transform(cx, |c, src, _, options| {
let input = Input::Source { src };
TransformTask {
c: c.clone(),

View file

@ -26,7 +26,7 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
use anyhow::Context;
use anyhow::{Context, Error};
use napi::{CallContext, JsBuffer, Status};
use serde::de::DeserializeOwned;
use std::any::type_name;
@ -41,6 +41,7 @@ pub trait MapErr<T>: Into<Result<T, anyhow::Error>> {
impl<T> MapErr<T> for Result<T, anyhow::Error> {}
pub trait CtxtExt {
fn get_buffer_as_string(&self, index: usize) -> napi::Result<String>;
/// Currently this uses JsBuffer
fn get_deserialized<T>(&self, index: usize) -> napi::Result<T>
where
@ -48,6 +49,12 @@ pub trait CtxtExt {
}
impl CtxtExt for CallContext<'_> {
fn get_buffer_as_string(&self, index: usize) -> napi::Result<String> {
let buffer = self.get::<JsBuffer>(index)?.into_value()?;
Ok(String::from_utf8_lossy(buffer.as_ref()).to_string())
}
fn get_deserialized<T>(&self, index: usize) -> napi::Result<T>
where
T: DeserializeOwned,
@ -67,3 +74,11 @@ impl CtxtExt for CallContext<'_> {
Ok(v)
}
}
pub(crate) fn deserialize_json<T>(s: &str) -> Result<T, Error>
where
T: DeserializeOwned,
{
serde_json::from_str(&s)
.with_context(|| format!("failed to deserialize as {}\nJSON: {}", type_name::<T>(), s))
}

View file

@ -1,4 +1,4 @@
use next_swc::custom_before_pass;
use next_swc::{custom_before_pass, TransformOptions};
use serde::de::DeserializeOwned;
use std::path::{Path, PathBuf};
use swc::Compiler;
@ -26,10 +26,9 @@ fn test(input: &Path, minify: bool) {
let c = Compiler::new(cm.clone());
let fm = cm.load_file(input).expect("failed to load file");
match c.process_js_with_custom_pass(
fm.clone(),
&handler,
&swc::config::Options {
let options = TransformOptions {
swc: swc::config::Options {
swcrc: true,
is_module: true,
output_path: Some(output.to_path_buf()),
@ -52,6 +51,19 @@ fn test(input: &Path, minify: bool) {
},
..Default::default()
},
disable_next_ssg: false,
disable_page_config: false,
pages_dir: None,
is_page_file: false,
is_development: true,
};
let options = options.patch(&fm);
match c.process_js_with_custom_pass(
fm.clone(),
&handler,
&options.swc,
custom_before_pass(&fm.name, &assert_json(&"{}")),
noop(),
) {

View file

@ -0,0 +1,7 @@
import mixed from 'esm';
console.log(mixed.foo);
module.exports = mixed;

View file

@ -0,0 +1,8 @@
"use strict";
var _esm = _interopRequireDefault(require("esm"));
function _interopRequireDefault(a) {
return a && a.__esModule ? a : {
default: a
};
}
console.log(_esm.default.foo), module.exports = _esm.default;