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

Bundle node modules on --target=node/electron #796

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
"babel-types": "^6.25.0",
"babylon": "^6.17.4",
"babylon-walk": "^1.0.2",
"browser-resolve": "^1.11.2",
"browserslist": "^2.11.2",
"chalk": "^2.1.0",
"chokidar": "^2.0.1",
Expand All @@ -36,13 +35,13 @@
"micromatch": "^3.0.4",
"mkdirp": "^0.5.1",
"node-forge": "^0.7.1",
"node-libs-browser": "^2.0.0",
"node-libs-browser": "^2.1.0",
"opn": "^5.1.0",
"physical-cpu-count": "^2.0.0",
"postcss": "^6.0.10",
"postcss-value-parser": "^3.3.0",
"posthtml": "^0.11.2",
"resolve": "^1.4.0",
"resolve": "^1.5.0",
"sanitize-filename": "^1.6.1",
"semver": "^5.4.1",
"serialize-to-js": "^1.1.1",
Expand Down
1 change: 1 addition & 0 deletions src/Bundler.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class Bundler extends EventEmitter {
? false
: typeof options.hmr === 'boolean' ? options.hmr : watch,
https: options.https || false,
bundleNodeModules: options.bundleNodeModules || target === 'browser',
logLevel: typeof options.logLevel === 'number' ? options.logLevel : 3,
mainFile: this.mainFile,
hmrPort: options.hmrPort || 0,
Expand Down
65 changes: 54 additions & 11 deletions src/Resolver.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
const promisify = require('./utils/promisify');
const resolve = require('browser-resolve');
const resolve = require('resolve');
const resolveAsync = promisify(resolve);
const builtins = require('./builtins');
const path = require('path');
const glob = require('glob');

const browserReplacements = require('node-libs-browser');
for (var key in browserReplacements) {
if (browserReplacements[key] == null) {
browserReplacements[key] = builtins['_empty'];
}
}

class Resolver {
constructor(options = {}) {
this.options = options;
Expand All @@ -13,14 +20,29 @@ class Resolver {

async resolve(filename, parent) {
var resolved = await this.resolveInternal(filename, parent, resolveAsync);
resolved = this.postResolve(resolved);
return this.saveCache(filename, parent, resolved);
}

resolveSync(filename, parent) {
var resolved = this.resolveInternal(filename, parent, resolve.sync);
resolved = this.postResolve(resolved);
return this.saveCache(filename, parent, resolved);
}

postResolve(resolved) {
if (Array.isArray(resolved)) {
resolved = {path: resolved[0], pkg: resolved[1]};
} else if (typeof resolved === 'string') {
resolved = {path: resolved, pkg: null};
}

if (this.options.target === 'browser' && browserReplacements[resolved.path])
resolved.path = browserReplacements[resolved.path];

return resolved;
}

resolveInternal(filename, parent, resolver) {
let key = this.getCacheKey(filename, parent);
if (this.cache.has(key)) {
Expand All @@ -31,6 +53,8 @@ class Resolver {
return {path: path.resolve(path.dirname(parent), filename)};
}

if (builtins[filename]) return {path: builtins[filename]};

let extensions = Object.keys(this.options.extensions);
if (parent) {
const parentExt = path.extname(parent);
Expand All @@ -39,11 +63,10 @@ class Resolver {
}

return resolver(filename, {
filename: parent,
paths: this.options.paths,
modules: builtins,
basedir: path.dirname(parent || filename),
extensions: extensions,
packageFilter(pkg, pkgfile) {
paths: this.options.paths,
packageFilter: (pkg, pkgfile) => {
// Expose the path to the package.json file
pkg.pkgfile = pkgfile;

Expand All @@ -57,7 +80,33 @@ class Resolver {
pkg.main = main;
}

if (
this.options.target === 'browser' &&
typeof pkg.browser === 'string'
) {
pkg.main = pkg.browser;
}

return pkg;
},
pathFilter: (pkg, absolutePath, relativePath) => {
if (this.options.target === 'browser' && pkg.browser) {
for (const ext of ['', '.js', '.json']) {
const key = './' + relativePath + ext;
if (typeof pkg.browser === 'string') {
if (key === pkg.main) return pkg.browser;
} else {
const replacement = pkg.browser[key];
if (replacement === false) {
return builtins['_empty'];
} else if (typeof replacement === 'string') {
return replacement;
}
}
}
}

return absolutePath;
}
});
}
Expand All @@ -67,12 +116,6 @@ class Resolver {
}

saveCache(filename, parent, resolved) {
if (Array.isArray(resolved)) {
resolved = {path: resolved[0], pkg: resolved[1]};
} else if (typeof resolved === 'string') {
resolved = {path: resolved, pkg: null};
}

this.cache.set(this.getCacheKey(filename, parent), resolved);
return resolved;
}
Expand Down
22 changes: 20 additions & 2 deletions src/assets/JSAsset.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@ const babel = require('../transforms/babel');
const generate = require('babel-generator').default;
const uglify = require('../transforms/uglify');
const SourceMap = require('../SourceMap');
const nodeLibsBrowser = require('node-libs-browser');

const IMPORT_RE = /\b(?:import\b|export\b|require\s*\()/;
const GLOBAL_RE = /\b(?:process|__dirname|__filename|global|Buffer)\b/;
const FS_RE = /\breadFileSync\b/;
const SW_RE = /\bnavigator\s*\.\s*serviceWorker\s*\.\s*register\s*\(/;
const WORKER_RE = /\bnew\s*Worker\s*\(/;

const nativeModules = new Set(Object.keys(nodeLibsBrowser));

class JSAsset extends Asset {
constructor(name, pkg, options) {
super(name, pkg, options);
Expand All @@ -27,6 +30,7 @@ class JSAsset extends Asset {
this.isES6Module = false;
this.outputCode = null;
this.cacheData.env = {};
this.hasFsDependency = false;
}

shouldInvalidate(cacheData) {
Expand Down Expand Up @@ -90,17 +94,27 @@ class JSAsset extends Asset {
walk.ancestor(this.ast, collectDependencies, this);
}

addDependency(name, opts) {
if (name === 'fs') this.hasFsDependency = true;

if (this.options.target !== 'browser' && nativeModules.has(name)) return;

return super.addDependency(name, opts);
}

async pretransform() {
await babel(this);
}

async transform() {
if (this.options.target === 'browser') {
if (this.dependencies.has('fs') && FS_RE.test(this.contents)) {
if (this.options.bundleNodeModules) {
if (this.hasFsDependency && FS_RE.test(this.contents)) {
await this.parseIfNeeded();
this.traverse(fsVisitor);
}
}

if (this.options.target === 'browser') {
if (GLOBAL_RE.test(this.contents)) {
await this.parseIfNeeded();
walk.ancestor(this.ast, insertGlobals, this);
Expand Down Expand Up @@ -155,6 +169,10 @@ class JSAsset extends Asset {
);
}

if (code.startsWith('#!')) {
code = code.slice(code.indexOf('\n'));
}

if (this.globals.size > 0) {
code = Array.from(this.globals.values()).join('\n') + '\n' + code;
if (this.options.sourceMaps) {
Expand Down
15 changes: 3 additions & 12 deletions src/builtins/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
var builtins = require('node-libs-browser');

for (var key in builtins) {
if (builtins[key] == null) {
builtins[key] = require.resolve('./_empty.js');
}
}

builtins['_bundle_loader'] = require.resolve('./bundle-loader.js');
builtins['_css_loader'] = require.resolve('./css-loader.js');

module.exports = builtins;
module.exports['_empty'] = require.resolve('./_empty.js');
module.exports['_bundle_loader'] = require.resolve('./bundle-loader.js');
module.exports['_css_loader'] = require.resolve('./css-loader.js');
12 changes: 12 additions & 0 deletions src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ program
.option('--no-hmr', 'disable hot module replacement')
.option('--no-cache', 'disable the filesystem cache')
.option('--no-source-maps', 'disable sourcemaps')
.option(
'--bundle-node-modules',
'force bundling node modules, even on node/electron target'
)
.option(
'-t, --target [target]',
'set the runtime environment, either "node", "browser" or "electron". defaults to "browser"',
Expand All @@ -57,6 +61,10 @@ program
.option('--no-hmr', 'disable hot module replacement')
.option('--no-cache', 'disable the filesystem cache')
.option('--no-source-maps', 'disable sourcemaps')
.option(
'--bundle-node-modules',
'force bundling node modules, even on node/electron target'
)
.option(
'-t, --target [target]',
'set the runtime environment, either "node", "browser" or "electron". defaults to "browser"',
Expand All @@ -77,6 +85,10 @@ program
)
.option('--no-minify', 'disable minification')
.option('--no-cache', 'disable the filesystem cache')
.option(
'--bundle-node-modules',
'force bundling node modules, even on node/electron target'
)
.option(
'-t, --target <target>',
'set the runtime environment, either "node", "browser" or "electron". defaults to "browser"',
Expand Down
22 changes: 21 additions & 1 deletion src/visitors/dependencies.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const types = require('babel-types');
const path = require('path');
const template = require('babel-template');
const urlJoin = require('../utils/urlJoin');
const isURL = require('../utils/is-url');
Expand Down Expand Up @@ -33,6 +34,25 @@ module.exports = {
CallExpression(node, asset, ancestors) {
let {callee, arguments: args} = node;

if (
args.length === 1 &&
types.isBinaryExpression(args[0], {operator: '+'})
) {
const left = args[0].left;
const right = args[0].right;

if (
types.isIdentifier(left) &&
left.name === '__dirname' &&
types.isStringLiteral(right)
) {
args[0] = node.arguments[0] = types.stringLiteral(
path.dirname(asset.name) + right.value
);
asset.isAstDirty = true;
}
}

let isRequire =
types.isIdentifier(callee) &&
callee.name === 'require' &&
Expand Down Expand Up @@ -87,7 +107,7 @@ module.exports = {
};

function addDependency(asset, node, opts = {}) {
if (asset.options.target !== 'browser') {
if (!asset.options.bundleNodeModules) {
const isRelativeImport =
node.value.startsWith('/') ||
node.value.startsWith('./') ||
Expand Down