@startupjs/babel-plugin-startupjs 0.61.2 → 0.61.3

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.
Files changed (2) hide show
  1. package/index.js +121 -30
  2. package/package.json +2 -2
package/index.js CHANGED
@@ -3,47 +3,95 @@ const { join } = require('path')
3
3
 
4
4
  const MAGIC_MODULE_NAME = 'startupjs-ui'
5
5
 
6
- module.exports = function ({ template, types: t }) {
6
+ module.exports = function ({ template, types: t }, { asyncImports = false } = {}) {
7
7
  const buildImport = template('import %%name%% from %%source%%')
8
8
  const buildExport = template('export { default as %%name%% } from %%source%%')
9
+ // when asyncImports is enabled, we replace imports with dynamic imports:
10
+ // import { Button, Card } from 'startupjs-ui'
11
+ // -->
12
+ // const [Button, Card] = await Promise.all([
13
+ // import('startupjs-ui/Button').then(m => m.default),
14
+ // import('startupjs-ui/Card').then(m => m.default)
15
+ // ])
16
+ // Note that this currently does not work in Expo because React Native
17
+ // does not support top-level await yet.
18
+ const buildAsyncImport = source => {
19
+ const importCall = t.callExpression(t.import(), [t.stringLiteral(source)])
20
+ return t.callExpression(
21
+ t.memberExpression(importCall, t.identifier('then')),
22
+ [t.arrowFunctionExpression(
23
+ [t.identifier('m')],
24
+ t.memberExpression(t.identifier('m'), t.identifier('default'))
25
+ )]
26
+ )
27
+ }
28
+ const buildPromiseAll = (names, imports) => t.variableDeclaration('const', [
29
+ t.variableDeclarator(
30
+ t.arrayPattern(names),
31
+ t.awaitExpression(
32
+ t.callExpression(
33
+ t.memberExpression(t.identifier('Promise'), t.identifier('all')),
34
+ [t.arrayExpression(imports)]
35
+ )
36
+ )
37
+ )
38
+ ])
9
39
 
10
40
  return {
11
41
  name: 'Unwrap imports for tree shaking.',
12
42
  visitor: {
13
43
  Program ($this) {
14
44
  let executed = false
45
+ const asyncImportNames = []
46
+ const asyncImportExpressions = []
47
+ const asyncExportSpecifiers = []
15
48
  $this.traverse({
16
- ImportDeclaration ($this) {
17
- if ($this.get('source').node.value !== MAGIC_MODULE_NAME) return
49
+ ImportDeclaration ($path) {
50
+ if ($path.get('source').node.value !== MAGIC_MODULE_NAME) return
18
51
  let transformed = false // needed to prevent infinite loops
19
- const theImports = $this.get('specifiers')
20
- .map($specifier => {
21
- // pluck out into a separate import
22
- if ($specifier.isImportSpecifier()) {
23
- transformed = true
24
- const originalName = $specifier.get('imported').node.name
25
- if (!checkNamedExportExists(originalName)) {
26
- throw $specifier.buildCodeFrameError(
27
- `Named export "${originalName}" does not exist in "${MAGIC_MODULE_NAME}" "exports" field in package.json`
28
- )
29
- }
30
- const importedName = $specifier.get('local').node.name
31
- return buildImport({
52
+ const theImports = []
53
+ $path.get('specifiers').forEach($specifier => {
54
+ // pluck out into a separate import
55
+ if ($specifier.isImportSpecifier()) {
56
+ transformed = true
57
+ const originalName = $specifier.get('imported').node.name
58
+ if (!checkNamedExportExists(originalName)) {
59
+ throw $specifier.buildCodeFrameError(
60
+ `Named export "${originalName}" does not exist in "${MAGIC_MODULE_NAME}" "exports" field in package.json`
61
+ )
62
+ }
63
+ const importedName = $specifier.get('local').node.name
64
+ if (asyncImports) {
65
+ asyncImportNames.push(t.identifier(importedName))
66
+ asyncImportExpressions.push(buildAsyncImport(`${MAGIC_MODULE_NAME}/${originalName}`))
67
+ } else {
68
+ theImports.push(buildImport({
32
69
  name: importedName,
33
70
  source: `${MAGIC_MODULE_NAME}/${originalName}`
34
- })
71
+ }))
35
72
  }
36
- // pass as is
37
- return t.importDeclaration([$specifier.node], t.stringLiteral(MAGIC_MODULE_NAME))
38
- })
73
+ return
74
+ }
75
+ // pass as is
76
+ theImports.push(t.importDeclaration([$specifier.node], t.stringLiteral(MAGIC_MODULE_NAME)))
77
+ })
39
78
  if (!transformed) return
40
- $this.replaceWithMultiple(theImports)
79
+ if (asyncImports) {
80
+ if (theImports.length > 0) {
81
+ $path.replaceWithMultiple(theImports)
82
+ } else {
83
+ $path.remove()
84
+ }
85
+ } else {
86
+ $path.replaceWithMultiple(theImports)
87
+ }
41
88
  executed = true
42
89
  },
43
- ExportNamedDeclaration ($this) {
44
- if ($this.get('source')?.node?.value !== MAGIC_MODULE_NAME) return
45
- const theExports = $this.get('specifiers')
46
- .map($specifier => {
90
+ ExportNamedDeclaration ($path) {
91
+ if ($path.get('source')?.node?.value !== MAGIC_MODULE_NAME) return
92
+ const specifiers = $path.get('specifiers')
93
+ if (asyncImports) {
94
+ specifiers.forEach($specifier => {
47
95
  const originalName = $specifier.get('local').node.name
48
96
  if (!checkNamedExportExists(originalName)) {
49
97
  throw $specifier.buildCodeFrameError(
@@ -51,15 +99,58 @@ module.exports = function ({ template, types: t }) {
51
99
  )
52
100
  }
53
101
  const exportedName = $specifier.get('exported').node.name
54
- return buildExport({
55
- name: exportedName,
56
- source: `${MAGIC_MODULE_NAME}/${originalName}`
57
- })
102
+ const localId = t.identifier(exportedName)
103
+ asyncImportNames.push(localId)
104
+ asyncImportExpressions.push(buildAsyncImport(`${MAGIC_MODULE_NAME}/${originalName}`))
105
+ asyncExportSpecifiers.push(t.exportSpecifier(localId, t.identifier(exportedName)))
106
+ })
107
+ if (specifiers.length > 0) {
108
+ $path.remove()
109
+ executed = true
110
+ }
111
+ return
112
+ }
113
+ const theExports = specifiers.map($specifier => {
114
+ const originalName = $specifier.get('local').node.name
115
+ if (!checkNamedExportExists(originalName)) {
116
+ throw $specifier.buildCodeFrameError(
117
+ `Named export "${originalName}" does not exist in "${MAGIC_MODULE_NAME}" "exports" field in package.json`
118
+ )
119
+ }
120
+ const exportedName = $specifier.get('exported').node.name
121
+ return buildExport({
122
+ name: exportedName,
123
+ source: `${MAGIC_MODULE_NAME}/${originalName}`
58
124
  })
59
- $this.replaceWithMultiple(theExports)
125
+ })
126
+ $path.replaceWithMultiple(theExports)
60
127
  executed = true
61
128
  }
62
129
  })
130
+ if (asyncImports && asyncImportNames.length > 0) {
131
+ const promiseAll = buildPromiseAll(asyncImportNames, asyncImportExpressions)
132
+ const bodyPaths = $this.get('body')
133
+ let lastImportPath
134
+ bodyPaths.forEach($path => {
135
+ if ($path.isImportDeclaration()) lastImportPath = $path
136
+ })
137
+ let promiseAllPath
138
+ if (lastImportPath) {
139
+ promiseAllPath = lastImportPath.insertAfter(promiseAll)[0]
140
+ } else {
141
+ $this.unshiftContainer('body', promiseAll)
142
+ promiseAllPath = $this.get('body.0')
143
+ }
144
+ if (asyncExportSpecifiers.length > 0) {
145
+ const exportDeclaration = t.exportNamedDeclaration(null, asyncExportSpecifiers)
146
+ if (promiseAllPath) {
147
+ promiseAllPath.insertAfter(exportDeclaration)
148
+ } else {
149
+ $this.pushContainer('body', exportDeclaration)
150
+ }
151
+ }
152
+ executed = true
153
+ }
63
154
  // re-crawl to update scope bindings
64
155
  if (executed) $this.scope.crawl()
65
156
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@startupjs/babel-plugin-startupjs",
3
- "version": "0.61.2",
3
+ "version": "0.61.3",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -31,5 +31,5 @@
31
31
  "babel-plugin-tester": "^9.1.0",
32
32
  "jest": "^29.2.1"
33
33
  },
34
- "gitHead": "00533aa32f235351daadab676a87ca9db0252cbd"
34
+ "gitHead": "4d14704ebbbcddd4de51dc8b1f5432f1f855b544"
35
35
  }