Skip to content

Commit

Permalink
feat: allow to run commands without webpack installation where it is …
Browse files Browse the repository at this point in the history
…unnecessary (#2907)
  • Loading branch information
alexander-akait authored and anshumanv committed Oct 20, 2021
1 parent de9a730 commit d54c6de
Show file tree
Hide file tree
Showing 15 changed files with 248 additions and 187 deletions.
2 changes: 2 additions & 0 deletions packages/configtest/src/index.ts
@@ -1,3 +1,5 @@
const WEBPACK_PACKAGE = process.env.WEBPACK_PACKAGE || "webpack";

class ConfigTestCommand {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
async apply(cli: any): Promise<void> {
Expand Down
8 changes: 3 additions & 5 deletions packages/generators/src/index.ts
Expand Up @@ -7,8 +7,6 @@ import initGenerator from "./init-generator";
class GeneratorsCommand {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
async apply(cli: any): Promise<void> {
const { logger } = cli;

await cli.makeCommand(
{
name: "init [generation-path]",
Expand Down Expand Up @@ -51,7 +49,7 @@ class GeneratorsCommand {
env.registerStub(initGenerator, generatorName);

env.run(generatorName, { cli, options }, () => {
logger.success("Project has been initialised with webpack!");
cli.logger.success("Project has been initialised with webpack!");
});
},
);
Expand Down Expand Up @@ -83,7 +81,7 @@ class GeneratorsCommand {
env.registerStub(loaderGenerator, generatorName);

env.run(generatorName, { cli, options }, () => {
logger.success("Loader template has been successfully scaffolded.");
cli.logger.success("Loader template has been successfully scaffolded.");
});
},
);
Expand Down Expand Up @@ -115,7 +113,7 @@ class GeneratorsCommand {
env.registerStub(pluginGenerator, generatorName);

env.run(generatorName, { cli, options }, () => {
logger.success("Plugin template has been successfully scaffolded.");
cli.logger.success("Plugin template has been successfully scaffolded.");
});
},
);
Expand Down
6 changes: 2 additions & 4 deletions packages/info/src/index.ts
Expand Up @@ -32,8 +32,6 @@ const DEFAULT_DETAILS: Information = {
class InfoCommand {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
async apply(cli: any): Promise<void> {
const { logger } = cli;

await cli.makeCommand(
{
name: "info",
Expand Down Expand Up @@ -71,7 +69,7 @@ class InfoCommand {
envinfoConfig["json"] = true;
break;
default:
logger.error(`'${output}' is not a valid value for output`);
cli.logger.error(`'${output}' is not a valid value for output`);
process.exit(2);
}
}
Expand All @@ -81,7 +79,7 @@ class InfoCommand {
info = info.replace(/npmPackages/g, "Packages");
info = info.replace(/npmGlobalPackages/g, "Global Packages");

logger.raw(info);
cli.logger.raw(info);
},
);
}
Expand Down
42 changes: 22 additions & 20 deletions packages/serve/src/index.ts
@@ -1,27 +1,27 @@
import { devServerOptionsType } from "./types";

const WEBPACK_PACKAGE = process.env.WEBPACK_PACKAGE || "webpack";
const WEBPACK_DEV_SERVER_PACKAGE = process.env.WEBPACK_DEV_SERVER_PACKAGE || "webpack-dev-server";

class ServeCommand {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
async apply(cli: any): Promise<void> {
const { logger, webpack } = cli;

const loadDevServerOptions = () => {
// TODO simplify this after drop webpack v4 and webpack-dev-server v3
// eslint-disable-next-line @typescript-eslint/no-var-requires, node/no-extraneous-require
const devServer = require("webpack-dev-server");
// eslint-disable-next-line @typescript-eslint/no-var-requires
const devServer = require(WEBPACK_DEV_SERVER_PACKAGE);
const isNewDevServerCLIAPI = typeof devServer.schema !== "undefined";

let options = {};

if (isNewDevServerCLIAPI) {
if (webpack.cli && typeof webpack.cli.getArguments === "function") {
options = webpack.cli.getArguments(devServer.schema);
if (cli.webpack.cli && typeof cli.webpack.cli.getArguments === "function") {
options = cli.webpack.cli.getArguments(devServer.schema);
} else {
options = devServer.cli.getArguments();
}
} else {
// eslint-disable-next-line node/no-extraneous-require
options = require("webpack-dev-server/bin/cli-flags");
options = require(`${WEBPACK_DEV_SERVER_PACKAGE}/bin/cli-flags`);
}

// Old options format
Expand Down Expand Up @@ -50,15 +50,17 @@ class ServeCommand {
description: "Run the webpack dev server.",
usage: "[entries...] [options]",
pkg: "@webpack-cli/serve",
dependencies: ["webpack-dev-server"],
dependencies: [WEBPACK_PACKAGE, WEBPACK_DEV_SERVER_PACKAGE],
},
() => {
async () => {
let devServerFlags = [];

cli.webpack = await cli.loadWebpack();

try {
devServerFlags = loadDevServerOptions();
} catch (error) {
logger.error(
cli.logger.error(
`You need to install 'webpack-dev-server' for running 'webpack serve'.\n${error}`,
);
process.exit(2);
Expand Down Expand Up @@ -182,17 +184,17 @@ class ServeCommand {
process.stdin.resume();
}

// eslint-disable-next-line @typescript-eslint/no-var-requires, node/no-extraneous-require
const DevServer = require("webpack-dev-server");
// eslint-disable-next-line @typescript-eslint/no-var-requires
const DevServer = require(WEBPACK_DEV_SERVER_PACKAGE);
const isNewDevServerCLIAPI = typeof DevServer.schema !== "undefined";

let devServerVersion;

try {
// eslint-disable-next-line node/no-extraneous-require, @typescript-eslint/no-var-requires
devServerVersion = require("webpack-dev-server/package.json").version;
// eslint-disable-next-line @typescript-eslint/no-var-requires
devServerVersion = require(`${WEBPACK_DEV_SERVER_PACKAGE}/package.json`).version;
} catch (err) {
logger.error(
cli.logger.error(
`You need to install 'webpack-dev-server' for running 'webpack serve'.\n${err}`,
);
process.exit(2);
Expand Down Expand Up @@ -223,8 +225,8 @@ class ServeCommand {
}, {});
const result = { ...(compilerForDevServer.options.devServer || {}) };
const problems = (
webpack.cli && typeof webpack.cli.processArguments === "function"
? webpack.cli
cli.webpack.cli && typeof cli.webpack.cli.processArguments === "function"
? cli.webpack.cli
: DevServer.cli
).processArguments(args, result, values);

Expand Down Expand Up @@ -358,9 +360,9 @@ class ServeCommand {
servers.push(server);
} catch (error) {
if (cli.isValidationError(error)) {
logger.error(error.message);
cli.logger.error(error.message);
} else {
logger.error(error);
cli.logger.error(error);
}

process.exit(2);
Expand Down
23 changes: 1 addition & 22 deletions packages/webpack-cli/bin/cli.js
Expand Up @@ -10,7 +10,6 @@ require("v8-compile-cache");

const importLocal = require("import-local");
const runCLI = require("../lib/bootstrap");
const utils = require("../lib/utils");

if (!process.env.WEBPACK_CLI_SKIP_IMPORT_LOCAL) {
// Prefer the local installation of `webpack-cli`
Expand All @@ -21,24 +20,4 @@ if (!process.env.WEBPACK_CLI_SKIP_IMPORT_LOCAL) {

process.title = "webpack";

if (utils.packageExists("webpack")) {
runCLI(process.argv, originalModuleCompile);
} else {
const { promptInstallation, logger, colors } = utils;

promptInstallation("webpack", () => {
utils.logger.error(`It looks like ${colors.bold("webpack")} is not installed.`);
})
.then(() => {
logger.success(`${colors.bold("webpack")} was installed successfully.`);

runCLI(process.argv, originalModuleCompile);
})
.catch(() => {
logger.error(
`Action Interrupted, Please try once again or install ${colors.bold("webpack")} manually.`,
);

process.exit(2);
});
}
runCLI(process.argv, originalModuleCompile);
88 changes: 65 additions & 23 deletions packages/webpack-cli/lib/webpack-cli.js
Expand Up @@ -6,10 +6,11 @@ const Module = require("module");
const { program, Option } = require("commander");
const utils = require("./utils");

const WEBPACK_PACKAGE = process.env.WEBPACK_PACKAGE || "webpack";
const WEBPACK_DEV_SERVER_PACKAGE = process.env.WEBPACK_DEV_SERVER_PACKAGE || "webpack-dev-server";

class WebpackCLI {
constructor() {
// Global
this.webpack = require(process.env.WEBPACK_PACKAGE || "webpack");
this.logger = utils.logger;
this.utils = utils;

Expand Down Expand Up @@ -73,19 +74,27 @@ class WebpackCLI {
return result;
}

loadJSONFile(pathToFile) {
loadJSONFile(pathToFile, handleError = true) {
let result;

try {
result = require(pathToFile);
} catch (error) {
this.logger.error(error);
process.exit(2);
if (handleError) {
this.logger.error(error);
process.exit(2);
} else {
throw error;
}
}

return result;
}

async loadWebpack(handleError = true) {
return this.tryRequireThenImport(WEBPACK_PACKAGE, handleError);
}

async makeCommand(commandOptions, options, action) {
const alreadyLoaded = this.program.commands.find(
(command) =>
Expand Down Expand Up @@ -139,13 +148,27 @@ class WebpackCLI {
continue;
}

const { promptInstallation, colors } = this.utils;
let skipInstallation = false;

// Allow to use `./path/to/webpack.js` outside `node_modules`
if (dependency === WEBPACK_PACKAGE && fs.existsSync(WEBPACK_PACKAGE)) {
skipInstallation = true;
}

await promptInstallation(dependency, () => {
// Allow to use `./path/to/webpack-dev-server.js` outside `node_modules`
if (dependency === WEBPACK_DEV_SERVER_PACKAGE && fs.existsSync(WEBPACK_PACKAGE)) {
skipInstallation = true;
}

if (skipInstallation) {
continue;
}

await this.utils.promptInstallation(dependency, () => {
this.logger.error(
`For using '${colors.green(
`For using '${this.utils.colors.green(
commandOptions.name.split(" ")[0],
)}' command you need to install: '${colors.green(dependency)}' package`,
)}' command you need to install: '${this.utils.colors.green(dependency)}' package.`,
);
});
}
Expand All @@ -159,11 +182,11 @@ class WebpackCLI {
commandOptions.description
} To see all available options you need to install ${commandOptions.dependencies
.map((dependency) => `'${dependency}'`)
.join(",")}.`,
.join(", ")}.`,
);
options = [];
} else {
options = options();
options = await options();
}
}

Expand Down Expand Up @@ -970,12 +993,14 @@ class WebpackCLI {
alias: ["bundle", "b"],
description: "Run webpack (default command, can be omitted).",
usage: "[entries...] [options]",
dependencies: [WEBPACK_PACKAGE],
};
const watchCommandOptions = {
name: "watch [entries...]",
alias: "w",
description: "Run webpack and watch for files changes.",
usage: "[entries...] [options]",
dependencies: [WEBPACK_PACKAGE],
};
const versionCommandOptions = {
name: "version [commands...]",
Expand Down Expand Up @@ -1076,11 +1101,15 @@ class WebpackCLI {
const isWatchCommandUsed = isCommand(commandName, watchCommandOptions);

if (isBuildCommandUsed || isWatchCommandUsed) {
const options = this.getBuiltInOptions();

await this.makeCommand(
isBuildCommandUsed ? buildCommandOptions : watchCommandOptions,
isWatchCommandUsed ? options.filter((option) => option.name !== "watch") : options,
async () => {
this.webpack = await this.loadWebpack();

return isWatchCommandUsed
? this.getBuiltInOptions().filter((option) => option.name !== "watch")
: this.getBuiltInOptions();
},
async (entries, options) => {
if (entries.length > 0) {
options.entry = [...entries, ...(options.entry || [])];
Expand All @@ -1093,7 +1122,7 @@ class WebpackCLI {
// Stub for the `help` command
this.makeCommand(helpCommandOptions, [], () => {});
} else if (isCommand(commandName, versionCommandOptions)) {
// Stub for the `help` command
// Stub for the `version` command
this.makeCommand(versionCommandOptions, [], () => {});
} else {
const builtInExternalCommandInfo = externalBuiltInCommandsInfo.find(
Expand Down Expand Up @@ -1121,7 +1150,7 @@ class WebpackCLI {

pkg = await promptInstallation(pkg, () => {
this.logger.error(
`For using this command you need to install: '${colors.green(pkg)}' package`,
`For using this command you need to install: '${colors.green(pkg)}' package.`,
);
});
}
Expand Down Expand Up @@ -1275,17 +1304,30 @@ class WebpackCLI {
}
}

let webpack;

try {
webpack = await this.loadWebpack(false);
} catch (_error) {
// Nothing
}

this.logger.raw(`webpack: ${webpack ? webpack.version : "not installed"}`);

const pkgJSON = this.loadJSONFile("../package.json");

this.logger.raw(`webpack ${this.webpack.version}`);
this.logger.raw(`webpack-cli ${pkgJSON.version}`);
this.logger.raw(`webpack-cli: ${pkgJSON.version}`);

if (this.utils.packageExists("webpack-dev-server")) {
const { version } = this.loadJSONFile("webpack-dev-server/package.json");
let devServer;

this.logger.raw(`webpack-dev-server ${version}`);
try {
devServer = await this.loadJSONFile("webpack-dev-server/package.json", false);
} catch (_error) {
// Nothing
}

this.logger.raw(`webpack-dev-server ${devServer ? devServer.version : "not installed"}`);

process.exit(0);
};
this.program.option(
Expand Down Expand Up @@ -1490,11 +1532,11 @@ class WebpackCLI {
);
if (typeof builtInCommandUsed !== "undefined") {
this.logger.error(
`For using '${name}' command you need to install '${builtInCommandUsed.pkg}' package`,
`For using '${name}' command you need to install '${builtInCommandUsed.pkg}' package.`,
);
} else {
this.logger.error(`Can't find and load command '${name}'`);
this.logger.error("Run 'webpack --help' to see available commands and options");
this.logger.error("Run 'webpack --help' to see available commands and options.");
}
process.exit(2);
}
Expand Down

0 comments on commit d54c6de

Please sign in to comment.