@swc-contrib/mut-cjs-exports 10.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) magic-akari
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # [SWC plugin] mutable CJS exports
2
+
3
+ Migrated from https://github.com/magic-akari/swc_mut_cjs_exports
4
+
5
+ This is a SWC plugin to emit mutable CJS exports.
6
+
7
+ This SWC plugin has only been tested for compatibility with jest. It should be used with `@swc/jest`.
8
+
9
+ This project was previously called jest_workaround
10
+
11
+ ## plugin version
12
+
13
+ https://swc.rs/docs/plugin/selecting-swc-core
14
+
15
+ ## usage
16
+
17
+ install
18
+
19
+ ```bash
20
+ npm i -D jest @swc/core @swc/jest @swc-contrib/mut-cjs-exports
21
+ ```
22
+
23
+ ```js
24
+ // jest.config.js
25
+ const fs = require("node:fs");
26
+
27
+ const swcrc = JSON.parse(fs.readFileSync(".swcrc", "utf8"));
28
+
29
+ // If you have other plugins, change this line.
30
+ ((swcrc.jsc ??= {}).experimental ??= {}).plugins = [
31
+ ["@swc-contrib/mut-cjs-exports", {}],
32
+ ];
33
+
34
+ module.exports = {
35
+ transform: {
36
+ "^.+\\.(t|j)sx?$": ["@swc/jest", swcrc],
37
+ },
38
+ };
39
+ ```
40
+
41
+ Alternative implementation without .swcrc file
42
+
43
+ ```JavaScript
44
+ // jest.config.js
45
+
46
+ module.exports = {
47
+ transform: {
48
+ "^.+\\.(t|j)sx?$": [
49
+ "@swc/jest",
50
+ {
51
+ jsc: {
52
+ experimental: {
53
+ plugins: [["@swc-contrib/mut-cjs-exports", {}]],
54
+ },
55
+ },
56
+ },
57
+ ],
58
+ },
59
+ };
60
+ ```
61
+
62
+ Make sure that `module.type` is `commonjs` in your `.swcrc` since this plugin
63
+ does not touch non-workaround parts, such as import statements.
64
+
65
+ ## FAQ
66
+
67
+ #### 1. When do I need this?
68
+
69
+ If you're using the swc compiler to transform your code to comply with the ESM
70
+ specification, but you're also using Jest to test it in a CJS environment, you
71
+ may encounter issues due to the immutable issue of `exports`.
72
+
73
+ This plugin can help by transforming the `export` statements into mutable
74
+ `exports`.
75
+
76
+ #### 2. Do I have a better choice?
77
+
78
+ You may have other options depending on your specific needs:
79
+
80
+ - If you're able to run Jest in an ESM environment, you can use swc to transpile
81
+ TypeScript/JSX syntax or downgrade JavaScript syntax without module
82
+ conversion. Simply set the value of `module.type` to `es6` to achieve this.
83
+
84
+ - It's possible that some issues related to running Jest in an ESM environment
85
+ will be resolved over time. Keep an eye on
86
+ [facebook/jest#9430](https://github.com/facebook/jest/issues/9430) for
87
+ updates.
88
+
89
+ - If you don't need the behavior of ESM specifically, you can stick with the CJS
90
+ syntax to get the CJS behavior of `exports`.
91
+
92
+ These options may be worth considering before using this plugin.
93
+
94
+ CJS syntax
95
+
96
+ ```JavaScript
97
+ exports.foo = function foo() {
98
+ return 42;
99
+ };
100
+ ```
101
+
102
+ CTS(CJS in TypeScript) syntax
103
+
104
+ ```TypeScript
105
+ export = {
106
+ foo: function foo() {
107
+ return 42;
108
+ },
109
+ };
110
+ ```
111
+
112
+ Notes:
113
+
114
+ - ESM style export means immutable exports when transformed into CJS
115
+ - ESM style import means hoisted require when transformed into CJS
116
+
117
+ #### 3. After upgrading the plugin version, the changes have not taken effect.
118
+
119
+ This is a known issue. You could remove the Jest cache by running
120
+ [`jest --clearCache`](https://jestjs.io/docs/cli#--clearcache) as a workaround.
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@swc-contrib/mut-cjs-exports",
3
+ "version": "10.7.0",
4
+ "description": "[SWC plugin] mutable CJS exports",
5
+ "author": "magic-akari <akari.ccino@gmail.com>",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "swc-plugin",
9
+ "swc",
10
+ "jest",
11
+ "cjs",
12
+ "commonjs"
13
+ ],
14
+ "main": "swc_mut_cjs_exports.wasm",
15
+ "files": [
16
+ "src",
17
+ "swc_mut_cjs_exports.wasm"
18
+ ],
19
+ "homepage": "https://github.com/magic-akari/swc_mut_cjs_exports#readme",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/magic-akari/swc_mut_cjs_exports.git"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/magic-akari/swc_mut_cjs_exports/issues"
26
+ },
27
+ "devDependencies": {
28
+ "@swc/core": "^1.10.0",
29
+ "@swc/jest": "^0.2.37",
30
+ "@types/jest": "^29.5.11",
31
+ "jest": "^29.7.0"
32
+ },
33
+ "peerDependencies": {
34
+ "@swc/core": "^1.10.0",
35
+ "@swc/jest": "^0.2.37"
36
+ },
37
+ "scripts": {
38
+ "build": "cargo build --release --target wasm32-unknown-unknown && cp ../../target/wasm32-unknown-unknown/release/swc_mut_cjs_exports.wasm .",
39
+ "build:debug": "cargo build --target wasm32-unknown-unknown && cp ../../target/wasm32-unknown-unknown/debug/swc_mut_cjs_exports.wasm ./swc_mut_cjs_exports_debug.wasm",
40
+ "test": "pnpm run build:debug && jest"
41
+ }
42
+ }
package/src/lib.rs ADDED
@@ -0,0 +1,325 @@
1
+ mod local_export_strip;
2
+ mod utils;
3
+
4
+ use local_export_strip::LocalExportStrip;
5
+ use rustc_hash::FxHashSet;
6
+ use swc_core::{
7
+ common::{util::take::Take, Mark, SyntaxContext, DUMMY_SP},
8
+ ecma::{
9
+ ast::*,
10
+ utils::{
11
+ for_each_binding_ident, member_expr, private_ident, quote_ident, quote_str,
12
+ ExprFactory, IntoIndirectCall,
13
+ },
14
+ visit::{noop_visit_mut_type, VisitMut, VisitMutWith},
15
+ },
16
+ plugin::{plugin_transform, proxies::TransformPluginProgramMetadata},
17
+ };
18
+ use utils::{emit_export_stmts, object_define_property};
19
+
20
+ #[derive(Debug)]
21
+ pub struct TransformVisitor {
22
+ unresolved_mark: Mark,
23
+
24
+ export_decl_id: FxHashSet<Id>,
25
+ }
26
+
27
+ impl VisitMut for TransformVisitor {
28
+ noop_visit_mut_type!();
29
+
30
+ fn visit_mut_script(&mut self, _: &mut Script) {
31
+ // skip
32
+ }
33
+
34
+ fn visit_mut_module(&mut self, n: &mut Module) {
35
+ let mut strip = LocalExportStrip::default();
36
+ n.visit_mut_with(&mut strip);
37
+
38
+ let LocalExportStrip {
39
+ has_export_assign,
40
+ export,
41
+ export_all,
42
+ export_decl_id,
43
+ ..
44
+ } = strip;
45
+
46
+ self.export_decl_id = export_decl_id;
47
+
48
+ let mut stmts: Vec<ModuleItem> = Vec::with_capacity(n.body.len() + 1);
49
+
50
+ if !has_export_assign && !export.is_empty() {
51
+ // keep module env
52
+ stmts.push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
53
+ NamedExport::dummy(),
54
+ )));
55
+
56
+ let exports = self.exports();
57
+
58
+ stmts.extend(
59
+ emit_export_stmts(exports, export)
60
+ .into_iter()
61
+ .map(Into::into),
62
+ );
63
+
64
+ if !self.export_decl_id.is_empty() {
65
+ n.visit_mut_children_with(self);
66
+ }
67
+ }
68
+
69
+ stmts.extend(export_all.into_iter().map(|id| self.export_all(id)));
70
+
71
+ stmts.extend(n.body.take());
72
+
73
+ n.body = stmts;
74
+ }
75
+
76
+ fn visit_mut_function(&mut self, node: &mut Function) {
77
+ let export_decl_id = self.export_decl_id.clone();
78
+
79
+ for_each_binding_ident(&node.params, |ident| {
80
+ self.export_decl_id.remove(&ident.id.to_id());
81
+ });
82
+
83
+ node.visit_mut_children_with(self);
84
+ self.export_decl_id = export_decl_id;
85
+ }
86
+
87
+ fn visit_mut_prop(&mut self, n: &mut Prop) {
88
+ match n {
89
+ Prop::Shorthand(ref_ident) => {
90
+ if self.export_decl_id.contains(&ref_ident.to_id()) {
91
+ *n = KeyValueProp {
92
+ key: ref_ident.clone().into(),
93
+ value: Box::new(self.exports().make_member(ref_ident.take().into()).into()),
94
+ }
95
+ .into()
96
+ }
97
+ }
98
+ _ => n.visit_mut_children_with(self),
99
+ }
100
+ }
101
+
102
+ fn visit_mut_expr(&mut self, n: &mut Expr) {
103
+ match n {
104
+ Expr::Ident(ref_ident) => {
105
+ if self.export_decl_id.contains(&ref_ident.to_id()) {
106
+ *n = self.exports().make_member(ref_ident.take().into()).into();
107
+ }
108
+ }
109
+
110
+ _ => n.visit_mut_children_with(self),
111
+ };
112
+ }
113
+
114
+ fn visit_mut_tagged_tpl(&mut self, n: &mut TaggedTpl) {
115
+ let is_indirect = n
116
+ .tag
117
+ .as_ident()
118
+ .map(|ident| self.export_decl_id.contains(&ident.to_id()))
119
+ .unwrap_or_default();
120
+
121
+ n.visit_mut_children_with(self);
122
+
123
+ if is_indirect {
124
+ *n = n.take().into_indirect()
125
+ }
126
+ }
127
+
128
+ fn visit_mut_callee(&mut self, n: &mut Callee) {
129
+ match n {
130
+ Callee::Expr(e) if e.is_ident() => {
131
+ let is_indirect_callee = e
132
+ .as_ident()
133
+ .map(|ident| self.export_decl_id.contains(&ident.to_id()))
134
+ .unwrap_or_default();
135
+
136
+ e.visit_mut_with(self);
137
+
138
+ if is_indirect_callee {
139
+ *n = n.take().into_indirect()
140
+ }
141
+ }
142
+
143
+ _ => n.visit_mut_children_with(self),
144
+ }
145
+ }
146
+
147
+ fn visit_mut_jsx_element_name(&mut self, n: &mut JSXElementName) {
148
+ match n {
149
+ JSXElementName::Ident(ref_ident) => {
150
+ if self.export_decl_id.contains(&ref_ident.to_id()) {
151
+ *n = JSXElementName::JSXMemberExpr(
152
+ JSXMemberExpr {
153
+ span: DUMMY_SP,
154
+ obj: self.exports().into(),
155
+ prop: ref_ident.clone().into(),
156
+ }
157
+ .into(),
158
+ );
159
+ }
160
+ }
161
+ _ => n.visit_mut_children_with(self),
162
+ };
163
+ }
164
+ }
165
+
166
+ impl TransformVisitor {
167
+ pub fn new(unresolved_mark: Mark) -> Self {
168
+ Self {
169
+ unresolved_mark,
170
+ export_decl_id: Default::default(),
171
+ }
172
+ }
173
+
174
+ fn exports(&self) -> Ident {
175
+ quote_ident!(
176
+ SyntaxContext::empty().apply_mark(self.unresolved_mark),
177
+ "exports"
178
+ )
179
+ }
180
+
181
+ /// ```JavaScript
182
+ /// Object.keys(_mod).forEach(function (key) {
183
+ /// if (key === "default" || key === "__esModule") return;
184
+ /// if (Object.prototype.hasOwnProperty.call(exports, key)) return;
185
+ /// Object.defineProperty(exports, key, {
186
+ /// enumerable: true,
187
+ /// get: function () {
188
+ /// return mod[key];
189
+ /// },
190
+ /// configurable: true
191
+ /// });
192
+ /// ```
193
+ fn export_all(&self, id: Id) -> ModuleItem {
194
+ let mod_name = Ident::from(id);
195
+ let key = private_ident!("key");
196
+
197
+ member_expr!(Default::default(), DUMMY_SP, Object.keys)
198
+ .as_call(DUMMY_SP, vec![mod_name.clone().as_arg()])
199
+ .make_member(quote_ident!("forEach"))
200
+ .as_call(
201
+ DUMMY_SP,
202
+ vec![Function {
203
+ params: vec![key.clone().into()],
204
+ span: DUMMY_SP,
205
+ body: Some(BlockStmt {
206
+ stmts: vec![
207
+ // if (key === "default" || key === "__esModule") return;
208
+ IfStmt {
209
+ span: DUMMY_SP,
210
+ test: BinExpr {
211
+ span: DUMMY_SP,
212
+ op: op!("||"),
213
+ left: BinExpr {
214
+ span: DUMMY_SP,
215
+ op: op!("==="),
216
+ left: key.clone().into(),
217
+ right: quote_str!("default").into(),
218
+ }
219
+ .into(),
220
+ right: BinExpr {
221
+ span: DUMMY_SP,
222
+ op: op!("==="),
223
+ left: key.clone().into(),
224
+ right: quote_str!("__esModule").into(),
225
+ }
226
+ .into(),
227
+ }
228
+ .into(),
229
+ cons: Box::new(
230
+ ReturnStmt {
231
+ span: DUMMY_SP,
232
+ arg: None,
233
+ }
234
+ .into(),
235
+ ),
236
+ alt: None,
237
+ }
238
+ .into(),
239
+ // if (Object.prototype.hasOwnProperty.call(exports, key)) return;
240
+ IfStmt {
241
+ span: DUMMY_SP,
242
+ test: Box::new(
243
+ member_expr!(
244
+ Default::default(),
245
+ DUMMY_SP,
246
+ Object.prototype.hasOwnProperty.call
247
+ )
248
+ .as_call(
249
+ DUMMY_SP,
250
+ vec![self.exports().as_arg(), key.clone().as_arg()],
251
+ ),
252
+ ),
253
+ cons: Box::new(
254
+ ReturnStmt {
255
+ span: DUMMY_SP,
256
+ arg: None,
257
+ }
258
+ .into(),
259
+ ),
260
+ alt: None,
261
+ }
262
+ .into(),
263
+ // Object.defineProperty(exports, key, {
264
+ // enumerable: true,
265
+ // get: function () {
266
+ // return mod[key];
267
+ // },
268
+ // configurable: true
269
+ // });
270
+ object_define_property(
271
+ self.exports().as_arg(),
272
+ key.clone().as_arg(),
273
+ ObjectLit {
274
+ span: DUMMY_SP,
275
+ props: vec![
276
+ PropOrSpread::Prop(Box::new(
277
+ KeyValueProp {
278
+ key: quote_ident!("enumerable").into(),
279
+ value: true.into(),
280
+ }
281
+ .into(),
282
+ )),
283
+ PropOrSpread::Prop(Box::new(
284
+ KeyValueProp {
285
+ key: quote_ident!("get").into(),
286
+ value: mod_name
287
+ .clone()
288
+ .computed_member(key.clone())
289
+ .into_lazy_fn(vec![])
290
+ .into(),
291
+ }
292
+ .into(),
293
+ )),
294
+ PropOrSpread::Prop(Box::new(
295
+ KeyValueProp {
296
+ key: quote_ident!("configurable").into(),
297
+ value: true.into(),
298
+ }
299
+ .into(),
300
+ )),
301
+ ],
302
+ }
303
+ .as_arg(),
304
+ )
305
+ .into_stmt(),
306
+ ],
307
+ ..Default::default()
308
+ }),
309
+ ..Default::default()
310
+ }
311
+ .as_arg()],
312
+ )
313
+ .into_stmt()
314
+ .into()
315
+ }
316
+ }
317
+
318
+ #[plugin_transform]
319
+ pub fn process_transform(
320
+ mut program: Program,
321
+ metadata: TransformPluginProgramMetadata,
322
+ ) -> Program {
323
+ program.visit_mut_with(&mut TransformVisitor::new(metadata.unresolved_mark));
324
+ program
325
+ }
@@ -0,0 +1,362 @@
1
+ use std::collections::BTreeMap;
2
+
3
+ use rustc_hash::FxHashSet;
4
+ use swc_core::{
5
+ atoms::Atom,
6
+ common::{util::take::Take, Span, DUMMY_SP},
7
+ ecma::{
8
+ ast::*,
9
+ utils::{find_pat_ids, private_ident, ExprFactory},
10
+ visit::{noop_visit_mut_type, VisitMut, VisitMutWith},
11
+ },
12
+ };
13
+
14
+ use crate::utils::{key_from_export_name, local_ident_from_export_name};
15
+
16
+ pub type Export = BTreeMap<Atom, ExportItem>;
17
+
18
+ #[derive(Debug)]
19
+ pub struct ExportItem(Span, Ident);
20
+
21
+ impl ExportItem {
22
+ pub fn new(export_name_span: Span, local_ident: Ident) -> Self {
23
+ Self(export_name_span, local_ident)
24
+ }
25
+
26
+ pub fn export_name_span(&self) -> Span {
27
+ self.0
28
+ }
29
+
30
+ pub fn into_local_ident(self) -> Ident {
31
+ self.1
32
+ }
33
+ }
34
+
35
+ #[derive(Debug, Default)]
36
+ pub(crate) struct LocalExportStrip {
37
+ pub(crate) has_export_assign: bool,
38
+ pub(crate) export: Export,
39
+ pub(crate) export_all: FxHashSet<Id>,
40
+ pub(crate) export_decl_id: FxHashSet<Id>,
41
+ export_default: Option<Stmt>,
42
+ }
43
+
44
+ impl VisitMut for LocalExportStrip {
45
+ noop_visit_mut_type!();
46
+
47
+ fn visit_mut_script(&mut self, _: &mut Script) {
48
+ // skip
49
+ }
50
+
51
+ fn visit_mut_module(&mut self, n: &mut Module) {
52
+ let mut list = Vec::with_capacity(n.body.len());
53
+
54
+ for item in n.body.drain(..) {
55
+ match item {
56
+ ModuleItem::Stmt(stmt) => list.push(stmt.into()),
57
+
58
+ ModuleItem::ModuleDecl(mut module_decl) => {
59
+ // collect link meta
60
+ module_decl.visit_mut_with(self);
61
+
62
+ // emit stmt
63
+ match module_decl {
64
+ ModuleDecl::ExportDecl(ExportDecl { decl, .. }) => {
65
+ list.push(Stmt::Decl(decl).into());
66
+ }
67
+ ModuleDecl::ExportNamed(NamedExport { src: None, .. }) => continue,
68
+ ModuleDecl::ExportNamed(
69
+ item @ NamedExport {
70
+ src: Some(..),
71
+ type_only: false,
72
+ ..
73
+ },
74
+ ) => {
75
+ let decl: ModuleDecl = self.convert_export_decl(item).into();
76
+ list.push(decl.into());
77
+ }
78
+ ModuleDecl::ExportAll(
79
+ e @ ExportAll {
80
+ type_only: false, ..
81
+ },
82
+ ) => {
83
+ let decl: ModuleDecl = self.convert_export_all(e).into();
84
+ list.push(decl.into());
85
+ }
86
+ ModuleDecl::ExportDefaultDecl(ExportDefaultDecl {
87
+ decl:
88
+ decl @ (DefaultDecl::Class(ClassExpr {
89
+ ident: Some(..), ..
90
+ })
91
+ | DefaultDecl::Fn(FnExpr {
92
+ ident: Some(..), ..
93
+ })),
94
+ ..
95
+ }) => match decl {
96
+ DefaultDecl::Class(class_expr) => list.extend(
97
+ class_expr
98
+ .as_class_decl()
99
+ .map(|decl| Stmt::Decl(Decl::Class(decl)))
100
+ .map(Into::into),
101
+ ),
102
+ DefaultDecl::Fn(fn_expr) => list.extend(
103
+ fn_expr
104
+ .as_fn_decl()
105
+ .map(|decl| Stmt::Decl(Decl::Fn(decl)))
106
+ .map(Into::into),
107
+ ),
108
+ _ => unreachable!(),
109
+ },
110
+ ModuleDecl::ExportDefaultExpr(..) => {
111
+ list.extend(self.export_default.take().map(From::from))
112
+ }
113
+ ModuleDecl::TsExportAssignment(..) => {
114
+ self.has_export_assign = true;
115
+ list.push(module_decl.into());
116
+ }
117
+ _ => list.push(module_decl.into()),
118
+ };
119
+ }
120
+ };
121
+ }
122
+
123
+ n.body = list;
124
+ }
125
+
126
+ /// ```javascript
127
+ /// export const foo = 1, bar = 2, { baz } = { baz: 3 };
128
+ /// export let a = 1, [b] = [2];
129
+ /// export function x() {}
130
+ /// export class y {}
131
+ /// ```
132
+ /// ->
133
+ /// ```javascript
134
+ /// const foo = 1, bar = 2, { baz } = { baz: 3 };
135
+ /// let a = 1, [b] = [2];
136
+ /// function x() {}
137
+ /// class y {}
138
+ /// ```
139
+ fn visit_mut_export_decl(&mut self, n: &mut ExportDecl) {
140
+ match &n.decl {
141
+ Decl::Class(ClassDecl { ident, .. }) | Decl::Fn(FnDecl { ident, .. }) => {
142
+ self.export.insert(
143
+ ident.sym.clone(),
144
+ ExportItem::new(ident.span, ident.clone()),
145
+ );
146
+ }
147
+
148
+ Decl::Var(v) => {
149
+ let ids = find_pat_ids::<_, Ident>(&v.decls);
150
+
151
+ self.export_decl_id.extend(ids.iter().map(Ident::to_id));
152
+
153
+ self.export.extend(
154
+ find_pat_ids::<_, Ident>(&v.decls)
155
+ .into_iter()
156
+ .map(|id| (id.sym.clone(), ExportItem::new(id.span, id))),
157
+ );
158
+ }
159
+ _ => {}
160
+ };
161
+ }
162
+
163
+ /// ```javascript
164
+ /// export { foo, foo as bar, foo as "baz" };
165
+ /// export { "foo", foo as bar, "foo" as "baz" } from "mod";
166
+ /// export * as foo from "mod";
167
+ /// export * as "bar" from "mod";
168
+ /// ```
169
+ fn visit_mut_named_export(&mut self, n: &mut NamedExport) {
170
+ if n.type_only || n.src.is_some() {
171
+ return;
172
+ }
173
+
174
+ let NamedExport { specifiers, .. } = n.take();
175
+
176
+ self.export.extend(specifiers.into_iter().map(|e| match e {
177
+ ExportSpecifier::Namespace(..) => {
178
+ unreachable!("`export *` without src is invalid")
179
+ }
180
+ ExportSpecifier::Default(..) => {
181
+ unreachable!("`export foo` without src is invalid")
182
+ }
183
+ ExportSpecifier::Named(ExportNamedSpecifier { orig, exported, .. }) => {
184
+ let orig = match orig {
185
+ ModuleExportName::Ident(id) => id,
186
+ ModuleExportName::Str(_) => {
187
+ unreachable!(r#"`export {{ "foo" }}` without src is invalid"#)
188
+ }
189
+ };
190
+
191
+ if let Some(exported) = exported {
192
+ let (export_name, export_name_span) = match exported {
193
+ ModuleExportName::Ident(Ident { span, sym, .. }) => (sym, span),
194
+ ModuleExportName::Str(Str { span, value, .. }) => (value, span),
195
+ };
196
+
197
+ (export_name, ExportItem::new(export_name_span, orig))
198
+ } else {
199
+ (orig.sym.clone(), ExportItem::new(orig.span, orig))
200
+ }
201
+ }
202
+ }))
203
+ }
204
+
205
+ /// ```javascript
206
+ /// export default class foo {};
207
+ /// export default class {};
208
+ /// export default function bar () {};
209
+ /// export default function () {};
210
+ /// ```
211
+ /// ->
212
+ /// ```javascript
213
+ /// class foo {};
214
+ /// class _default {};
215
+ /// function bar () {};
216
+ /// function _default () {};
217
+ /// ```
218
+ fn visit_mut_export_default_decl(&mut self, n: &mut ExportDefaultDecl) {
219
+ match &mut n.decl {
220
+ DefaultDecl::Class(class_expr) => {
221
+ if let Some(ident) = class_expr.ident.clone() {
222
+ self.export
223
+ .insert("default".into(), ExportItem::new(n.span, ident));
224
+ }
225
+ }
226
+ DefaultDecl::Fn(fn_expr) => {
227
+ if let Some(ident) = fn_expr.ident.clone() {
228
+ self.export
229
+ .insert("default".into(), ExportItem::new(n.span, ident));
230
+ }
231
+ }
232
+ DefaultDecl::TsInterfaceDecl(_) => {}
233
+ }
234
+ }
235
+
236
+ /// ```javascript
237
+ /// export default foo;
238
+ /// export default 1
239
+ /// ```
240
+ /// ->
241
+ /// ```javascript
242
+ /// var _default = foo;
243
+ /// var _default = 1;
244
+ /// ```
245
+ fn visit_mut_export_default_expr(&mut self, n: &mut ExportDefaultExpr) {
246
+ let ident = private_ident!(n.span, "_default");
247
+
248
+ self.export
249
+ .insert("default".into(), ExportItem::new(n.span, ident.clone()));
250
+
251
+ self.export_default = Some(Stmt::Decl(
252
+ n.expr
253
+ .take()
254
+ .into_var_decl(VarDeclKind::Const, ident.into())
255
+ .into(),
256
+ ));
257
+ }
258
+ }
259
+
260
+ impl LocalExportStrip {
261
+ fn convert_export_decl(&mut self, n: NamedExport) -> ImportDecl {
262
+ let NamedExport {
263
+ span,
264
+ specifiers,
265
+ src,
266
+ type_only,
267
+ with,
268
+ } = n;
269
+
270
+ let src = src.unwrap();
271
+
272
+ let specifiers = specifiers
273
+ .into_iter()
274
+ .flat_map(|s| self.convert_export_specifier(s))
275
+ .collect();
276
+
277
+ ImportDecl {
278
+ span,
279
+ specifiers,
280
+ src,
281
+ type_only,
282
+ with,
283
+ phase: Default::default(),
284
+ }
285
+ }
286
+
287
+ fn convert_export_specifier(&mut self, s: ExportSpecifier) -> Option<ImportSpecifier> {
288
+ match s {
289
+ ExportSpecifier::Namespace(ExportNamespaceSpecifier { span, name }) => {
290
+ let (export_name, export_span) = key_from_export_name(&name);
291
+ let local = local_ident_from_export_name(name);
292
+ self.export
293
+ .insert(export_name, ExportItem::new(export_span, local.clone()));
294
+
295
+ Some(ImportSpecifier::Namespace(ImportStarAsSpecifier {
296
+ span,
297
+ local,
298
+ }))
299
+ }
300
+ ExportSpecifier::Default(ExportDefaultSpecifier { exported }) => {
301
+ let (export_name, export_span) = (exported.sym.clone(), exported.span);
302
+ let local = exported.into_private();
303
+ self.export
304
+ .insert(export_name, ExportItem::new(export_span, local.clone()));
305
+
306
+ Some(ImportSpecifier::Default(ImportDefaultSpecifier {
307
+ local,
308
+ span: DUMMY_SP,
309
+ }))
310
+ }
311
+ ExportSpecifier::Named(ExportNamedSpecifier {
312
+ span,
313
+ orig,
314
+ exported,
315
+ is_type_only: false,
316
+ }) => {
317
+ // export { "x-1" as "y-1" } from "foo"
318
+ // ->
319
+ // import { "x-1" as x1 } from "foo"
320
+ // export { x1 as "y-1" }
321
+ let name = exported.as_ref().unwrap_or(&orig);
322
+
323
+ let (export_name, export_span) = key_from_export_name(name);
324
+ let local = local_ident_from_export_name(orig.clone());
325
+ self.export
326
+ .insert(export_name, ExportItem::new(export_span, local.clone()));
327
+
328
+ Some(ImportSpecifier::Named(ImportNamedSpecifier {
329
+ span,
330
+ local,
331
+ imported: Some(orig),
332
+ is_type_only: false,
333
+ }))
334
+ }
335
+ _ => None,
336
+ }
337
+ }
338
+
339
+ fn convert_export_all(&mut self, e: ExportAll) -> ImportDecl {
340
+ let ExportAll {
341
+ span, src, with, ..
342
+ } = e;
343
+
344
+ let mod_name = private_ident!("mod");
345
+
346
+ self.export_all.insert(mod_name.to_id());
347
+
348
+ let star = ImportStarAsSpecifier {
349
+ span,
350
+ local: mod_name.clone(),
351
+ };
352
+
353
+ ImportDecl {
354
+ span,
355
+ specifiers: vec![star.into()],
356
+ src,
357
+ type_only: false,
358
+ with,
359
+ phase: Default::default(),
360
+ }
361
+ }
362
+ }
package/src/utils.rs ADDED
@@ -0,0 +1,143 @@
1
+ use swc_core::{
2
+ atoms::Atom,
3
+ common::{Span, DUMMY_SP},
4
+ ecma::{
5
+ ast::*,
6
+ utils::{member_expr, private_ident, quote_ident, quote_str, ExprFactory},
7
+ },
8
+ };
9
+
10
+ use crate::local_export_strip::Export;
11
+
12
+ /// ```javascript
13
+ /// {
14
+ /// get() { return ident; }
15
+ /// }
16
+ /// ```
17
+ pub(crate) fn prop_method_getter(ident: Ident) -> Prop {
18
+ let key = quote_ident!("get").into();
19
+
20
+ MethodProp {
21
+ key,
22
+ function: ident.into_lazy_fn(Default::default()).into(),
23
+ }
24
+ .into()
25
+ }
26
+
27
+ /// ```javascript
28
+ /// {
29
+ /// set(v) { ident = v; }
30
+ /// }
31
+ /// ```
32
+ pub(crate) fn prop_method_setter(ident: Ident) -> Prop {
33
+ let key = quote_ident!("set").into();
34
+
35
+ let setter_param = private_ident!("v");
36
+ let params = vec![setter_param.clone().into()];
37
+
38
+ let body = BlockStmt {
39
+ stmts: vec![setter_param
40
+ .make_assign_to(op!("="), ident.clone().into())
41
+ .into_stmt()],
42
+ ..Default::default()
43
+ };
44
+
45
+ MethodProp {
46
+ key,
47
+ function: Function {
48
+ params,
49
+ body: Some(body),
50
+ ..Default::default()
51
+ }
52
+ .into(),
53
+ }
54
+ .into()
55
+ }
56
+
57
+ /// Creates
58
+ ///
59
+ ///```js
60
+ ///
61
+ /// Object.defineProperty(target, prop_name, {
62
+ /// ...props
63
+ /// });
64
+ /// ```
65
+ pub(super) fn object_define_property(
66
+ target: ExprOrSpread,
67
+ prop_name: ExprOrSpread,
68
+ descriptor: ExprOrSpread,
69
+ ) -> Expr {
70
+ member_expr!(Default::default(), DUMMY_SP, Object.defineProperty)
71
+ .as_call(DUMMY_SP, vec![target, prop_name, descriptor])
72
+ }
73
+
74
+ pub(crate) fn object_define_enumerable_configurable(
75
+ target: ExprOrSpread,
76
+ prop_name: ExprOrSpread,
77
+ getter: PropOrSpread,
78
+ setter: PropOrSpread,
79
+ ) -> Expr {
80
+ object_define_property(
81
+ target,
82
+ prop_name,
83
+ ObjectLit {
84
+ span: DUMMY_SP,
85
+ props: vec![
86
+ PropOrSpread::Prop(Box::new(
87
+ KeyValueProp {
88
+ key: quote_ident!("enumerable").into(),
89
+ value: Box::new(true.into()),
90
+ }
91
+ .into(),
92
+ )),
93
+ getter,
94
+ setter,
95
+ PropOrSpread::Prop(Box::new(
96
+ KeyValueProp {
97
+ key: quote_ident!("configurable").into(),
98
+ value: Box::new(true.into()),
99
+ }
100
+ .into(),
101
+ )),
102
+ ],
103
+ }
104
+ .as_arg(),
105
+ )
106
+ }
107
+
108
+ pub(crate) fn emit_export_stmts(exports: Ident, export: Export) -> Vec<Stmt> {
109
+ export
110
+ .into_iter()
111
+ .map(|(export_name, export_item)| {
112
+ let prop_name = quote_str!(export_item.export_name_span(), export_name);
113
+ let local_ident = export_item.into_local_ident();
114
+
115
+ object_define_enumerable_configurable(
116
+ exports.clone().as_arg(),
117
+ prop_name.as_arg(),
118
+ prop_method_getter(local_ident.clone()).into(),
119
+ prop_method_setter(local_ident).into(),
120
+ )
121
+ .into_stmt()
122
+ })
123
+ .collect()
124
+ }
125
+
126
+ pub(crate) fn key_from_export_name(n: &ModuleExportName) -> (Atom, Span) {
127
+ match n {
128
+ ModuleExportName::Ident(ident) => (ident.sym.clone(), ident.span),
129
+ ModuleExportName::Str(str) => (str.value.clone(), str.span),
130
+ }
131
+ }
132
+
133
+ pub(crate) fn local_ident_from_export_name(n: ModuleExportName) -> Ident {
134
+ let name = match n {
135
+ ModuleExportName::Ident(ident) => ident.sym,
136
+ ModuleExportName::Str(str) => str.value,
137
+ };
138
+
139
+ match Ident::verify_symbol(&name) {
140
+ Ok(_) => private_ident!(name),
141
+ Err(s) => private_ident!(s),
142
+ }
143
+ }
Binary file