Implement custom Turbopack Next transformers (#47137)

Builds on https://github.com/vercel/turbo/pull/4202 to implement custom Next.js Transformers in Turbopack.

This is the final piece to moving the `next-*` crates to Next. While we've _technically_ moved everything, Turbopack didn't support running custom transformers. So we're actually stuck on the last version we cut before deleting the next crates, running the transformers that exist in the turbopack repo. With the new support, we're almost back to the tip of main branch (there's still some snafu with `swc_core` upgrading that I'm working on).

Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
This commit is contained in:
Justin Ridgewell 2023-03-15 15:10:59 -04:00 committed by GitHub
parent 4824d96fab
commit 02125cf3b1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 579 additions and 471 deletions

File diff suppressed because it is too large Load diff

View file

@ -8,9 +8,9 @@ members = [
"crates/next-core",
"crates/next-dev",
"crates/next-dev-tests",
# "crates/next-font",
# "crates/next-transform-dynamic",
# "crates/next-transform-strip-page-exports",
"crates/next-font",
"crates/next-transform-dynamic",
"crates/next-transform-strip-page-exports",
]
[profile.dev.package.swc_css_prefixer]
@ -26,55 +26,55 @@ lto = true
[workspace.dependencies]
# Workspace crates
next-binding = { path = "crates/next-binding" }
next-core = { path = "crates/next-core" }
next-core = { path = "crates/next-core", default-features = false }
next-dev = { path = "crates/next-dev" }
next-dev-tests = { path = "crates/next-dev-tests" }
# next-font = { path = "crates/next-font" }
# next-transform-dynamic = { path = "crates/next-transform-dynamic" }
# next-transform-strip-page-exports = { path = "crates/next-transform-strip-page-exports" }
next-font = { path = "crates/next-font" }
next-transform-dynamic = { path = "crates/next-transform-dynamic" }
next-transform-strip-page-exports = { path = "crates/next-transform-strip-page-exports" }
# SWC crates
# Keep consistent with preset_env_base through swc_core
browserslist-rs = { version = "0.12.2" }
mdxjs = { version = "0.1.6" }
modularize_imports = { version = "0.26.4" }
styled_components = { version = "0.53.4" }
styled_jsx = { version = "0.30.4" }
swc_core = { version = "0.59.26" }
swc_emotion = { version = "0.29.4" }
mdxjs = { version = "0.1.8" }
modularize_imports = { version = "0.26.10" }
styled_components = { version = "0.53.10" }
styled_jsx = { version = "0.30.10" }
swc_core = { version = "0.69.6" }
swc_emotion = { version = "0.29.10" }
testing = { version = "0.31.31" }
# Turbo crates
auto-hash-map = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
node-file-trace = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
swc-ast-explorer = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbo-malloc = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94", default-features = false }
turbo-tasks = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbo-tasks-build = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbo-tasks-env = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbo-tasks-fetch = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbo-tasks-fs = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbo-tasks-hash = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbo-tasks-macros = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbo-tasks-macros-shared = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbo-tasks-memory = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbo-tasks-testing = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbo-updater = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbopack = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbopack-cli-utils = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbopack-core = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbopack-create-test-app = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbopack-css = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbopack-dev-server = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbopack-ecmascript = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbopack-env = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbopack-json = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbopack-mdx = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbopack-node = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbopack-static = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbopack-swc-utils = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbopack-test-utils = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
turbopack-tests = { git = "https://github.com/vercel/turbo.git", rev = "8a8038f94" }
auto-hash-map = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
node-file-trace = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
swc-ast-explorer = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbo-malloc = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2", default-features = false }
turbo-tasks = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbo-tasks-build = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbo-tasks-env = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbo-tasks-fetch = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2", default-features = false }
turbo-tasks-fs = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbo-tasks-hash = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbo-tasks-macros = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbo-tasks-macros-shared = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbo-tasks-memory = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbo-tasks-testing = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbo-updater = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbopack = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbopack-cli-utils = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbopack-core = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbopack-create-test-app = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbopack-css = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbopack-dev-server = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbopack-ecmascript = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbopack-env = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbopack-json = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbopack-mdx = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbopack-node = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbopack-static = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbopack-swc-utils = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbopack-test-utils = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
turbopack-tests = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230315.2" }
# General Deps

View file

@ -179,44 +179,46 @@ impl Fold for NextDynamicPatcher {
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: Callee::Expr(Box::new(Expr::Member(
MemberExpr {
obj: Box::new(Expr::Ident(Ident {
sym: js_word!("require"),
body: Box::new(BlockStmtOrExpr::Expr(Box::new(Expr::Array(
ArrayLit {
elems: vec![Some(ExprOrSpread {
expr: Box::new(Expr::Call(CallExpr {
callee: Callee::Expr(Box::new(Expr::Member(
MemberExpr {
obj: Box::new(Expr::Ident(Ident {
sym: js_word!("require"),
span: DUMMY_SP,
optional: false,
})),
prop: MemberProp::Ident(Ident {
sym: "resolveWeak".into(),
span: DUMMY_SP,
optional: false,
}),
span: DUMMY_SP,
optional: false,
})),
prop: MemberProp::Ident(Ident {
sym: "resolveWeak".into(),
},
))),
args: vec![ExprOrSpread {
expr: Box::new(Expr::Lit(Lit::Str(Str {
value: self
.dynamically_imported_specifier
.as_ref()
.unwrap()
.clone()
.into(),
span: DUMMY_SP,
optional: false,
}),
span: DUMMY_SP,
},
))),
args: vec![ExprOrSpread {
expr: Box::new(Expr::Lit(Lit::Str(Str {
value: self
.dynamically_imported_specifier
.as_ref()
.unwrap()
.clone()
.into(),
span: DUMMY_SP,
raw: None,
}))),
spread: None,
}],
span: DUMMY_SP,
type_args: None,
})),
spread: None,
})],
span: DUMMY_SP,
}))),
raw: None,
}))),
spread: None,
}],
span: DUMMY_SP,
type_args: None,
})),
spread: None,
})],
span: DUMMY_SP,
},
)))),
is_async: false,
is_generator: false,
span: DUMMY_SP,

View file

@ -191,7 +191,7 @@ impl<C: Comments> ServerActions<C> {
);
if let Some(a) = arrow {
if let BlockStmtOrExpr::BlockStmt(block) = &mut a.body {
if let BlockStmtOrExpr::BlockStmt(block) = &mut *a.body {
block.visit_mut_with(&mut ClosureReplacer {
closure_arg: &closure_arg,
used_ids: &ids_from_closure,
@ -201,7 +201,7 @@ impl<C: Comments> ServerActions<C> {
let new_arrow = ArrowExpr {
span: DUMMY_SP,
params: a.params.clone(),
body: BlockStmtOrExpr::Expr(Box::new(Expr::Call(call))),
body: Box::new(BlockStmtOrExpr::Expr(Box::new(Expr::Call(call)))),
is_async: a.is_async,
is_generator: a.is_generator,
type_params: Default::default(),
@ -402,7 +402,7 @@ impl<C: Comments> VisitMut for ServerActions<C> {
// Arrow expressions need to be visited in prepass to determine if it's
// an action function or not.
let is_action_fn = self.get_action_info(
if let BlockStmtOrExpr::BlockStmt(block) = &mut a.body {
if let BlockStmtOrExpr::BlockStmt(block) = &mut *a.body {
Some(block)
} else {
None
@ -503,7 +503,7 @@ impl<C: Comments> VisitMut for ServerActions<C> {
if !self.in_action_file {
if let Expr::Arrow(a) = n {
let is_action_fn = self.get_action_info(
if let BlockStmtOrExpr::BlockStmt(block) = &mut a.body {
if let BlockStmtOrExpr::BlockStmt(block) = &mut *a.body {
Some(block)
} else {
None
@ -1194,7 +1194,7 @@ impl TryFrom<&'_ OptChainExpr> for Name {
type Error = ();
fn try_from(value: &OptChainExpr) -> Result<Self, Self::Error> {
match &value.base {
match &*value.base {
OptChainBase::Member(value) => match &value.prop {
MemberProp::Ident(prop) => {
let mut obj: Name = value.obj.as_ref().try_into()?;
@ -1223,11 +1223,11 @@ impl From<Name> for Expr {
expr = Expr::OptChain(OptChainExpr {
span: DUMMY_SP,
question_dot_token: DUMMY_SP,
base: OptChainBase::Member(MemberExpr {
base: Box::new(OptChainBase::Member(MemberExpr {
span: DUMMY_SP,
obj: expr.into(),
prop: MemberProp::Ident(Ident::new(prop, DUMMY_SP)),
}),
})),
});
}
}

View file

@ -30,6 +30,9 @@ turbopack-dev-server = { workspace = true }
turbopack-ecmascript = { workspace = true }
turbopack-env = { workspace = true }
turbopack-node = { workspace = true }
next-transform-strip-page-exports = { workspace = true }
next-font = { workspace = true }
next-transform-dynamic = { workspace = true }
swc_core = { workspace = true, features = ["ecma_ast", "common"] }

View file

@ -1,5 +1,6 @@
#![feature(async_closure)]
#![feature(min_specialization)]
#![feature(box_syntax)]
mod app_render;
mod app_source;

View file

@ -1,6 +1,6 @@
use anyhow::Result;
use next_transform_strip_page_exports::ExportFilter;
use turbopack::module_options::ModuleRule;
use turbopack_ecmascript::NextJsPageExportFilter;
use crate::{
next_client::context::ClientContextType,
@ -22,8 +22,7 @@ pub async fn get_next_client_transforms_rules(
let pages_dir = match context_ty {
ClientContextType::Pages { pages_dir } => {
rules.push(
get_next_pages_transforms_rule(pages_dir, NextJsPageExportFilter::StripDataExports)
.await?,
get_next_pages_transforms_rule(pages_dir, ExportFilter::StripDataExports).await?,
);
Some(pages_dir)
}
@ -32,9 +31,7 @@ pub async fn get_next_client_transforms_rules(
}
};
rules.push(get_next_dynamic_transform_rule(
true, false, false, pages_dir,
));
rules.push(get_next_dynamic_transform_rule(true, false, false, pages_dir).await?);
Ok(rules)
}

View file

@ -1,6 +1,6 @@
use anyhow::Result;
use next_transform_strip_page_exports::ExportFilter;
use turbopack::module_options::ModuleRule;
use turbopack_ecmascript::NextJsPageExportFilter;
use crate::{
next_server::context::ServerContextType,
@ -21,11 +21,7 @@ pub async fn get_next_server_transforms_rules(
ServerContextType::Pages { pages_dir } => (false, Some(pages_dir)),
ServerContextType::PagesData { pages_dir } => {
rules.push(
get_next_pages_transforms_rule(
pages_dir,
NextJsPageExportFilter::StripDefaultExport,
)
.await?,
get_next_pages_transforms_rule(pages_dir, ExportFilter::StripDefaultExport).await?,
);
(false, Some(pages_dir))
}
@ -35,12 +31,7 @@ pub async fn get_next_server_transforms_rules(
ServerContextType::Middleware { .. } => (false, None),
};
rules.push(get_next_dynamic_transform_rule(
true,
true,
is_server_components,
pages_dir,
));
rules.push(get_next_dynamic_transform_rule(true, true, is_server_components, pages_dir).await?);
Ok(rules)
}

View file

@ -1,19 +1,34 @@
use std::path::PathBuf;
use anyhow::Result;
use turbo_tasks::primitives::StringsVc;
use next_transform_dynamic::{next_dynamic, NextDynamicMode};
use next_transform_strip_page_exports::{next_transform_strip_page_exports, ExportFilter};
use swc_core::{
common::{util::take::Take, FileName},
ecma::{
ast::{Module, ModuleItem, Program},
atoms::JsWord,
visit::{FoldWith, VisitMutWith},
},
};
use turbo_tasks_fs::FileSystemPathVc;
use turbopack::module_options::{ModuleRule, ModuleRuleCondition, ModuleRuleEffect};
use turbopack_core::reference_type::{ReferenceType, UrlReferenceSubType};
use turbopack_ecmascript::{
EcmascriptInputTransform, EcmascriptInputTransformsVc, NextJsPageExportFilter,
CustomTransformVc, CustomTransformer, EcmascriptInputTransform, EcmascriptInputTransformsVc,
TransformContext,
};
/// Returns a rule which applies the Next.js page export stripping transform.
pub async fn get_next_pages_transforms_rule(
pages_dir: FileSystemPathVc,
export_filter: NextJsPageExportFilter,
export_filter: ExportFilter,
) -> Result<ModuleRule> {
// Apply the Next SSG transform to all pages.
let strip_transform = EcmascriptInputTransform::NextJsStripPageExports(export_filter);
let strip_transform =
EcmascriptInputTransform::Custom(CustomTransformVc::cell(box NextJsStripPageExports {
export_filter,
}));
Ok(ModuleRule::new(
ModuleRuleCondition::all(vec![
ModuleRuleCondition::all(vec![
@ -37,51 +52,111 @@ pub async fn get_next_pages_transforms_rule(
))
}
#[derive(Debug)]
struct NextJsStripPageExports {
export_filter: ExportFilter,
}
impl CustomTransformer for NextJsStripPageExports {
fn transform(&self, program: &mut Program, _ctx: &TransformContext<'_>) -> Option<Program> {
// TODO(alexkirsz) Connect the eliminated_packages to telemetry.
let eliminated_packages = Default::default();
let module_program = unwrap_module_program(program);
Some(
module_program.fold_with(&mut next_transform_strip_page_exports(
self.export_filter,
eliminated_packages,
)),
)
}
}
/// Returns a rule which applies the Next.js dynamic transform.
pub fn get_next_dynamic_transform_rule(
pub async fn get_next_dynamic_transform_rule(
is_development: bool,
is_server: bool,
is_server_components: bool,
pages_dir: Option<FileSystemPathVc>,
) -> ModuleRule {
let dynamic_transform = EcmascriptInputTransform::NextJsDynamic {
is_development,
is_server,
is_server_components,
pages_dir,
};
ModuleRule::new(
) -> Result<ModuleRule> {
let dynamic_transform =
EcmascriptInputTransform::Custom(CustomTransformVc::cell(box NextJsDynamic {
is_development,
is_server,
is_server_components,
pages_dir: match pages_dir {
None => None,
Some(path) => Some(path.await?.path.clone().into()),
},
}));
Ok(ModuleRule::new(
module_rule_match_js_no_url(),
vec![ModuleRuleEffect::AddEcmascriptTransforms(
EcmascriptInputTransformsVc::cell(vec![dynamic_transform]),
)],
)
))
}
#[derive(Debug)]
struct NextJsDynamic {
is_development: bool,
is_server: bool,
is_server_components: bool,
pages_dir: Option<PathBuf>,
}
impl CustomTransformer for NextJsDynamic {
fn transform(&self, program: &mut Program, ctx: &TransformContext<'_>) -> Option<Program> {
let module_program = unwrap_module_program(program);
Some(module_program.fold_with(&mut next_dynamic(
self.is_development,
self.is_server,
self.is_server_components,
NextDynamicMode::Turbo,
FileName::Real(ctx.file_path_str.into()),
self.pages_dir.clone(),
)))
}
}
/// Returns a rule which applies the Next.js font transform.
pub fn get_next_font_transform_rule() -> ModuleRule {
#[allow(unused_mut)] // This is mutated when next-font-local is enabled
let mut font_loaders = vec![
"next/font/google".to_owned(),
"@next/font/google".to_owned(),
];
let mut font_loaders = vec!["next/font/google".into(), "@next/font/google".into()];
#[cfg(feature = "next-font-local")]
{
font_loaders.push("next/font/local".to_owned());
font_loaders.push("@next/font/local".to_owned());
font_loaders.push("next/font/local".into());
font_loaders.push("@next/font/local".into());
}
let transformer =
EcmascriptInputTransform::Custom(CustomTransformVc::cell(box NextJsFont { font_loaders }));
ModuleRule::new(
// TODO: Only match in pages (not pages/api), app/, etc.
module_rule_match_js_no_url(),
vec![ModuleRuleEffect::AddEcmascriptTransforms(
EcmascriptInputTransformsVc::cell(vec![EcmascriptInputTransform::NextJsFont(
StringsVc::cell(font_loaders),
)]),
EcmascriptInputTransformsVc::cell(vec![transformer]),
)],
)
}
#[derive(Debug)]
struct NextJsFont {
font_loaders: Vec<JsWord>,
}
impl CustomTransformer for NextJsFont {
fn transform(&self, program: &mut Program, ctx: &TransformContext<'_>) -> Option<Program> {
let mut next_font = next_font::next_font_loaders(next_font::Config {
font_loaders: self.font_loaders.clone(),
relative_file_path_from_root: ctx.file_name_str.into(),
});
program.visit_mut_with(&mut next_font);
None
}
}
fn module_rule_match_js_no_url() -> ModuleRuleCondition {
ModuleRuleCondition::all(vec![
ModuleRuleCondition::not(ModuleRuleCondition::ReferenceType(ReferenceType::Url(
@ -95,3 +170,18 @@ fn module_rule_match_js_no_url() -> ModuleRuleCondition {
]),
])
}
fn unwrap_module_program(program: &mut Program) -> Program {
match program {
Program::Module(module) => Program::Module(module.take()),
Program::Script(s) => Program::Module(Module {
span: s.span,
body: s
.body
.iter()
.map(|stmt| ModuleItem::Stmt(stmt.clone()))
.collect(),
shebang: s.shebang.clone(),
}),
}
}

View file

@ -60,7 +60,11 @@ async fn middleware_files(page_extensions: StringsVc) -> Result<StringsVc> {
let extensions = page_extensions.await?;
let files = ["middleware.", "src/middleware."]
.into_iter()
.flat_map(|f| extensions.iter().map(move |ext| String::from(f) + ext))
.flat_map(|f| {
extensions
.iter()
.map(move |ext| String::from(f) + ext.as_str())
})
.collect();
Ok(StringsVc::cell(files))
}

View file

@ -47,9 +47,7 @@ pub enum NextDynamicMode {
/// 2. The relative imported module id.
///
/// This key is of the form:
/// ```
/// {currentModulePath} -> {relativeImportedModulePath}
/// ```
///
/// It corresponds to an entry in the React Loadable Manifest generated by
/// the React Loadable Webpack plugin.
@ -262,40 +260,42 @@ impl Fold for NextDynamicPatcher {
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: Callee::Expr(Box::new(Expr::Member(
MemberExpr {
obj: Box::new(Expr::Ident(Ident {
sym: js_word!("require"),
body: Box::new(BlockStmtOrExpr::Expr(Box::new(Expr::Array(
ArrayLit {
elems: vec![Some(ExprOrSpread {
expr: Box::new(Expr::Call(CallExpr {
callee: Callee::Expr(Box::new(Expr::Member(
MemberExpr {
obj: Box::new(Expr::Ident(Ident {
sym: js_word!("require"),
span: DUMMY_SP,
optional: false,
})),
prop: MemberProp::Ident(Ident {
sym: "resolveWeak".into(),
span: DUMMY_SP,
optional: false,
}),
span: DUMMY_SP,
optional: false,
})),
prop: MemberProp::Ident(Ident {
sym: "resolveWeak".into(),
},
))),
args: vec![ExprOrSpread {
expr: Box::new(Expr::Lit(Lit::Str(Str {
value: dynamically_imported_specifier
.into(),
span: DUMMY_SP,
optional: false,
}),
span: DUMMY_SP,
},
))),
args: vec![ExprOrSpread {
expr: Box::new(Expr::Lit(Lit::Str(Str {
value: dynamically_imported_specifier
.into(),
span: DUMMY_SP,
raw: None,
}))),
spread: None,
}],
span: DUMMY_SP,
type_args: None,
})),
spread: None,
})],
span: DUMMY_SP,
}))),
raw: None,
}))),
spread: None,
}],
span: DUMMY_SP,
type_args: None,
})),
spread: None,
})],
span: DUMMY_SP,
},
)))),
is_async: false,
is_generator: false,
span: DUMMY_SP,