Skip to content

Commit ccdce35

Browse files
authored
refactor: use webpack-cli@4 (#2845)
1 parent 74e4297 commit ccdce35

27 files changed

+767
-2422
lines changed

.github/workflows/nodejs.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ jobs:
8484
- name: Install webpack ${{ matrix.webpack-version }}
8585
run: npm i webpack@${{ matrix.webpack-version }}
8686

87+
- name: Link webpack-dev-server
88+
run: |
89+
npm link
90+
npm link webpack-dev-server
91+
8792
- name: Run tests for webpack version ${{ matrix.webpack-version }}
8893
run: npm run test:coverage -- --ci
8994

CONTRIBUTING.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ Run the relevant [examples](https://github.com/webpack/webpack-dev-server/tree/m
2929

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

32+
3. Run `npm link && npm link webpack-dev-server` to link the current project to `node_modules`.
33+
3234
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.
3335

3436
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.

bin/cli-flags.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ module.exports = {
3737
describe: 'Do not refresh page if HMR fails',
3838
group: ADVANCED_GROUP,
3939
},
40+
{
41+
name: 'setup-exit-signals',
42+
type: Boolean,
43+
describe: 'Close and exit the process on SIGINT and SIGTERM',
44+
group: ADVANCED_GROUP,
45+
defaultValue: true,
46+
negative: true,
47+
},
4048
{
4149
name: 'stdin',
4250
type: Boolean,

bin/options.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ const options = {
3535
describe: 'Do not refresh page if HMR fails',
3636
group: ADVANCED_GROUP,
3737
},
38+
'setup-exit-signals': {
39+
type: 'boolean',
40+
describe: 'Close and exit the process on SIGINT and SIGTERM',
41+
group: ADVANCED_GROUP,
42+
},
3843
stdin: {
3944
type: 'boolean',
4045
describe: 'close when stdin ends',

bin/webpack-dev-server.js

Lines changed: 137 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,126 +1,158 @@
11
#!/usr/bin/env node
2+
/* Based on webpack/bin/webpack.js */
3+
/* eslint-disable no-console */
24

35
'use strict';
46

5-
/* eslint-disable no-shadow, no-console */
6-
7-
const debug = require('debug')('webpack-dev-server');
8-
const importLocal = require('import-local');
9-
const yargs = require('yargs');
10-
const webpack = require('webpack');
11-
const Server = require('../lib/Server');
12-
const setupExitSignals = require('../lib/utils/setupExitSignals');
13-
const colors = require('../lib/utils/colors');
14-
const processOptions = require('../lib/utils/processOptions');
15-
const getVersions = require('../lib/utils/getVersions');
16-
const getColorsOption = require('../lib/utils/getColorsOption');
17-
const options = require('./options');
18-
19-
let server;
20-
const serverData = {
21-
server: null,
7+
/**
8+
* @param {string} command process to run
9+
* @param {string[]} args command line arguments
10+
* @returns {Promise<void>} promise
11+
*/
12+
const runCommand = (command, args) => {
13+
const cp = require('child_process');
14+
return new Promise((resolve, reject) => {
15+
const executedCommand = cp.spawn(command, args, {
16+
stdio: 'inherit',
17+
shell: true,
18+
});
19+
20+
executedCommand.on('error', (error) => {
21+
reject(error);
22+
});
23+
24+
executedCommand.on('exit', (code) => {
25+
if (code === 0) {
26+
resolve();
27+
} else {
28+
reject();
29+
}
30+
});
31+
});
2232
};
23-
// we must pass an object that contains the server object as a property so that
24-
// we can update this server property later, and setupExitSignals will be able to
25-
// recognize that the server has been instantiated, because we will set
26-
// serverData.server to the new server object.
27-
setupExitSignals(serverData);
2833

29-
// Prefer the local installation of webpack-dev-server
30-
if (importLocal(__filename)) {
31-
debug('Using local install of webpack-dev-server');
34+
/**
35+
* @param {string} packageName name of the package
36+
* @returns {boolean} is the package installed?
37+
*/
38+
const isInstalled = (packageName) => {
39+
try {
40+
require.resolve(packageName);
3241

33-
return;
34-
}
42+
return true;
43+
} catch (err) {
44+
return false;
45+
}
46+
};
3547

36-
try {
37-
require.resolve('webpack-cli');
38-
} catch (err) {
39-
console.error('The CLI moved into a separate package: webpack-cli');
40-
console.error(
41-
"Please install 'webpack-cli' in addition to webpack itself to use the CLI"
42-
);
43-
console.error('-> When using npm: npm i -D webpack-cli');
44-
console.error('-> When using yarn: yarn add -D webpack-cli');
48+
/**
49+
* @param {CliOption} cli options
50+
* @returns {void}
51+
*/
52+
const runCli = (cli) => {
53+
if (cli.preprocess) {
54+
cli.preprocess();
55+
}
56+
const path = require('path');
57+
const pkgPath = require.resolve(`${cli.package}/package.json`);
58+
// eslint-disable-next-line import/no-dynamic-require
59+
const pkg = require(pkgPath);
60+
// eslint-disable-next-line import/no-dynamic-require
61+
require(path.resolve(path.dirname(pkgPath), pkg.bin[cli.binName]));
62+
};
4563

46-
process.exitCode = 1;
47-
}
64+
/**
65+
* @typedef {Object} CliOption
66+
* @property {string} name display name
67+
* @property {string} package npm package name
68+
* @property {string} binName name of the executable file
69+
* @property {boolean} installed currently installed?
70+
* @property {string} url homepage
71+
* @property {function} preprocess preprocessor
72+
*/
73+
74+
/** @type {CliOption} */
75+
const cli = {
76+
name: 'webpack-cli',
77+
package: 'webpack-cli',
78+
binName: 'webpack-cli',
79+
installed: isInstalled('webpack-cli'),
80+
url: 'https://github.com/webpack/webpack-cli',
81+
preprocess() {
82+
process.argv.splice(2, 0, 'serve');
83+
},
84+
};
4885

49-
yargs.usage(
50-
`${getVersions()}\nUsage: https://webpack.js.org/configuration/dev-server/`
51-
);
52-
53-
// [email protected] path : 'webpack-cli/bin/config/config-yargs'
54-
let configYargsPath;
55-
try {
56-
require.resolve('webpack-cli/bin/config/config-yargs');
57-
configYargsPath = 'webpack-cli/bin/config/config-yargs';
58-
} catch (e) {
59-
configYargsPath = 'webpack-cli/bin/config-yargs';
60-
}
61-
// eslint-disable-next-line import/no-extraneous-dependencies
62-
// eslint-disable-next-line import/no-dynamic-require
63-
require(configYargsPath)(yargs);
64-
65-
// It is important that this is done after the webpack yargs config,
66-
// so it overrides webpack's version info.
67-
yargs.version(getVersions());
68-
yargs.options(options);
69-
70-
const argv = yargs.argv;
71-
72-
// [email protected] path : 'webpack-cli/bin/utils/convert-argv'
73-
let convertArgvPath;
74-
try {
75-
require.resolve('webpack-cli/bin/utils/convert-argv');
76-
convertArgvPath = 'webpack-cli/bin/utils/convert-argv';
77-
} catch (e) {
78-
convertArgvPath = 'webpack-cli/bin/convert-argv';
79-
}
80-
// eslint-disable-next-line import/no-extraneous-dependencies
81-
// eslint-disable-next-line import/no-dynamic-require
82-
const config = require(convertArgvPath)(yargs, argv, {
83-
outputFilename: '/bundle.js',
84-
});
86+
if (!cli.installed) {
87+
const path = require('path');
88+
const fs = require('graceful-fs');
89+
const readLine = require('readline');
8590

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

89-
const configArr = config instanceof Array ? config : [config];
90-
const statsColors = getColorsOption(configArr);
93+
console.error(notify);
9194

92-
try {
93-
compiler = webpack(config);
94-
} catch (err) {
95-
if (err instanceof webpack.WebpackOptionsValidationError) {
96-
console.error(colors.error(statsColors, err.message));
97-
// eslint-disable-next-line no-process-exit
98-
process.exit(1);
99-
}
95+
let packageManager;
10096

101-
throw err;
97+
if (fs.existsSync(path.resolve(process.cwd(), 'yarn.lock'))) {
98+
packageManager = 'yarn';
99+
} else if (fs.existsSync(path.resolve(process.cwd(), 'pnpm-lock.yaml'))) {
100+
packageManager = 'pnpm';
101+
} else {
102+
packageManager = 'npm';
102103
}
103104

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

114-
throw err;
115-
}
107+
console.error(
108+
`We will use "${packageManager}" to install the CLI via "${packageManager} ${installOptions.join(
109+
' '
110+
)}".`
111+
);
116112

117-
server.listen(options.port, options.host, (err) => {
118-
if (err) {
119-
throw err;
113+
const question = `Do you want to install 'webpack-cli' (yes/no): `;
114+
115+
const questionInterface = readLine.createInterface({
116+
input: process.stdin,
117+
output: process.stderr,
118+
});
119+
120+
// In certain scenarios (e.g. when STDIN is not in terminal mode), the callback function will not be
121+
// executed. Setting the exit code here to ensure the script exits correctly in those cases. The callback
122+
// function is responsible for clearing the exit code if the user wishes to install webpack-cli.
123+
process.exitCode = 1;
124+
questionInterface.question(question, (answer) => {
125+
questionInterface.close();
126+
127+
const normalizedAnswer = answer.toLowerCase().startsWith('y');
128+
129+
if (!normalizedAnswer) {
130+
console.error(
131+
"You need to install 'webpack-cli' to use webpack via CLI.\n" +
132+
'You can also install the CLI manually.'
133+
);
134+
135+
return;
120136
}
137+
process.exitCode = 0;
138+
139+
console.log(
140+
`Installing '${
141+
cli.package
142+
}' (running '${packageManager} ${installOptions.join(' ')} ${
143+
cli.package
144+
}')...`
145+
);
146+
147+
runCommand(packageManager, installOptions.concat(cli.package))
148+
.then(() => {
149+
runCli(cli);
150+
})
151+
.catch((error) => {
152+
console.error(error);
153+
process.exitCode = 1;
154+
});
121155
});
156+
} else {
157+
runCli(cli);
122158
}
123-
124-
processOptions(config, argv, (config, options) => {
125-
startDevServer(config, options);
126-
});

examples/cli/stdin/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# CLI: Stdin Option
2+
3+
Specifying this option instructs the server to close when `stdin` ends.
4+
5+
```console
6+
npm run webpack-dev-server -- --stdin
7+
```
8+
9+
## What Should Happen
10+
11+
1. The server should begin running.
12+
2. Press `CTL+D` on your keyboard.
13+
3. The server should close.
14+
15+
_Note: the keyboard shortcut for terminating `stdin` can vary depending on the
16+
operating systems._

examples/cli/stdin/app.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
'use strict';
2+
3+
const target = document.querySelector('#target');
4+
5+
target.innerHTML =
6+
'Press <code>CTL+D</code> on your keyboard to close the server.';

examples/cli/stdin/webpack.config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
'use strict';
2+
3+
module.exports = {
4+
context: __dirname,
5+
entry: './app.js',
6+
};

examples/util.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ const fs = require('fs');
66
const path = require('path');
77
const HtmlWebpackPlugin = require('html-webpack-plugin');
88
const marked = require('marked');
9-
const webpack = require('webpack');
109

1110
module.exports = {
1211
setup(config) {
@@ -65,7 +64,6 @@ module.exports = {
6564

6665
marked(readme, { renderer });
6766

68-
result.plugins.push(new webpack.NamedModulesPlugin());
6967
result.plugins.push(
7068
new HtmlWebpackPlugin({
7169
filename: 'index.html',

0 commit comments

Comments
 (0)