Skip to content

Commit dedd67b

Browse files
committed
fix(cli): use webpack-cli@4
1 parent 1aa0d2d commit dedd67b

16 files changed

+348
-1071
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/webpack-dev-server.js

Lines changed: 131 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,126 +1,151 @@
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+
const isYarn = fs.existsSync(path.resolve(process.cwd(), 'yarn.lock'));
10096

101-
throw err;
102-
}
97+
const packageManager = isYarn ? 'yarn' : 'npm';
98+
const installOptions = [isYarn ? 'add' : 'install', '-D'];
10399

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-
}
100+
console.error(
101+
`We will use "${packageManager}" to install the CLI via "${packageManager} ${installOptions.join(
102+
' '
103+
)}".`
104+
);
113105

114-
throw err;
115-
}
106+
const question = `Do you want to install 'webpack-cli' (yes/no): `;
107+
108+
const questionInterface = readLine.createInterface({
109+
input: process.stdin,
110+
output: process.stderr,
111+
});
112+
113+
// In certain scenarios (e.g. when STDIN is not in terminal mode), the callback function will not be
114+
// executed. Setting the exit code here to ensure the script exits correctly in those cases. The callback
115+
// function is responsible for clearing the exit code if the user wishes to install webpack-cli.
116+
process.exitCode = 1;
117+
questionInterface.question(question, (answer) => {
118+
questionInterface.close();
119+
120+
const normalizedAnswer = answer.toLowerCase().startsWith('y');
116121

117-
server.listen(options.port, options.host, (err) => {
118-
if (err) {
119-
throw err;
122+
if (!normalizedAnswer) {
123+
console.error(
124+
"You need to install 'webpack-cli' to use webpack via CLI.\n" +
125+
'You can also install the CLI manually.'
126+
);
127+
128+
return;
120129
}
130+
process.exitCode = 0;
131+
132+
console.log(
133+
`Installing '${
134+
cli.package
135+
}' (running '${packageManager} ${installOptions.join(' ')} ${
136+
cli.package
137+
}')...`
138+
);
139+
140+
runCommand(packageManager, installOptions.concat(cli.package))
141+
.then(() => {
142+
runCli(cli);
143+
})
144+
.catch((error) => {
145+
console.error(error);
146+
process.exitCode = 1;
147+
});
121148
});
149+
} else {
150+
runCli(cli);
122151
}
123-
124-
processOptions(config, argv, (config, options) => {
125-
startDevServer(config, options);
126-
});

lib/utils/defaultPort.js

Lines changed: 0 additions & 3 deletions
This file was deleted.

lib/utils/findPort.js

Lines changed: 0 additions & 39 deletions
This file was deleted.

lib/utils/getVersions.js

Lines changed: 0 additions & 10 deletions
This file was deleted.

lib/utils/processOptions.js

Lines changed: 0 additions & 40 deletions
This file was deleted.

lib/utils/setupExitSignals.js

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)