Skip to content

Commit

Permalink
feat: added build command (aliases - 'bundle' and 'b')
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-akait committed Jan 14, 2021
1 parent 87b51c3 commit 7590f66
Show file tree
Hide file tree
Showing 13 changed files with 131 additions and 57 deletions.
8 changes: 5 additions & 3 deletions OPTIONS.md
@@ -1,8 +1,10 @@
```
Usage: webpack [options]
Alternative usage: webpack bundle [options]
Alternative usage: webpack --config <config> [options]
Alternative usage: webpack bundle --config <config> [options]
Alternative usage: webpack build [options]
Alternative usage: webpack bundle [options]
Alternative usage: webpack b [options]
Alternative usage: webpack build --config <config> [options]
The build tool for modern web applications.
Expand Down Expand Up @@ -697,7 +699,7 @@ Global options:
-h, --help [verbose] Display help for commands and options.
Commands:
bundle|b [options] Run webpack (default command, can be omitted).
build|bundle|b [options] Run webpack (default command, can be omitted).
version|v Output the version number of 'webpack', 'webpack-cli' and 'webpack-dev-server' and commands.
help|h Display help for commands and options.
serve|s [options] Run the webpack dev server.
Expand Down
103 changes: 59 additions & 44 deletions packages/webpack-cli/lib/webpack-cli.js
Expand Up @@ -31,7 +31,7 @@ class WebpackCLI {

async makeCommand(commandOptions, options, action) {
const alreadyLoaded = this.program.commands.find(
(command) => command.name() === commandOptions.name || command.alias() === commandOptions.alias,
(command) => command.name() === commandOptions.name || command.aliases().includes(commandOptions.alias),
);

if (alreadyLoaded) {
Expand Down Expand Up @@ -229,9 +229,9 @@ class WebpackCLI {

async run(args, parseOptions) {
// Built-in internal commands
const bundleCommandOptions = {
name: 'bundle',
alias: 'b',
const buildCommandOptions = {
name: 'build',
alias: ['bundle', 'b'],
description: 'Run webpack (default command, can be omitted).',
usage: '[options]',
};
Expand Down Expand Up @@ -286,8 +286,25 @@ class WebpackCLI {
},
];

const knownCommands = [bundleCommandOptions, versionCommandOptions, helpCommandOptions, ...externalBuiltInCommandsInfo];
const isKnownCommand = (name) => knownCommands.find((command) => command.name === name || command.alias === name);
const knownCommands = [buildCommandOptions, versionCommandOptions, helpCommandOptions, ...externalBuiltInCommandsInfo];
const isKnownCommand = (name) =>
knownCommands.find(
(command) =>
command.name === name || (Array.isArray(command.alias) ? command.alias.includes(name) : command.alias === name),
);
const isBuildCommand = (name) =>
buildCommandOptions.name === name ||
(Array.isArray(buildCommandOptions.alias) ? buildCommandOptions.alias.includes(name) : buildCommandOptions.alias === name);
const isHelpCommand = (name) =>
helpCommandOptions.name === name ||
(Array.isArray(helpCommandOptions.alias) ? helpCommandOptions.alias.includes(name) : helpCommandOptions.alias === name);
const isVersionCommand = (name) =>
versionCommandOptions.name === name ||
(Array.isArray(versionCommandOptions.alias)
? versionCommandOptions.alias.includes(name)
: versionCommandOptions.alias === name);
const findCommandByName = (name) =>
this.program.commands.find((command) => name === command.name() || command.alias().includes(name));

const getCommandNameAndOptions = (args) => {
let commandName;
Expand All @@ -311,16 +328,15 @@ class WebpackCLI {

const isDefault = typeof commandName === 'undefined';

return { commandName: isDefault ? bundleCommandOptions.name : commandName, options, isDefault };
return { commandName: isDefault ? buildCommandOptions.name : commandName, options, isDefault };
};
const loadCommandByName = async (commandName, allowToInstall = false) => {
if (commandName === bundleCommandOptions.name || commandName === bundleCommandOptions.alias) {
// Make `bundle|b [options]` command
await this.makeCommand(bundleCommandOptions, this.getBuiltInOptions(), async (program) => {
if (isBuildCommand(commandName)) {
await this.makeCommand(buildCommandOptions, this.getBuiltInOptions(), async (program) => {
const options = program.opts();

if (program.args.length > 0) {
const possibleCommands = [].concat([bundleCommandOptions.name]).concat(program.args);
const possibleCommands = [].concat([buildCommandOptions.name]).concat(program.args);

logger.error('Running multiple commands at the same time is not possible');
logger.error(`Found commands: ${possibleCommands.map((item) => `'${item}'`).join(', ')}`);
Expand All @@ -330,16 +346,19 @@ class WebpackCLI {

await this.bundleCommand(options);
});
} else if (commandName === helpCommandOptions.name || commandName === helpCommandOptions.alias) {
} else if (isHelpCommand(commandName)) {
// Stub for the `help` command
this.makeCommand(helpCommandOptions, [], () => {});
} else if (commandName === versionCommandOptions.name || commandName === helpCommandOptions.alias) {
} else if (isVersionCommand(commandName)) {
// Stub for the `help` command
this.makeCommand(versionCommandOptions, [], () => {});
} else {
const builtInExternalCommandInfo = externalBuiltInCommandsInfo.find(
(externalBuiltInCommandInfo) =>
externalBuiltInCommandInfo.name === commandName || externalBuiltInCommandInfo.alias === commandName,
externalBuiltInCommandInfo.name === commandName ||
(typeof Array.isArray(externalBuiltInCommandInfo.alias)
? externalBuiltInCommandInfo.alias.includes(commandName)
: externalBuiltInCommandInfo.alias === commandName),
);

let pkg;
Expand Down Expand Up @@ -420,9 +439,7 @@ class WebpackCLI {
const { commandName } = getCommandNameAndOptions(this.program.args);

if (commandName) {
const command = this.program.commands.find(
(command) => command.name() === commandName || command.alias() === commandName,
);
const command = findCommandByName(commandName);

if (!command) {
logger.error(`Can't find and load command '${commandName}'`);
Expand Down Expand Up @@ -470,13 +487,7 @@ class WebpackCLI {
const outputVersion = async (options) => {
// Filter `bundle`, `version` and `help` commands
const possibleCommandNames = options.filter(
(options) =>
options !== bundleCommandOptions.name &&
options !== bundleCommandOptions.alias &&
options !== versionCommandOptions.name &&
options !== versionCommandOptions.alias &&
options !== helpCommandOptions.name &&
options !== helpCommandOptions.alias,
(option) => !isBuildCommand(option) && !isVersionCommand(option) && !isHelpCommand(option),
);

possibleCommandNames.forEach((possibleCommandName) => {
Expand All @@ -495,9 +506,7 @@ class WebpackCLI {
await Promise.all(possibleCommandNames.map((possibleCommand) => loadCommandByName(possibleCommand)));

for (const possibleCommandName of possibleCommandNames) {
const foundCommand = this.program.commands.find(
(command) => command.name() === possibleCommandName || command.alias() === possibleCommandName,
);
const foundCommand = findCommandByName(possibleCommandName);

if (!foundCommand) {
logger.error(`Unknown command '${possibleCommandName}'`);
Expand Down Expand Up @@ -563,9 +572,7 @@ class WebpackCLI {
}),
);

const bundleCommand = this.program.commands.find(
(command) => command.name() === bundleCommandOptions.name || command.alias() === bundleCommandOptions.alias,
);
const bundleCommand = findCommandByName(buildCommandOptions.name);

if (!isVerbose) {
hideVerboseOptions(bundleCommand);
Expand All @@ -574,10 +581,10 @@ class WebpackCLI {
let helpInformation = bundleCommand
.helpInformation()
.trimRight()
.replace(bundleCommandOptions.description, 'The build tool for modern web applications.')
.replace(buildCommandOptions.description, 'The build tool for modern web applications.')
.replace(
/Usage:.+/,
'Usage: webpack [options]\nAlternative usage: webpack bundle [options]\nAlternative usage: webpack --config <config> [options]\nAlternative usage: webpack bundle --config <config> [options]',
'Usage: webpack [options]\nAlternative usage: webpack --config <config> [options]\nAlternative usage: webpack build [options]\nAlternative usage: webpack bundle [options]\nAlternative usage: webpack b [options]\nAlternative usage: webpack build --config <config> [options]',
);

logger.raw(helpInformation);
Expand All @@ -598,7 +605,7 @@ class WebpackCLI {

await loadCommandByName(name);

const command = this.program.commands.find((command) => command.name() === name || command.alias() === name);
const command = findCommandByName(name);

if (!command) {
logger.error(`Can't find and load command '${name}'`);
Expand All @@ -612,30 +619,36 @@ class WebpackCLI {

let helpInformation = command.helpInformation().trimRight();

if (name === bundleCommandOptions.name || name === bundleCommandOptions.alias) {
if (isBuildCommand(name)) {
helpInformation = helpInformation
.replace(bundleCommandOptions.description, 'The build tool for modern web applications.')
.replace(buildCommandOptions.description, 'The build tool for modern web applications.')
.replace(
/Usage:.+/,
'Usage: webpack [options]\nAlternative usage: webpack bundle [options]\nAlternative usage: webpack --config <config> [options]\nAlternative usage: webpack bundle --config <config> [options]',
'Usage: webpack [options]\nAlternative usage: webpack --config <config> [options]\nAlternative usage: webpack build [options]\nAlternative usage: webpack bundle [options]\nAlternative usage: webpack b [options]\nAlternative usage: webpack build --config <config> [options]',
);
}

logger.raw(helpInformation);
}

const globalOptions = program.helpInformation().match(/Options:\n(?<globalOptions>.+)\nCommands:\n/s);
const programHelpInformation = program.helpInformation();
const globalOptions = programHelpInformation.match(/Options:\n(?<globalOptions>.+)\nCommands:\n/s);

if (globalOptions && globalOptions.groups.globalOptions) {
logger.raw('\nGlobal options:');
logger.raw(globalOptions.groups.globalOptions.trimRight());
}

if (isGlobal) {
const globalCommands = program.helpInformation().match(/Commands:\n(?<globalCommands>.+)/s);
const globalCommands = programHelpInformation.match(/Commands:\n(?<globalCommands>.+)/s);

if (isGlobal && globalCommands.groups.globalCommands) {
logger.raw('\nCommands:');
logger.raw(globalCommands.groups.globalCommands.trimRight());
logger.raw(
globalCommands.groups.globalCommands
.trimRight()
// `commander` doesn't support multiple alias in help
.replace('build|bundle [options] ', 'build|bundle|b [options]'),
);
}

logger.raw("\nTo see list of all supported commands and options run 'webpack --help=verbose'.\n");
Expand Down Expand Up @@ -665,7 +678,7 @@ class WebpackCLI {

const opts = program.opts();

if (opts.help || commandName === helpCommandOptions.name || commandName === helpCommandOptions.alias) {
if (opts.help || isHelpCommand(commandName)) {
let isVerbose = false;

if (opts.help) {
Expand All @@ -686,7 +699,7 @@ class WebpackCLI {
await outputHelp(optionsForHelp, isVerbose, program);
}

if (opts.version || commandName === versionCommandOptions.name || commandName === versionCommandOptions.alias) {
if (opts.version || isVersionCommand(commandName)) {
const optionsForVersion = [].concat(opts.version ? [commandName] : []).concat(options);

await outputVersion(optionsForVersion, program);
Expand All @@ -700,7 +713,9 @@ class WebpackCLI {
const found = knownCommands.find((commandOptions) => distance(commandName, commandOptions.name) < 3);

if (found) {
logger.error(`Did you mean '${found.name}' (alias '${found.alias}')?`);
logger.error(
`Did you mean '${found.name}' (alias '${Array.isArray(found.alias) ? found.alias.join(', ') : found.alias}')?`,
);
}

logger.error("Run 'webpack --help' to see available commands and options");
Expand Down Expand Up @@ -1313,7 +1328,7 @@ class WebpackCLI {
}
};

options.argv = { ...options, env: { WEBPACK_BUNDLE: true, ...options.env } };
options.argv = { ...options, env: { WEBPACK_BUNDLE: true, WEBPACK_BUILD: true, ...options.env } };

compiler = await this.createCompiler(options, callback);

Expand Down
16 changes: 12 additions & 4 deletions test/bundle/basic/basic.test.js → test/build/basic/basic.test.js
Expand Up @@ -4,27 +4,35 @@ const { run } = require('../../utils/test-utils');

describe('bundle command', () => {
it('should work', async () => {
const { exitCode, stderr, stdout } = run(__dirname, ['bundle'], false);
const { exitCode, stderr, stdout } = run(__dirname, ['build'], false);

expect(exitCode).toBe(0);
expect(stderr).toBeFalsy();
expect(stdout).toBeTruthy();
});

it('should work with alias', async () => {
it('should work with the "b" alias', async () => {
const { exitCode, stderr, stdout } = run(__dirname, ['b'], false);

expect(exitCode).toBe(0);
expect(stderr).toBeFalsy();
expect(stdout).toBeTruthy();
});

it('should work with "bundle" alias', async () => {
const { exitCode, stderr, stdout } = run(__dirname, ['bundle'], false);

expect(exitCode).toBe(0);
expect(stderr).toBeFalsy();
expect(stdout).toBeTruthy();
});

it('should log error with multi commands', async () => {
const { exitCode, stderr, stdout } = run(__dirname, ['bundle', 'info'], false);

expect(exitCode).toBe(2);
expect(stderr).toContain('Running multiple commands at the same time is not possible');
expect(stderr).toContain("Found commands: 'bundle', 'info'");
expect(stderr).toContain("Found commands: 'build', 'info'");
expect(stderr).toContain("Run 'webpack --help' to see available commands and options");
expect(stdout).toBeFalsy();
});
Expand All @@ -34,7 +42,7 @@ describe('bundle command', () => {

expect(exitCode).toBe(2);
expect(stderr).toContain('Running multiple commands at the same time is not possible');
expect(stderr).toContain("Found commands: 'bundle', 'i'");
expect(stderr).toContain("Found commands: 'build', 'i'");
expect(stderr).toContain("Run 'webpack --help' to see available commands and options");
expect(stdout).toBeFalsy();
});
Expand Down
File renamed without changes.
File renamed without changes.
24 changes: 24 additions & 0 deletions test/build/build-variable/webpack.config.js
@@ -0,0 +1,24 @@
const isInProcess = process.env.WEBPACK_BUNDLE;

class CustomTestPlugin {
constructor(isInEnvironment) {
this.isInEnvironment = isInEnvironment;
}
apply(compiler) {
compiler.hooks.done.tap('testPlugin', () => {
if (!isInProcess && this.isInEnvironment) {
console.log('PASS');
} else {
console.log('FAIL');
}
});
}
}

module.exports = (env) => {
return {
mode: 'development',
devtool: false,
plugins: [new CustomTestPlugin(env.WEBPACK_BUILD)],
};
};
13 changes: 13 additions & 0 deletions test/build/bundle-variable/bundle-variable.test.js
@@ -0,0 +1,13 @@
'use strict';

const { run } = require('../../utils/test-utils');

describe('bundle variable', () => {
it('compiles without flags and export variable', async () => {
const { exitCode, stderr, stdout } = run(__dirname, [], false);

expect(exitCode).toBe(0);
expect(stderr).toBeFalsy();
expect(stdout).toContain('PASS');
});
});
1 change: 1 addition & 0 deletions test/build/bundle-variable/src/index.js
@@ -0,0 +1 @@
console.log('hello world');
Expand Up @@ -10,7 +10,8 @@ describe('function configuration', () => {
expect(exitCode).toBe(0);
expect(stderr).toBeFalsy();
expect(stdout).toBeTruthy();
expect(stdout).toContain("{ argv: { mode: 'development', env: { WEBPACK_BUNDLE: true } } }");
expect(stdout).toContain('WEBPACK_BUNDLE: true');
expect(stdout).toContain('WEBPACK_BUILD: true');
expect(stdout).toContain("mode: 'development'");
expect(existsSync(resolve(__dirname, './dist/dev.js')));
});
Expand Down

0 comments on commit 7590f66

Please sign in to comment.