Skip to content
This repository was archived by the owner on Aug 4, 2021. It is now read-only.

Commit f5e57a3

Browse files
authored
Handle module side effects (#219)
* Handle module side-effects specified as an array of patterns * Refine code ordering and variable names * Speed up builtins lookup by converting them to a Set * Fix minimum npm version in Travis * Do not ignore scripts, add Node 12 * Or ignore scripts again * Also cache mutated package.json information * Use new dependencies and require at least node 1.11.0
1 parent ae49cb0 commit f5e57a3

File tree

22 files changed

+337
-283
lines changed

22 files changed

+337
-283
lines changed

Diff for: .travis.yml

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ node_js:
33
- "6"
44
- "8"
55
- "10"
6+
- "12"
67
env:
78
global:
89
- BUILD_TIMEOUT=10000
10+
install: npm ci --ignore-scripts
11+
before_install:
12+
- if [[ $TRAVIS_NODE_VERSION -lt 8 ]]; then npm install --global npm@5; fi

Diff for: package-lock.json

+131-227
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+9-4
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44
"version": "4.2.4",
55
"devDependencies": {
66
"@babel/core": "7.4.4",
7-
"@babel/register": "^7.4.4",
87
"@babel/preset-env": "^7.4.4",
8+
"@babel/register": "^7.4.4",
99
"es5-ext": "^0.10.50",
1010
"eslint": "^5.16.0",
1111
"mocha": "^6.1.4",
12-
"rollup": "^1.11.3",
12+
"rollup": "^1.12.0",
1313
"rollup-plugin-babel": "^4.3.2",
14-
"rollup-plugin-commonjs": "^9.3.4",
14+
"rollup-plugin-commonjs": "^10.0.0",
15+
"rollup-plugin-json": "^4.0.0",
1516
"string-capitalize": "^1.0.1",
1617
"typescript": "^3.4.5"
1718
},
@@ -36,7 +37,11 @@
3637
"@types/resolve": "0.0.8",
3738
"builtin-modules": "^3.1.0",
3839
"is-module": "^1.0.0",
39-
"resolve": "^1.10.1"
40+
"resolve": "^1.10.1",
41+
"rollup-pluginutils": "^2.7.0"
42+
},
43+
"peerDependencies": {
44+
"rollup": ">=1.11.0"
4045
},
4146
"repository": "rollup/rollup-plugin-node-resolve",
4247
"keywords": [

Diff for: rollup.config.js

+13-8
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
import babel from 'rollup-plugin-babel';
2+
import json from 'rollup-plugin-json';
23

34
export default {
45
input: 'src/index.js',
5-
plugins: [ babel({
6-
presets: [['@babel/preset-env', {
7-
targets: {
8-
node: 6
9-
}
10-
}]]
11-
}) ],
12-
external: [ 'path', 'fs', 'builtin-modules', 'resolve', 'browser-resolve', 'is-module' ],
6+
plugins: [
7+
json(),
8+
babel({
9+
presets: [['@babel/preset-env', {
10+
targets: {
11+
node: 6
12+
}
13+
}]
14+
]
15+
})
16+
],
17+
external: [ 'path', 'fs', 'builtin-modules', 'resolve', 'browser-resolve', 'is-module', 'rollup-pluginutils' ],
1318
output: [
1419
{ file: 'dist/rollup-plugin-node-resolve.cjs.js', format: 'cjs' },
1520
{ file: 'dist/rollup-plugin-node-resolve.es.js', format: 'es' }

Diff for: src/index.js

+92-44
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
import {dirname, extname, join, normalize, resolve, sep} from 'path';
2-
import builtins from 'builtin-modules';
2+
import builtinList from 'builtin-modules';
33
import resolveId from 'resolve';
44
import isModule from 'is-module';
55
import fs from 'fs';
6+
import {createFilter} from 'rollup-pluginutils';
7+
import {peerDependencies} from '../package.json';
8+
9+
const builtins = builtinList.reduce((set, id) => set.add(id), new Set());
610

711
const ES6_BROWSER_EMPTY = resolve( __dirname, '../src/empty.js' );
812
// It is important that .mjs occur before .js so that Rollup will interpret npm modules
913
// which deploy both ESM .mjs and CommonJS .js files as ESM.
1014
const DEFAULT_EXTS = [ '.mjs', '.js', '.json', '.node' ];
1115

1216
const readFileAsync = file => new Promise((fulfil, reject) => fs.readFile(file, (err, contents) => err ? reject(err) : fulfil(contents)));
17+
1318
const statAsync = file => new Promise((fulfil, reject) => fs.stat(file, (err, contents) => err ? reject(err) : fulfil(contents)));
19+
1420
const cache = fn => {
1521
const cache = new Map();
1622
const wrapped = (param, done) => {
@@ -25,12 +31,16 @@ const cache = fn => {
2531
wrapped.clear = () => cache.clear();
2632
return wrapped;
2733
};
34+
2835
const ignoreENOENT = err => {
2936
if (err.code === 'ENOENT') return false;
3037
throw err;
3138
};
39+
3240
const readFileCached = cache(readFileAsync);
41+
3342
const isDirCached = cache(file => statAsync(file).then(stat => stat.isDirectory(), ignoreENOENT));
43+
3444
const isFileCached = cache(file => statAsync(file).then(stat => stat.isFile(), ignoreENOENT));
3545

3646
function getMainFields (options) {
@@ -63,6 +73,8 @@ function getMainFields (options) {
6373
return mainFields;
6474
}
6575

76+
const alwaysNull = () => null;
77+
6678
const resolveIdAsync = (file, opts) => new Promise((fulfil, reject) => resolveId(file, opts, (err, contents) => err ? reject(err) : fulfil(contents)));
6779

6880
export default function nodeResolve ( options = {} ) {
@@ -85,13 +97,79 @@ export default function nodeResolve ( options = {} ) {
8597
throw new Error( 'options.skip is no longer supported — you should use the main Rollup `external` option instead' );
8698
}
8799

100+
const extensions = options.extensions || DEFAULT_EXTS;
101+
const packageInfoCache = new Map();
102+
103+
function getCachedPackageInfo (pkg, pkgPath) {
104+
if (packageInfoCache.has(pkgPath)) {
105+
return packageInfoCache.get(pkgPath);
106+
}
107+
const pkgRoot = dirname( pkgPath );
108+
109+
let overriddenMain = false;
110+
for ( let i = 0; i < mainFields.length; i++ ) {
111+
const field = mainFields[i];
112+
if ( typeof pkg[ field ] === 'string' ) {
113+
pkg[ 'main' ] = pkg[ field ];
114+
overriddenMain = true;
115+
break;
116+
}
117+
}
118+
119+
const packageInfo = {
120+
cachedPkg: pkg,
121+
hasModuleSideEffects: alwaysNull,
122+
hasPackageEntry: overriddenMain !== false || mainFields.indexOf( 'main' ) !== -1,
123+
packageBrowserField: useBrowserOverrides && typeof pkg[ 'browser' ] === 'object' &&
124+
Object.keys(pkg[ 'browser' ]).reduce((browser, key) => {
125+
let resolved = pkg[ 'browser' ][ key ];
126+
if (resolved && resolved[0] === '.') {
127+
resolved = resolve( pkgRoot, resolved );
128+
}
129+
browser[ key ] = resolved;
130+
if ( key[0] === '.' ) {
131+
const absoluteKey = resolve( pkgRoot, key );
132+
browser[ absoluteKey ] = resolved;
133+
if ( !extname(key) ) {
134+
extensions.reduce( ( browser, ext ) => {
135+
browser[ absoluteKey + ext ] = browser[ key ];
136+
return browser;
137+
}, browser );
138+
}
139+
}
140+
return browser;
141+
}, {})
142+
};
143+
144+
const packageSideEffects = pkg['sideEffects'];
145+
if (typeof packageSideEffects === 'boolean') {
146+
packageInfo.hasModuleSideEffects = () => packageSideEffects;
147+
} else if (Array.isArray(packageSideEffects)) {
148+
const filter = createFilter(packageSideEffects, null, {resolve: pkgRoot});
149+
packageInfo.hasModuleSideEffects = id => !filter(id);
150+
}
151+
152+
packageInfoCache.set(pkgPath, packageInfo);
153+
return packageInfo;
154+
}
155+
88156
let preserveSymlinks;
89157

90158
return {
91159
name: 'node-resolve',
92160

93161
options ( options ) {
94162
preserveSymlinks = options.preserveSymlinks;
163+
const [major, minor] = this.meta.rollupVersion.split('.').map(Number);
164+
const minVersion = peerDependencies.rollup.slice(2);
165+
const [minMajor, minMinor] = minVersion.split('.').map(Number);
166+
if (major < minMajor || (major === minMajor && minor < minMinor)) {
167+
this.error(
168+
`Insufficient Rollup version: "rollup-plugin-node-resolve" requires at least rollup@${minVersion} but found rollup@${
169+
this.meta.rollupVersion
170+
}.`
171+
);
172+
}
95173
},
96174

97175
generateBundle () {
@@ -134,48 +212,17 @@ export default function nodeResolve ( options = {} ) {
134212

135213
if (only && !only.some(pattern => pattern.test(id))) return null;
136214

137-
let disregardResult = false;
215+
let hasModuleSideEffects = alwaysNull;
216+
let hasPackageEntry = true;
138217
let packageBrowserField = false;
139-
const extensions = options.extensions || DEFAULT_EXTS;
140218

141219
const resolveOptions = {
142220
basedir,
143221
packageFilter ( pkg, pkgPath ) {
144-
const pkgRoot = dirname( pkgPath );
145-
if (useBrowserOverrides && typeof pkg[ 'browser' ] === 'object') {
146-
packageBrowserField = Object.keys(pkg[ 'browser' ]).reduce((browser, key) => {
147-
let resolved = pkg[ 'browser' ][ key ];
148-
if (resolved && resolved[0] === '.') {
149-
resolved = resolve( pkgRoot, pkg[ 'browser' ][ key ] );
150-
}
151-
browser[ key ] = resolved;
152-
if ( key[0] === '.' ) {
153-
const absoluteKey = resolve( pkgRoot, key );
154-
browser[ absoluteKey ] = resolved;
155-
if ( !extname(key) ) {
156-
extensions.reduce( ( browser, ext ) => {
157-
browser[ absoluteKey + ext ] = browser[ key ];
158-
return browser;
159-
}, browser );
160-
}
161-
}
162-
return browser;
163-
}, {});
164-
}
165-
166-
let overriddenMain = false;
167-
for ( let i = 0; i < mainFields.length; i++ ) {
168-
const field = mainFields[i];
169-
if ( typeof pkg[ field ] === 'string' ) {
170-
pkg[ 'main' ] = pkg[ field ];
171-
overriddenMain = true;
172-
break;
173-
}
174-
}
175-
if ( overriddenMain === false && mainFields.indexOf( 'main' ) === -1 ) {
176-
disregardResult = true;
177-
}
178-
return pkg;
222+
let cachedPkg;
223+
({cachedPkg, hasModuleSideEffects, hasPackageEntry, packageBrowserField} =
224+
getCachedPackageInfo(pkg, pkgPath));
225+
return cachedPkg;
179226
},
180227
readFile: readFileCached,
181228
isFile: isFileCached,
@@ -192,7 +239,7 @@ export default function nodeResolve ( options = {} ) {
192239
Object.assign( resolveOptions, customResolveOptions )
193240
)
194241
.then(resolved => {
195-
if ( resolved && useBrowserOverrides && packageBrowserField ) {
242+
if ( resolved && packageBrowserField ) {
196243
if ( packageBrowserField.hasOwnProperty(resolved) ) {
197244
if (!packageBrowserField[resolved]) {
198245
browserMapCache[resolved] = packageBrowserField;
@@ -203,14 +250,14 @@ export default function nodeResolve ( options = {} ) {
203250
browserMapCache[resolved] = packageBrowserField;
204251
}
205252

206-
if ( !disregardResult ) {
253+
if ( hasPackageEntry ) {
207254
if ( !preserveSymlinks && resolved && fs.existsSync( resolved ) ) {
208255
resolved = fs.realpathSync( resolved );
209256
}
210257

211-
if ( ~builtins.indexOf( resolved ) ) {
258+
if ( builtins.has( resolved ) ) {
212259
return null;
213-
} else if ( ~builtins.indexOf( importee ) && preferBuiltins ) {
260+
} else if ( builtins.has( importee ) && preferBuiltins ) {
214261
if ( !isPreferBuiltinsSet ) {
215262
this.warn(
216263
`preferring built-in module '${importee}' over local alternative ` +
@@ -225,9 +272,10 @@ export default function nodeResolve ( options = {} ) {
225272
}
226273

227274
if ( resolved && options.modulesOnly ) {
228-
return readFileAsync( resolved, 'utf-8').then(code => isModule( code ) ? resolved : null);
275+
return readFileAsync( resolved, 'utf-8')
276+
.then(code => isModule( code ) ? {id: resolved, moduleSideEffects: hasModuleSideEffects(resolved)} : null);
229277
} else {
230-
return resolved;
278+
return {id: resolved, moduleSideEffects: hasModuleSideEffects(resolved)};
231279
}
232280
})
233281
.catch(() => null);

Diff for: test/node_modules/side-effects-array/dep1.js

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: test/node_modules/side-effects-array/dep2.js

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: test/node_modules/side-effects-array/dep3-free.js

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: test/node_modules/side-effects-array/index.js

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: test/node_modules/side-effects-array/nested/dep4.js

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: test/node_modules/side-effects-array/nested/dep5-free.js

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: test/node_modules/side-effects-array/package.json

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: test/node_modules/side-effects-false/dep1.js

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: test/node_modules/side-effects-false/dep2.js

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: test/node_modules/side-effects-false/index.js

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: test/node_modules/side-effects-false/package.json

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: test/node_modules/side-effects-true/dep1.js

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: test/node_modules/side-effects-true/dep2.js

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: test/node_modules/side-effects-true/index.js

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: test/node_modules/side-effects-true/package.json

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: test/samples/side-effects/main.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export {value1 as falseValue} from 'side-effects-false';
2+
export {value1 as trueValue} from 'side-effects-true';
3+
import 'side-effects-array';

0 commit comments

Comments
 (0)