Skip to content

Commit c43330b

Browse files
feat(node-resolve): dedupe by package name (#99)
BREAKING CHANGE: those depending on the past behavior, in which deep imports are not deduplicated, may experience unexpected results. * feat(node-resolve): dedupe by package name * chore: add comments to package name function * docs(node-resolve): update dedupe docs Co-authored-by: Andrew Powell <[email protected]>
1 parent 0a0de65 commit c43330b

File tree

15 files changed

+102
-3
lines changed

15 files changed

+102
-3
lines changed

packages/node-resolve/README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,21 @@ Default: `[]`
120120
Force resolving for these modules to root's node_modules that helps to prevent bundling the same package multiple times if package is imported from dependencies.
121121

122122
```
123-
dedupe: [ 'react', 'react-dom' ]
123+
dedupe: [ 'my-package', '@namespace/my-package' ]
124+
```
125+
126+
This will deduplicate bare imports such as:
127+
128+
```js
129+
import 'my-package';
130+
import '@namespace/my-package';
131+
```
132+
133+
And it will deduplicate deep imports such as:
134+
135+
```js
136+
import 'my-package/foo.js';
137+
import '@namespace/my-package/bar.js';
124138
```
125139

126140
### `customResolveOptions`

packages/node-resolve/src/index.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,25 @@ function resolveImportSpecifiers(importSpecifierList, resolveOptions) {
136136
return p;
137137
}
138138

139+
// returns the imported package name for bare module imports
140+
function getPackageName(id) {
141+
if (id.startsWith('.') || id.startsWith('/')) {
142+
return null;
143+
}
144+
145+
const split = id.split('/');
146+
147+
// @my-scope/my-package/foo.js -> @my-scope/my-package
148+
// @my-scope/my-package -> @my-scope/my-package
149+
if (split[0][0] === '@') {
150+
return `${split[0]}/${split[1]}`;
151+
}
152+
153+
// my-package/foo.js -> my-package
154+
// my-package -> my-package
155+
return split[0];
156+
}
157+
139158
export default function nodeResolve(options = {}) {
140159
const mainFields = getMainFields(options);
141160
const useBrowserOverrides = mainFields.indexOf('browser') !== -1;
@@ -166,7 +185,9 @@ export default function nodeResolve(options = {}) {
166185
const idToPackageInfo = new Map();
167186

168187
const shouldDedupe =
169-
typeof dedupe === 'function' ? dedupe : (importee) => dedupe.includes(importee);
188+
typeof dedupe === 'function'
189+
? dedupe
190+
: (importee) => dedupe.includes(importee) || dedupe.includes(getPackageName(importee));
170191

171192
function getCachedPackageInfo(pkg, pkgPath) {
172193
if (packageInfoCache.has(pkgPath)) {

packages/node-resolve/test/dedupe.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,34 @@ test('single module version is bundled if dedupe is set', async (t) => {
2323
t.snapshot(module.exports);
2424
});
2525

26+
test('dedupes deep imports by package name if dedupe is set', async (t) => {
27+
const bundle = await rollup({
28+
input: 'react-app-deep-import.js',
29+
plugins: [
30+
nodeResolve({
31+
dedupe: ['react']
32+
})
33+
]
34+
});
35+
const { module } = await testBundle(t, bundle);
36+
37+
t.snapshot(module.exports);
38+
});
39+
40+
test('dedupes scoped deep imports by package name if dedupe is set', async (t) => {
41+
const bundle = await rollup({
42+
input: 'scoped-deep-import.js',
43+
plugins: [
44+
nodeResolve({
45+
dedupe: ['@scoped/deduped']
46+
})
47+
]
48+
});
49+
const { module } = await testBundle(t, bundle);
50+
51+
t.snapshot(module.exports);
52+
});
53+
2654
test('single module version is bundled if dedupe is set as a function', async (t) => {
2755
const bundle = await rollup({
2856
input: 'react-app.js',

packages/node-resolve/test/fixtures/node_modules/@scoped/deduped/deep.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/node-resolve/test/fixtures/node_modules/react-consumer/deep.js

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/node-resolve/test/fixtures/node_modules/react-consumer/node_modules/react/deep.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/node-resolve/test/fixtures/node_modules/react-consumer/node_modules/react/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/node-resolve/test/fixtures/node_modules/react/deep.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/node-resolve/test/fixtures/node_modules/scoped-deduped-consumer/index.js

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/node-resolve/test/fixtures/node_modules/scoped-deduped-consumer/node_modules/.gitkeep

Whitespace-only changes.

packages/node-resolve/test/fixtures/node_modules/scoped-deduped-consumer/node_modules/@scoped/deduped/deep.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import React from 'react/deep.js';
2+
import ReactConsumer from 'react-consumer/deep.js';
3+
4+
export { React, ReactConsumer };
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import ScopedDeduped from '@scoped/deduped/deep.js';
2+
import ScopedDedupedConsumer from 'scoped-deduped-consumer';
3+
4+
export { ScopedDeduped, ScopedDedupedConsumer };

packages/node-resolve/test/snapshots/dedupe.js.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,24 @@ The actual snapshot is saved in `dedupe.js.snap`.
44

55
Generated by [AVA](https://ava.li).
66

7+
## dedupes deep imports by package name if dedupe is set
8+
9+
> Snapshot 1
10+
11+
{
12+
React: 'react/deep.js:root',
13+
ReactConsumer: 'react-consumer:react/deep.js:root',
14+
}
15+
16+
## dedupes scoped deep imports by package name if dedupe is set
17+
18+
> Snapshot 1
19+
20+
{
21+
ScopedDeduped: 'scoped-deduped:root',
22+
ScopedDedupedConsumer: 'scoped-deduped-consumer:scoped-deduped:root',
23+
}
24+
725
## multiple module versions are bundled if dedupe is not set
826

927
> Snapshot 1
116 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)