Skip to content

Commit b4c8b20

Browse files
authored
fix(optimizer): minification breaks imports and exports (#122)
fixes #119
1 parent f8394c6 commit b4c8b20

File tree

6 files changed

+169
-70
lines changed

6 files changed

+169
-70
lines changed

src/optimizer/core/src/code_move.rs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::collector::{GlobalCollect, ImportKind};
1+
use crate::collector::{new_ident_from_id, GlobalCollect, Id, ImportKind};
22
use crate::parse::{emit_source_code, HookAnalysis, PathData, TransformModule, TransformOutput};
33

44
use std::collections::HashMap;
@@ -15,7 +15,7 @@ pub fn new_module(
1515
path: &PathData,
1616
name: &str,
1717
origin: &str,
18-
local_idents: &[JsWord],
18+
local_idents: &[Id],
1919
global: &GlobalCollect,
2020
) -> Result<(ast::Module, SingleThreadedComments), Error> {
2121
let comments = SingleThreadedComments::default();
@@ -25,26 +25,27 @@ pub fn new_module(
2525
body: Vec::with_capacity(max_cap),
2626
shebang: None,
2727
};
28-
for ident in local_idents {
29-
if let Some(import) = global.imports.get(ident) {
28+
29+
for id in local_idents {
30+
if let Some(import) = global.imports.get(id) {
3031
let specifier = match import.kind {
3132
ImportKind::Named => ast::ImportSpecifier::Named(ast::ImportNamedSpecifier {
3233
is_type_only: false,
3334
span: DUMMY_SP,
34-
imported: if &import.specifier == ident {
35+
imported: if import.specifier == id.0 {
3536
None
3637
} else {
3738
Some(ast::Ident::new(import.specifier.clone(), DUMMY_SP))
3839
},
39-
local: ast::Ident::new(ident.clone(), DUMMY_SP),
40+
local: new_ident_from_id(id),
4041
}),
4142
ImportKind::Default => ast::ImportSpecifier::Default(ast::ImportDefaultSpecifier {
4243
span: DUMMY_SP,
43-
local: ast::Ident::new(ident.clone(), DUMMY_SP),
44+
local: new_ident_from_id(id),
4445
}),
4546
ImportKind::All => ast::ImportSpecifier::Namespace(ast::ImportStarAsSpecifier {
4647
span: DUMMY_SP,
47-
local: ast::Ident::new(ident.clone(), DUMMY_SP),
48+
local: new_ident_from_id(id),
4849
}),
4950
};
5051
module
@@ -63,7 +64,12 @@ pub fn new_module(
6364
specifiers: vec![specifier],
6465
},
6566
)));
66-
} else if let Some(export) = global.exports.get(ident) {
67+
} else if let Some(export) = global.exports.get(id) {
68+
let imported = if export == &id.0 {
69+
None
70+
} else {
71+
Some(ast::Ident::new(export.clone(), DUMMY_SP))
72+
};
6773
module
6874
.body
6975
.push(ast::ModuleItem::ModuleDecl(ast::ModuleDecl::Import(
@@ -80,8 +86,8 @@ pub fn new_module(
8086
specifiers: vec![ast::ImportSpecifier::Named(ast::ImportNamedSpecifier {
8187
is_type_only: false,
8288
span: DUMMY_SP,
83-
imported: None,
84-
local: ast::Ident::new(export.clone(), DUMMY_SP),
89+
imported,
90+
local: new_ident_from_id(id),
8591
})],
8692
},
8793
)));

src/optimizer/core/src/collector.rs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
11
use std::collections::{BTreeMap, HashSet};
22

33
use swc_atoms::{js_word, JsWord};
4+
use swc_common::{BytePos, Span, SyntaxContext};
45
use swc_ecmascript::ast;
56
use swc_ecmascript::visit::{noop_visit_type, visit_expr, visit_stmt, Visit, VisitWith};
67

78
macro_rules! id {
89
($ident: expr) => {
9-
$ident.sym.clone()
10+
($ident.sym.clone(), $ident.span.ctxt())
1011
};
1112
}
1213

14+
pub type Id = (JsWord, SyntaxContext);
15+
16+
pub fn new_ident_from_id(id: &Id) -> ast::Ident {
17+
ast::Ident::new(
18+
id.0.clone(),
19+
Span {
20+
lo: BytePos(0),
21+
hi: BytePos(0),
22+
ctxt: id.1,
23+
},
24+
)
25+
}
26+
1327
#[derive(PartialEq, Clone, Copy)]
1428
pub enum ImportKind {
1529
Named,
@@ -24,8 +38,8 @@ pub struct Import {
2438
}
2539

2640
pub struct GlobalCollect {
27-
pub imports: BTreeMap<JsWord, Import>,
28-
pub exports: BTreeMap<JsWord, JsWord>,
41+
pub imports: BTreeMap<Id, Import>,
42+
pub exports: BTreeMap<Id, JsWord>,
2943
in_export_decl: bool,
3044
}
3145

@@ -177,7 +191,7 @@ enum ExprOrSkip {
177191
#[derive(Debug)]
178192
pub struct HookCollect {
179193
pub local_decl: HashSet<JsWord>,
180-
pub local_idents: HashSet<JsWord>,
194+
pub local_idents: HashSet<Id>,
181195
expr_ctxt: Vec<ExprOrSkip>,
182196
}
183197

@@ -192,9 +206,9 @@ impl HookCollect {
192206
collect
193207
}
194208

195-
pub fn get_words(self) -> (Vec<JsWord>, Vec<JsWord>) {
209+
pub fn get_words(self) -> (Vec<JsWord>, Vec<Id>) {
196210
let mut local_decl: Vec<JsWord> = self.local_decl.into_iter().collect();
197-
let mut local_idents: Vec<JsWord> = self.local_idents.into_iter().collect();
211+
let mut local_idents: Vec<Id> = self.local_idents.into_iter().collect();
198212
local_idents.sort();
199213
local_decl.sort();
200214
(local_decl, local_idents)
@@ -422,7 +436,7 @@ impl Visit for HookCollect {
422436

423437
fn visit_ident(&mut self, node: &ast::Ident) {
424438
if let Some(ExprOrSkip::Expr) = self.expr_ctxt.last() {
425-
self.local_idents.insert(node.sym.clone());
439+
self.local_idents.insert(id!(node));
426440
}
427441
}
428442

src/optimizer/core/src/parse.rs

Lines changed: 46 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,8 @@ use swc_ecmascript::minifier::option::{
2626
use swc_ecmascript::parser::lexer::Lexer;
2727
use swc_ecmascript::parser::{EsConfig, PResult, Parser, StringInput, Syntax, TsConfig};
2828
use swc_ecmascript::transforms::{
29-
fixer,
30-
hygiene::{self, hygiene_with_config},
31-
optimization::simplify,
32-
pass, react, resolver_with_mark, typescript,
29+
fixer, hygiene::hygiene_with_config, optimization::simplify, pass, react, resolver_with_mark,
30+
typescript,
3331
};
3432
use swc_ecmascript::visit::{FoldWith, VisitMutWith};
3533

@@ -142,28 +140,13 @@ pub fn transform_code(config: TransformCodeOptions) -> Result<TransformOutput, a
142140

143141
swc_common::GLOBALS.set(&Globals::new(), || {
144142
swc_common::errors::HANDLER.set(&handler, || {
145-
let mut react_options = react::Options::default();
146-
if is_jsx {
147-
react_options.use_spread = true;
148-
react_options.import_source = "@builder.io/qwik".to_string();
149-
react_options.pragma = "h".to_string();
150-
react_options.pragma_frag = "Fragment".to_string();
151-
};
152-
153-
let collect = global_collect(&main_module);
154143
let global_mark = Mark::fresh(Mark::root());
155-
156-
let mut hooks: Vec<Hook> = vec![];
157144
let mut main_module = main_module;
158145

159-
main_module.visit_mut_with(&mut resolver_with_mark(global_mark));
160-
161-
let mut main_module = {
146+
// Transpile JSX
147+
if transpile && is_type_script {
162148
let mut passes = chain!(
163-
pass::Optional::new(
164-
typescript::strip(global_mark),
165-
transpile && is_type_script && !is_jsx
166-
),
149+
pass::Optional::new(typescript::strip(global_mark), !is_jsx),
167150
pass::Optional::new(
168151
typescript::strip_with_jsx(
169152
Lrc::clone(&source_map),
@@ -175,31 +158,46 @@ pub fn transform_code(config: TransformCodeOptions) -> Result<TransformOutput, a
175158
Some(&comments),
176159
global_mark,
177160
),
178-
transpile && is_type_script && is_jsx
179-
),
180-
pass::Optional::new(
181-
react::react(
182-
Lrc::clone(&source_map),
183-
Some(&comments),
184-
react_options,
185-
global_mark
186-
),
187-
transpile && is_jsx
188-
),
189-
HookTransform::new(
190-
config.context,
191-
&path_data,
192-
config.entry_policy,
193-
Some(&comments),
194-
&mut hooks
161+
is_jsx
195162
),
196163
);
197-
main_module.fold_with(&mut passes)
198-
};
164+
main_module = main_module.fold_with(&mut passes)
165+
}
166+
167+
// Resolve with mark
168+
main_module.visit_mut_with(&mut resolver_with_mark(global_mark));
169+
170+
// Transpile JSX
171+
if transpile && is_jsx {
172+
let mut react_options = react::Options::default();
173+
if is_jsx {
174+
react_options.use_spread = true;
175+
react_options.import_source = "@builder.io/qwik".to_string();
176+
react_options.pragma = "h".to_string();
177+
react_options.pragma_frag = "Fragment".to_string();
178+
};
179+
main_module = main_module.fold_with(&mut react::react(
180+
Lrc::clone(&source_map),
181+
Some(&comments),
182+
react_options,
183+
global_mark,
184+
));
185+
}
186+
187+
// Collect import/export metadata
188+
let collect = global_collect(&main_module);
189+
let mut hooks: Vec<Hook> = vec![];
190+
191+
// Run main transform
192+
main_module = main_module.fold_with(&mut HookTransform::new(
193+
config.context,
194+
&path_data,
195+
config.entry_policy,
196+
Some(&comments),
197+
&mut hooks,
198+
));
199199

200200
if config.minify != MinifyMode::None {
201-
let top_level_mark = Mark::fresh(Mark::root());
202-
main_module.visit_mut_with(&mut resolver_with_mark(top_level_mark));
203201
main_module =
204202
main_module.fold_with(&mut simplify::simplifier(Default::default()));
205203

@@ -221,7 +219,9 @@ pub fn transform_code(config: TransformCodeOptions) -> Result<TransformOutput, a
221219
wrap: false,
222220
enclose: false,
223221
},
224-
&ExtraOptions { top_level_mark },
222+
&ExtraOptions {
223+
top_level_mark: global_mark,
224+
},
225225
);
226226
}
227227

@@ -253,7 +253,6 @@ pub fn transform_code(config: TransformCodeOptions) -> Result<TransformOutput, a
253253
)?;
254254

255255
if config.minify == MinifyMode::Minify {
256-
hook_module = hook_module.fold_with(&mut resolver_with_mark(hook_mark));
257256
hook_module = optimize(
258257
hook_module,
259258
Lrc::clone(&source_map),
@@ -277,9 +276,7 @@ pub fn transform_code(config: TransformCodeOptions) -> Result<TransformOutput, a
277276
);
278277

279278
hook_module = hook_module
280-
.fold_with(&mut hygiene_with_config(hygiene::Config {
281-
..Default::default()
282-
}))
279+
.fold_with(&mut hygiene_with_config(Default::default()))
283280
.fold_with(&mut fixer(None));
284281
}
285282

@@ -298,7 +295,7 @@ pub fn transform_code(config: TransformCodeOptions) -> Result<TransformOutput, a
298295
entry: h.entry,
299296
canonical_filename: h.canonical_filename,
300297
local_decl: h.local_decl,
301-
local_idents: h.local_idents,
298+
local_idents: h.local_idents.into_iter().map(|id| id.0).collect(),
302299
});
303300
modules.push(TransformModule {
304301
code,
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
---
2+
source: src/optimizer/core/src/test.rs
3+
expression: output
4+
5+
---
6+
==INPUT==
7+
8+
9+
import {qHook, h} from '@builderio/qwik';
10+
import thing from 'lib';
11+
import * as all from 'lib';
12+
import {s as se} from 'lib';
13+
14+
15+
export const Header = qComponent({
16+
onMount: <div/>,
17+
onRender: qHook(() => <Footer>{thing}{all()}{se()}</Footer>)
18+
});
19+
20+
export const Footer = qComponent();
21+
22+
23+
24+
============================= project/test.js ==
25+
26+
import{qHook as a,h as b}from"@builderio/qwik";export const Header=/*#__PURE__*/ qComponent({onMount:/*#__PURE__*/ b("div",null),onRender:a(()=>import("../entry_hooks"),"Header_onRender")});export const Footer=/*#__PURE__*/ qComponent();
27+
============================= h_test_header_onrender.js ==
28+
29+
import{Footer as a}from"./project/test";import*as b from"lib";import{h as c}from"@builderio/qwik";import{qHook as d}from"@builderio/qwik";import{s as e}from"lib";import f from"lib";export const Header_onRender=/*#__PURE__*/ d(()=>c(a,null,f,b(),e()));
30+
============================= entry_hooks.js (ENTRY POINT)==
31+
32+
export { Header_onRender } from "./h_test_header_onrender";
33+
34+
== HOOKS ==
35+
36+
[
37+
{
38+
"origin": "project/test.tsx",
39+
"name": "Header_onRender",
40+
"entry": "entry_hooks",
41+
"canonicalFilename": "h_test_header_onrender",
42+
"localDecl": [],
43+
"localIdents": [
44+
"Footer",
45+
"all",
46+
"h",
47+
"qHook",
48+
"se",
49+
"thing"
50+
]
51+
}
52+
]
53+
54+
== DIAGNOSTICS ==
55+
56+
[]

src/optimizer/core/src/test.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,32 @@ export const cache = patternCache[cacheKey] || (patternCache[cacheKey]={});
307307
);
308308
}
309309

310+
#[test]
311+
fn issue_118() {
312+
test_input(
313+
"project/test.tsx",
314+
r#"
315+
import {qHook, h} from '@builderio/qwik';
316+
import thing from 'lib';
317+
import * as all from 'lib';
318+
import {s as se} from 'lib';
319+
320+
321+
export const Header = qComponent({
322+
onMount: <div/>,
323+
onRender: qHook(() => <Footer>{thing}{all()}{se()}</Footer>)
324+
});
325+
326+
export const Footer = qComponent();
327+
328+
329+
"#,
330+
EntryStrategy::Single,
331+
MinifyMode::Minify,
332+
true,
333+
);
334+
}
335+
310336
// fn test_fixture(folder: &str) {
311337
// let res = transform_workdir(&FSConfig {
312338
// project_root: folder.to_string(),

0 commit comments

Comments
 (0)