@@ -82,11 +82,13 @@ EXPORTS_LITERAL_COMPUTED_ASSIGN: EXPORTS_IDENTIFIER COMMENT_SPACE `[` COMMENT_SP |
82 | 82 |
|
83 | 83 | EXPORTS_LITERAL_PROP: (IDENTIFIER (COMMENT_SPACE `:` COMMENT_SPACE IDENTIFIER)?) | (IDENTIFIER_STRING COMMENT_SPACE `:` COMMENT_SPACE IDENTIFIER) |
84 | 84 |
|
| 85 | +EXPORTS_SPREAD: `...` COMMENT_SPACE (IDENTIFIER | REQUIRE) |
| 86 | +
|
85 | 87 | EXPORTS_MEMBER: EXPORTS_DOT_ASSIGN | EXPORTS_LITERAL_COMPUTED_ASSIGN |
86 | 88 |
|
87 | | -EXPORTS_DEFINE: `Object` COMMENT_SPACE `.` COMMENT_SPACE `defineProperty COMMENT_SPACE `(` EXPORTS_IDENTIFIER COMMENT_SPACE `,` COMMENT_SPACE IDENTIFIER_STRING |
| 89 | +ES_MODULE_DEFINE: `Object` COMMENT_SPACE `.` COMMENT_SPACE `defineProperty COMMENT_SPACE `(` COMMENT_SPACE `__esModule` COMMENT_SPACE `,` COMMENT_SPACE IDENTIFIER_STRING |
88 | 90 |
|
89 | | -EXPORTS_LITERAL: MODULE_EXPORTS COMMENT_SPACE `=` COMMENT_SPACE `{` COMMENT_SPACE (EXPORTS_LITERAL_PROP COMMENT_SPACE `,` COMMENT_SPACE)+ `}` |
| 91 | +EXPORTS_LITERAL: MODULE_EXPORTS COMMENT_SPACE `=` COMMENT_SPACE `{` COMMENT_SPACE (EXPORTS_LITERAL_PROP | EXPORTS_SPREAD) COMMENT_SPACE `,` COMMENT_SPACE)+ `}` |
90 | 92 |
|
91 | 93 | REQUIRE: `require` COMMENT_SPACE `(` COMMENT_SPACE STRING_LITERAL COMMENT_SPACE `)` |
92 | 94 |
|
@@ -101,15 +103,22 @@ EXPORT_STAR_LIB: `Object.keys(` IDENTIFIER$1 `).forEach(function (` IDENTIFIER$2 |
101 | 103 | `if (` IDENTIFIER$2 `===` ( `'default'` | `"default"` ) `||` IDENTIFIER$2 `===` ( '__esModule' | `"__esModule"` ) `) return` `;`? | |
102 | 104 | `if (` IDENTIFIER$2 `!==` ( `'default'` | `"default"` ) `)` |
103 | 105 | ) |
| 106 | + ( |
| 107 | + `if (` IDENTIFIER$2 `in` EXPORTS_IDENTIFIER `&&` EXPORTS_IDENTIFIER `[` IDENTIFIER$2 `] ===` IDENTIFIER$1 `[` IDENTIFIER$2 `]) return` `;`? |
| 108 | + )? |
104 | 109 | ( |
105 | 110 | EXPORTS_IDENTIFIER `[` IDENTIFIER$2 `] =` IDENTIFIER$1 `[` IDENTIFIER$2 `]` `;`? | |
106 | 111 | `Object.defineProperty(` EXPORTS_IDENTIFIER `, ` IDENTIFIER$2 `, { enumerable: true, get: function () { return ` IDENTIFIER$1 `[` IDENTIFIER$2 `]` `;`? } })` `;`? |
107 | 112 | ) |
108 | 113 | `})` |
109 | 114 | ``` |
110 | 115 |
|
111 | | -* The returned export names are the matched `IDENTIFIER` and `IDENTIFIER_STRING` slots for all `EXPORTS_MEMBER`, `EXPORTS_DEFINE` and `EXPORTS_LITERAL` matches. |
112 | | -* The reexport specifiers are taken to be the `STRING_LITERAL` slots of all `MODULE_EXPORTS_ASSIGN` as well as all _top-level_ `EXPORT_STAR` `REQUIRE` matches and `EXPORTS_ASSIGN` matches whose `IDENTIFIER` also matches the first `IDENTIFIER` in `EXPORT_STAR_LIB`. |
| 116 | +* The returned export names are taken to be the combination of: |
| 117 | + 1. `IDENTIFIER` and `IDENTIFIER_STRING` slots for all `EXPORTS_MEMBER` and `EXPORTS_LITERAL` matches. |
| 118 | + 2. `__esModule` if there is an `ES_MODULE_DEFINE` match. |
| 119 | +* The reexport specifiers are taken to be the the combination of: |
| 120 | + 1. The `REQUIRE` matches of the last matched of either `MODULE_EXPORTS_ASSIGN` or `EXPORTS_LITERAL`. |
| 121 | + 2. All _top-level_ `EXPORT_STAR` `REQUIRE` matches and `EXPORTS_ASSIGN` matches whose `IDENTIFIER` also matches the first `IDENTIFIER` in `EXPORT_STAR_LIB`. |
113 | 122 |
|
114 | 123 | ### Parsing Examples |
115 | 124 |
|
@@ -118,11 +127,10 @@ EXPORT_STAR_LIB: `Object.keys(` IDENTIFIER$1 `).forEach(function (` IDENTIFIER$2 |
118 | 127 | The basic matching rules for named exports are `exports.name`, `exports['name']` or `Object.defineProperty(exports, 'name', ...)`. This matching is done without scope analysis and regardless of the expression position: |
119 | 128 |
|
120 | 129 | ```js |
121 | | -// DETECTS EXPORTS: a, b, c |
| 130 | +// DETECTS EXPORTS: a, b |
122 | 131 | (function (exports) { |
123 | 132 | exports.a = 'a'; |
124 | 133 | exports['b'] = 'b'; |
125 | | - Object.defineProperty(exports, 'c', { value: 'c' }); |
126 | 134 | })(exports); |
127 | 135 | ``` |
128 | 136 |
|
@@ -134,21 +142,32 @@ Because there is no scope analysis, the above detection may overclassify: |
134 | 142 | exports.a = 'a'; |
135 | 143 | exports['b'] = 'b'; |
136 | 144 | if (false) |
137 | | - Object.defineProperty(exports, 'c', { value: 'c' }); |
| 145 | + exports.c = 'c'; |
138 | 146 | })(NOT_EXPORTS, NOT_OBJECT); |
139 | 147 | ``` |
140 | 148 |
|
141 | 149 | It will in turn underclassify in cases where the identifiers are renamed: |
142 | 150 |
|
143 | 151 | ```js |
144 | 152 | // DETECTS: NO EXPORTS |
145 | | -(function (e, defineProperty) { |
| 153 | +(function (e) { |
146 | 154 | e.a = 'a'; |
147 | 155 | e['b'] = 'b'; |
148 | | - defineProperty(e, 'c', { value: 'c' }); |
149 | | -})(exports, defineProperty); |
| 156 | +})(exports); |
| 157 | +``` |
| 158 | + |
| 159 | +#### __esModule Detection |
| 160 | + |
| 161 | +In addition, `__esModule` is detected as an export when set by `Object.defineProperty`: |
| 162 | + |
| 163 | +```js |
| 164 | +// DETECTS: __esModule |
| 165 | +Object.defineProperty(exports, 'a', { value: 'a' }); |
| 166 | +Object.defineProperty(exports, '__esModule', { value: true }); |
150 | 167 | ``` |
151 | 168 |
|
| 169 | +No other named exports are detected for `defineProperty` calls in order not to trigger getters or non-enumerable properties unnecessarily. |
| 170 | + |
152 | 171 | #### Exports Object Assignment |
153 | 172 |
|
154 | 173 | A best-effort is made to detect `module.exports` object assignments, but because this is not a full parser, arbitrary expressions are not handled in the |
@@ -160,17 +179,19 @@ Simple object definitions are supported: |
160 | 179 | // DETECTS EXPORTS: a, b, c |
161 | 180 | module.exports = { |
162 | 181 | a, |
163 | | - b: 'c', |
164 | | - c: c |
| 182 | + 'b': b, |
| 183 | + c: c, |
| 184 | + ...d |
165 | 185 | }; |
166 | 186 | ``` |
167 | 187 |
|
168 | | -Object properties that are not identifiers or string expressions will bail out of the object detection: |
| 188 | +Object properties that are not identifiers or string expressions will bail out of the object detection, while spreads are ignored: |
169 | 189 |
|
170 | 190 | ```js |
171 | 191 | // DETECTS EXPORTS: a, b |
172 | 192 | module.exports = { |
173 | 193 | a, |
| 194 | + ...d, |
174 | 195 | b: require('c'), |
175 | 196 | c: "not detected since require('c') above bails the object detection" |
176 | 197 | } |
@@ -180,16 +201,27 @@ module.exports = { |
180 | 201 |
|
181 | 202 | #### module.exports reexport assignment |
182 | 203 |
|
183 | | -Any `module.exports = require('mod')` assignment is detected as a reexport: |
| 204 | +Any `module.exports = require('mod')` assignment is detected as a reexport, but only the last one is returned: |
184 | 205 |
|
185 | 206 | ```js |
186 | | -// DETECTS REEXPORTS: a, b, c |
| 207 | +// DETECTS REEXPORTS: c |
187 | 208 | module.exports = require('a'); |
188 | 209 | (module => module.exports = require('b'))(NOT_MODULE); |
189 | 210 | if (false) module.exports = require('c'); |
190 | 211 | ``` |
191 | 212 |
|
192 | | -As a result, the total list of exports would be inferred as the union of all of these reexported modules, which can lead to possible over-classification. |
| 213 | +This is to avoid over-classification in Webpack bundles with externals which include `module.exports = require('external')` in their source for every external dependency. |
| 214 | + |
| 215 | +In exports object assignment, any spread of `require()` are detected as multiple separate reexports: |
| 216 | + |
| 217 | +```js |
| 218 | +// DETECTS REEXPORTS: a, b |
| 219 | +module.exports = require('ignored'); |
| 220 | +module.exports = { |
| 221 | + ...require('a'), |
| 222 | + ...require('b') |
| 223 | +}; |
| 224 | +``` |
193 | 225 |
|
194 | 226 | #### Transpiler Re-exports |
195 | 227 |
|
@@ -249,71 +281,72 @@ Current results: |
249 | 281 | JS Build: |
250 | 282 |
|
251 | 283 | ``` |
| 284 | +--- JS Build --- |
252 | 285 | Module load time |
253 | 286 | > 2ms |
254 | 287 | Cold Run, All Samples |
255 | 288 | test/samples/*.js (3635 KiB) |
256 | | -> 333ms |
| 289 | +> 311ms |
257 | 290 |
|
258 | 291 | Warm Runs (average of 25 runs) |
259 | 292 | test/samples/angular.js (1410 KiB) |
260 | | -> 16.48ms |
| 293 | +> 14.76ms |
261 | 294 | test/samples/angular.min.js (303 KiB) |
262 | | -> 5.36ms |
| 295 | +> 5.04ms |
263 | 296 | test/samples/d3.js (553 KiB) |
264 | | -> 8.32ms |
| 297 | +> 7.12ms |
265 | 298 | test/samples/d3.min.js (250 KiB) |
266 | | -> 4.28ms |
| 299 | +> 4ms |
267 | 300 | test/samples/magic-string.js (34 KiB) |
268 | | -> 1ms |
| 301 | +> 0.84ms |
269 | 302 | test/samples/magic-string.min.js (20 KiB) |
270 | | -> 0.36ms |
| 303 | +> 0.08ms |
271 | 304 | test/samples/rollup.js (698 KiB) |
272 | | -> 10.48ms |
| 305 | +> 9.08ms |
273 | 306 | test/samples/rollup.min.js (367 KiB) |
274 | | -> 6.64ms |
| 307 | +> 6ms |
275 | 308 |
|
276 | 309 | Warm Runs, All Samples (average of 25 runs) |
277 | 310 | test/samples/*.js (3635 KiB) |
278 | | -> 49.28ms |
| 311 | +> 41.32ms |
279 | 312 | ``` |
280 | 313 |
|
281 | 314 | Wasm Build: |
282 | 315 | ``` |
283 | 316 | Module load time |
284 | | -> 11ms |
| 317 | +> 10ms |
285 | 318 | Cold Run, All Samples |
286 | 319 | test/samples/*.js (3635 KiB) |
287 | | -> 48ms |
| 320 | +> 47ms |
288 | 321 |
|
289 | 322 | Warm Runs (average of 25 runs) |
290 | 323 | test/samples/angular.js (1410 KiB) |
291 | | -> 12.32ms |
| 324 | +> 12.96ms |
292 | 325 | test/samples/angular.min.js (303 KiB) |
293 | | -> 3.76ms |
| 326 | +> 4ms |
294 | 327 | test/samples/d3.js (553 KiB) |
295 | | -> 6.08ms |
| 328 | +> 6.12ms |
296 | 329 | test/samples/d3.min.js (250 KiB) |
297 | | -> 3ms |
| 330 | +> 3.08ms |
298 | 331 | test/samples/magic-string.js (34 KiB) |
299 | | -> 0.24ms |
| 332 | +> 0.32ms |
300 | 333 | test/samples/magic-string.min.js (20 KiB) |
301 | 334 | > 0ms |
302 | 335 | test/samples/rollup.js (698 KiB) |
303 | | -> 7.2ms |
| 336 | +> 7.8ms |
304 | 337 | test/samples/rollup.min.js (367 KiB) |
305 | | -> 4.2ms |
| 338 | +> 4.64ms |
306 | 339 |
|
307 | 340 | Warm Runs, All Samples (average of 25 runs) |
308 | 341 | test/samples/*.js (3635 KiB) |
309 | | -> 33.6ms |
| 342 | +> 35.64ms |
310 | 343 | ``` |
311 | 344 |
|
312 | 345 | ### Wasm Build Steps |
313 | 346 |
|
314 | | -To build download the WASI SDK from https://github.com/CraneStation/wasi-sdk/releases. |
| 347 | +To build download the WASI SDK from https://github.com/WebAssembly/wasi-sdk/releases. |
315 | 348 |
|
316 | | -The Makefile assumes the existence of "wasi-sdk-10.0", "binaryen" and "wabt" (both optional) as sibling folders to this project. |
| 349 | +The Makefile assumes the existence of "wasi-sdk-11.0" and "wabt" (optional) as sibling folders to this project. |
317 | 350 |
|
318 | 351 | The build through the Makefile is then run via `make lib/lexer.wasm`, which can also be triggered via `npm run build-wasm` to create `dist/lexer.js`. |
319 | 352 |
|
|