Skip to content

Commit f51585a

Browse files
committed
Make webpack-dev-server optional
1 parent 9f31ac1 commit f51585a

File tree

6 files changed

+190
-5
lines changed

6 files changed

+190
-5
lines changed

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,23 @@ Encore.configureDevServerOptions((options) => {
7272
});
7373
```
7474

75+
* #1336 Make webpack-dev-server optional (@Kocal)
76+
77+
The `webpack-dev-server` package is now an optional peer dependency.
78+
It has been removed because some projects may not use it, and it was installing a bunch of unnecessary dependencies.
79+
80+
Removing the `webpack-dev-server` dependency from Encore reduces the number of dependencies from **626** to **295** (**-331**!),
81+
it helps to reduce the size of the `node_modules` directory and the number of possible vulnerabilities.
82+
83+
To use the `webpack-dev-server` again, you need to install it manually:
84+
```shell
85+
npm install webpack-dev-server --save-dev
86+
# or
87+
yarn add webpack-dev-server --dev
88+
# or
89+
pnpm install webpack-dev-server --save-dev
90+
```
91+
7592
## 4.7.0
7693

7794
### Features

bin/encore.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const parseRuntime = require('../lib/config/parse-runtime');
1414
const context = require('../lib/context');
1515
const pc = require('picocolors');
1616
const logger = require('../lib/logger');
17+
const featuresHelper = require("../lib/features");
1718

1819
const runtimeConfig = parseRuntime(
1920
require('yargs-parser')(process.argv.slice(2)),
@@ -56,6 +57,13 @@ if (runtimeConfig.helpRequested) {
5657
}
5758

5859
if (runtimeConfig.useDevServer) {
60+
try {
61+
featuresHelper.ensurePackagesExistAndAreCorrectVersion('webpack-dev-server', 'the webpack Development Server');
62+
} catch (e) {
63+
console.log(e);
64+
process.exit(1);
65+
}
66+
5967
console.log('Running webpack-dev-server ...');
6068
console.log();
6169

lib/WebpackConfig.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const crypto = require('crypto');
3131
const logger = require('./logger');
3232
const regexpEscaper = require('./utils/regexp-escaper');
3333
const { calculateDevServerUrl } = require('./config/path-util');
34+
const featuresHelper = require('./features');
3435

3536
/**
3637
* @param {RuntimeConfig|null} runtimeConfig
@@ -594,6 +595,8 @@ class WebpackConfig {
594595
* @param {OptionsCallback<import('webpack-dev-server').Configuration>} callback
595596
*/
596597
configureDevServerOptions(callback) {
598+
featuresHelper.ensurePackagesExistAndAreCorrectVersion('webpack-dev-server');
599+
597600
if (typeof callback !== 'function') {
598601
throw new Error('Argument 1 to configureDevServerOptions() must be a callback function.');
599602
}

lib/features.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ const packageHelper = require('./package-helper');
1414
/**
1515
* An object that holds internal configuration about different
1616
* "loaders"/"plugins" that can be enabled/used.
17+
*
18+
* @type {{[key: string]: {
19+
* method: string,
20+
* packages: Array<{ name: string, version?: string } | Array<{ name: string }>>,
21+
* description: string,
22+
* }}}
1723
*/
1824
const features = {
1925
sass: {
@@ -146,7 +152,14 @@ const features = {
146152
{ name: 'svelte-loader', enforce_version: true }
147153
],
148154
description: 'process Svelte JS files'
149-
}
155+
},
156+
'webpack-dev-server': {
157+
method: 'configureDevServerOptions()',
158+
packages: [
159+
{ name: 'webpack-dev-server' }
160+
],
161+
description: 'run the Webpack development server'
162+
},
150163
};
151164

152165
function getFeatureConfig(featureName) {
@@ -158,12 +171,12 @@ function getFeatureConfig(featureName) {
158171
}
159172

160173
module.exports = {
161-
ensurePackagesExistAndAreCorrectVersion: function(featureName) {
174+
ensurePackagesExistAndAreCorrectVersion: function(featureName, method = null) {
162175
const config = getFeatureConfig(featureName);
163176

164177
packageHelper.ensurePackagesExist(
165178
packageHelper.addPackagesVersionConstraint(config.packages),
166-
config.method
179+
method || config.method
167180
);
168181
},
169182

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
"tapable": "^2.2.1",
4343
"terser-webpack-plugin": "^5.3.0",
4444
"tmp": "^0.2.1",
45-
"webpack-dev-server": "^5.0.4",
4645
"yargs-parser": "^21.0.0"
4746
},
4847
"devDependencies": {
@@ -97,6 +96,7 @@
9796
"vue-loader": "^17.0.0",
9897
"webpack": "^5.72",
9998
"webpack-cli": "^5.1.4",
99+
"webpack-dev-server": "^5.0.4",
100100
"webpack-notifier": "^1.15.0"
101101
},
102102
"peerDependencies": {
@@ -211,6 +211,9 @@
211211
"webpack-cli": {
212212
"optional": false
213213
},
214+
"webpack-dev-server": {
215+
"optional": true
216+
},
214217
"webpack-notifier": {
215218
"optional": true
216219
}

test/bin/encore.js

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ const expect = chai.expect;
1515
const path = require('path');
1616
const testSetup = require('../helpers/setup');
1717
const fs = require('fs-extra');
18-
const exec = require('child_process').exec;
18+
const { exec, execSync, spawn } = require('child_process');
19+
20+
const projectDir = path.resolve(__dirname, '../', '../');
1921

2022
describe('bin/encore.js', function() {
2123
// being functional tests, these can take quite long
@@ -213,4 +215,143 @@ module.exports = Encore.getWebpackConfig();
213215
done();
214216
});
215217
});
218+
219+
it('Run the webpack-dev-server successfully', (done) => {
220+
testSetup.emptyTmpDir();
221+
const testDir = testSetup.createTestAppDir();
222+
223+
fs.writeFileSync(
224+
path.join(testDir, 'package.json'),
225+
`{
226+
"devDependencies": {
227+
"@symfony/webpack-encore": "*"
228+
}
229+
}`
230+
);
231+
232+
fs.writeFileSync(
233+
path.join(testDir, 'webpack.config.js'),
234+
`
235+
const Encore = require('../../index.js');
236+
Encore
237+
.enableSingleRuntimeChunk()
238+
.setOutputPath('build/')
239+
.setPublicPath('/build')
240+
.addEntry('main', './js/no_require')
241+
;
242+
243+
module.exports = Encore.getWebpackConfig();
244+
`
245+
);
246+
247+
const binPath = path.resolve(__dirname, '../', '../', 'bin', 'encore.js');
248+
const abortController = new AbortController();
249+
const node = spawn('node', [binPath, 'dev-server', `--context=${testDir}`], {
250+
cwd: testDir,
251+
env: Object.assign({}, process.env, { NO_COLORS: 'true' }),
252+
signal: abortController.signal
253+
});
254+
255+
let stdout = '';
256+
let stderr = '';
257+
258+
node.stdout.on('data', (data) => {
259+
stdout += data.toString();
260+
});
261+
262+
node.stderr.on('data', (data) => {
263+
stderr += data.toString();
264+
});
265+
266+
node.on('error', (error) => {
267+
if (error.name !== 'AbortError') {
268+
throw new Error('Error executing encore', { cause: error });
269+
}
270+
// TODO: debug CI
271+
console.log({ stdout, stderr });
272+
273+
expect(stdout).to.contain('Running webpack-dev-server ...');
274+
expect(stdout).to.contain('Compiled successfully in');
275+
expect(stdout).to.contain('webpack compiled successfully');
276+
277+
expect(stderr).to.contain('[webpack-dev-server] Project is running at:');
278+
expect(stderr).to.contain('[webpack-dev-server] Loopback: http://localhost:8080/, http://127.0.0.1:8080/');
279+
expect(stderr).to.contain('[webpack-dev-server] Content not from webpack is served from');
280+
281+
done();
282+
});
283+
284+
setTimeout(() => {
285+
abortController.abort();
286+
}, 5000);
287+
});
288+
289+
describe('Without webpack-dev-server installed', () => {
290+
const webpackDevServerConstraint = require('../../package.json').devDependencies['webpack-dev-server'] || null;
291+
if (!webpackDevServerConstraint) {
292+
throw new Error('Missing "webpack-dev-server" as dev dependency in package.json.');
293+
}
294+
295+
before(() => {
296+
execSync('yarn remove webpack-dev-server --dev', { cwd: projectDir });
297+
});
298+
299+
after(() => {
300+
// Re-install webpack-dev-server and ensure the project is in a clean state
301+
execSync(`yarn add webpack-dev-server@${webpackDevServerConstraint} --dev`, { cwd: projectDir });
302+
execSync('git checkout yarn.lock', { cwd: projectDir });
303+
execSync('yarn install', { cwd: projectDir });
304+
});
305+
306+
it('Throw an error when trying to use the webpack-dev-server if not installed', done => {
307+
testSetup.emptyTmpDir();
308+
const testDir = testSetup.createTestAppDir();
309+
310+
fs.writeFileSync(
311+
path.join(testDir, 'package.json'),
312+
`{
313+
"devDependencies": {
314+
"@symfony/webpack-encore": "*"
315+
}
316+
}`
317+
);
318+
319+
fs.writeFileSync(
320+
path.join(testDir, 'webpack.config.js'),
321+
`
322+
const Encore = require('../../index.js');
323+
Encore
324+
.enableSingleRuntimeChunk()
325+
.setOutputPath('build/')
326+
.setPublicPath('/build')
327+
.addEntry('main', './js/no_require')
328+
;
329+
330+
module.exports = Encore.getWebpackConfig();
331+
`
332+
);
333+
334+
const binPath = path.resolve(projectDir, 'bin', 'encore.js');
335+
exec(
336+
`node ${binPath} dev-server --context=${testDir}`,
337+
{
338+
cwd: testDir,
339+
env: Object.assign({}, process.env, { NO_COLORS: 'true' })
340+
},
341+
(err, stdout, stderr) => {
342+
// TODO: debug CI
343+
console.log({ stdout, stderr });
344+
345+
expect(stdout).to.contain('Install webpack-dev-server to use the webpack Development Server');
346+
expect(stdout).to.contain('npm install webpack-dev-server --save-dev');
347+
expect(stderr).to.equal('');
348+
349+
expect(stdout).not.to.contain('Running webpack-dev-server ...');
350+
expect(stdout).not.to.contain('Compiled successfully in');
351+
expect(stdout).not.to.contain('webpack compiled successfully');
352+
353+
done();
354+
});
355+
});
356+
});
216357
});

0 commit comments

Comments
 (0)