Skip to content

Commit

Permalink
Dependency Extraction: output asset.php files for shared chunks too
Browse files Browse the repository at this point in the history
  • Loading branch information
jsnajdr authored and gziolo committed Jul 1, 2022
1 parent e06c583 commit 3a44214
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 100 deletions.
143 changes: 57 additions & 86 deletions packages/dependency-extraction-webpack-plugin/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ class DependencyExtractionWebpackPlugin {

if ( isWebpack4 ) {
compiler.hooks.emit.tap( this.constructor.name, ( compilation ) =>
this.addAssets( compilation, compiler )
this.addAssets( compilation )
);
} else {
compiler.hooks.thisCompilation.tap(
Expand All @@ -129,14 +129,14 @@ class DependencyExtractionWebpackPlugin {
stage: compiler.webpack.Compilation
.PROCESS_ASSETS_STAGE_ANALYSE,
},
() => this.addAssets( compilation, compiler )
() => this.addAssets( compilation )
);
}
);
}
}

addAssets( compilation, compiler ) {
addAssets( compilation ) {
const {
combineAssets,
combinedOutputFile,
Expand All @@ -162,124 +162,102 @@ class DependencyExtractionWebpackPlugin {

const combinedAssetsData = {};

// Process each entry point independently.
for ( const [
entrypointName,
entrypoint,
] of compilation.entrypoints.entries() ) {
const entrypointExternalizedWpDeps = new Set();
// Accumulate all entrypoint chunks, some of them shared
const entrypointChunks = new Set();
for ( const entrypoint of compilation.entrypoints.values() ) {
for ( const chunk of entrypoint.chunks ) {
entrypointChunks.add( chunk );
}
}

// Process each entrypoint chunk independently
for ( const chunk of entrypointChunks ) {
const chunkFiles = Array.from( chunk.files );

const chunkJSFile = chunkFiles.find( ( f ) => /\.js$/i.test( f ) );
if ( ! chunkJSFile ) {
// There's no JS file in this chunk, no work for us. Typically a `style.css` from cache group.
continue;
}

const chunkDeps = new Set();
if ( injectPolyfill ) {
entrypointExternalizedWpDeps.add( 'wp-polyfill' );
chunkDeps.add( 'wp-polyfill' );
}

const processModule = ( { userRequest } ) => {
if ( this.externalizedDeps.has( userRequest ) ) {
const scriptDependency =
this.mapRequestToDependency( userRequest );
entrypointExternalizedWpDeps.add( scriptDependency );
chunkDeps.add( this.mapRequestToDependency( userRequest ) );
}
};

// Search for externalized modules in all chunks.
for ( const chunk of entrypoint.chunks ) {
const modulesIterable = isWebpack4
? chunk.modulesIterable
: compilation.chunkGraph.getChunkModules( chunk );
for ( const chunkModule of modulesIterable ) {
processModule( chunkModule );
// Loop through submodules of ConcatenatedModule.
if ( chunkModule.modules ) {
for ( const concatModule of chunkModule.modules ) {
processModule( concatModule );
}
const modulesIterable = isWebpack4
? chunk.modulesIterable
: compilation.chunkGraph.getChunkModules( chunk );
for ( const chunkModule of modulesIterable ) {
processModule( chunkModule );
// Loop through submodules of ConcatenatedModule.
if ( chunkModule.modules ) {
for ( const concatModule of chunkModule.modules ) {
processModule( concatModule );
}
}
}

const { hashFunction, hashDigest, hashDigestLength } =
compilation.outputOptions;

// Go through the assets and hash the sources. We can't just use
// `entrypointChunk.contentHash` because that's not updated when
// `chunk.contentHash` because that's not updated when
// assets are minified. In practice the hash is updated by
// `RealContentHashPlugin` after minification, but it only modifies
// already-produced asset filenames and the updated hash is not
// available to plugins.
const hash = createHash( hashFunction );
for ( const filename of entrypoint.getFiles().sort() ) {
const asset = compilation.getAsset( filename );
hash.update( asset.source.buffer() );
}
const version = hash
const { hashFunction, hashDigest, hashDigestLength } =
compilation.outputOptions;

const contentHash = chunkFiles
.sort()
.reduce( ( hash, filename ) => {
const asset = compilation.getAsset( filename );
return hash.update( asset.source.buffer() );
}, createHash( hashFunction ) )
.digest( hashDigest )
.slice( 0, hashDigestLength );

const entrypointChunk = isWebpack4
? entrypoint.chunks.find( ( c ) => c.name === entrypointName )
: entrypoint.getEntrypointChunk();

const assetData = {
// Get a sorted array so we can produce a stable, stringified representation.
dependencies: Array.from( entrypointExternalizedWpDeps ).sort(),
version,
dependencies: Array.from( chunkDeps ).sort(),
version: contentHash,
};

const assetString = this.stringify( assetData );
const contentHash = createHash( hashFunction )
.update( assetString )
.digest( hashDigest )
.slice( 0, hashDigestLength );

// Determine a filename for the asset file.
const [ filename, query ] = entrypointName.split( '?', 2 );
const buildFilename = compilation.getPath(
compiler.options.output.filename,
{
chunk: entrypointChunk,
filename,
query,
basename: basename( filename ),
contentHash,
}
);

if ( combineAssets ) {
combinedAssetsData[ buildFilename ] = assetData;
combinedAssetsData[ chunkJSFile ] = assetData;
continue;
}

let assetFilename;

if ( outputFilename ) {
assetFilename = compilation.getPath( outputFilename, {
chunk: entrypointChunk,
filename,
query,
basename: basename( filename ),
chunk,
filename: chunkJSFile,
contentHash,
} );
} else {
assetFilename = buildFilename.replace(
/\.js$/i,
'.asset.' + ( outputFormat === 'php' ? 'php' : 'json' )
);
const suffix =
'.asset.' + ( outputFormat === 'php' ? 'php' : 'json' );
assetFilename = compilation
.getPath( '[file]', { filename: chunkJSFile } )
.replace( /\.js$/i, suffix );
}

// Add source and file into compilation for webpack to output.
compilation.assets[ assetFilename ] = new RawSource( assetString );
entrypointChunk.files[ isWebpack4 ? 'push' : 'add' ](
assetFilename
compilation.assets[ assetFilename ] = new RawSource(
this.stringify( assetData )
);
chunk.files[ isWebpack4 ? 'push' : 'add' ]( assetFilename );
}

if ( combineAssets ) {
// Assert the `string` type for output path.
// The type indicates the option may be `undefined`.
// However, at this point in compilation, webpack has filled the options in if
// they were not provided.
const outputFolder = /** @type {{path:string}} */ (
compiler.options.output
).path;
const outputFolder = compilation.outputOptions.path;

const assetsFilePath = path.resolve(
outputFolder,
Expand All @@ -299,11 +277,4 @@ class DependencyExtractionWebpackPlugin {
}
}

function basename( name ) {
if ( ! name.includes( '/' ) ) {
return name;
}
return name.substr( name.lastIndexOf( '/' ) + 1 );
}

module.exports = DependencyExtractionWebpackPlugin;
Original file line number Diff line number Diff line change
Expand Up @@ -207,12 +207,17 @@ Array [
`;

exports[`DependencyExtractionWebpackPlugin Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'a.asset.php' should match snapshot 1`] = `
"<?php return array('dependencies' => array('wp-blob'), 'version' => '39c05211520759f42c9d');
"<?php return array('dependencies' => array('wp-blob'), 'version' => '09a0c551770a351c5ca7');
"
`;

exports[`DependencyExtractionWebpackPlugin Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'b.asset.php' should match snapshot 1`] = `
"<?php return array('dependencies' => array('lodash', 'wp-blob'), 'version' => '70fbf918dd6a71b65cf6');
"<?php return array('dependencies' => array('lodash', 'wp-blob'), 'version' => 'c9f00d690a9f72438910');
"
`;

exports[`DependencyExtractionWebpackPlugin Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'runtime.asset.php' should match snapshot 1`] = `
"<?php return array('dependencies' => array(), 'version' => '46ea0ff11ac53fa5e88b');
"
`;

Expand All @@ -235,7 +240,7 @@ Array [
`;

exports[`DependencyExtractionWebpackPlugin Webpack \`style-imports\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = `
"<?php return array('dependencies' => array('lodash', 'wp-blob'), 'version' => '04b9da7eff6fbfcb0452');
"<?php return array('dependencies' => array('lodash', 'wp-blob'), 'version' => 'd8c0ee89d933a3809c0e');
"
`;

Expand Down
13 changes: 2 additions & 11 deletions packages/dependency-extraction-webpack-plugin/test/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,8 @@ describe( 'DependencyExtractionWebpackPlugin', () => {
const assetFiles = glob(
`${ outputDirectory }/+(*.asset|assets).@(json|php)`
);
const hasCombinedAssets = ( options.plugins || [] ).some(
( plugin ) => !! ( plugin.options || {} ).combineAssets
);
const entrypointCount =
typeof options.entry === 'object'
? Object.keys( options.entry ).length
: 1;
const expectedLength = hasCombinedAssets
? 1
: entrypointCount;
expect( assetFiles ).toHaveLength( expectedLength );

expect( assetFiles.length ).toBeGreaterThan( 0 );

// Asset files should match.
assetFiles.forEach( ( assetFile ) => {
Expand Down

0 comments on commit 3a44214

Please sign in to comment.