Skip to content
This repository was archived by the owner on May 16, 2025. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all 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
322 changes: 164 additions & 158 deletions src/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,197 +32,203 @@ const depFromErr = (err) => {
return matches[1];
};

function NpmInstallPlugin(options) {
this.preCompiler = null;
this.compiler = null;
this.options = Object.assign(installer.defaultOptions, options);
this.resolving = {};

installer.checkBabel();
}

NpmInstallPlugin.prototype.apply = (compiler) => {
this.compiler = compiler;

const plugin = { name: 'NpmInstallPlugin' };

// Recursively install missing dependencies so primary build doesn't fail
compiler.hooks.watchRun.tapAsync(plugin, this.preCompile.bind(this));

// Install externals that wouldn't normally be resolved
if (Array.isArray(compiler.options.externals)) {
compiler.options.externals.unshift(this.resolveExternal.bind(this));
class NpmInstallPlugin {
constructor(options) {
this.preCompiler = null;
this.compiler = null;
this.options = Object.assign(installer.defaultOptions, options);
this.resolving = {};

installer.checkBabel();
}
apply(compiler) {
this.compiler = compiler;
const plugin = { name: 'NpmInstallPlugin' };

compiler.hooks.afterResolvers.tap(plugin, () => {
// Install loaders on demand
compiler.resolverFactory.hooks.resolver.tap(
'loader',
plugin,
(resolver) => {
resolver.hooks.module.tapAsync(
'NpmInstallPlugin',
this.resolveLoader.bind(this)
);
}
);
// Recursively install missing dependencies so primary build doesn't fail
compiler.hooks.watchRun.tapAsync(plugin, this.preCompile.bind(this));

// Install project dependencies on demand
compiler.resolverFactory.hooks.resolver.tap(
'normal',
plugin,
(resolver) => {
resolver.hooks.module.tapAsync(
'NpmInstallPlugin',
this.resolveModule.bind(this)
);
}
);
});
};
// Install externals that wouldn't normally be resolved
if (Array.isArray(compiler.options.externals)) {
compiler.options.externals.unshift(this.resolveExternal.bind(this));
}

NpmInstallPlugin.prototype.install = (result) => {
if (!result) {
return;
compiler.hooks.afterResolvers.tap(plugin, () => {
// Install loaders on demand
compiler.resolverFactory.hooks.resolver.tap(
'loader',
plugin,
(resolver) => {
resolver.hooks.module.tapAsync(
'NpmInstallPlugin',
this.resolveLoader.bind(this)
);
}
);

// Install project dependencies on demand
compiler.resolverFactory.hooks.resolver.tap(
'normal',
plugin,
(resolver) => {
resolver.hooks.module.tapAsync(
'NpmInstallPlugin',
this.resolveModule.bind(this)
);
}
);
});
}

const dep = installer.check(result.request);

if (dep) {
let { dev } = this.options;

if (typeof this.options.dev === 'function') {
dev = !!this.options.dev(result.request, result.path);
install(result) {
if (!result) {
return;
}

installer.install(dep, Object.assign({}, this.options, { dev }));
}
};
const dep = installer.check(result.request);

if (dep) {
let { dev } = this.options;

NpmInstallPlugin.prototype.preCompile = (compilation, next) => {
if (!this.preCompiler) {
const { options } = this.compiler;
const config = Object.assign(
// Start with new config object
{},
// Inherit the current config
options,
{
// Ensure fresh cache
cache: {},
// Register plugin to install missing deps
plugins: [new NpmInstallPlugin(this.options)],
if (typeof this.options.dev === 'function') {
dev = !!this.options.dev(result.request, result.path);
}
);

this.preCompiler = webpack(config);
this.preCompiler.outputFileSystem = createFsFromVolume(new Volume());
this.preCompiler.outputFileSystem.join = path.join.bind(path);
installer.install(dep, Object.assign({}, this.options, { dev }));
}
}

this.preCompiler.run(next);
};

NpmInstallPlugin.prototype.resolveExternal = (context, request, callback) => {
// Only install direct dependencies, not sub-dependencies
if (context.match('node_modules')) {
return callback();
}
preCompile(compilation, next) {
if (!this.preCompiler) {
const { options } = this.compiler;
const config = Object.assign(
// Start with new config object
{},
// Inherit the current config
options,
{
// Ensure fresh cache
cache: {},
// Register plugin to install missing deps
plugins: [new NpmInstallPlugin(this.options)],
}
);

this.preCompiler = webpack(config);
this.preCompiler.outputFileSystem = createFsFromVolume(new Volume());
this.preCompiler.outputFileSystem.join = path.join.bind(path);
}

// Ignore !!bundle?lazy!./something
if (request.match(/(\?|\!)/)) {
return callback();
this.preCompiler.run(next);
}

const result = {
context: {},
path: context,
request,
};

this.resolve(
'normal',
result,
// eslint-disable-next-line func-names
function(err) {
if (err) {
this.install(Object.assign({}, result, { request: depFromErr(err) }));
}
resolveExternal(context, request, callback) {
// Only install direct dependencies, not sub-dependencies
if (context.match('node_modules')) {
return callback();
}

callback();
}.bind(this)
);
};
// Ignore !!bundle?lazy!./something
if (request.match(/(\?|\!)/)) {
return callback();
}

NpmInstallPlugin.prototype.resolve = (resolver, result, callback) => {
// eslint-disable-next-line
const { version } = require('webpack');
const major = version.split('.').shift();
const result = {
context: {},
path: context,
request,
};

if (major === '4') {
return this.compiler.resolverFactory
.get(resolver)
.resolve(result.context || {}, result.path, result.request, {}, callback);
this.resolve(
'normal',
result,
// eslint-disable-next-line func-names
function(err) {
if (err) {
this.install(Object.assign({}, result, { request: depFromErr(err) }));
}

callback();
}.bind(this)
);
}

throw new Error(`Unsupported Webpack version: ${version}`);
};
resolve(resolver, result, callback) {
// eslint-disable-next-line
const { version } = require('webpack');
const major = version.split('.').shift();

if (major === '4') {
return this.compiler.resolverFactory
.get(resolver)
.resolve(
result.context || {},
result.path,
result.request,
{},
callback
);
}

NpmInstallPlugin.prototype.resolveLoader = (result, resolveContext, next) => {
// Only install direct dependencies, not sub-dependencies
if (result.path.match('node_modules')) {
return next();
throw new Error(`Unsupported Webpack version: ${version}`);
}

if (this.resolving[result.request]) {
return next();
}
resolveLoader(result, resolveContext, next) {
// Only install direct dependencies, not sub-dependencies
if (result.path.match('node_modules')) {
return next();
}

this.resolving[result.request] = true;
if (this.resolving[result.request]) {
return next();
}

this.resolve(
'loader',
result,
// eslint-disable-next-line func-names
function(err) {
this.resolving[result.request] = false;
this.resolving[result.request] = true;

if (err) {
const loader = utils.normalizeLoader(result.request);
this.install(Object.assign({}, result, { request: loader }));
}
this.resolve(
'loader',
result,
// eslint-disable-next-line func-names
function(err) {
this.resolving[result.request] = false;

if (err) {
const loader = utils.normalizeLoader(result.request);
this.install(Object.assign({}, result, { request: loader }));
}

return next();
}.bind(this)
);
}

resolveModule(result, resolveContext, next) {
// Only install direct dependencies, not sub-dependencies
if (result.path.match('node_modules')) {
return next();
}.bind(this)
);
};
}

NpmInstallPlugin.prototype.resolveModule = (result, resolveContext, next) => {
// Only install direct dependencies, not sub-dependencies
if (result.path.match('node_modules')) {
return next();
}
if (this.resolving[result.request]) {
return next();
}

if (this.resolving[result.request]) {
return next();
}
this.resolving[result.request] = true;

this.resolving[result.request] = true;
this.resolve(
'normal',
result,
// eslint-disable-next-line func-names
function(err) {
this.resolving[result.request] = false;

this.resolve(
'normal',
result,
// eslint-disable-next-line func-names
function(err) {
this.resolving[result.request] = false;
if (err) {
this.install(Object.assign({}, result, { request: depFromErr(err) }));
}

if (err) {
this.install(Object.assign({}, result, { request: depFromErr(err) }));
}

return next();
}.bind(this)
);
};
return next();
}.bind(this)
);
}
}

module.exports = NpmInstallPlugin;
Loading