Skip to content

Commit

Permalink
feat: Add __nextjs_pure back (#57328)
Browse files Browse the repository at this point in the history
### 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
kdy1 authored Oct 24, 2023
1 parent c05a119 commit 2b16a74
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 1 deletion.
104 changes: 104 additions & 0 deletions packages/next-swc/crates/core/src/import_analyzer.rs
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(),
}
}
5 changes: 4 additions & 1 deletion packages/next-swc/crates/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,13 @@ pub mod amp_attributes;
mod auto_cjs;
pub mod cjs_optimizer;
pub mod disallow_re_export_all_in_page;
mod import_analyzer;
pub mod named_import_transform;
pub mod next_ssg;
pub mod optimize_barrel;
pub mod optimize_server_react;
pub mod page_config;
pub mod pure;
pub mod react_server_components;
pub mod server_actions;
pub mod shake_exports;
Expand Down Expand Up @@ -310,7 +312,7 @@ where
Some(config) => Either::Left(server_actions::server_actions(
&file.name,
config.clone(),
comments,
comments.clone(),
)),
None => Either::Right(noop()),
},
Expand All @@ -320,6 +322,7 @@ where
},
None => Either::Right(noop()),
},
pure::pure_magic(comments),
)
}

Expand Down
88 changes: 88 additions & 0 deletions packages/next-swc/crates/core/src/pure.rs
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!();
}
21 changes: 21 additions & 0 deletions packages/next-swc/crates/core/tests/fixture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use next_swc::{
optimize_barrel::optimize_barrel,
optimize_server_react::optimize_server_react,
page_config::page_config_test,
pure::pure_magic,
react_server_components::server_components,
server_actions::{
server_actions, {self},
Expand Down Expand Up @@ -571,3 +572,23 @@ where
{
serde_json::from_str(s).expect("failed to deserialize")
}

#[fixture("tests/fixture/pure/**/input.js")]
fn pure(input: PathBuf) {
let output = input.parent().unwrap().join("output.js");
test_fixture(
syntax(),
&|tr| {
let unresolved_mark = Mark::new();
let top_level_mark = Mark::new();

chain!(
resolver(unresolved_mark, top_level_mark, false),
pure_magic(tr.comments.clone())
)
},
&input,
&output,
Default::default(),
);
}
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!'))
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!"));
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!'))
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
;
/*#__PURE__*/ console.log("test!");
3 changes: 3 additions & 0 deletions packages/next/src/build/swc/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function __nextjs_pure<T>(expr: T): T {
return expr
}

0 comments on commit 2b16a74

Please sign in to comment.