Skip to content

fix(cli): use webpack-cli@4 #2845

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

Merged
merged 5 commits into from
Nov 23, 2020
Merged
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
5 changes: 5 additions & 0 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ jobs:
- name: Install webpack ${{ matrix.webpack-version }}
run: npm i webpack@${{ matrix.webpack-version }}

- name: Link webpack-dev-server
run: |
npm link
npm link webpack-dev-server

- name: Run tests for webpack version ${{ matrix.webpack-version }}
run: npm run test:coverage -- --ci

Expand Down
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ Run the relevant [examples](https://github.com/webpack/webpack-dev-server/tree/m

2. Run `npm install` in the root `webpack-dev-server` folder.

3. Run `npm link && npm link webpack-dev-server` to link the current project to `node_modules`.

Once it is done, you can modify any file locally. In the `examples/` directory you'll find a lot of examples with instructions on how to run it. This can be very handy when testing if your code works.

If you are modifying a file in the `client/` directory, be sure to run `npm run build:client` after it. This will recompile the files.
Expand Down
8 changes: 8 additions & 0 deletions bin/cli-flags.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ module.exports = {
describe: 'Do not refresh page if HMR fails',
group: ADVANCED_GROUP,
},
{
name: 'setup-exit-signals',
type: Boolean,
describe: 'Close and exit the process on SIGINT and SIGTERM',
group: ADVANCED_GROUP,
defaultValue: true,
negative: true,
},
{
name: 'stdin',
type: Boolean,
Expand Down
5 changes: 5 additions & 0 deletions bin/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ const options = {
describe: 'Do not refresh page if HMR fails',
group: ADVANCED_GROUP,
},
'setup-exit-signals': {
type: 'boolean',
describe: 'Close and exit the process on SIGINT and SIGTERM',
group: ADVANCED_GROUP,
},
stdin: {
type: 'boolean',
describe: 'close when stdin ends',
Expand Down
242 changes: 137 additions & 105 deletions bin/webpack-dev-server.js
Original file line number Diff line number Diff line change
@@ -1,126 +1,158 @@
#!/usr/bin/env node
/* Based on webpack/bin/webpack.js */
/* eslint-disable no-console */

'use strict';

/* eslint-disable no-shadow, no-console */

const debug = require('debug')('webpack-dev-server');
const importLocal = require('import-local');
const yargs = require('yargs');
const webpack = require('webpack');
const Server = require('../lib/Server');
const setupExitSignals = require('../lib/utils/setupExitSignals');
const colors = require('../lib/utils/colors');
const processOptions = require('../lib/utils/processOptions');
const getVersions = require('../lib/utils/getVersions');
const getColorsOption = require('../lib/utils/getColorsOption');
const options = require('./options');

let server;
const serverData = {
server: null,
/**
* @param {string} command process to run
* @param {string[]} args command line arguments
* @returns {Promise<void>} promise
*/
const runCommand = (command, args) => {
const cp = require('child_process');
return new Promise((resolve, reject) => {
const executedCommand = cp.spawn(command, args, {
stdio: 'inherit',
shell: true,
});

executedCommand.on('error', (error) => {
reject(error);
});

executedCommand.on('exit', (code) => {
if (code === 0) {
resolve();
} else {
reject();
}
});
});
};
// we must pass an object that contains the server object as a property so that
// we can update this server property later, and setupExitSignals will be able to
// recognize that the server has been instantiated, because we will set
// serverData.server to the new server object.
setupExitSignals(serverData);

// Prefer the local installation of webpack-dev-server
if (importLocal(__filename)) {
debug('Using local install of webpack-dev-server');
/**
* @param {string} packageName name of the package
* @returns {boolean} is the package installed?
*/
const isInstalled = (packageName) => {
try {
require.resolve(packageName);

return;
}
return true;
} catch (err) {
return false;
}
};

try {
require.resolve('webpack-cli');
} catch (err) {
console.error('The CLI moved into a separate package: webpack-cli');
console.error(
"Please install 'webpack-cli' in addition to webpack itself to use the CLI"
);
console.error('-> When using npm: npm i -D webpack-cli');
console.error('-> When using yarn: yarn add -D webpack-cli');
/**
* @param {CliOption} cli options
* @returns {void}
*/
const runCli = (cli) => {
if (cli.preprocess) {
cli.preprocess();
}
const path = require('path');
const pkgPath = require.resolve(`${cli.package}/package.json`);
// eslint-disable-next-line import/no-dynamic-require
const pkg = require(pkgPath);
// eslint-disable-next-line import/no-dynamic-require
require(path.resolve(path.dirname(pkgPath), pkg.bin[cli.binName]));
};

process.exitCode = 1;
}
/**
* @typedef {Object} CliOption
* @property {string} name display name
* @property {string} package npm package name
* @property {string} binName name of the executable file
* @property {boolean} installed currently installed?
* @property {string} url homepage
* @property {function} preprocess preprocessor
*/

/** @type {CliOption} */
const cli = {
name: 'webpack-cli',
package: 'webpack-cli',
binName: 'webpack-cli',
installed: isInstalled('webpack-cli'),
url: 'https://github.com/webpack/webpack-cli',
preprocess() {
process.argv.splice(2, 0, 'serve');
},
Comment on lines +81 to +83
Copy link
Contributor Author

@ylemkimon ylemkimon Nov 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is copied from webpack/bin/webpack.js and these lines were added.

};

yargs.usage(
`${getVersions()}\nUsage: https://webpack.js.org/configuration/dev-server/`
);

// [email protected] path : 'webpack-cli/bin/config/config-yargs'
let configYargsPath;
try {
require.resolve('webpack-cli/bin/config/config-yargs');
configYargsPath = 'webpack-cli/bin/config/config-yargs';
} catch (e) {
configYargsPath = 'webpack-cli/bin/config-yargs';
}
// eslint-disable-next-line import/no-extraneous-dependencies
// eslint-disable-next-line import/no-dynamic-require
require(configYargsPath)(yargs);

// It is important that this is done after the webpack yargs config,
// so it overrides webpack's version info.
yargs.version(getVersions());
yargs.options(options);

const argv = yargs.argv;

// [email protected] path : 'webpack-cli/bin/utils/convert-argv'
let convertArgvPath;
try {
require.resolve('webpack-cli/bin/utils/convert-argv');
convertArgvPath = 'webpack-cli/bin/utils/convert-argv';
} catch (e) {
convertArgvPath = 'webpack-cli/bin/convert-argv';
}
// eslint-disable-next-line import/no-extraneous-dependencies
// eslint-disable-next-line import/no-dynamic-require
const config = require(convertArgvPath)(yargs, argv, {
outputFilename: '/bundle.js',
});
if (!cli.installed) {
const path = require('path');
const fs = require('graceful-fs');
const readLine = require('readline');

function startDevServer(config, options) {
let compiler;
const notify = `CLI for webpack must be installed.\n ${cli.name} (${cli.url})\n`;

const configArr = config instanceof Array ? config : [config];
const statsColors = getColorsOption(configArr);
console.error(notify);

try {
compiler = webpack(config);
} catch (err) {
if (err instanceof webpack.WebpackOptionsValidationError) {
console.error(colors.error(statsColors, err.message));
// eslint-disable-next-line no-process-exit
process.exit(1);
}
let packageManager;

throw err;
if (fs.existsSync(path.resolve(process.cwd(), 'yarn.lock'))) {
packageManager = 'yarn';
} else if (fs.existsSync(path.resolve(process.cwd(), 'pnpm-lock.yaml'))) {
packageManager = 'pnpm';
} else {
packageManager = 'npm';
}

try {
server = new Server(compiler, options);
serverData.server = server;
} catch (err) {
if (err.name === 'ValidationError') {
console.error(colors.error(statsColors, err.message));
// eslint-disable-next-line no-process-exit
process.exit(1);
}
const installOptions = [packageManager === 'yarn' ? 'add' : 'install', '-D'];

throw err;
}
console.error(
`We will use "${packageManager}" to install the CLI via "${packageManager} ${installOptions.join(
' '
)}".`
);

server.listen(options.port, options.host, (err) => {
if (err) {
throw err;
const question = `Do you want to install 'webpack-cli' (yes/no): `;

const questionInterface = readLine.createInterface({
input: process.stdin,
output: process.stderr,
});

// In certain scenarios (e.g. when STDIN is not in terminal mode), the callback function will not be
// executed. Setting the exit code here to ensure the script exits correctly in those cases. The callback
// function is responsible for clearing the exit code if the user wishes to install webpack-cli.
process.exitCode = 1;
questionInterface.question(question, (answer) => {
questionInterface.close();

const normalizedAnswer = answer.toLowerCase().startsWith('y');

if (!normalizedAnswer) {
console.error(
"You need to install 'webpack-cli' to use webpack via CLI.\n" +
'You can also install the CLI manually.'
);

return;
}
process.exitCode = 0;

console.log(
`Installing '${
cli.package
}' (running '${packageManager} ${installOptions.join(' ')} ${
cli.package
}')...`
);

runCommand(packageManager, installOptions.concat(cli.package))
.then(() => {
runCli(cli);
})
.catch((error) => {
console.error(error);
process.exitCode = 1;
});
});
} else {
runCli(cli);
}

processOptions(config, argv, (config, options) => {
startDevServer(config, options);
});
16 changes: 16 additions & 0 deletions examples/cli/stdin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# CLI: Stdin Option

Specifying this option instructs the server to close when `stdin` ends.

```console
npm run webpack-dev-server -- --stdin
```

## What Should Happen

1. The server should begin running.
2. Press `CTL+D` on your keyboard.
3. The server should close.

_Note: the keyboard shortcut for terminating `stdin` can vary depending on the
operating systems._
6 changes: 6 additions & 0 deletions examples/cli/stdin/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use strict';

const target = document.querySelector('#target');

target.innerHTML =
'Press <code>CTL+D</code> on your keyboard to close the server.';
6 changes: 6 additions & 0 deletions examples/cli/stdin/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use strict';

module.exports = {
context: __dirname,
entry: './app.js',
};
2 changes: 0 additions & 2 deletions examples/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ const fs = require('fs');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const marked = require('marked');
const webpack = require('webpack');

module.exports = {
setup(config) {
Expand Down Expand Up @@ -65,7 +64,6 @@ module.exports = {

marked(readme, { renderer });

result.plugins.push(new webpack.NamedModulesPlugin());
result.plugins.push(
new HtmlWebpackPlugin({
filename: 'index.html',
Expand Down
Loading