-
Notifications
You must be signed in to change notification settings - Fork 26.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add next dynamic swc transform (#27745)
Ported `packages/next/build/babel/plugins/react-loadable-plugin.ts` to swc
- Loading branch information
Showing
13 changed files
with
307 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
use swc_atoms::js_word; | ||
use swc_common::{FileName, DUMMY_SP}; | ||
use swc_ecmascript::ast::{ | ||
ArrayLit, ArrowExpr, BinExpr, BinaryOp, BlockStmtOrExpr, CallExpr, Expr, ExprOrSpread, | ||
ExprOrSuper, Ident, ImportDecl, ImportSpecifier, KeyValueProp, Lit, MemberExpr, ObjectLit, Prop, | ||
PropName, PropOrSpread, Str, StrKind, | ||
}; | ||
use swc_ecmascript::utils::{ | ||
ident::{Id, IdentLike}, | ||
HANDLER, | ||
}; | ||
use swc_ecmascript::visit::{Fold, FoldWith}; | ||
|
||
pub fn next_dynamic(filename: FileName) -> impl Fold { | ||
NextDynamicPatcher { | ||
filename, | ||
dynamic_bindings: vec![], | ||
} | ||
} | ||
|
||
#[derive(Debug)] | ||
struct NextDynamicPatcher { | ||
filename: FileName, | ||
dynamic_bindings: Vec<Id>, | ||
} | ||
|
||
impl Fold for NextDynamicPatcher { | ||
fn fold_import_decl(&mut self, decl: ImportDecl) -> ImportDecl { | ||
let ImportDecl { | ||
ref src, | ||
ref specifiers, | ||
.. | ||
} = decl; | ||
if &src.value == "next/dynamic" { | ||
for specifier in specifiers { | ||
if let ImportSpecifier::Default(default_specifier) = specifier { | ||
self.dynamic_bindings.push(default_specifier.local.to_id()); | ||
} | ||
} | ||
} | ||
|
||
decl | ||
} | ||
|
||
fn fold_call_expr(&mut self, expr: CallExpr) -> CallExpr { | ||
let mut expr = expr.fold_children_with(self); | ||
if let ExprOrSuper::Expr(i) = &expr.callee { | ||
if let Expr::Ident(identifier) = &**i { | ||
if self.dynamic_bindings.contains(&identifier.to_id()) { | ||
if expr.args.len() == 0 { | ||
HANDLER.with(|handler| { | ||
handler | ||
.struct_span_err( | ||
identifier.span, | ||
"next/dynamic requires at least one argument", | ||
) | ||
.emit() | ||
}); | ||
} else if expr.args.len() > 2 { | ||
HANDLER.with(|handler| { | ||
handler | ||
.struct_span_err(identifier.span, "next/dynamic only accepts 2 arguments") | ||
.emit() | ||
}); | ||
} | ||
|
||
let mut import_specifier = None; | ||
if let Expr::Arrow(ArrowExpr { | ||
body: BlockStmtOrExpr::Expr(e), | ||
.. | ||
}) = &*expr.args[0].expr | ||
{ | ||
if let Expr::Call(CallExpr { | ||
args: a, callee, .. | ||
}) = &**e | ||
{ | ||
if let ExprOrSuper::Expr(e) = callee { | ||
if let Expr::Ident(Ident { sym, .. }) = &**e { | ||
if sym == "import" { | ||
if a.len() == 0 { | ||
// Do nothing, import_specifier will remain None | ||
// triggering error below | ||
} else if let Expr::Lit(Lit::Str(Str { value, .. })) = &*a[0].expr { | ||
import_specifier = Some(value.clone()); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
if let None = import_specifier { | ||
HANDLER.with(|handler| { | ||
handler | ||
.struct_span_err( | ||
identifier.span, | ||
"First argument for next/dynamic must be an arrow function returning a valid \ | ||
dynamic import call e.g. `dynamic(() => import('../some-component'))`", | ||
) | ||
.emit() | ||
}); | ||
} | ||
|
||
// loadableGenerated: { | ||
// webpack: () => [require.resolveWeak('../components/hello')], | ||
// modules: | ||
// ["/project/src/file-being-transformed.js -> " + '../components/hello'] } | ||
let generated = Box::new(Expr::Object(ObjectLit { | ||
span: DUMMY_SP, | ||
props: vec![ | ||
PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp { | ||
key: PropName::Ident(Ident::new("webpack".into(), DUMMY_SP)), | ||
value: Box::new(Expr::Arrow(ArrowExpr { | ||
params: vec![], | ||
body: BlockStmtOrExpr::Expr(Box::new(Expr::Array(ArrayLit { | ||
elems: vec![Some(ExprOrSpread { | ||
expr: Box::new(Expr::Call(CallExpr { | ||
callee: ExprOrSuper::Expr(Box::new(Expr::Member(MemberExpr { | ||
obj: ExprOrSuper::Expr(Box::new(Expr::Ident(Ident { | ||
sym: js_word!("require"), | ||
span: DUMMY_SP, | ||
optional: false, | ||
}))), | ||
prop: Box::new(Expr::Ident(Ident { | ||
sym: "resolveWeak".into(), | ||
span: DUMMY_SP, | ||
optional: false, | ||
})), | ||
computed: false, | ||
span: DUMMY_SP, | ||
}))), | ||
args: vec![ExprOrSpread { | ||
expr: Box::new(Expr::Lit(Lit::Str(Str { | ||
value: self.filename.to_string().into(), | ||
span: DUMMY_SP, | ||
kind: StrKind::Synthesized {}, | ||
has_escape: false, | ||
}))), | ||
spread: None, | ||
}], | ||
span: DUMMY_SP, | ||
type_args: None, | ||
})), | ||
spread: None, | ||
})], | ||
span: DUMMY_SP, | ||
}))), | ||
is_async: false, | ||
is_generator: false, | ||
span: DUMMY_SP, | ||
return_type: None, | ||
type_params: None, | ||
})), | ||
}))), | ||
PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp { | ||
key: PropName::Ident(Ident::new("modules".into(), DUMMY_SP)), | ||
value: Box::new(Expr::Array(ArrayLit { | ||
elems: vec![Some(ExprOrSpread { | ||
expr: Box::new(Expr::Bin(BinExpr { | ||
span: DUMMY_SP, | ||
op: BinaryOp::Add, | ||
left: Box::new(Expr::Lit(Lit::Str(Str { | ||
value: format!("{} -> ", self.filename).into(), | ||
span: DUMMY_SP, | ||
kind: StrKind::Synthesized {}, | ||
has_escape: false, | ||
}))), | ||
right: Box::new(Expr::Lit(Lit::Str(Str { | ||
value: import_specifier.unwrap(), | ||
span: DUMMY_SP, | ||
kind: StrKind::Normal { | ||
contains_quote: false, | ||
}, | ||
has_escape: false, | ||
}))), | ||
})), | ||
spread: None, | ||
})], | ||
span: DUMMY_SP, | ||
})), | ||
}))), | ||
], | ||
})); | ||
|
||
let mut props = vec![PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp { | ||
key: PropName::Ident(Ident::new("loadableGenerated".into(), DUMMY_SP)), | ||
value: generated, | ||
})))]; | ||
|
||
if expr.args.len() == 2 { | ||
if let Expr::Object(ObjectLit { | ||
props: options_props, | ||
.. | ||
}) = &*expr.args[1].expr | ||
{ | ||
props.extend(options_props.iter().cloned()); | ||
} | ||
} | ||
|
||
let second_arg = ExprOrSpread { | ||
spread: None, | ||
expr: Box::new(Expr::Object(ObjectLit { | ||
span: DUMMY_SP, | ||
props, | ||
})), | ||
}; | ||
|
||
expr.args.push(second_arg); | ||
} | ||
} | ||
} | ||
expr | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5 changes: 5 additions & 0 deletions
5
packages/next/build/swc/tests/fixture/next-dynamic/duplicated-imports/input.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import dynamic1 from 'next/dynamic' | ||
import dynamic2 from 'next/dynamic' | ||
|
||
const DynamicComponent1 = dynamic1(() => import('../components/hello1')) | ||
const DynamicComponent2 = dynamic2(() => import('../components/hello2')) |
14 changes: 14 additions & 0 deletions
14
packages/next/build/swc/tests/fixture/next-dynamic/duplicated-imports/output.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import dynamic1 from 'next/dynamic' | ||
import dynamic2 from 'next/dynamic' | ||
const DynamicComponent1 = dynamic1(() => import('../components/hello1'), { | ||
loadableGenerated: { | ||
webpack: () => [require.resolveWeak('/some-project/src/some-file.js')], | ||
modules: ['/some-project/src/some-file.js -> ' + '../components/hello1'], | ||
}, | ||
}) | ||
const DynamicComponent2 = dynamic2(() => import('../components/hello2'), { | ||
loadableGenerated: { | ||
webpack: () => [require.resolveWeak('/some-project/src/some-file.js')], | ||
modules: ['/some-project/src/some-file.js -> ' + '../components/hello2'], | ||
}, | ||
}) |
5 changes: 5 additions & 0 deletions
5
packages/next/build/swc/tests/fixture/next-dynamic/member-with-same-name/input.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import dynamic from 'next/dynamic' | ||
import somethingElse from 'something-else' | ||
|
||
const DynamicComponent = dynamic(() => import('../components/hello')) | ||
somethingElse.dynamic('should not be transformed') |
10 changes: 10 additions & 0 deletions
10
packages/next/build/swc/tests/fixture/next-dynamic/member-with-same-name/output.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import dynamic from 'next/dynamic' | ||
import somethingElse from 'something-else' | ||
|
||
const DynamicComponent = dynamic(() => import('../components/hello'), { | ||
loadableGenerated: { | ||
webpack: () => [require.resolveWeak('/some-project/src/some-file.js')], | ||
modules: ['/some-project/src/some-file.js -> ' + '../components/hello'], | ||
}, | ||
}) | ||
somethingElse.dynamic('should not be transformed') |
3 changes: 3 additions & 0 deletions
3
packages/next/build/swc/tests/fixture/next-dynamic/no-options/input.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import dynamic from 'next/dynamic' | ||
|
||
const DynamicComponent = dynamic(() => import('../components/hello')) |
8 changes: 8 additions & 0 deletions
8
packages/next/build/swc/tests/fixture/next-dynamic/no-options/output.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import dynamic from 'next/dynamic' | ||
|
||
const DynamicComponent = dynamic(() => import('../components/hello'), { | ||
loadableGenerated: { | ||
webpack: () => [require.resolveWeak('/some-project/src/some-file.js')], | ||
modules: ['/some-project/src/some-file.js -> ' + '../components/hello'], | ||
}, | ||
}) |
6 changes: 6 additions & 0 deletions
6
packages/next/build/swc/tests/fixture/next-dynamic/with-options/input.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import dynamic from 'next/dynamic' | ||
|
||
const DynamicComponentWithCustomLoading = dynamic( | ||
() => import('../components/hello'), | ||
{ loading: () => <p>...</p> } | ||
) |
13 changes: 13 additions & 0 deletions
13
packages/next/build/swc/tests/fixture/next-dynamic/with-options/output.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import dynamic from 'next/dynamic' | ||
|
||
const DynamicComponentWithCustomLoading = dynamic( | ||
() => import('../components/hello'), | ||
{ loading: () => <p>...</p> }, | ||
{ | ||
loadableGenerated: { | ||
webpack: () => [require.resolveWeak('/some-project/src/some-file.js')], | ||
modules: ['/some-project/src/some-file.js -> ' + '../components/hello'], | ||
}, | ||
loading: () => <p>...</p>, | ||
} | ||
) |
Binary file not shown.