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 committed Jun 3, 2022
1 parent 404a6db commit e051f98
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 101 deletions.
146 changes: 58 additions & 88 deletions packages/dependency-extraction-webpack-plugin/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,14 @@ class DependencyExtractionWebpackPlugin {
compiler.webpack.Compilation
.PROCESS_ASSETS_STAGE_ANALYSE,
},
() => this.addAssets( compilation, compiler )
() => this.addAssets( compilation )
);
}
);
}
}

addAssets( compilation, compiler ) {
addAssets( compilation ) {
const {
combineAssets,
combinedOutputFile,
Expand All @@ -163,127 +163,97 @@ 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 chunkFilename = Array.from( chunk.files ).find( ( f ) =>
/\.js$/i.test( f )
);
if ( ! chunkFilename ) {
// There's no JS file in this chunk, there's no work for us.
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 = createHash( hashFunction )
.update( compilation.getAsset( chunkFilename ).source.buffer() )
.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[ chunkFilename ] = assetData;
continue;
}

let assetFilename;

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

// 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 Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,12 +212,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' => 'afb97a54745e17cee963');
"<?php return array('dependencies' => array('wp-blob'), 'version' => '4514ed711f6c035e0887');
"
`;

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' => 'ed750e4e046b77c317cb');
"<?php return array('dependencies' => array('lodash', 'wp-blob'), 'version' => '168d32b5fb42f9e5d8ce');
"
`;

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' => 'd3c2ce2cb84ff74b92e0');
"
`;

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 e051f98

Please sign in to comment.