@plaudit/webpack-extensions 2.27.0 → 2.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,11 +3,12 @@ export default class BlockJSONManagingPlugin implements WebpackPluginInstance {
3
3
  private readonly standaloneBlocks;
4
4
  private readonly processingModules;
5
5
  private static readonly sourceToOutputMapping;
6
- private static readonly moduleSourcesToOutputs;
6
+ private static readonly blockJsonToEntrypointsMap;
7
+ private static readonly blockJSONAssetSources;
7
8
  constructor(standaloneBlocks: boolean, processingModules: boolean);
8
9
  apply(compiler: Compiler): void;
9
- findCommonAncestor(...paths: string[]): string[];
10
- findRelativeRouteBetween(path1: string, path2: string): string;
10
+ static findCommonAncestor(...paths: string[]): string[];
11
+ static findRelativeRouteBetween(path1: string, path2: string): string;
11
12
  private static hashThingForAsset;
12
- private static resolveFilesFromStats;
13
+ private static stripFilePrefix;
13
14
  }
@@ -12,186 +12,205 @@ class BlockJSONManagingPlugin {
12
12
  standaloneBlocks;
13
13
  processingModules;
14
14
  static sourceToOutputMapping = new Map();
15
- static moduleSourcesToOutputs = new Map();
15
+ static blockJsonToEntrypointsMap = new Map();
16
+ static blockJSONAssetSources = new Map();
16
17
  constructor(standaloneBlocks, processingModules) {
17
18
  this.standaloneBlocks = standaloneBlocks;
18
19
  this.processingModules = processingModules;
19
20
  }
20
21
  apply(compiler) {
21
- compiler.hooks.compilation.tap("BlockJSONManagingPlugin", compilation => {
22
- compilation.hooks.afterProcessAssets.tap("BlockJSONStyleRemappingPlugin_ProcessAssets", compilationAssets => {
23
- let assetSourceFilesCache = undefined;
24
- let singleFileInputToOutputNameAndHashCache = undefined;
25
- const sourceFileMappers = () => {
26
- if (singleFileInputToOutputNameAndHashCache === undefined || assetSourceFilesCache === undefined) {
27
- const stats = compilation.getStats().toJson({
28
- hash: true,
29
- publicPath: true,
30
- assets: true,
31
- moduleAssets: true,
32
- chunks: true,
33
- modules: true,
34
- source: true,
35
- errorDetails: false,
36
- timings: false
37
- });
38
- if (!stats.assets) {
39
- throw new Error("Stats did not include assets despite them being requested");
22
+ compiler.hooks.compilation.tap(this.constructor.name, compilation => {
23
+ compilation.hooks.processAssets.tap({ name: this.constructor.name, stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ANALYSE }, () => {
24
+ for (const blockEntrypoint of compilation.entrypoints.values()) {
25
+ if (blockEntrypoint.name?.endsWith("/block.json")) {
26
+ const source = blockEntrypoint.origins.map(origin => origin.request)[0];
27
+ const name = blockEntrypoint.getEntrypointChunk().name;
28
+ if (!source || !name) {
29
+ continue;
40
30
  }
41
- if (!stats.modules) {
42
- throw new Error("Stats did not include modules despite them being requested");
31
+ BlockJSONManagingPlugin.blockJSONAssetSources.set(name, source);
32
+ const dependencies = blockEntrypoint.getEntrypointChunk().getEntryOptions()?.dependOn;
33
+ if (dependencies) {
34
+ for (const dependency of dependencies) {
35
+ const entrypoint = compilation.entrypoints.get(dependency);
36
+ if (!entrypoint) {
37
+ continue;
38
+ }
39
+ const srcPath = entrypoint.origins.map(origin => origin.request)[0];
40
+ if (!srcPath) {
41
+ continue;
42
+ }
43
+ const destPath = /m?[jt]sx?$/i.test(node_path_1.default.extname(srcPath))
44
+ ? entrypoint.getFiles().find(file => file.endsWith(".mjs")) ?? entrypoint.getFiles().find(file => file.endsWith(".js"))
45
+ : entrypoint.getFiles().find(file => file.endsWith(".css"));
46
+ if (!destPath) {
47
+ continue;
48
+ }
49
+ const entryMeta = { hash: entrypoint.getEntrypointChunk().hash ?? destPath, path: destPath };
50
+ const currentEntrypoints = BlockJSONManagingPlugin.blockJsonToEntrypointsMap.get(name);
51
+ if (currentEntrypoints) {
52
+ currentEntrypoints.set(srcPath, entryMeta);
53
+ }
54
+ else {
55
+ BlockJSONManagingPlugin.blockJsonToEntrypointsMap.set(name, new Map([[srcPath, entryMeta]]));
56
+ }
57
+ }
43
58
  }
44
- singleFileInputToOutputNameAndHashCache = BlockJSONManagingPlugin.resolveFilesFromStats(compilationAssets, stats);
45
- assetSourceFilesCache = new Map(stats.assets
46
- .map(asset => [asset.name, asset.info?.sourceFilename])
47
- .filter((v) => v[0] !== undefined && v[1] !== undefined));
48
59
  }
49
- return { assetSourceFiles: assetSourceFilesCache, singleFileInputToOutputNameAndHash: singleFileInputToOutputNameAndHashCache };
50
- };
60
+ }
61
+ });
62
+ compilation.hooks.afterProcessAssets.tap(`${this.constructor.name}_ProcessBlockJSONFiles`, compilationAssets => {
63
+ // We start by trimming away the unused metadata files that WebPack creates.
64
+ for (const name of Object.keys(compilationAssets)) {
65
+ if (name.endsWith("block.json.asset.php")) {
66
+ compilation.deleteAsset(name);
67
+ compilation.deleteAsset(name.substring(0, name.length - 10) + ".js"); // This removes the block.json.js and block.json.js.map files
68
+ }
69
+ }
70
+ if (this.processingModules) {
71
+ // We perform the processing in the non-modules half ONLY because we don't necessarily have all the information we need during the modules half.
72
+ for (const name of BlockJSONManagingPlugin.blockJSONAssetSources.keys()) {
73
+ compilation.deleteAsset(name); // While the method is called, "deleteAsset", it doesn't actually delete it - just halts emission.
74
+ }
75
+ return;
76
+ }
77
+ const blockDirConfigData = {};
78
+ const mappableKeys = ["editorStyle", "style", "viewStyle", "viewScript", "script", "editorScript", "viewScriptModule", "scriptModule"];
51
79
  const remapValue = (value, name) => {
52
80
  if (value.startsWith("file:")) {
53
81
  let res = BlockJSONManagingPlugin.sourceToOutputMapping.get(`${name};${value}`);
54
82
  if (res !== undefined) {
55
83
  return res;
56
84
  }
57
- const { assetSourceFiles, singleFileInputToOutputNameAndHash } = sourceFileMappers();
58
- const dirname = node_path_1.default.dirname(assetSourceFiles.get(name) ?? name);
59
- const styleInputPath = node_path_1.default.normalize(node_path_1.default.join(compiler.context, dirname, value.substring(5)));
60
- const styleOutputPath = singleFileInputToOutputNameAndHash.get(styleInputPath);
61
- if (styleOutputPath !== undefined) {
85
+ const sourceDir = node_path_1.default.dirname(BlockJSONManagingPlugin.blockJSONAssetSources.get(name) ?? name);
86
+ const inputPath = node_path_1.default.normalize(node_path_1.default.join(sourceDir, value.substring(5)));
87
+ const output = BlockJSONManagingPlugin.blockJsonToEntrypointsMap.get(name)?.get(inputPath);
88
+ if (output !== undefined) {
62
89
  const prefix = value.startsWith("./", 5) ? "./" : "";
63
- const relativePath = node_path_1.default.relative(node_path_1.default.dirname(name), styleOutputPath[0]);
64
- const res = [`file:${prefix}${relativePath}`, typeof styleOutputPath[1] === 'string' ? styleOutputPath[1] : styleOutputPath[1]()];
90
+ const relativePath = node_path_1.default.relative(node_path_1.default.dirname(name), output.path);
91
+ const res = [`file:${prefix}${relativePath}`, output.hash];
65
92
  BlockJSONManagingPlugin.sourceToOutputMapping.set(`${name};${value}`, res);
66
93
  return res;
67
94
  }
68
95
  }
69
96
  return [value, ""];
70
97
  };
71
- if (this.processingModules) {
72
- for (const [key, value] of sourceFileMappers().singleFileInputToOutputNameAndHash.entries()) {
73
- BlockJSONManagingPlugin.moduleSourcesToOutputs.set(key, value);
98
+ for (const [name, source] of BlockJSONManagingPlugin.blockJSONAssetSources.entries()) {
99
+ const sourceDir = node_path_1.default.dirname(source);
100
+ const asset = compilation.assets[name]?.buffer().toString();
101
+ if (!asset) {
102
+ continue;
74
103
  }
75
- }
76
- const blockDirConfigData = {};
77
- const mappableKeys = ["editorStyle", "style", "viewStyle", "viewScript", "script", "editorScript", "viewScriptModule", "scriptModule"];
78
- for (const [name, asset] of Object.entries(compilationAssets)) {
79
- if (name.endsWith("block.json")) {
80
- let compositeHash = "";
81
- blockDirConfigData[name] = true;
82
- if (asset.constructor.name === 'RawSource') {
83
- const json = JSON.parse(asset.source().toString());
84
- for (const mappableKey of mappableKeys) {
85
- if (mappableKey in json) {
86
- const unmappedValue = json[mappableKey];
87
- if (Array.isArray(unmappedValue)) {
88
- const remappedValue = unmappedValue.map(value => remapValue(value, name));
89
- json[mappableKey] = remappedValue.map(([resource]) => resource);
90
- compositeHash += "~" + remappedValue.map(([_, hash]) => hash).join("~");
91
- }
92
- else if (typeof unmappedValue === 'string') {
93
- const remappedValue = remapValue(unmappedValue, name);
94
- json[mappableKey] = remappedValue[0];
95
- compositeHash += "~" + remappedValue[1];
96
- }
97
- }
98
- }
99
- if (json["version"]) {
100
- json["version"] = `${json["version"]}-${BlockJSONManagingPlugin.hashThingForAsset(compositeHash)}`;
104
+ blockDirConfigData[name] = true;
105
+ const json = JSON.parse(asset);
106
+ let compositeHash = "";
107
+ for (const mappableKey of mappableKeys) {
108
+ if (mappableKey in json) {
109
+ const unmappedValue = json[mappableKey];
110
+ if (Array.isArray(unmappedValue)) {
111
+ const remappedValue = unmappedValue.map(value => remapValue(value, name));
112
+ json[mappableKey] = remappedValue.map(([resource]) => resource);
113
+ compositeHash += "~" + remappedValue.map(([_, hash]) => hash).join("~");
101
114
  }
102
- else {
103
- json["version"] = BlockJSONManagingPlugin.hashThingForAsset(compositeHash);
115
+ else if (typeof unmappedValue === 'string') {
116
+ const remappedValue = remapValue(unmappedValue, name);
117
+ json[mappableKey] = remappedValue[0];
118
+ compositeHash += "~" + remappedValue[1];
104
119
  }
105
- const pathsNeedRemapping = !this.standaloneBlocks && json["plaudit"] !== "simple";
106
- const { assetSourceFiles } = sourceFileMappers();
107
- const sourceDir = node_path_1.default.join(compiler.context, node_path_1.default.dirname(assetSourceFiles.get(name) ?? name));
108
- const outputDir = node_path_1.default.join(compiler.outputPath, node_path_1.default.dirname(name));
109
- const stripFilePrefix = (file) => file.startsWith("file:./") ? file.substring(7) : file;
110
- const rawSetupFiles = json["plaudit"]?.["setup"]
111
- ? (typeof json["plaudit"]["setup"] === 'string' ? [json["plaudit"]["setup"]] : json["plaudit"]["setup"])
112
- : ["setup.php"];
113
- const setupFiles = pathsNeedRemapping
114
- ? rawSetupFiles
115
- .map(p => node_path_1.default.normalize(node_path_1.default.join(sourceDir, stripFilePrefix(p))))
116
- .filter(p => node_fs_1.default.existsSync(p))
117
- .map(p => `file:./${this.findRelativeRouteBetween(outputDir, p)}`)
118
- : rawSetupFiles
119
- .filter(p => node_fs_1.default.existsSync(node_path_1.default.normalize(node_path_1.default.join(sourceDir, stripFilePrefix(p)))));
120
- if (setupFiles.length === 0) {
121
- if (json["plaudit"]?.["setup"] !== undefined) {
122
- delete json["plaudit"]["setup"];
123
- }
124
- }
125
- else {
126
- if (typeof json["plaudit"] !== 'object') {
127
- if (json["plaudit"] === "native") {
128
- json["plaudit"] = { type: "native" };
129
- }
130
- else {
131
- json["plaudit"] = {};
132
- }
133
- }
134
- json["plaudit"]["setup"] = setupFiles.length === 1 ? setupFiles[0] : setupFiles;
135
- }
136
- if (json["acf"]) {
137
- if (json["acf"]["renderTemplate"]) {
138
- json["render_template"] = json["acf"]["renderTemplate"];
139
- delete json["acf"]["renderTemplate"];
140
- }
141
- else if (json["acf"]["render_template"]) {
142
- json["render_template"] = json["acf"]["render_template"];
143
- delete json["acf"]["render_template"];
144
- }
145
- }
146
- const blockName = json["name"]?.toString() || "non-existent/block-name";
147
- let rawRenderTemplate = json["render"] ?? json["render_template"];
148
- rawRenderTemplate = (rawRenderTemplate
149
- ? (typeof rawRenderTemplate === 'string' ? [rawRenderTemplate] : rawRenderTemplate)
150
- : [`${blockName.substring(blockName.indexOf('/') + 1)}.php`, "template.php", "template.twig"]);
151
- const renderTemplate = pathsNeedRemapping
152
- ? rawRenderTemplate
153
- .map(p => node_path_1.default.normalize(node_path_1.default.join(sourceDir, stripFilePrefix(p))))
154
- .filter(p => node_fs_1.default.existsSync(p))
155
- .map(p => `file:./${this.findRelativeRouteBetween(outputDir, p)}`)
156
- : rawRenderTemplate
157
- .filter(p => node_fs_1.default.existsSync(node_path_1.default.normalize(node_path_1.default.join(sourceDir, stripFilePrefix(p)))));
158
- if (renderTemplate.length === 0) {
159
- delete json["render_template"];
160
- delete json["render"];
120
+ }
121
+ }
122
+ if (json["version"]) {
123
+ json["version"] = `${json["version"]}-${BlockJSONManagingPlugin.hashThingForAsset(compositeHash)}`;
124
+ }
125
+ else {
126
+ json["version"] = BlockJSONManagingPlugin.hashThingForAsset(compositeHash);
127
+ }
128
+ const outputDir = node_path_1.default.join(compiler.outputPath, node_path_1.default.dirname(name));
129
+ const pathsNeedRemapping = !this.standaloneBlocks && json["plaudit"] !== "simple";
130
+ const rawSetupFiles = json["plaudit"]?.["setup"]
131
+ ? (typeof json["plaudit"]["setup"] === 'string' ? [json["plaudit"]["setup"]] : json["plaudit"]["setup"])
132
+ : ["setup.php"];
133
+ const setupFiles = pathsNeedRemapping
134
+ ? rawSetupFiles
135
+ .map(p => node_path_1.default.normalize(node_path_1.default.join(sourceDir, BlockJSONManagingPlugin.stripFilePrefix(p))))
136
+ .filter(p => node_fs_1.default.existsSync(p))
137
+ .map(p => `file:./${BlockJSONManagingPlugin.findRelativeRouteBetween(outputDir, p)}`)
138
+ : rawSetupFiles
139
+ .filter(p => node_fs_1.default.existsSync(node_path_1.default.normalize(node_path_1.default.join(sourceDir, BlockJSONManagingPlugin.stripFilePrefix(p)))));
140
+ if (setupFiles.length === 0) {
141
+ if (json["plaudit"]?.["setup"] !== undefined) {
142
+ delete json["plaudit"]["setup"];
143
+ }
144
+ }
145
+ else {
146
+ if (typeof json["plaudit"] !== 'object') {
147
+ if (json["plaudit"] === "native") {
148
+ json["plaudit"] = { type: "native" };
161
149
  }
162
150
  else {
163
- let validTemplateLocation, invalidTemplateLocation;
164
- if (json["acf"] || (json["plaudit"] && (json["plaudit"] !== "native" && (typeof json["plaudit"] !== 'object' || json["plaudit"]?.["type"] !== "native")))) {
165
- // ACF-like blocks need to have the template stored in render_template.
166
- // Because we can statically detect ACF-like blocks, we can make the move here instead of at run-time.
167
- validTemplateLocation = "render_template";
168
- invalidTemplateLocation = "render";
169
- }
170
- else {
171
- validTemplateLocation = "render";
172
- invalidTemplateLocation = "render_template";
173
- }
174
- delete json[invalidTemplateLocation];
175
- if (renderTemplate.length > 1) {
176
- const error = new webpack_1.WebpackError("Encountered a block with multiple possible render files");
177
- error.file = node_path_1.default.join(sourceDir, 'block.json');
178
- compilation.warnings.push(error);
179
- json[validTemplateLocation] = renderTemplate.find(p => p.endsWith(".php")) ?? renderTemplate[0];
180
- }
181
- else {
182
- json[validTemplateLocation] = renderTemplate[0];
183
- }
151
+ json["plaudit"] = {};
184
152
  }
185
- compilation.updateAsset(name, new webpack_1.sources.RawSource(JSON.stringify(json, undefined, " ")));
186
153
  }
154
+ json["plaudit"]["setup"] = setupFiles.length === 1 ? setupFiles[0] : setupFiles;
187
155
  }
156
+ if (json["acf"]) {
157
+ if (json["acf"]["renderTemplate"]) {
158
+ json["render_template"] = json["acf"]["renderTemplate"];
159
+ delete json["acf"]["renderTemplate"];
160
+ }
161
+ else if (json["acf"]["render_template"]) {
162
+ json["render_template"] = json["acf"]["render_template"];
163
+ delete json["acf"]["render_template"];
164
+ }
165
+ }
166
+ const blockName = json["name"]?.toString() || "non-existent/block-name";
167
+ let rawRenderTemplate = json["render"] ?? json["render_template"];
168
+ rawRenderTemplate = (rawRenderTemplate
169
+ ? (typeof rawRenderTemplate === 'string' ? [rawRenderTemplate] : rawRenderTemplate)
170
+ : [`${blockName.substring(blockName.indexOf('/') + 1)}.php`, "template.php", "template.twig"]);
171
+ const renderTemplate = pathsNeedRemapping
172
+ ? rawRenderTemplate
173
+ .map(p => node_path_1.default.normalize(node_path_1.default.join(sourceDir, BlockJSONManagingPlugin.stripFilePrefix(p))))
174
+ .filter(p => node_fs_1.default.existsSync(p))
175
+ .map(p => `file:./${BlockJSONManagingPlugin.findRelativeRouteBetween(outputDir, p)}`)
176
+ : rawRenderTemplate
177
+ .filter(p => node_fs_1.default.existsSync(node_path_1.default.normalize(node_path_1.default.join(sourceDir, BlockJSONManagingPlugin.stripFilePrefix(p)))));
178
+ if (renderTemplate.length === 0) {
179
+ delete json["render_template"];
180
+ delete json["render"];
181
+ }
182
+ else {
183
+ let validTemplateLocation, invalidTemplateLocation;
184
+ if (json["acf"] || (json["plaudit"] && (json["plaudit"] !== "native" && (typeof json["plaudit"] !== 'object' || json["plaudit"]?.["type"] !== "native")))) {
185
+ // ACF-like blocks need to have the template stored in render_template.
186
+ // Because we can statically detect ACF-like blocks, we can make the move here instead of at run-time.
187
+ validTemplateLocation = "render_template";
188
+ invalidTemplateLocation = "render";
189
+ }
190
+ else {
191
+ validTemplateLocation = "render";
192
+ invalidTemplateLocation = "render_template";
193
+ }
194
+ delete json[invalidTemplateLocation];
195
+ if (renderTemplate.length > 1) {
196
+ const error = new webpack_1.WebpackError("Encountered a block with multiple possible render files");
197
+ error.file = node_path_1.default.join(sourceDir, 'block.json');
198
+ compilation.warnings.push(error);
199
+ json[validTemplateLocation] = renderTemplate.find(p => p.endsWith(".php")) ?? renderTemplate[0];
200
+ }
201
+ else {
202
+ json[validTemplateLocation] = renderTemplate[0];
203
+ }
204
+ }
205
+ compilation[name in compilation.assets ? 'updateAsset' : 'emitAsset'](name, new webpack_1.sources.RawSource(JSON.stringify(json, undefined, " ")));
188
206
  }
189
- const sortedBlockDirConfigData = Object.fromEntries(Object.entries(blockDirConfigData).sort(([a], [b]) => a.localeCompare(b)));
207
+ const sortedBlockDirConfigData = Object.fromEntries(Object.entries(blockDirConfigData)
208
+ .sort(([a], [b]) => a.localeCompare(b)));
190
209
  compilation.emitAsset("blockdir.config", new webpack_1.sources.RawSource((0, php_serializer_1.default)(sortedBlockDirConfigData)));
191
210
  });
192
211
  });
193
212
  }
194
- findCommonAncestor(...paths) {
213
+ static findCommonAncestor(...paths) {
195
214
  return paths.map(p => node_path_1.default.normalize(p).split(node_path_1.default.sep)).reduce((prior, current) => {
196
215
  for (let i = 0, limit = Math.min(prior.length, current.length); i < limit; i++) {
197
216
  if (prior[i] !== current[i]) {
@@ -201,8 +220,8 @@ class BlockJSONManagingPlugin {
201
220
  return current.length < prior.length ? current : prior;
202
221
  });
203
222
  }
204
- findRelativeRouteBetween(path1, path2) {
205
- const commonAncestor = this.findCommonAncestor(path1, path2);
223
+ static findRelativeRouteBetween(path1, path2) {
224
+ const commonAncestor = BlockJSONManagingPlugin.findCommonAncestor(path1, path2);
206
225
  const route = Array(path1.split(node_path_1.default.sep).length - commonAncestor.length).fill("..");
207
226
  route.push(node_path_1.default.relative(commonAncestor.join(node_path_1.default.sep), path2));
208
227
  return route.join(node_path_1.default.sep);
@@ -210,34 +229,8 @@ class BlockJSONManagingPlugin {
210
229
  static hashThingForAsset(thing) {
211
230
  return node_crypto_1.default.createHash('md5').update(thing).digest("hex").substring(0, 20).toLowerCase();
212
231
  }
213
- static resolveFilesFromStats(compilationAssets, stats) {
214
- const singleFileChunkToOutputName = new Map(stats.assets
215
- .filter((asset) => asset.chunks?.length === 1)
216
- .filter(asset => !asset.name.endsWith("-rtl.css"))
217
- .filter(asset => !asset.name.endsWith('.asset.php'))
218
- .map(asset => {
219
- let assetHash = asset.info.contenthash ?? asset.info.fullhash;
220
- if (Array.isArray(assetHash)) {
221
- assetHash = BlockJSONManagingPlugin.hashThingForAsset(assetHash.join('~'));
222
- }
223
- return [asset.chunks[0], [asset.name, assetHash ?? (() => {
224
- const realAsset = compilationAssets[asset.name];
225
- return BlockJSONManagingPlugin.hashThingForAsset(realAsset ? realAsset.source().toString() : Date.now().toString());
226
- })]];
227
- }));
228
- const res = new Map(stats.modules
229
- .map(module => {
230
- if (module.nameForCondition !== undefined && module.chunks?.length === 1) {
231
- const output = singleFileChunkToOutputName.get(module.chunks[0]);
232
- return output !== undefined ? [module.nameForCondition, output] : undefined;
233
- }
234
- return undefined;
235
- })
236
- .filter((v) => v !== undefined));
237
- for (const [key, value] of BlockJSONManagingPlugin.moduleSourcesToOutputs.entries()) {
238
- res.set(key, value);
239
- }
240
- return res;
232
+ static stripFilePrefix(file) {
233
+ return file.startsWith("file:./") ? file.substring(7) : file;
241
234
  }
242
235
  }
243
236
  exports.default = BlockJSONManagingPlugin;
@@ -13,7 +13,7 @@ class ExtensionsConfigFileGeneratorPlugin {
13
13
  this.extensionsPath = extensionsPath;
14
14
  }
15
15
  apply(compiler) {
16
- compiler.hooks.make.tapPromise('ExtensionsConfigFileGeneratorPlugin', async (compilation) => {
16
+ compiler.hooks.make.tapPromise(this.constructor.name, async (compilation) => {
17
17
  if (!compilation.contextDependencies.has(this.extensionsPath)) {
18
18
  compilation.contextDependencies.add(this.extensionsPath);
19
19
  }
@@ -28,9 +28,9 @@ class ExtensionsConfigFileGeneratorPlugin {
28
28
  }
29
29
  await Promise.all(emissionPromises);
30
30
  });
31
- compiler.hooks.thisCompilation.tap("ExtensionsConfigFileGeneratorPlugin", compilation => {
31
+ compiler.hooks.thisCompilation.tap(this.constructor.name, compilation => {
32
32
  compilation.hooks.processAssets.tap({
33
- name: "ExtensionsConfigFileGeneratorPlugin_ProcessAssets",
33
+ name: `${this.constructor.name}_ProcessAssets`,
34
34
  stage: webpack_1.Compilation.PROCESS_ASSETS_STAGE_ANALYSE
35
35
  }, compilationAssets => {
36
36
  const stats = compilation.getStats().toJson({
@@ -15,6 +15,12 @@ type PlauditWordpressWebpackConfig = {
15
15
  postcss?: {
16
16
  functions?: (variables: (name: string) => unknown) => PostcssFunctionsOptions['functions'];
17
17
  };
18
+ externals?: {
19
+ [dep: string]: string | {
20
+ import: string;
21
+ handle: string;
22
+ };
23
+ };
18
24
  };
19
25
  declare const _default: (config: PlauditWordpressWebpackConfig, webpackConfig?: Configuration[] | Configuration) => Configuration[];
20
26
  export = _default;
@@ -10,19 +10,20 @@ const ExtensionsConfigFileGeneratorPlugin_1 = __importDefault(require("./wordpre
10
10
  const VariablesJSMonitorPlugin_1 = __importDefault(require("./wordpress-scripts-wrapper/VariablesJSMonitorPlugin"));
11
11
  const BrowserSyncPlugin_1 = require("./wordpress-scripts-wrapper/BrowserSyncPlugin");
12
12
  const static_configs_1 = require("./wordpress-scripts-wrapper/static-configs");
13
+ const dependency_extraction_webpack_plugin_1 = __importDefault(require("@wordpress/dependency-extraction-webpack-plugin"));
13
14
  const copy_webpack_plugin_1 = __importDefault(require("copy-webpack-plugin"));
14
15
  const fork_ts_checker_webpack_plugin_1 = __importDefault(require("fork-ts-checker-webpack-plugin"));
15
16
  const webpack_remove_empty_scripts_1 = __importDefault(require("webpack-remove-empty-scripts"));
16
17
  function joinPossiblyAbsolutePaths(...paths) {
17
18
  return paths.reduce((res, p) => !res || node_path_1.default.isAbsolute(p) ? p : node_path_1.default.join(res, p), '') || '.';
18
19
  }
19
- function mapToRealEntrypoints(entrypoint, dir, mapper = (entrypoint) => entrypoint) {
20
+ function mapToRealEntrypoints(entrypoint, dir, mapper = (entrypoint) => entrypoint, lazyDependent) {
20
21
  return (Array.isArray(entrypoint) ? entrypoint : [entrypoint])
21
22
  .map(ep => joinPossiblyAbsolutePaths(dir, mapper(ep)))
22
23
  .filter(ep => node_fs_1.default.statSync(ep, { throwIfNoEntry: false })?.isFile())
23
24
  .map(ep => {
24
25
  const parsedEntrypoint = node_path_1.default.parse(ep);
25
- return [joinPossiblyAbsolutePaths(node_path_1.default.basename(parsedEntrypoint.dir), parsedEntrypoint.name), { import: ep }];
26
+ return [joinPossiblyAbsolutePaths(node_path_1.default.basename(parsedEntrypoint.dir), parsedEntrypoint.name), { import: ep, lazyDependent }];
26
27
  });
27
28
  }
28
29
  let isInThemeCache = undefined;
@@ -224,10 +225,10 @@ function processIndividualWebpackConfig(config, webpackConfig, sources) {
224
225
  scriptExtension = scriptWithoutModuleExtension;
225
226
  entrypointFields = ["editorStyle", "viewStyle", "style", "editorScript", "viewScript", "script"];
226
227
  }
227
- const { standaloneBlocks, variablesFilePath, verbose } = config;
228
+ const { standaloneBlocks, variablesFilePath, verbose, externals } = config;
228
229
  let currentVariables = config.currentVariables;
229
230
  replaceDefaultURLProcessing(webpackConfig);
230
- const fixedRules = injectPostcssConfigOverrides(webpackConfig, name => currentVariables[name], config.postcss.functions ?? (() => ({})), verbose);
231
+ const fixedRules = injectPostcssConfigOverrides(webpackConfig, name => currentVariables[name], config.postcss.functions ?? (() => ({})), verbose) ?? [];
231
232
  return sources.map(([src, dest]) => {
232
233
  const srcRoots = (typeof dest !== 'string' && dest.withLegacyBlocksIn
233
234
  ? [...src.split(','), ...resolveLegacyBlockScriptsInFolder(dest.withLegacyBlocksIn)]
@@ -281,6 +282,37 @@ function processIndividualWebpackConfig(config, webpackConfig, sources) {
281
282
  if (srcIsDirectory && (typeof dest !== 'string' && dest.directoryLayout === 'extensions')) {
282
283
  plugins.push(new ExtensionsConfigFileGeneratorPlugin_1.default(srcRoot));
283
284
  }
285
+ if (externals) {
286
+ const pluginIndex = plugins.findIndex(plugin => plugin instanceof dependency_extraction_webpack_plugin_1.default);
287
+ if (pluginIndex === -1) {
288
+ console.error("Cannot apply externals when they have been disabled via CLI flag.");
289
+ }
290
+ else {
291
+ plugins[pluginIndex] = new dependency_extraction_webpack_plugin_1.default({
292
+ requestToExternal(request) {
293
+ const possibleExternal = externals[request];
294
+ if (typeof possibleExternal === 'string') {
295
+ return possibleExternal;
296
+ }
297
+ else if (possibleExternal !== undefined) {
298
+ return possibleExternal['import'];
299
+ }
300
+ else {
301
+ return undefined;
302
+ }
303
+ },
304
+ requestToHandle(request) {
305
+ const possibleExternal = externals[request];
306
+ if (possibleExternal !== undefined && typeof possibleExternal !== 'string') {
307
+ return typeof possibleExternal.handle;
308
+ }
309
+ else {
310
+ return request;
311
+ }
312
+ }
313
+ });
314
+ }
315
+ }
284
316
  if (process.argv.includes('--browser-sync') || process.env['BROWSER_SYNC'] === 'true') {
285
317
  plugins.push(new BrowserSyncPlugin_1.BrowserSyncPlugin());
286
318
  }
@@ -304,11 +336,13 @@ function processIndividualWebpackConfig(config, webpackConfig, sources) {
304
336
  const res = [];
305
337
  try {
306
338
  const blockJSON = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(dir, 'block.json'), 'utf8'));
339
+ const blockJSONChunkName = node_path_1.default.join(node_path_1.default.basename(dir), "block.json");
307
340
  for (const key of entrypointFields) {
308
341
  if (key in blockJSON) {
309
- res.push(...mapToRealEntrypoints(blockJSON[key], dir, ep => ep.startsWith("file:") ? ep.substring(5) : ep));
342
+ res.push(...mapToRealEntrypoints(blockJSON[key], dir, ep => ep.startsWith("file:") ? ep.substring(5) : ep, blockJSONChunkName));
310
343
  }
311
344
  }
345
+ res.push([blockJSONChunkName, { import: node_path_1.default.join(dir, 'block.json') }]);
312
346
  return res;
313
347
  }
314
348
  catch (e) {
@@ -341,6 +375,27 @@ function processIndividualWebpackConfig(config, webpackConfig, sources) {
341
375
  }
342
376
  }
343
377
  }
378
+ // This is used to allow for block.json dependencies to correctly account for name-deduplication
379
+ for (const [key, entry] of Object.entries(currentEntry)) {
380
+ if (typeof entry === 'object' && !Array.isArray(entry) && 'lazyDependent' in entry && typeof entry.lazyDependent === 'string') {
381
+ const target = currentEntry[entry.lazyDependent];
382
+ if (typeof target === 'object' && !Array.isArray(target)) {
383
+ let dependOn;
384
+ if (target.dependOn === undefined) {
385
+ dependOn = target.dependOn = [];
386
+ }
387
+ else if (typeof target.dependOn === 'string') {
388
+ dependOn = target.dependOn = [target.dependOn];
389
+ }
390
+ else {
391
+ dependOn = target.dependOn;
392
+ }
393
+ if (!dependOn.includes(key)) {
394
+ dependOn.push(key);
395
+ }
396
+ }
397
+ }
398
+ }
344
399
  return currentEntry;
345
400
  };
346
401
  }
@@ -395,7 +450,16 @@ function processIndividualWebpackConfig(config, webpackConfig, sources) {
395
450
  },
396
451
  module: {
397
452
  ...webpackConfig.module,
398
- rules: fixedRules
453
+ rules: [...fixedRules, {
454
+ test: /block\.json/i,
455
+ type: 'asset/resource',
456
+ generator: {
457
+ filename(pathData) {
458
+ // This mess of a name-generator accounts for WebPack's calculated name changing when block.json has dependents.
459
+ return node_path_1.default.join(node_path_1.default.basename(node_path_1.default.dirname(pathData.filename ?? pathData.runtime?.toString() ?? "[name]")), "[name][ext]");
460
+ }
461
+ }
462
+ }]
399
463
  },
400
464
  resolve: {
401
465
  ...webpackConfig.resolve,
@@ -405,7 +469,7 @@ function processIndividualWebpackConfig(config, webpackConfig, sources) {
405
469
  stats: config.stats,
406
470
  plugins: copyFiles
407
471
  ? plugins.map(plugin => plugin.constructor.name === 'CopyPlugin'
408
- ? new copy_webpack_plugin_1.default({ patterns: [{ from: standaloneBlocks ? '**/(block.json|*.(php|twig|svg))' : '**/(block.json|*.(asset\.php|svg))',
472
+ ? new copy_webpack_plugin_1.default({ patterns: [{ from: standaloneBlocks ? '**/(*.(php|twig|svg))' : '**/(*.(asset\.php|svg))',
409
473
  context: srcRoot, noErrorOnMissing: true }] })
410
474
  : plugin)
411
475
  : (srcIsDirectory
@@ -419,17 +483,17 @@ function processIndividualWebpackConfig(config, webpackConfig, sources) {
419
483
  return entry();
420
484
  }
421
485
  };
422
- }).filter((cfg) => cfg !== undefined);
486
+ }).filter(cfg => cfg !== undefined);
423
487
  }
424
488
  module.exports = function (config, webpackConfig = require("@wordpress/scripts/config/webpack.config")) {
425
489
  testForDuplicatedEntryPaths(config);
426
- const { standaloneBlocks = false, stats = 'errors-warnings', variables: rawVariables, verbose = process.argv.includes('--verbose') || process.env['VERBOSE'] === 'true', postcss = {} } = config;
490
+ const { standaloneBlocks = false, stats = 'errors-warnings', variables: rawVariables, verbose = process.argv.includes('--verbose') || process.env['VERBOSE'] === 'true', postcss = {}, externals } = config;
427
491
  let variablesFilePath = undefined;
428
492
  const currentVariables = rawVariables ?? {};
429
493
  if (!rawVariables) {
430
494
  variablesFilePath = ["variables.js", "src/site/variables.js"].map(p => node_path_1.default.join(process.cwd(), p)).filter(p => node_fs_1.default.existsSync(p))[0];
431
495
  }
432
- const cfg = { currentVariables, postcss, standaloneBlocks, stats, variablesFilePath, verbose };
496
+ const cfg = { currentVariables, postcss, standaloneBlocks, stats, variablesFilePath, verbose, externals };
433
497
  const sources = Array.isArray(config.src) ? config.src.map(s => [s, s]) : Object.entries(config.src);
434
498
  if (Array.isArray(webpackConfig)) {
435
499
  return webpackConfig.flatMap(wpCfg => processIndividualWebpackConfig(cfg, wpCfg, sources));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plaudit/webpack-extensions",
3
- "version": "2.27.0",
3
+ "version": "2.29.0",
4
4
  "scripts": {
5
5
  "prepublishOnly": "rm -rf build && mkdir build && tsc",
6
6
  "build": "tsc",
@@ -29,7 +29,7 @@
29
29
  "postcss-load-config": "^4.0.2",
30
30
  "postcss-loader": "^7.3.4",
31
31
  "ts-node": "^10.9.2",
32
- "typescript": "^5.5.3"
32
+ "typescript": "^5.6.2"
33
33
  },
34
34
  "dependencies": {
35
35
  "@plaudit/postcss-color-function": "^5.0.0",