Skip to content

Commit

Permalink
support reexports via require spreads
Browse files Browse the repository at this point in the history
  • Loading branch information
guybedford committed Oct 21, 2020
1 parent b9218fa commit 5d6485e
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 12 deletions.
27 changes: 22 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,13 @@ EXPORTS_LITERAL_COMPUTED_ASSIGN: EXPORTS_IDENTIFIER COMMENT_SPACE `[` COMMENT_SP
EXPORTS_LITERAL_PROP: (IDENTIFIER (COMMENT_SPACE `:` COMMENT_SPACE IDENTIFIER)?) | (IDENTIFIER_STRING COMMENT_SPACE `:` COMMENT_SPACE IDENTIFIER)
EXPORTS_SPREAD: `...` COMMENT_SPACE (IDENTIFIER | REQUIRE)
EXPORTS_MEMBER: EXPORTS_DOT_ASSIGN | EXPORTS_LITERAL_COMPUTED_ASSIGN
EXPORTS_DEFINE: `Object` COMMENT_SPACE `.` COMMENT_SPACE `defineProperty COMMENT_SPACE `(` EXPORTS_IDENTIFIER COMMENT_SPACE `,` COMMENT_SPACE IDENTIFIER_STRING
EXPORTS_LITERAL: MODULE_EXPORTS COMMENT_SPACE `=` COMMENT_SPACE `{` COMMENT_SPACE (EXPORTS_LITERAL_PROP COMMENT_SPACE `,` COMMENT_SPACE)+ `}`
EXPORTS_LITERAL: MODULE_EXPORTS COMMENT_SPACE `=` COMMENT_SPACE `{` COMMENT_SPACE (EXPORTS_LITERAL_PROP | EXPORTS_SPREAD) COMMENT_SPACE `,` COMMENT_SPACE)+ `}`
REQUIRE: `require` COMMENT_SPACE `(` COMMENT_SPACE STRING_LITERAL COMMENT_SPACE `)`
Expand All @@ -112,7 +114,9 @@ EXPORT_STAR_LIB: `Object.keys(` IDENTIFIER$1 `).forEach(function (` IDENTIFIER$2
```

* The returned export names are the matched `IDENTIFIER` and `IDENTIFIER_STRING` slots for all `EXPORTS_MEMBER`, `EXPORTS_DEFINE` and `EXPORTS_LITERAL` matches.
* The reexport specifiers are taken to be the `STRING_LITERAL` slot of the last `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`.
* The reexport specifiers are taken to be the the combination of:
1. The `REQUIRE` matches of the last matched of either `MODULE_EXPORTS_ASSIGN` or `EXPORTS_LITERAL`.
2. All _top-level_ `EXPORT_STAR` `REQUIRE` matches and `EXPORTS_ASSIGN` matches whose `IDENTIFIER` also matches the first `IDENTIFIER` in `EXPORT_STAR_LIB`.

### Parsing Examples

Expand Down Expand Up @@ -164,16 +168,18 @@ Simple object definitions are supported:
module.exports = {
a,
'b': b,
c: c
c: c,
...d
};
```

Object properties that are not identifiers or string expressions will bail out of the object detection:
Object properties that are not identifiers or string expressions will bail out of the object detection, while spreads are ignored:

```js
// DETECTS EXPORTS: a, b
module.exports = {
a,
...d,
b: require('c'),
c: "not detected since require('c') above bails the object detection"
}
Expand All @@ -192,7 +198,18 @@ module.exports = require('a');
if (false) module.exports = require('c');
```

This is to avoid overclassification in Webpack bundles with externals which include `module.exports = require('external')` in their source for every external dependency.
This is to avoid over-classification in Webpack bundles with externals which include `module.exports = require('external')` in their source for every external dependency.

In exports object assignment, any spread of `require()` are detected as multiple separate reexports:

```js
// DETECTS REEXPORTS: a, b
module.exports = require('ignored');
module.exports = {
...require('a'),
...require('b')
};
```

#### Transpiler Re-exports

Expand Down
27 changes: 20 additions & 7 deletions lexer.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ let openTokenDepth,
nextBraceIsClass,
starExportMap,
lastStarExportSpecifier,
lastExportsAssignSpecifier,
lastExportsAssignReexports,
_exports,
reexports;

Expand All @@ -26,7 +26,7 @@ function resetState () {
nextBraceIsClass = false;
starExportMap = Object.create(null);
lastStarExportSpecifier = null;
lastExportsAssignSpecifier = null;
lastExportsAssignReexports = new Set();

_exports = new Set();
reexports = new Set();
Expand All @@ -49,8 +49,8 @@ module.exports = function parseCJS (source, name = '@') {
e.loc = pos;
throw e;
}
if (lastExportsAssignSpecifier)
reexports.add(lastExportsAssignSpecifier);
for (const reexport of lastExportsAssignReexports)
reexports.add(reexport);
const result = { exports: [..._exports], reexports: [...reexports] };
resetState();
return result;
Expand Down Expand Up @@ -677,6 +677,8 @@ function tryParseExportsDotAssign (assign) {
// module.exports =
case 61/*=*/: {
if (assign) {
if (lastExportsAssignReexports.size)
lastExportsAssignReexports = new Set();
pos++;
ch = commentWhitespace();
// { ... }
Expand All @@ -696,9 +698,9 @@ function tryParseExportsDotAssign (assign) {

function tryParseRequire (requireType) {
// require('...')
const revertPos = pos;
if (source.startsWith('equire', pos + 1)) {
pos += 7;
const revertPos = pos - 1;
let ch = commentWhitespace();
if (ch === 40/*(*/) {
pos++;
Expand All @@ -711,7 +713,7 @@ function tryParseRequire (requireType) {
if (ch === 41/*)*/) {
switch (requireType) {
case ExportAssign:
lastExportsAssignSpecifier = source.slice(reexportStart, reexportEnd);
lastExportsAssignReexports.add(source.slice(reexportStart, reexportEnd));
return true;
case ExportStar:
reexports.add(source.slice(reexportStart, reexportEnd));
Expand All @@ -729,7 +731,7 @@ function tryParseRequire (requireType) {
if (ch === 41/*)*/) {
switch (requireType) {
case ExportAssign:
lastExportsAssignSpecifier = source.slice(reexportStart, reexportEnd);
lastExportsAssignReexports.add(source.slice(reexportStart, reexportEnd));
return true;
case ExportStar:
reexports.add(source.slice(reexportStart, reexportEnd));
Expand Down Expand Up @@ -766,6 +768,17 @@ function tryParseLiteralExports () {
}
addExport(source.slice(startPos, endPos));
}
else if (ch === 46/*.*/ && source.startsWith('..', pos + 1)) {
pos += 3;
if (source.charCodeAt(pos) === 114/*r*/ && tryParseRequire(ExportAssign)) {
pos++;
}
else if (!identifier()) {
pos = revertPos;
return;
}
ch = commentWhitespace();
}
else if (ch === 39/*'*/ || ch === 34/*"*/) {
const startPos = ++pos;
if (identifier() && source.charCodeAt(pos) === ch) {
Expand Down
19 changes: 19 additions & 0 deletions test/_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,25 @@ suite('Lexer', () => {
assert.equal(exports.length, 0);
});

test('module exports reexport spread', () => {
const { exports, reexports } = parse(`
module.exports = {
...a,
...b,
...require('dep1'),
c: d,
...require('dep2'),
name
};
`);
assert.equal(exports.length, 2);
assert.equal(exports[0], 'c');
assert.equal(exports[1], 'name');
assert.equal(reexports.length, 2);
assert.equal(reexports[0], 'dep1');
assert.equal(reexports[1], 'dep2');
});

test('Regexp case', () => {
parse(`
class Number {
Expand Down

0 comments on commit 5d6485e

Please sign in to comment.