-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Description
What problem does this address?
Consider a project with the following files:
package.json
{
"type": "module",
"dependencies": {
"@wordpress/dependency-extraction-webpack-plugin": "^6.10.0",
"@wordpress/url": "^4.10.0",
"webpack": "^5.95.0",
"webpack-cli": "^5.1.4"
}
}webpack.config.cjs
const DependencyExtractionWebpackPlugin = require( '@wordpress/dependency-extraction-webpack-plugin' );
module.exports = {
mode: 'production',
plugins: [ new DependencyExtractionWebpackPlugin() ],
}src/index.js
import { hello } from './utils.js';
hello();src/utils.js
import { buildQueryString } from '@wordpress/url';
export function hello() {
console.log( 'hello, world' );
}
export function makeUrl( query ) {
return 'https://example.org/?' + buildQueryString( query );
}If you build this project (e.g. npm install && npm exec webpack), you get the following files as output
dist/main.js
(()=>{"use strict";window.wp.url,console.log("hello, world")})();dist/main.asset.php
<?php return array('dependencies' => array('wp-url'), 'version' => 'e7734f2fdec823e92bd0');Note that, even though the use of @wordpress/url was optimized out (and @wordpress/url declares sideEffects: false in its package.json), it's still depended on by the bundle.
What is your proposed solution?
Unfortunately I don't have a perfect solution. Webpack doesn't currently provide a way for externals to indicate whether they have side effects or not. The closest issue related to that seems to be webpack/webpack#15486.
It turns out that Webpack 5's Module class declares a method getSideEffectsConnectionState which is used by the builtin SideEffectsFlagPlugin plugin and other places to remove side-effect-free modules. For ExternalModule this always returns true (i.e. "has side effects"), so external modules are never removed. For normal modules, SideEffectsFlagPlugin plugin examines the sideEffects in package.json and the module's code to determine what it should return.
We can't directly affect the ExternalModule instances as they're created by ExternalsPlugin (no hooks, unfortunately). But we could potentially do something like this:
- Change
this.externalizedDepsinto a Map. - When
externalizeWpDepsexternalizes a dep, do like this to record it (notegetResolveandcontextare part of thewebpack.ExternalItemFunctionDataargument to the function):getResolve()( context, request, ( err, result, resolveRequest ) => { // Don't care about an error, just save the undefined. this.externalizedDeps.set( request, resolveRequest?.descriptionFileData?.sideEffects ); callback( null, externalRequest ); } );
- Then add this hook to munge the ExternalModule instances just before they start being checked:
compilation.hooks.optimizeDependencies.tap( { name: this.constructor.name, before: 'SideEffectsFlagPlugin', }, modules => { for ( const module of modules ) { if ( this.externalizedDeps.get( module.userRequest ) === false ) { module.getSideEffectsConnectionState = () => false; } } } );
If that seems reasonable, let me know and I'll put together a PR. Or feel free to do so yourself if you'd rather or if you have a better idea.