-
Notifications
You must be signed in to change notification settings - Fork 27.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add
__nextjs_pure
back (#57328)
### What? Add a magic to next-swc, agina. ### Why? It's required, but it causes some issues with build process. ### How? Closes WEB-1838
- Loading branch information
Showing
9 changed files
with
230 additions
and
1 deletion.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
use turbopack_binding::swc::core::{ | ||
atoms::JsWord, | ||
common::collections::{AHashMap, AHashSet}, | ||
ecma::{ | ||
ast::{ | ||
Expr, Id, ImportDecl, ImportNamedSpecifier, ImportSpecifier, MemberExpr, MemberProp, | ||
Module, ModuleExportName, | ||
}, | ||
visit::{noop_visit_type, Visit, VisitWith}, | ||
}, | ||
}; | ||
|
||
#[derive(Debug, Default)] | ||
pub(crate) struct ImportMap { | ||
/// Map from module name to (module path, exported symbol) | ||
imports: AHashMap<Id, (JsWord, JsWord)>, | ||
|
||
namespace_imports: AHashMap<Id, JsWord>, | ||
|
||
imported_modules: AHashSet<JsWord>, | ||
} | ||
|
||
#[allow(unused)] | ||
impl ImportMap { | ||
pub fn is_module_imported(&mut self, module: &JsWord) -> bool { | ||
self.imported_modules.contains(module) | ||
} | ||
|
||
/// Returns true if `e` is an import of `orig_name` from `module`. | ||
pub fn is_import(&self, e: &Expr, module: &str, orig_name: &str) -> bool { | ||
match e { | ||
Expr::Ident(i) => { | ||
if let Some((i_src, i_sym)) = self.imports.get(&i.to_id()) { | ||
i_src == module && i_sym == orig_name | ||
} else { | ||
false | ||
} | ||
} | ||
|
||
Expr::Member(MemberExpr { | ||
obj: box Expr::Ident(obj), | ||
prop: MemberProp::Ident(prop), | ||
.. | ||
}) => { | ||
if let Some(obj_src) = self.namespace_imports.get(&obj.to_id()) { | ||
obj_src == module && prop.sym == *orig_name | ||
} else { | ||
false | ||
} | ||
} | ||
|
||
_ => false, | ||
} | ||
} | ||
|
||
pub fn analyze(m: &Module) -> Self { | ||
let mut data = ImportMap::default(); | ||
|
||
m.visit_with(&mut Analyzer { data: &mut data }); | ||
|
||
data | ||
} | ||
} | ||
|
||
struct Analyzer<'a> { | ||
data: &'a mut ImportMap, | ||
} | ||
|
||
impl Visit for Analyzer<'_> { | ||
noop_visit_type!(); | ||
|
||
fn visit_import_decl(&mut self, import: &ImportDecl) { | ||
self.data.imported_modules.insert(import.src.value.clone()); | ||
|
||
for s in &import.specifiers { | ||
let (local, orig_sym) = match s { | ||
ImportSpecifier::Named(ImportNamedSpecifier { | ||
local, imported, .. | ||
}) => match imported { | ||
Some(imported) => (local.to_id(), orig_name(imported)), | ||
_ => (local.to_id(), local.sym.clone()), | ||
}, | ||
ImportSpecifier::Default(s) => (s.local.to_id(), "default".into()), | ||
ImportSpecifier::Namespace(s) => { | ||
self.data | ||
.namespace_imports | ||
.insert(s.local.to_id(), import.src.value.clone()); | ||
continue; | ||
} | ||
}; | ||
|
||
self.data | ||
.imports | ||
.insert(local, (import.src.value.clone(), orig_sym)); | ||
} | ||
} | ||
} | ||
|
||
fn orig_name(n: &ModuleExportName) -> JsWord { | ||
match n { | ||
ModuleExportName::Ident(v) => v.sym.clone(), | ||
ModuleExportName::Str(v) => v.value.clone(), | ||
} | ||
} |
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,88 @@ | ||
use turbopack_binding::swc::core::{ | ||
common::{comments::Comments, errors::HANDLER, util::take::Take, Span, Spanned, DUMMY_SP}, | ||
ecma::{ | ||
ast::{CallExpr, Callee, EmptyStmt, Expr, Module, ModuleDecl, ModuleItem, Stmt}, | ||
visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith}, | ||
}, | ||
}; | ||
|
||
use crate::import_analyzer::ImportMap; | ||
|
||
pub fn pure_magic<C>(comments: C) -> impl Fold | ||
where | ||
C: Comments, | ||
{ | ||
as_folder(PureTransform { | ||
imports: Default::default(), | ||
comments, | ||
}) | ||
} | ||
|
||
struct PureTransform<C> | ||
where | ||
C: Comments, | ||
{ | ||
imports: ImportMap, | ||
comments: C, | ||
} | ||
|
||
const MODULE: &str = "next/dist/build/swc/helpers"; | ||
const FN_NAME: &str = "__nextjs_pure"; | ||
|
||
impl<C> VisitMut for PureTransform<C> | ||
where | ||
C: Comments, | ||
{ | ||
fn visit_mut_expr(&mut self, e: &mut Expr) { | ||
e.visit_mut_children_with(self); | ||
|
||
if let Expr::Call(CallExpr { | ||
span, | ||
callee: Callee::Expr(callee), | ||
args, | ||
.. | ||
}) = e | ||
{ | ||
if !self.imports.is_import(callee, MODULE, FN_NAME) { | ||
return; | ||
} | ||
|
||
if args.len() != 1 { | ||
HANDLER.with(|handler| { | ||
handler | ||
.struct_span_err(*span, "markAsPure() does not support multiple arguments") | ||
.emit(); | ||
}); | ||
return; | ||
} | ||
|
||
*e = *args[0].expr.take(); | ||
|
||
let mut lo = e.span().lo; | ||
if lo.is_dummy() { | ||
lo = Span::dummy_with_cmt().lo; | ||
} | ||
|
||
self.comments.add_pure_comment(lo); | ||
} | ||
} | ||
|
||
fn visit_mut_module(&mut self, m: &mut Module) { | ||
self.imports = ImportMap::analyze(m); | ||
|
||
m.visit_mut_children_with(self); | ||
} | ||
|
||
fn visit_mut_module_item(&mut self, m: &mut ModuleItem) { | ||
if let ModuleItem::ModuleDecl(ModuleDecl::Import(import)) = m { | ||
if import.src.value == MODULE { | ||
*m = ModuleItem::Stmt(Stmt::Empty(EmptyStmt { span: DUMMY_SP })); | ||
return; | ||
} | ||
} | ||
|
||
m.visit_mut_children_with(self); | ||
} | ||
|
||
noop_visit_mut_type!(); | ||
} |
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
3 changes: 3 additions & 0 deletions
3
packages/next-swc/crates/core/tests/fixture/pure/no-name-clash/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 { __nextjs_pure } from 'not-next-magic' | ||
|
||
__nextjs_pure(console.log('test!')) |
2 changes: 2 additions & 0 deletions
2
packages/next-swc/crates/core/tests/fixture/pure/no-name-clash/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,2 @@ | ||
import { __nextjs_pure } from 'not-next-magic'; | ||
__nextjs_pure(console.log("test!")); |
3 changes: 3 additions & 0 deletions
3
packages/next-swc/crates/core/tests/fixture/pure/simple/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 { __nextjs_pure } from 'next/dist/build/swc/helpers' | ||
|
||
__nextjs_pure(console.log('test!')) |
2 changes: 2 additions & 0 deletions
2
packages/next-swc/crates/core/tests/fixture/pure/simple/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,2 @@ | ||
; | ||
/*#__PURE__*/ console.log("test!"); |
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 @@ | ||
export function __nextjs_pure<T>(expr: T): T { | ||
return expr | ||
} |