Skip to content

Make webpack-dev-server optional #1336

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
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
8 changes: 8 additions & 0 deletions .github/workflows/low-depends.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ jobs:

- name: Force Lowest Dependencies
run: node ./scripts/force-lowest-dependencies

# We have some tests that need to "git checkout" package.json file.
# Commit them prevent the tests from re-using the locked dependencies.
- name: Commit Changes, to preserve a clean working directory
run: |
git config --global user.email ""
git config --global user.name "Symfony"
git commit -am "Force Lowest Dependencies"

- name: Install Yarn Dependencies
run: yarn install
Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,23 @@ Encore.configureDevServerOptions((options) => {
});
```

* #1336 Make webpack-dev-server optional (@Kocal)

The `webpack-dev-server` package is now an optional peer dependency.
It has been removed because some projects may not use it, and it was installing a bunch of unnecessary dependencies.

Removing the `webpack-dev-server` dependency from Encore reduces the number of dependencies from **626** to **295** (**-331**!),
it helps to reduce the size of the `node_modules` directory and the number of possible vulnerabilities.

To use the `webpack-dev-server` again, you need to install it manually:
```shell
npm install webpack-dev-server --save-dev
# or
yarn add webpack-dev-server --dev
# or
pnpm install webpack-dev-server --save-dev
```

## 4.7.0

### Features
Expand Down
8 changes: 8 additions & 0 deletions bin/encore.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const parseRuntime = require('../lib/config/parse-runtime');
const context = require('../lib/context');
const pc = require('picocolors');
const logger = require('../lib/logger');
const featuresHelper = require("../lib/features");

const runtimeConfig = parseRuntime(
require('yargs-parser')(process.argv.slice(2)),
Expand Down Expand Up @@ -56,6 +57,13 @@ if (runtimeConfig.helpRequested) {
}

if (runtimeConfig.useDevServer) {
try {
featuresHelper.ensurePackagesExistAndAreCorrectVersion('webpack-dev-server', 'the webpack Development Server');
} catch (e) {
console.log(e);
process.exit(1);
}
Comment on lines +60 to +65
Copy link
Member Author

Choose a reason for hiding this comment

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

I could have used isPackageInstalled(), but I would have to rewrite a lot of logic here to enforce the version, generate the good command, etc...

Adding method parameter to featuresHelper.ensurePackagesExistAndAreCorrectVersion was a lot easier and clean.


console.log('Running webpack-dev-server ...');
console.log();

Expand Down
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,7 @@ class Encore {
* });
* ```
*
* @param {OptionsCallback<import('webpack-dev-server').Configuration>} callback
* @param {OptionsCallback<object>} callback
* @returns {Encore}
*/
configureDevServerOptions(callback) {
Expand Down
7 changes: 5 additions & 2 deletions lib/WebpackConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const crypto = require('crypto');
const logger = require('./logger');
const regexpEscaper = require('./utils/regexp-escaper');
const { calculateDevServerUrl } = require('./config/path-util');
const featuresHelper = require('./features');

/**
* @param {RuntimeConfig|null} runtimeConfig
Expand Down Expand Up @@ -165,7 +166,7 @@ class WebpackConfig {
this.splitChunksConfigurationCallback = () => {};
/** @type {OptionsCallback<Exclude<webpack.Configuration['watchOptions'], undefined>>} */
this.watchOptionsConfigurationCallback = () => {};
/** @type {OptionsCallback<import('webpack-dev-server').Configuration>} */
/** @type {OptionsCallback<object>} */
this.devServerOptionsConfigurationCallback = () => {};
/** @type {OptionsCallback<object>} */
this.vueLoaderOptionsCallback = () => {};
Expand Down Expand Up @@ -591,9 +592,11 @@ class WebpackConfig {
}

/**
* @param {OptionsCallback<import('webpack-dev-server').Configuration>} callback
* @param {OptionsCallback<object>} callback
*/
configureDevServerOptions(callback) {
featuresHelper.ensurePackagesExistAndAreCorrectVersion('webpack-dev-server');

if (typeof callback !== 'function') {
throw new Error('Argument 1 to configureDevServerOptions() must be a callback function.');
}
Expand Down
19 changes: 16 additions & 3 deletions lib/features.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ const packageHelper = require('./package-helper');
/**
* An object that holds internal configuration about different
* "loaders"/"plugins" that can be enabled/used.
*
* @type {{[key: string]: {
* method: string,
* packages: Array<{ name: string, enforce_version?: boolean } | Array<{ name: string }>>,
* description: string,
* }}}
*/
const features = {
sass: {
Expand Down Expand Up @@ -146,7 +152,14 @@ const features = {
{ name: 'svelte-loader', enforce_version: true }
],
description: 'process Svelte JS files'
}
},
'webpack-dev-server': {
method: 'configureDevServerOptions()',
packages: [
{ name: 'webpack-dev-server' }
],
description: 'run the Webpack development server'
},
};

function getFeatureConfig(featureName) {
Expand All @@ -158,12 +171,12 @@ function getFeatureConfig(featureName) {
}

module.exports = {
ensurePackagesExistAndAreCorrectVersion: function(featureName) {
ensurePackagesExistAndAreCorrectVersion: function(featureName, method = null) {
const config = getFeatureConfig(featureName);

packageHelper.ensurePackagesExist(
packageHelper.addPackagesVersionConstraint(config.packages),
config.method
method || config.method
);
},

Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
"tapable": "^2.2.1",
"terser-webpack-plugin": "^5.3.0",
"tmp": "^0.2.1",
"webpack-dev-server": "^5.0.4",
"yargs-parser": "^21.0.0"
},
"devDependencies": {
Expand Down Expand Up @@ -97,6 +96,7 @@
"vue-loader": "^17.0.0",
"webpack": "^5.72",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.0.4",
"webpack-notifier": "^1.15.0"
},
"peerDependencies": {
Expand Down Expand Up @@ -211,6 +211,9 @@
"webpack-cli": {
"optional": false
},
"webpack-dev-server": {
"optional": true
},
"webpack-notifier": {
"optional": true
}
Expand Down
132 changes: 131 additions & 1 deletion test/bin/encore.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ const expect = chai.expect;
const path = require('path');
const testSetup = require('../helpers/setup');
const fs = require('fs-extra');
const exec = require('child_process').exec;
const { exec, execSync, spawn } = require('child_process');

const projectDir = path.resolve(__dirname, '../', '../');

describe('bin/encore.js', function() {
// being functional tests, these can take quite long
Expand Down Expand Up @@ -213,4 +215,132 @@ module.exports = Encore.getWebpackConfig();
done();
});
});

it('Run the webpack-dev-server successfully', (done) => {
testSetup.emptyTmpDir();
const testDir = testSetup.createTestAppDir();

fs.writeFileSync(
path.join(testDir, 'package.json'),
`{
"devDependencies": {
"@symfony/webpack-encore": "*"
}
}`
);

fs.writeFileSync(
path.join(testDir, 'webpack.config.js'),
`
const Encore = require('../../index.js');
Encore
.enableSingleRuntimeChunk()
.setOutputPath('build/')
.setPublicPath('/build')
.addEntry('main', './js/no_require')
;

module.exports = Encore.getWebpackConfig();
`
);

const binPath = path.resolve(__dirname, '../', '../', 'bin', 'encore.js');
const abortController = new AbortController();
const node = spawn('node', [binPath, 'dev-server', `--context=${testDir}`], {
cwd: testDir,
env: Object.assign({}, process.env, { NO_COLOR: 'true' }),
signal: abortController.signal
});

let stdout = '';
let stderr = '';

node.stdout.on('data', (data) => {
stdout += data.toString();
});

node.stderr.on('data', (data) => {
stderr += data.toString();
});

node.on('error', (error) => {
if (error.name !== 'AbortError') {
throw new Error('Error executing encore', { cause: error });
}

expect(stdout).to.contain('Running webpack-dev-server ...');
expect(stdout).to.contain('Compiled successfully in');
expect(stdout).to.contain('webpack compiled successfully');

expect(stderr).to.contain('[webpack-dev-server] Project is running at:');
expect(stderr).to.contain('[webpack-dev-server] Loopback: http://localhost:8080/');
expect(stderr).to.contain('[webpack-dev-server] Content not from webpack is served from');

done();
});

setTimeout(() => {
abortController.abort();
}, 5000);
});

describe('Without webpack-dev-server installed', () => {
before(() => {
execSync('yarn remove webpack-dev-server --dev', { cwd: projectDir });
});

after(() => {
// Re-install webpack-dev-server and ensure the project is in a clean state
execSync('git checkout package.json', { cwd: projectDir });
execSync('yarn install', { cwd: projectDir });
});

it('Throw an error when trying to use the webpack-dev-server if not installed', done => {
testSetup.emptyTmpDir();
const testDir = testSetup.createTestAppDir();

fs.writeFileSync(
path.join(testDir, 'package.json'),
`{
"devDependencies": {
"@symfony/webpack-encore": "*"
}
}`
);

fs.writeFileSync(
path.join(testDir, 'webpack.config.js'),
`
const Encore = require('../../index.js');
Encore
.enableSingleRuntimeChunk()
.setOutputPath('build/')
.setPublicPath('/build')
.addEntry('main', './js/no_require')
;

module.exports = Encore.getWebpackConfig();
`
);

const binPath = path.resolve(projectDir, 'bin', 'encore.js');
exec(
`node ${binPath} dev-server --context=${testDir}`,
{
cwd: testDir,
env: Object.assign({}, process.env, { NO_COLOR: 'true' })
},
(err, stdout, stderr) => {
expect(stdout).to.contain('Install webpack-dev-server to use the webpack Development Server');
expect(stdout).to.contain('npm install webpack-dev-server --save-dev');
expect(stderr).to.equal('');

expect(stdout).not.to.contain('Running webpack-dev-server ...');
expect(stdout).not.to.contain('Compiled successfully in');
expect(stdout).not.to.contain('webpack compiled successfully');

done();
});
});
});
});