Skip to content

Commit c7dee41

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

File tree

6 files changed

+185
-5
lines changed

6 files changed

+185
-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: 137 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,138 @@ 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+
271+
expect(stdout).to.contain('Running webpack-dev-server ...');
272+
expect(stdout).to.contain('Compiled successfully in');
273+
expect(stdout).to.contain('webpack compiled successfully');
274+
275+
expect(stderr).to.contain('[webpack-dev-server] Project is running at:');
276+
expect(stderr).to.contain('[webpack-dev-server] Loopback: http://localhost:8080/, http://127.0.0.1:8080/');
277+
expect(stderr).to.contain('[webpack-dev-server] Content not from webpack is served from');
278+
279+
done();
280+
});
281+
282+
setTimeout(() => {
283+
abortController.abort();
284+
}, 5000);
285+
});
286+
287+
describe('Without webpack-dev-server installed', () => {
288+
const webpackDevServerConstraint = require('../../package.json').devDependencies['webpack-dev-server'] || null;
289+
if (!webpackDevServerConstraint) {
290+
throw new Error('Missing "webpack-dev-server" as dev dependency in package.json.');
291+
}
292+
293+
before(() => {
294+
execSync('yarn remove webpack-dev-server --dev', { cwd: projectDir });
295+
});
296+
297+
after(() => {
298+
// Re-install webpack-dev-server and ensure the project is in a clean state
299+
execSync(`yarn add webpack-dev-server@${webpackDevServerConstraint} --dev`, { cwd: projectDir });
300+
execSync('git checkout yarn.lock', { cwd: projectDir });
301+
execSync('yarn install', { cwd: projectDir });
302+
});
303+
304+
it('Throw an error when trying to use the webpack-dev-server if not installed', done => {
305+
testSetup.emptyTmpDir();
306+
const testDir = testSetup.createTestAppDir();
307+
308+
fs.writeFileSync(
309+
path.join(testDir, 'package.json'),
310+
`{
311+
"devDependencies": {
312+
"@symfony/webpack-encore": "*"
313+
}
314+
}`
315+
);
316+
317+
fs.writeFileSync(
318+
path.join(testDir, 'webpack.config.js'),
319+
`
320+
const Encore = require('../../index.js');
321+
Encore
322+
.enableSingleRuntimeChunk()
323+
.setOutputPath('build/')
324+
.setPublicPath('/build')
325+
.addEntry('main', './js/no_require')
326+
;
327+
328+
module.exports = Encore.getWebpackConfig();
329+
`
330+
);
331+
332+
const binPath = path.resolve(projectDir, 'bin', 'encore.js');
333+
exec(
334+
`node ${binPath} dev-server --context=${testDir}`,
335+
{
336+
cwd: testDir,
337+
env: Object.assign({}, process.env, { NO_COLORS: 'true' })
338+
},
339+
(err, stdout, stderr) => {
340+
expect(stdout).to.contain('Install webpack-dev-server to use the webpack Development Server');
341+
expect(stdout).to.contain('npm install webpack-dev-server --save-dev');
342+
expect(stderr).to.equal('');
343+
344+
expect(stdout).not.to.contain('Running webpack-dev-server ...');
345+
expect(stdout).not.to.contain('Compiled successfully in');
346+
expect(stdout).not.to.contain('webpack compiled successfully');
347+
348+
done();
349+
});
350+
});
351+
});
216352
});

0 commit comments

Comments
 (0)