Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic require #206

Merged
merged 13 commits into from
Mar 26, 2020
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions packages/commonjs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,33 @@ Then call `rollup` either via the [CLI](https://www.rollupjs.org/guide/en/#comma

## Options

### `dynamicRequireTargets`

Type: `String|Array[String]`<br>
Default: `[]`

Some modules contain dynamic `require` calls, or require modules that contain circular dependencies, which are not handled well by static imports.
Including those modules as `dynamicRequireTargets` will simulate a CommonJS (NodeJS-like) environment for them with support for dynamic and circular dependencies.

_Note: In extreme cases, this feature may result in some paths being rendered as absolute in the final bundle. The plugin tries to avoid exposing paths from the local machine, but if you are `dynamicRequirePaths` with paths that are far away from your project's folder, that may require replacing strings like `"/Users/John/Desktop/foo-project/"` -> `"/"`._

Example:

```js
commonjs({
dynamicRequireTargets: [
// include using a glob pattern (either a string or an array of strings)
'node_modules/logform/*.js',

// exclude files that are known to not be required dynamically, this allows for better optimizations
'!node_modules/logform/index.js',
'!node_modules/logform/format.js',
'!node_modules/logform/levels.js',
'!node_modules/logform/browser.js'
]
});
```

### `exclude`

Type: `String` | `Array[...String]`<br>
Expand Down
2 changes: 2 additions & 0 deletions packages/commonjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@
},
"dependencies": {
"@rollup/pluginutils": "^3.0.8",
"commondir": "^1.0.1",
"estree-walker": "^1.0.1",
"glob": "^7.1.2",
"is-reference": "^1.1.2",
"magic-string": "^0.25.2",
"resolve": "^1.11.0"
Expand Down
27 changes: 27 additions & 0 deletions packages/commonjs/src/dynamic-require-paths.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { statSync } from 'fs';
import glob from 'glob';
import { resolve } from 'path';
import { normalizePathSlashes } from './transform';

export default function getDynamicRequirePaths(patterns) {
const dynamicRequireModuleSet = new Set();
for (const pattern of (!patterns || Array.isArray(patterns)) ? patterns || [] : [patterns]) {
const isNegated = pattern.startsWith('!');
const modifySet = Set.prototype[isNegated ? 'delete' : 'add'].bind(
dynamicRequireModuleSet
);
for (const path of glob.sync(isNegated ? pattern.substr(1) : pattern)) {
modifySet(normalizePathSlashes(resolve(path)));
}
}
const dynamicRequireModuleDirPaths = Array.from(dynamicRequireModuleSet.values()).filter(path => {
try {
if (statSync(path).isDirectory())
return true;
} catch (ignored) {
// Nothing to do here
}
return false;
});
return { dynamicRequireModuleSet, dynamicRequireModuleDirPaths };
}
138 changes: 133 additions & 5 deletions packages/commonjs/src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,24 @@ export const EXTERNAL_SUFFIX = '?commonjs-external';
export const getExternalProxyId = (id) => `\0${id}${EXTERNAL_SUFFIX}`;
export const getIdFromExternalProxyId = (proxyId) => proxyId.slice(1, -EXTERNAL_SUFFIX.length);

export const VIRTUAL_PATH_BASE = '/$$rollup_base$$';
export const getVirtualPathForDynamicRequirePath = (path, commonDir) => {
if (path.startsWith(commonDir))
return VIRTUAL_PATH_BASE + path.slice(commonDir.length);
return path;
};

export const DYNAMIC_REGISTER_PREFIX = '\0commonjs-dynamic-register:';
export const DYNAMIC_JSON_PREFIX = '\0commonjs-dynamic-json:';
export const DYNAMIC_PACKAGES_ID = '\0commonjs-dynamic-packages';

export const HELPERS_ID = '\0commonjsHelpers.js';

// `x['default']` is used instead of `x.default` for backward compatibility with ES3 browsers.
// Minifiers like uglify will usually transpile it back if compatibility with ES3 is not enabled.
export const HELPERS = `
export var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};

export function commonjsRequire () {
throw new Error('Dynamic requires are not currently supported by @rollup/plugin-commonjs');
}

export function unwrapExports (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
Expand All @@ -27,4 +34,125 @@ export function createCommonjsModule(fn, module) {

export function getCjsExportFromNamespace (n) {
return n && n['default'] || n;
}`;
}
`;

export const HELPER_NON_DYNAMIC = `
export function commonjsRequire () {
throw new Error('Dynamic requires are not currently supported by @rollup/plugin-commonjs');
}
`;

export const HELPERS_DYNAMIC = `
export function commonjsRegister (path, loader) {
DYNAMIC_REQUIRE_LOADERS[path] = loader;
}

const DYNAMIC_REQUIRE_LOADERS = Object.create(null);
const DYNAMIC_REQUIRE_CACHE = Object.create(null);
const DEFAULT_PARENT_MODULE = {
id: '<' + 'rollup>', exports: {}, parent: undefined, filename: null, loaded: false, children: [], paths: []
};
const CHECKED_EXTENSIONS = ['', '.js', '.json'];

function normalize (path) {
path = path.replace(/\\\\/g, '/');
const parts = path.split('/');
const slashed = parts[0] === '';
for (let i = 1; i < parts.length; i++) {
if (parts[i] === '.' || parts[i] === '') {
parts.splice(i--, 1);
}
}
for (let i = 1; i < parts.length; i++) {
if (parts[i] !== '..') continue;
if (i > 0 && parts[i - 1] !== '..' && parts[i - 1] !== '.') {
parts.splice(--i, 2);
i--;
}
}
path = parts.join('/');
if (slashed && path[0] !== '/')
path = '/' + path;
else if (path.length === 0)
path = '.';
return path;
}

function join () {
if (arguments.length === 0)
return '.';
let joined;
for (let i = 0; i < arguments.length; ++i) {
let arg = arguments[i];
if (arg.length > 0) {
if (joined === undefined)
joined = arg;
else
joined += '/' + arg;
}
}
if (joined === undefined)
return '.';

return joined;
}

function isPossibleNodeModulesPath (modulePath) {
let c0 = modulePath[0];
if (c0 === '/' || c0 === '\\\\') return false;
let c1 = modulePath[1], c2 = modulePath[2];
if ((c0 === '.' && (!c1 || c1 === '/' || c1 === '\\\\')) ||
(c0 === '.' && c1 === '.' && (!c2 || c2 === '/' || c2 === '\\\\'))) return false;
if (c1 === ':' && (c2 === '/' || c2 === '\\\\'))
return false;
return true;
}

export function commonjsRequire (path, originalModuleDir) {
const shouldTryNodeModules = isPossibleNodeModulesPath(path);
path = normalize(path);
let relPath;
while (true) {
if (!shouldTryNodeModules) {
relPath = originalModuleDir ? normalize(originalModuleDir + '/' + path) : path;
} else if (originalModuleDir) {
relPath = normalize(originalModuleDir + '/node_modules/' + path);
} else {
relPath = normalize(join('node_modules', path));
}
for (let extensionIndex = 0; extensionIndex < CHECKED_EXTENSIONS.length; extensionIndex++) {
const resolvedPath = relPath + CHECKED_EXTENSIONS[extensionIndex];
let cachedModule = DYNAMIC_REQUIRE_CACHE[resolvedPath];
if (cachedModule) return cachedModule.exports;
const loader = DYNAMIC_REQUIRE_LOADERS[resolvedPath];
if (loader) {
DYNAMIC_REQUIRE_CACHE[resolvedPath] = cachedModule = {
id: resolvedPath,
filename: resolvedPath,
exports: {},
parent: DEFAULT_PARENT_MODULE,
loaded: false,
children: [],
paths: []
};
try {
loader.call(commonjsGlobal, cachedModule, cachedModule.exports);
} catch (error) {
delete DYNAMIC_REQUIRE_CACHE[resolvedPath];
throw error;
}
cachedModule.loaded = true;
return cachedModule.exports;
};
}
if (!shouldTryNodeModules) break;
const nextDir = normalize(originalModuleDir + '/..');
if (nextDir === originalModuleDir) break;
originalModuleDir = nextDir;
}
return require(path);
}

commonjsRequire.cache = DYNAMIC_REQUIRE_CACHE;
`;
Loading