Skip to content

Commit 08ab82c

Browse files
committed
feat(commonjs): add dynamicRequireRoot option (#1038)
1 parent 55e7e2d commit 08ab82c

File tree

11 files changed

+295
-398
lines changed

11 files changed

+295
-398
lines changed

packages/commonjs/README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,13 @@ By default, this plugin will try to hoist `require` statements as imports to the
5555

5656
Setting this option to `true` will wrap all CommonJS files in functions which are executed when they are required for the first time, preserving NodeJS semantics. Note that this can have an impact on the size and performance of the generated code.
5757

58-
The default value of `"auto"` will only wrap CommonJS files when they are part of a CommonJS dependency cycle, e.g. an index file that is required by many of its dependencies. All other CommonJS files are hoisted. This is the recommended setting for most code bases.
58+
The default value of `"auto"` will only wrap CommonJS files when they are part of a CommonJS dependency cycle, e.g. an index file that is required by some of its dependencies, or if they are only required in a potentially "conditional" way like from within an if-statement or a function. All other CommonJS files are hoisted. This is the recommended setting for most code bases. Note that the detection of conditional requires can be subject to race conditions if there are both conditional and unconditional requires of the same file, which in edge cases may result in inconsistencies between builds. If you think this is a problem for you, you can avoid this by using any value other than `"auto"` or `"debug"`.
5959

6060
`false` will entirely prevent wrapping and hoist all files. This may still work depending on the nature of cyclic dependencies but will often cause problems.
6161

6262
You can also provide a [minimatch pattern](https://github.com/isaacs/minimatch), or array of patterns, to only specify a subset of files which should be wrapped in functions for proper `require` semantics.
6363

64-
`"debug"` works like `"auto"` but after bundling, it will display a warning containing a list of ids that have been wrapped which can be used as minimatch pattern for fine-tuning.
64+
`"debug"` works like `"auto"` but after bundling, it will display a warning containing a list of ids that have been wrapped which can be used as minimatch pattern for fine-tuning or to avoid the potential race conditions mentioned for `"auto"`.
6565

6666
### `dynamicRequireTargets`
6767

@@ -90,6 +90,13 @@ commonjs({
9090
});
9191
```
9292

93+
### `dynamicRequireRoot`
94+
95+
Type: `string`<br>
96+
Default: `process.cwd()`
97+
98+
To avoid long paths when using the `dynamicRequireTargets` option, you can use this option to specify a directory that is a common parent for all files that use dynamic require statements. Using a directory higher up such as `/` may lead to unnecessarily long paths in the generated code and may expose directory names on your machine like your home directory name. By default it uses the current working directory.
99+
93100
### `exclude`
94101

95102
Type: `string | string[]`<br>

packages/commonjs/src/dynamic-modules.js

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { existsSync, readFileSync, statSync } from 'fs';
2-
import { join, resolve } from 'path';
2+
import { join, resolve, dirname } from 'path';
3+
4+
import getCommonDir from 'commondir';
35

46
import glob from 'glob';
57

@@ -30,8 +32,9 @@ function isDirectory(path) {
3032
return false;
3133
}
3234

33-
export function getDynamicRequireModules(patterns) {
35+
export function getDynamicRequireModules(patterns, dynamicRequireRoot) {
3436
const dynamicRequireModules = new Map();
37+
const dirNames = new Set();
3538
for (const pattern of !patterns || Array.isArray(patterns) ? patterns || [] : [patterns]) {
3639
const isNegated = pattern.startsWith('!');
3740
const modifyMap = (targetPath, resolvedPath) =>
@@ -42,15 +45,20 @@ export function getDynamicRequireModules(patterns) {
4245
const resolvedPath = resolve(path);
4346
const requirePath = normalizePathSlashes(resolvedPath);
4447
if (isDirectory(resolvedPath)) {
48+
dirNames.add(resolvedPath);
4549
const modulePath = resolve(join(resolvedPath, getPackageEntryPoint(path)));
4650
modifyMap(requirePath, modulePath);
4751
modifyMap(normalizePathSlashes(modulePath), modulePath);
4852
} else {
53+
dirNames.add(dirname(resolvedPath));
4954
modifyMap(requirePath, resolvedPath);
5055
}
5156
}
5257
}
53-
return dynamicRequireModules;
58+
return {
59+
commonDir: dirNames.size ? getCommonDir([...dirNames, dynamicRequireRoot]) : null,
60+
dynamicRequireModules
61+
};
5462
}
5563

5664
const FAILED_REQUIRE_ERROR = `throw new Error('Could not dynamically require "' + path + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');`;
@@ -77,9 +85,9 @@ export function getDynamicModuleRegistry(
7785
const dynamicModuleProps = [...dynamicRequireModules.keys()]
7886
.map(
7987
(id, index) =>
80-
`\t\t${JSON.stringify(
81-
getVirtualPathForDynamicRequirePath(normalizePathSlashes(id), commonDir)
82-
)}: ${id.endsWith('.json') ? `function () { return json${index}; }` : `require${index}`}`
88+
`\t\t${JSON.stringify(getVirtualPathForDynamicRequirePath(id, commonDir))}: ${
89+
id.endsWith('.json') ? `function () { return json${index}; }` : `require${index}`
90+
}`
8391
)
8492
.join(',\n');
8593
return `${dynamicModuleImports}
@@ -93,7 +101,7 @@ ${dynamicModuleProps}
93101
}
94102
95103
export function commonjsRequire(path, originalModuleDir) {
96-
var resolvedPath = commonjsResolveImpl(path, originalModuleDir, true);
104+
var resolvedPath = commonjsResolveImpl(path, originalModuleDir);
97105
if (resolvedPath !== null) {
98106
return getDynamicModules()[resolvedPath]();
99107
}
@@ -115,17 +123,15 @@ function commonjsResolveImpl (path, originalModuleDir) {
115123
path = normalize(path);
116124
var relPath;
117125
if (path[0] === '/') {
118-
originalModuleDir = '/';
126+
originalModuleDir = '';
119127
}
120128
var modules = getDynamicModules();
121129
var checkedExtensions = ['', '.js', '.json'];
122130
while (true) {
123131
if (!shouldTryNodeModules) {
124-
relPath = originalModuleDir ? normalize(originalModuleDir + '/' + path) : path;
125-
} else if (originalModuleDir) {
126-
relPath = normalize(originalModuleDir + '/node_modules/' + path);
132+
relPath = normalize(originalModuleDir + '/' + path);
127133
} else {
128-
relPath = normalize(join('node_modules', path));
134+
relPath = normalize(originalModuleDir + '/node_modules/' + path);
129135
}
130136
131137
if (relPath.endsWith('/..')) {
@@ -176,21 +182,5 @@ function normalize (path) {
176182
if (slashed && path[0] !== '/') path = '/' + path;
177183
else if (path.length === 0) path = '.';
178184
return path;
179-
}
180-
181-
function join () {
182-
if (arguments.length === 0) return '.';
183-
var joined;
184-
for (var i = 0; i < arguments.length; ++i) {
185-
var arg = arguments[i];
186-
if (arg.length > 0) {
187-
if (joined === undefined)
188-
joined = arg;
189-
else
190-
joined += '/' + arg;
191-
}
192-
}
193-
if (joined === undefined) return '.';
194-
return joined;
195185
}`;
196186
}

packages/commonjs/src/index.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { extname, relative } from 'path';
1+
import { extname, relative, resolve } from 'path';
22

33
import { createFilter } from '@rollup/pluginutils';
4-
import getCommonDir from 'commondir';
54

65
import { peerDependencies } from '../package.json';
76

@@ -64,13 +63,16 @@ export default function commonjs(options = {}) {
6463
getWrappedIds,
6564
isRequiredId
6665
} = getResolveRequireSourcesAndGetMeta(extensions, detectCyclesAndConditional);
67-
const dynamicRequireModules = getDynamicRequireModules(options.dynamicRequireTargets);
68-
const isDynamicRequireModulesEnabled = dynamicRequireModules.size > 0;
69-
// TODO Lukas replace with new dynamicRequireRoot to replace CWD
66+
const dynamicRequireRoot =
67+
typeof options.dynamicRequireRoot === 'string'
68+
? resolve(options.dynamicRequireRoot)
69+
: process.cwd();
7070
// TODO Lukas throw if require from outside commondir
71-
const commonDir = isDynamicRequireModulesEnabled
72-
? getCommonDir(null, Array.from(dynamicRequireModules.keys()).concat(process.cwd()))
73-
: null;
71+
const { commonDir, dynamicRequireModules } = getDynamicRequireModules(
72+
options.dynamicRequireTargets,
73+
dynamicRequireRoot
74+
);
75+
const isDynamicRequireModulesEnabled = dynamicRequireModules.size > 0;
7476

7577
const esModulesWithDefaultExport = new Set();
7678
const esModulesWithNamedExports = new Set();

packages/commonjs/src/utils.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable import/prefer-default-export */
22

3-
import { basename, dirname, extname } from 'path';
3+
import { basename, dirname, extname, relative } from 'path';
44

55
import { createFilter, makeLegalIdentifier } from '@rollup/pluginutils';
66

@@ -35,7 +35,7 @@ export function normalizePathSlashes(path) {
3535
}
3636

3737
export const getVirtualPathForDynamicRequirePath = (path, commonDir) =>
38-
normalizePathSlashes(path).slice(commonDir.length);
38+
`/${normalizePathSlashes(relative(commonDir, path))}`;
3939

4040
export function capitalize(name) {
4141
return name[0].toUpperCase() + name.slice(1);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
description: 'supports specifying a dynamic require root',
3+
pluginOptions: {
4+
dynamicRequireTargets: ['fixtures/function/dynamic-require-root/submodule.js'],
5+
dynamicRequireRoot: 'fixtures/function/dynamic-require-root'
6+
}
7+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/* eslint-disable import/no-dynamic-require, global-require */
2+
3+
let message;
4+
5+
function takeModule(withName) {
6+
return require(`./${withName}`);
7+
}
8+
9+
try {
10+
const submodule = takeModule('submodule');
11+
message = submodule();
12+
} catch (err) {
13+
({ message } = err);
14+
}
15+
16+
t.is(message, 'Hello there');
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = function () {
2+
return 'Hello there';
3+
};

0 commit comments

Comments
 (0)