UNPKG

4.39 kBJavaScriptView Raw
1/**
2 * External dependencies
3 */
4const { createHash } = require( 'crypto' );
5const json2php = require( 'json2php' );
6const { ExternalsPlugin } = require( 'webpack' );
7const { RawSource } = require( 'webpack-sources' );
8
9/**
10 * Internal dependencies
11 */
12const { defaultRequestToExternal, defaultRequestToHandle } = require( './util' );
13
14class DependencyExtractionWebpackPlugin {
15 constructor( options ) {
16 this.options = Object.assign(
17 {
18 injectPolyfill: false,
19 outputFormat: 'php',
20 useDefaults: true,
21 },
22 options
23 );
24
25 // Track requests that are externalized.
26 //
27 // Because we don't have a closed set of dependencies, we need to track what has
28 // been externalized so we can recognize them in a later phase when the dependency
29 // lists are generated.
30 this.externalizedDeps = new Set();
31
32 // Offload externalization work to the ExternalsPlugin.
33 this.externalsPlugin = new ExternalsPlugin( 'this', this.externalizeWpDeps.bind( this ) );
34 }
35
36 externalizeWpDeps( context, request, callback ) {
37 let externalRequest;
38
39 // Handle via options.requestToExternal first
40 if ( typeof this.options.requestToExternal === 'function' ) {
41 externalRequest = this.options.requestToExternal( request );
42 }
43
44 // Cascade to default if unhandled and enabled
45 if ( typeof externalRequest === 'undefined' && this.options.useDefaults ) {
46 externalRequest = defaultRequestToExternal( request );
47 }
48
49 if ( externalRequest ) {
50 this.externalizedDeps.add( request );
51
52 return callback( null, { this: externalRequest } );
53 }
54
55 return callback();
56 }
57
58 mapRequestToDependency( request ) {
59 // Handle via options.requestToHandle first
60 if ( typeof this.options.requestToHandle === 'function' ) {
61 const scriptDependency = this.options.requestToHandle( request );
62 if ( scriptDependency ) {
63 return scriptDependency;
64 }
65 }
66
67 // Cascade to default if enabled
68 if ( this.options.useDefaults ) {
69 const scriptDependency = defaultRequestToHandle( request );
70 if ( scriptDependency ) {
71 return scriptDependency;
72 }
73 }
74
75 // Fall back to the request name
76 return request;
77 }
78
79 stringify( asset ) {
80 if ( this.options.outputFormat === 'php' ) {
81 return `<?php return ${ json2php( JSON.parse( JSON.stringify( asset ) ) ) };`;
82 }
83
84 return JSON.stringify( asset );
85 }
86
87 apply( compiler ) {
88 this.externalsPlugin.apply( compiler );
89
90 const { output } = compiler.options;
91 const { filename: outputFilename } = output;
92
93 compiler.hooks.emit.tap( this.constructor.name, ( compilation ) => {
94 const { injectPolyfill, outputFormat } = this.options;
95
96 // Process each entry point independently.
97 for ( const [ entrypointName, entrypoint ] of compilation.entrypoints.entries() ) {
98 const entrypointExternalizedWpDeps = new Set();
99 if ( injectPolyfill ) {
100 entrypointExternalizedWpDeps.add( 'wp-polyfill' );
101 }
102
103 // Search for externalized modules in all chunks.
104 for ( const chunk of entrypoint.chunks ) {
105 for ( const { userRequest } of chunk.modulesIterable ) {
106 if ( this.externalizedDeps.has( userRequest ) ) {
107 const scriptDependency = this.mapRequestToDependency( userRequest );
108 entrypointExternalizedWpDeps.add( scriptDependency );
109 }
110 }
111 }
112
113 const runtimeChunk = entrypoint.getRuntimeChunk();
114
115 // Get a stable, stringified representation of the WordPress script asset.
116 const assetString = this.stringify( {
117 dependencies: Array.from( entrypointExternalizedWpDeps ).sort(),
118 version: runtimeChunk.hash,
119 } );
120
121 // Determine a filename for the asset file.
122 const [ filename, query ] = entrypointName.split( '?', 2 );
123 const assetFilename = compilation
124 .getPath( outputFilename, {
125 chunk: runtimeChunk,
126 filename,
127 query,
128 basename: basename( filename ),
129 contentHash: createHash( 'md4' )
130 .update( assetString )
131 .digest( 'hex' ),
132 } )
133 .replace( /\.js$/i, '.asset.' + ( outputFormat === 'php' ? 'php' : 'json' ) );
134
135 // Add source and file into compilation for webpack to output.
136 compilation.assets[ assetFilename ] = new RawSource( assetString );
137 runtimeChunk.files.push( assetFilename );
138 }
139 } );
140 }
141}
142
143function basename( name ) {
144 if ( ! name.includes( '/' ) ) {
145 return name;
146 }
147 return name.substr( name.lastIndexOf( '/' ) + 1 );
148}
149
150module.exports = DependencyExtractionWebpackPlugin;