fix(webpack/ast): Handle typescript (#3045)

swc_webpack_ast:
 - Add `proces_file`.
 - Add support for typescript.
This commit is contained in:
Donny/강동윤 2021-12-15 18:33:35 +09:00 committed by GitHub
parent 9b36abbe75
commit 24179bd9be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 315 additions and 37 deletions

View File

@ -9,6 +9,7 @@ export RUST_BACKTRACE=1
cargo test --no-run cargo test --no-run
UPDATE=1 cargo test -q UPDATE=1 cargo test -q
(cd next.js/packages/next-swc && yarn build-native) (cd next.js/packages/next-swc && yarn build-native --release)
(cd next.js && NEXT_PRIVATE_LOCAL_WEBPACK5=1 yarn next dev test/integration/production) (cd next.js && NEXT_PRIVATE_LOCAL_WEBPACK5=1 yarn testheadless $@ || true)
(cd next.js && NEXT_PRIVATE_LOCAL_WEBPACK5=1 yarn next dev $@)

View File

@ -1,7 +1,12 @@
use crate::reducer::ast_reducer; use crate::reducer::ast_reducer;
use anyhow::{Context, Error}; use anyhow::{anyhow, Context, Error};
use swc_common::{sync::Lrc, Mark, SourceFile, SourceMap}; use serde::Serialize;
use swc_common::{
errors::HANDLER, sync::Lrc, FileName, FilePathMapping, Globals, Mark, SourceFile, SourceMap,
GLOBALS,
};
use swc_ecma_ast::*; use swc_ecma_ast::*;
use swc_ecma_parser::{lexer::Lexer, EsConfig, Parser, StringInput, Syntax, TsConfig};
use swc_ecma_transforms_base::resolver::resolver_with_mark; use swc_ecma_transforms_base::resolver::resolver_with_mark;
use swc_ecma_visit::VisitMutWith; use swc_ecma_visit::VisitMutWith;
use swc_estree_ast::flavor::Flavor; use swc_estree_ast::flavor::Flavor;
@ -10,6 +15,73 @@ use swc_timer::timer;
pub mod reducer; pub mod reducer;
#[derive(Serialize)]
pub struct AstOutput {
ast: String,
src: Option<Lrc<String>>,
}
pub fn process_file<F>(load_file: F, include_src: bool) -> Result<AstOutput, Error>
where
F: FnOnce(&Lrc<SourceMap>) -> Result<Lrc<SourceFile>, Error>,
{
let globals = Globals::new();
let cm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
let fm = load_file(&cm).context("failed to load file")?;
// Default
let syntax = Syntax::Es(EsConfig {
jsx: true,
..Default::default()
});
let syntax = match &fm.name {
FileName::Real(path) => match path.extension() {
Some(ext) => {
if ext == "tsx" {
Syntax::Typescript(TsConfig {
tsx: true,
no_early_errors: true,
..Default::default()
})
} else if ext == "ts" {
Syntax::Typescript(TsConfig {
no_early_errors: true,
..Default::default()
})
} else {
syntax
}
}
_ => syntax,
},
_ => syntax,
};
let module = {
let lexer = Lexer::new(syntax, EsVersion::latest(), StringInput::from(&*fm), None);
let mut parser = Parser::new_from(lexer);
parser.parse_module().map_err(|err| {
HANDLER.with(|h| {
err.into_diagnostic(h).emit();
anyhow!("failed to parse module")
})
})?
};
let ast = GLOBALS.set(&globals, || webpack_ast(cm.clone(), fm.clone(), module))?;
Ok(AstOutput {
ast,
src: if include_src {
Some(fm.src.clone())
} else {
None
},
})
}
/// `n` is expected to be pure (`resolver` is not applied) /// `n` is expected to be pure (`resolver` is not applied)
pub fn webpack_ast( pub fn webpack_ast(
cm: Lrc<SourceMap>, cm: Lrc<SourceMap>,
@ -27,6 +99,7 @@ pub fn webpack_ast(
let _timer = timer!("resolver"); let _timer = timer!("resolver");
n.visit_mut_with(&mut resolver_with_mark(top_level_mark)); n.visit_mut_with(&mut resolver_with_mark(top_level_mark));
} }
{ {
n.visit_mut_with(&mut ast_reducer(top_level_mark)); n.visit_mut_with(&mut ast_reducer(top_level_mark));
} }

View File

@ -1,11 +1,11 @@
use self::flatten::contains_import; use self::{flatten::contains_import, typescript::ts_remover};
use std::{iter::once, sync::Arc}; use std::{iter::once, sync::Arc};
use swc_atoms::js_word; use swc_atoms::js_word;
use swc_common::{ use swc_common::{
collections::AHashSet, collections::{AHashMap, AHashSet},
pass::{Repeat, Repeated}, pass::{Repeat, Repeated},
util::take::Take, util::take::Take,
Mark, Span, Spanned, SyntaxContext, Mark, Span, Spanned, SyntaxContext, DUMMY_SP,
}; };
use swc_ecma_ast::*; use swc_ecma_ast::*;
use swc_ecma_utils::{ident::IdentLike, Id, IsEmpty, StmtLike, StmtOrModuleItem}; use swc_ecma_utils::{ident::IdentLike, Id, IsEmpty, StmtLike, StmtOrModuleItem};
@ -13,6 +13,7 @@ use swc_ecma_visit::{Visit, VisitMut, VisitMutWith, VisitWith};
use swc_timer::timer; use swc_timer::timer;
mod flatten; mod flatten;
mod typescript;
/// # Usage /// # Usage
/// ///
@ -68,11 +69,24 @@ pub fn ast_reducer(top_level_mark: Mark) -> impl VisitMut {
}) })
} }
#[derive(Debug, Clone, Copy, Default)]
struct BindingInfo {
used_as_type: bool,
used_as_var: bool,
}
struct Analyzer { struct Analyzer {
amd_requires: AHashSet<Id>, amd_requires: AHashSet<Id>,
used_refs: AHashMap<Id, BindingInfo>,
} }
impl Visit for Analyzer { impl Visit for Analyzer {
fn visit_assign_pat_prop(&mut self, p: &AssignPatProp) {
p.visit_children_with(self);
self.used_refs.entry(p.key.to_id()).or_default().used_as_var = true;
}
fn visit_call_expr(&mut self, e: &CallExpr) { fn visit_call_expr(&mut self, e: &CallExpr) {
e.visit_children_with(self); e.visit_children_with(self);
@ -124,6 +138,64 @@ impl Visit for Analyzer {
_ => {} _ => {}
} }
} }
fn visit_expr(&mut self, e: &Expr) {
e.visit_children_with(self);
match e {
Expr::Ident(i) => {
self.used_refs.entry(i.to_id()).or_default().used_as_var = true;
}
_ => {}
}
}
fn visit_member_expr(&mut self, e: &MemberExpr) {
e.obj.visit_with(self);
if e.computed {
e.prop.visit_with(self);
}
}
fn visit_pat(&mut self, p: &Pat) {
p.visit_children_with(self);
match p {
Pat::Ident(s) => {
self.used_refs.entry(s.to_id()).or_default().used_as_var = true;
}
_ => {}
}
}
fn visit_prop(&mut self, p: &Prop) {
p.visit_children_with(self);
match p {
Prop::Shorthand(s) => {
self.used_refs.entry(s.to_id()).or_default().used_as_var = true;
}
_ => {}
}
}
fn visit_ts_type_ref(&mut self, ty: &TsTypeRef) {
fn left_most(n: &TsEntityName) -> &Ident {
match n {
TsEntityName::Ident(i) => i,
TsEntityName::TsQualifiedName(q) => left_most(&q.left),
}
}
ty.visit_children_with(self);
let left = left_most(&ty.type_name);
self.used_refs.entry(left.to_id()).or_default().used_as_type = true;
}
} }
#[derive(Default)] #[derive(Default)]
@ -131,6 +203,9 @@ struct ScopeData {
imported_ids: AHashSet<Id>, imported_ids: AHashSet<Id>,
/// amd `require` modules. /// amd `require` modules.
amd_requires: AHashSet<Id>, amd_requires: AHashSet<Id>,
#[allow(unused)]
used_refs: AHashMap<Id, BindingInfo>,
} }
impl ScopeData { impl ScopeData {
@ -160,7 +235,8 @@ impl ScopeData {
} }
let mut analyzer = Analyzer { let mut analyzer = Analyzer {
amd_requires: AHashSet::default(), amd_requires: Default::default(),
used_refs: Default::default(),
}; };
items.visit_with(&mut analyzer); items.visit_with(&mut analyzer);
@ -168,6 +244,7 @@ impl ScopeData {
ScopeData { ScopeData {
imported_ids, imported_ids,
amd_requires: analyzer.amd_requires, amd_requires: analyzer.amd_requires,
used_refs: analyzer.used_refs,
} }
} }
@ -791,22 +868,6 @@ impl VisitMut for ReduceAst {
*e = *expr.arg.take(); *e = *expr.arg.take();
} }
Expr::TsAs(expr) => {
*e = *expr.expr.take();
}
Expr::TsConstAssertion(expr) => {
*e = *expr.expr.take();
}
Expr::TsTypeAssertion(expr) => {
*e = *expr.expr.take();
}
Expr::TsNonNull(expr) => {
*e = *expr.expr.take();
}
Expr::TaggedTpl(expr) => { Expr::TaggedTpl(expr) => {
let mut exprs = Vec::with_capacity(expr.tpl.exprs.len() + 1); let mut exprs = Vec::with_capacity(expr.tpl.exprs.len() + 1);
exprs.push(expr.tag.take()); exprs.push(expr.tag.take());
@ -1318,9 +1379,17 @@ impl VisitMut for ReduceAst {
fn visit_mut_module_items(&mut self, stmts: &mut Vec<ModuleItem>) { fn visit_mut_module_items(&mut self, stmts: &mut Vec<ModuleItem>) {
if !self.collected_data { if !self.collected_data {
let _timer = timer!("analyze before reducing");
self.collected_data = true; self.collected_data = true;
self.data = Arc::new(ScopeData::analyze(&stmts));
{
let _timer = timer!("analyze before reducing");
self.data = Arc::new(ScopeData::analyze(&stmts));
}
{
let _timer = timer!("remove typescript nodes");
stmts.visit_mut_with(&mut ts_remover());
}
} }
let _timer = timer!("reduce ast (single pass)"); let _timer = timer!("reduce ast (single pass)");
@ -1438,7 +1507,12 @@ impl VisitMut for ReduceAst {
} }
match pat { match pat {
Pat::Assign(..) => {} Pat::Assign(..) => {
let old = self.can_remove_pat;
self.can_remove_pat = false;
pat.visit_mut_children_with(self);
self.can_remove_pat = old;
}
_ => { _ => {
pat.visit_mut_children_with(self); pat.visit_mut_children_with(self);
} }
@ -1566,7 +1640,7 @@ impl VisitMut for ReduceAst {
fn visit_mut_stmt(&mut self, stmt: &mut Stmt) { fn visit_mut_stmt(&mut self, stmt: &mut Stmt) {
match stmt { match stmt {
Stmt::Debugger(_) | Stmt::Break(_) | Stmt::Continue(_) => { Stmt::Debugger(_) | Stmt::Break(_) | Stmt::Continue(_) => {
*stmt = Stmt::Empty(EmptyStmt { span: stmt.span() }); *stmt = Stmt::Empty(EmptyStmt { span: DUMMY_SP });
return; return;
} }

View File

@ -0,0 +1,106 @@
use swc_common::util::take::Take;
use swc_ecma_ast::*;
use swc_ecma_visit::{VisitMut, VisitMutWith};
pub fn ts_remover() -> impl VisitMut {
TsRemover {}
}
struct TsRemover {}
impl VisitMut for TsRemover {
fn visit_mut_array_pat(&mut self, p: &mut ArrayPat) {
p.visit_mut_children_with(self);
p.optional = false;
}
fn visit_mut_expr(&mut self, e: &mut Expr) {
e.visit_mut_children_with(self);
match e {
Expr::TsAs(expr) => {
*e = *expr.expr.take();
}
Expr::TsConstAssertion(expr) => {
*e = *expr.expr.take();
}
Expr::TsTypeAssertion(expr) => {
*e = *expr.expr.take();
}
Expr::TsNonNull(expr) => {
*e = *expr.expr.take();
}
_ => {}
}
}
fn visit_mut_ident(&mut self, i: &mut Ident) {
i.visit_mut_children_with(self);
i.optional = false;
}
fn visit_mut_module_item(&mut self, s: &mut ModuleItem) {
s.visit_mut_children_with(self);
match s {
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
decl: Decl::TsInterface(_) | Decl::TsTypeAlias(_),
..
}))
| ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
type_only: true, ..
}))
| ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(NamedExport {
type_only: true,
..
})) => {
s.take();
return;
}
_ => {}
}
}
fn visit_mut_object_pat(&mut self, p: &mut ObjectPat) {
p.visit_mut_children_with(self);
p.optional = false;
}
fn visit_mut_opt_ts_type(&mut self, ty: &mut Option<Box<TsType>>) {
*ty = None;
}
fn visit_mut_opt_ts_type_ann(&mut self, ty: &mut Option<TsTypeAnn>) {
*ty = None;
}
fn visit_mut_opt_ts_type_param_decl(&mut self, t: &mut Option<TsTypeParamDecl>) {
*t = None;
}
fn visit_mut_opt_ts_type_param_instantiation(
&mut self,
t: &mut Option<TsTypeParamInstantiation>,
) {
*t = None;
}
fn visit_mut_stmt(&mut self, s: &mut Stmt) {
s.visit_mut_children_with(self);
match s {
Stmt::Decl(Decl::TsTypeAlias(..) | Decl::TsInterface(..)) => {
s.take();
}
_ => {}
}
}
}

View File

@ -1,5 +1,5 @@
use std::{fs, path::PathBuf}; use std::{fs, path::PathBuf};
use swc_common::{chain, Mark}; use swc_common::{chain, Mark, Span};
use swc_ecma_ast::*; use swc_ecma_ast::*;
use swc_ecma_parser::{EsConfig, Parser, StringInput, Syntax, TsConfig}; use swc_ecma_parser::{EsConfig, Parser, StringInput, Syntax, TsConfig};
use swc_ecma_transforms_base::resolver::resolver_with_mark; use swc_ecma_transforms_base::resolver::resolver_with_mark;
@ -33,14 +33,24 @@ fn fixture(input: PathBuf) {
struct AssertValid; struct AssertValid;
impl VisitMut for AssertValid { impl VisitMut for AssertValid {
fn visit_mut_array_pat(&mut self, a: &mut ArrayPat) {
if a.optional {
panic!("found an optional pattern: {:?}", a)
}
a.visit_mut_children_with(self);
}
fn visit_mut_empty_stmt(&mut self, i: &mut EmptyStmt) { fn visit_mut_empty_stmt(&mut self, i: &mut EmptyStmt) {
panic!("found empty: {:?}", i) panic!("found empty: {:?}", i)
} }
fn visit_mut_ident(&mut self, i: &mut Ident) { fn visit_mut_ident(&mut self, i: &mut Ident) {
if i.span.is_dummy() { if i.optional {
panic!("found an identifier with dummy span: {:?}", i) panic!("found an optional pattern: {:?}", i)
} }
i.visit_mut_children_with(self);
} }
fn visit_mut_if_stmt(&mut self, s: &mut IfStmt) { fn visit_mut_if_stmt(&mut self, s: &mut IfStmt) {
@ -59,11 +69,27 @@ impl VisitMut for AssertValid {
fn visit_mut_module(&mut self, m: &mut Module) { fn visit_mut_module(&mut self, m: &mut Module) {
dbg!(&*m); dbg!(&*m);
m.visit_mut_children_with(self); m.body.visit_mut_with(self);
}
fn visit_mut_object_pat(&mut self, pat: &mut ObjectPat) {
if pat.optional {
panic!("found a optional pattern: {:?}", pat)
}
pat.visit_mut_children_with(self);
}
fn visit_mut_span(&mut self, sp: &mut Span) {
assert!(!sp.is_dummy());
}
fn visit_mut_ts_type(&mut self, ty: &mut TsType) {
panic!("found a typescript type: {:?}", ty)
} }
} }
#[testing::fixture("../swc_ecma_parser/tests/typescript/tsc/**/input.ts")] #[testing::fixture("../swc_ecma_parser/tests/typescript/**/input.ts")]
#[testing::fixture("../swc/tests/tsc-references/**/output.js")] #[testing::fixture("../swc/tests/tsc-references/**/output.js")]
fn assert_no_invalid(input: PathBuf) { fn assert_no_invalid(input: PathBuf) {
testing::run_test(false, |cm, _handler| { testing::run_test(false, |cm, _handler| {

View File

@ -53,8 +53,7 @@ class Container extends React.Component {
} }
export const emitter = mitt(); export const emitter = mitt();
let CachedComponent; let CachedComponent;
export async function initNext(opts = { export async function initNext() {
}) {
let initialErr; let initialErr;
const appEntrypoint = null; const appEntrypoint = null;
const { component: app , exports: mod } = null; const { component: app , exports: mod } = null;

View File

@ -78,8 +78,7 @@ class Container {
const emitter = null; const emitter = null;
exports.emitter = null; exports.emitter = null;
let CachedComponent; let CachedComponent;
(function*(opts = { (function*() {
}) {
let initialErr; let initialErr;
const appEntrypoint = null; const appEntrypoint = null;
const { component: app , exports: mod } = null; const { component: app , exports: mod } = null;