@plaudit/webpack-extensions 2.28.0 → 2.30.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.
@@ -1,13 +1,15 @@
1
- import { type Compiler, type WebpackPluginInstance } from "webpack";
1
+ import { type Compilation, type Compiler, type WebpackPluginInstance } from "webpack";
2
2
  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 resolveDestinationBySourceExtension(srcPath: string, entrypoint: Compilation['asyncEntrypoints'][number]): string | undefined;
11
+ static findCommonAncestor(...paths: string[]): string[];
12
+ static findRelativeRouteBetween(path1: string, path2: string): string;
11
13
  private static hashThingForAsset;
12
- private static resolveFilesFromStats;
14
+ private static stripFilePrefix;
13
15
  }
@@ -6,192 +6,221 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const node_crypto_1 = __importDefault(require("node:crypto"));
7
7
  const node_fs_1 = __importDefault(require("node:fs"));
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
- const php_serializer_1 = __importDefault(require("./php-serializer"));
10
9
  const webpack_1 = require("webpack");
10
+ const php_serializer_1 = __importDefault(require("./php-serializer"));
11
11
  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 = BlockJSONManagingPlugin.resolveDestinationBySourceExtension(srcPath, entrypoint);
44
+ if (!destPath) {
45
+ continue;
46
+ }
47
+ const entryMeta = { hash: entrypoint.getEntrypointChunk().hash ?? destPath, path: destPath };
48
+ const currentEntrypoints = BlockJSONManagingPlugin.blockJsonToEntrypointsMap.get(name);
49
+ if (currentEntrypoints) {
50
+ currentEntrypoints.set(srcPath, entryMeta);
51
+ }
52
+ else {
53
+ BlockJSONManagingPlugin.blockJsonToEntrypointsMap.set(name, new Map([[srcPath, entryMeta]]));
54
+ }
55
+ }
43
56
  }
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
57
  }
49
- return { assetSourceFiles: assetSourceFilesCache, singleFileInputToOutputNameAndHash: singleFileInputToOutputNameAndHashCache };
50
- };
58
+ }
59
+ });
60
+ compilation.hooks.afterProcessAssets.tap(`${this.constructor.name}_ProcessBlockJSONFiles`, compilationAssets => {
61
+ // We start by trimming away the unused metadata files that WebPack creates.
62
+ for (const name of Object.keys(compilationAssets)) {
63
+ if (name.endsWith("block.json.asset.php")) {
64
+ compilation.deleteAsset(name);
65
+ compilation.deleteAsset(name.substring(0, name.length - 10) + ".js"); // This removes the block.json.js and block.json.js.map files
66
+ }
67
+ }
68
+ if (this.processingModules) {
69
+ // 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.
70
+ for (const name of BlockJSONManagingPlugin.blockJSONAssetSources.keys()) {
71
+ compilation.deleteAsset(name); // While the method is called, "deleteAsset", it doesn't actually delete it - just halts emission.
72
+ }
73
+ return;
74
+ }
75
+ const blockDirConfigData = {};
76
+ const mappableKeys = ["editorStyle", "style", "viewStyle", "viewScript", "script", "editorScript", "viewScriptModule", "scriptModule"];
51
77
  const remapValue = (value, name) => {
52
78
  if (value.startsWith("file:")) {
53
79
  let res = BlockJSONManagingPlugin.sourceToOutputMapping.get(`${name};${value}`);
54
80
  if (res !== undefined) {
55
81
  return res;
56
82
  }
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) {
83
+ const sourceDir = node_path_1.default.dirname(BlockJSONManagingPlugin.blockJSONAssetSources.get(name) ?? name);
84
+ const inputPath = node_path_1.default.normalize(node_path_1.default.join(sourceDir, value.substring(5)));
85
+ const output = BlockJSONManagingPlugin.blockJsonToEntrypointsMap.get(name)?.get(inputPath);
86
+ if (output !== undefined) {
62
87
  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]()];
88
+ const relativePath = node_path_1.default.relative(node_path_1.default.dirname(name), output.path);
89
+ const res = [`file:${prefix}${relativePath}`, output.hash];
65
90
  BlockJSONManagingPlugin.sourceToOutputMapping.set(`${name};${value}`, res);
66
91
  return res;
67
92
  }
68
93
  }
69
94
  return [value, ""];
70
95
  };
71
- if (this.processingModules) {
72
- for (const [key, value] of sourceFileMappers().singleFileInputToOutputNameAndHash.entries()) {
73
- BlockJSONManagingPlugin.moduleSourcesToOutputs.set(key, value);
96
+ for (const [name, source] of BlockJSONManagingPlugin.blockJSONAssetSources.entries()) {
97
+ const sourceDir = node_path_1.default.dirname(source);
98
+ const asset = compilation.assets[name]?.buffer().toString();
99
+ if (!asset) {
100
+ continue;
74
101
  }
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)}`;
101
- }
102
- else {
103
- json["version"] = BlockJSONManagingPlugin.hashThingForAsset(compositeHash);
104
- }
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;
102
+ blockDirConfigData[name] = true;
103
+ const json = JSON.parse(asset);
104
+ let compositeHash = "";
105
+ for (const mappableKey of mappableKeys) {
106
+ if (mappableKey in json) {
107
+ const unmappedValue = json[mappableKey];
108
+ if (Array.isArray(unmappedValue)) {
109
+ const remappedValue = unmappedValue.map(value => remapValue(value, name));
110
+ json[mappableKey] = remappedValue.map(([resource]) => resource);
111
+ compositeHash += "~" + remappedValue.map(([_, hash]) => hash).join("~");
135
112
  }
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
- }
113
+ else if (typeof unmappedValue === 'string') {
114
+ const remappedValue = remapValue(unmappedValue, name);
115
+ json[mappableKey] = remappedValue[0];
116
+ compositeHash += "~" + remappedValue[1];
145
117
  }
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"];
118
+ }
119
+ }
120
+ if (json["version"]) {
121
+ json["version"] = `${json["version"]}-${BlockJSONManagingPlugin.hashThingForAsset(compositeHash)}`;
122
+ }
123
+ else {
124
+ json["version"] = BlockJSONManagingPlugin.hashThingForAsset(compositeHash);
125
+ }
126
+ const outputDir = node_path_1.default.join(compiler.outputPath, node_path_1.default.dirname(name));
127
+ const pathsNeedRemapping = !this.standaloneBlocks && json["plaudit"] !== "simple";
128
+ const rawSetupFiles = json["plaudit"]?.["setup"]
129
+ ? (typeof json["plaudit"]["setup"] === 'string' ? [json["plaudit"]["setup"]] : json["plaudit"]["setup"])
130
+ : ["setup.php"];
131
+ const setupFiles = pathsNeedRemapping
132
+ ? rawSetupFiles
133
+ .map(p => node_path_1.default.normalize(node_path_1.default.join(sourceDir, BlockJSONManagingPlugin.stripFilePrefix(p))))
134
+ .filter(p => node_fs_1.default.existsSync(p))
135
+ .map(p => `file:./${BlockJSONManagingPlugin.findRelativeRouteBetween(outputDir, p)}`)
136
+ : rawSetupFiles
137
+ .filter(p => node_fs_1.default.existsSync(node_path_1.default.normalize(node_path_1.default.join(sourceDir, BlockJSONManagingPlugin.stripFilePrefix(p)))));
138
+ if (setupFiles.length === 0) {
139
+ if (json["plaudit"]?.["setup"] !== undefined) {
140
+ delete json["plaudit"]["setup"];
141
+ }
142
+ }
143
+ else {
144
+ if (typeof json["plaudit"] !== 'object') {
145
+ if (json["plaudit"] === "native") {
146
+ json["plaudit"] = { type: "native" };
161
147
  }
162
148
  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
- }
149
+ json["plaudit"] = {};
184
150
  }
185
- compilation.updateAsset(name, new webpack_1.sources.RawSource(JSON.stringify(json, undefined, " ")));
186
151
  }
152
+ json["plaudit"]["setup"] = setupFiles.length === 1 ? setupFiles[0] : setupFiles;
187
153
  }
154
+ if (json["acf"]) {
155
+ if (json["acf"]["renderTemplate"]) {
156
+ json["render_template"] = json["acf"]["renderTemplate"];
157
+ delete json["acf"]["renderTemplate"];
158
+ }
159
+ else if (json["acf"]["render_template"]) {
160
+ json["render_template"] = json["acf"]["render_template"];
161
+ delete json["acf"]["render_template"];
162
+ }
163
+ }
164
+ const blockName = json["name"]?.toString() || "non-existent/block-name";
165
+ let rawRenderTemplate = json["render"] ?? json["render_template"];
166
+ rawRenderTemplate = (rawRenderTemplate
167
+ ? (typeof rawRenderTemplate === 'string' ? [rawRenderTemplate] : rawRenderTemplate)
168
+ : [`${blockName.substring(blockName.indexOf('/') + 1)}.php`, "template.php", "template.twig"]);
169
+ const renderTemplate = pathsNeedRemapping
170
+ ? rawRenderTemplate
171
+ .map(p => node_path_1.default.normalize(node_path_1.default.join(sourceDir, BlockJSONManagingPlugin.stripFilePrefix(p))))
172
+ .filter(p => node_fs_1.default.existsSync(p))
173
+ .map(p => `file:./${BlockJSONManagingPlugin.findRelativeRouteBetween(outputDir, p)}`)
174
+ : rawRenderTemplate
175
+ .filter(p => node_fs_1.default.existsSync(node_path_1.default.normalize(node_path_1.default.join(sourceDir, BlockJSONManagingPlugin.stripFilePrefix(p)))));
176
+ if (renderTemplate.length === 0) {
177
+ delete json["render_template"];
178
+ delete json["render"];
179
+ }
180
+ else {
181
+ let validTemplateLocation, invalidTemplateLocation;
182
+ if (json["acf"] || (json["plaudit"] && (json["plaudit"] !== "native" && (typeof json["plaudit"] !== 'object' || json["plaudit"]?.["type"] !== "native")))) {
183
+ // ACF-like blocks need to have the template stored in render_template.
184
+ // Because we can statically detect ACF-like blocks, we can make the move here instead of at run-time.
185
+ validTemplateLocation = "render_template";
186
+ invalidTemplateLocation = "render";
187
+ }
188
+ else {
189
+ validTemplateLocation = "render";
190
+ invalidTemplateLocation = "render_template";
191
+ }
192
+ delete json[invalidTemplateLocation];
193
+ if (renderTemplate.length > 1) {
194
+ const error = new webpack_1.WebpackError("Encountered a block with multiple possible render files");
195
+ error.file = node_path_1.default.join(sourceDir, 'block.json');
196
+ compilation.warnings.push(error);
197
+ json[validTemplateLocation] = renderTemplate.find(p => p.endsWith(".php")) ?? renderTemplate[0];
198
+ }
199
+ else {
200
+ json[validTemplateLocation] = renderTemplate[0];
201
+ }
202
+ }
203
+ compilation[name in compilation.assets ? 'updateAsset' : 'emitAsset'](name, new webpack_1.sources.RawSource(JSON.stringify(json, undefined, " ")));
188
204
  }
189
- const sortedBlockDirConfigData = Object.fromEntries(Object.entries(blockDirConfigData).sort(([a], [b]) => a.localeCompare(b)));
205
+ const sortedBlockDirConfigData = Object.fromEntries(Object.entries(blockDirConfigData)
206
+ .sort(([a], [b]) => a.localeCompare(b)));
190
207
  compilation.emitAsset("blockdir.config", new webpack_1.sources.RawSource((0, php_serializer_1.default)(sortedBlockDirConfigData)));
191
208
  });
192
209
  });
193
210
  }
194
- findCommonAncestor(...paths) {
211
+ static resolveDestinationBySourceExtension(srcPath, entrypoint) {
212
+ const extensionMatch = /(m?)[jt]sx?$/i.exec(node_path_1.default.extname(srcPath));
213
+ if (extensionMatch === null) {
214
+ return entrypoint.getFiles().find(file => file.endsWith(".css"));
215
+ }
216
+ else if (extensionMatch[1]) {
217
+ return entrypoint.getFiles().find(file => file.endsWith(".mjs")) ?? entrypoint.getFiles().find(file => file.endsWith(".js"));
218
+ }
219
+ else {
220
+ return entrypoint.getFiles().find(file => file.endsWith(".js"));
221
+ }
222
+ }
223
+ static findCommonAncestor(...paths) {
195
224
  return paths.map(p => node_path_1.default.normalize(p).split(node_path_1.default.sep)).reduce((prior, current) => {
196
225
  for (let i = 0, limit = Math.min(prior.length, current.length); i < limit; i++) {
197
226
  if (prior[i] !== current[i]) {
@@ -201,8 +230,8 @@ class BlockJSONManagingPlugin {
201
230
  return current.length < prior.length ? current : prior;
202
231
  });
203
232
  }
204
- findRelativeRouteBetween(path1, path2) {
205
- const commonAncestor = this.findCommonAncestor(path1, path2);
233
+ static findRelativeRouteBetween(path1, path2) {
234
+ const commonAncestor = BlockJSONManagingPlugin.findCommonAncestor(path1, path2);
206
235
  const route = Array(path1.split(node_path_1.default.sep).length - commonAncestor.length).fill("..");
207
236
  route.push(node_path_1.default.relative(commonAncestor.join(node_path_1.default.sep), path2));
208
237
  return route.join(node_path_1.default.sep);
@@ -210,34 +239,8 @@ class BlockJSONManagingPlugin {
210
239
  static hashThingForAsset(thing) {
211
240
  return node_crypto_1.default.createHash('md5').update(thing).digest("hex").substring(0, 20).toLowerCase();
212
241
  }
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;
242
+ static stripFilePrefix(file) {
243
+ return file.startsWith("file:./") ? file.substring(7) : file;
241
244
  }
242
245
  }
243
246
  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({
@@ -17,22 +17,19 @@ const webpack_remove_empty_scripts_1 = __importDefault(require("webpack-remove-e
17
17
  function joinPossiblyAbsolutePaths(...paths) {
18
18
  return paths.reduce((res, p) => !res || node_path_1.default.isAbsolute(p) ? p : node_path_1.default.join(res, p), '') || '.';
19
19
  }
20
- function mapToRealEntrypoints(entrypoint, dir, mapper = (entrypoint) => entrypoint) {
20
+ function mapToRealEntrypoints(entrypoint, dir, mapper = (entrypoint) => entrypoint, lazyDependent) {
21
21
  return (Array.isArray(entrypoint) ? entrypoint : [entrypoint])
22
22
  .map(ep => joinPossiblyAbsolutePaths(dir, mapper(ep)))
23
23
  .filter(ep => node_fs_1.default.statSync(ep, { throwIfNoEntry: false })?.isFile())
24
24
  .map(ep => {
25
25
  const parsedEntrypoint = node_path_1.default.parse(ep);
26
- 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 }];
27
27
  });
28
28
  }
29
29
  let isInThemeCache = undefined;
30
30
  function isInTheme() {
31
31
  return isInThemeCache ?? (isInThemeCache = node_fs_1.default.existsSync(node_path_1.default.join(process.cwd(), "theme.json")));
32
32
  }
33
- function isTruthy(value) {
34
- return !!value;
35
- }
36
33
  function groupEntrypointsByAssetFile(entrypoints, entrypointNameExtractor) {
37
34
  const seenPaths = new Map();
38
35
  for (const entrypoint of entrypoints) {
@@ -129,64 +126,86 @@ function testForDuplicatedEntryPaths(config) {
129
126
  process.exit(1);
130
127
  }
131
128
  }
132
- function replaceDefaultURLProcessing(webpackConfig) {
129
+ function replaceDefaultURLProcessing(rules) {
133
130
  const cssLoader = require.resolve('css-loader');
134
- if (cssLoader && webpackConfig.module?.rules) {
135
- for (const rule of webpackConfig.module.rules) {
136
- if (isTruthy(rule) && typeof rule === 'object' && "use" in rule && Array.isArray(rule.use)) {
137
- for (const useElement of rule.use) {
138
- if (isTruthy(useElement) && typeof useElement === 'object' && useElement.loader === cssLoader && typeof useElement.options === 'object') {
139
- useElement.options['url'] = {
140
- filter(url) {
141
- return !url.startsWith("/") && !!url.match(/\.(woff|woff2|eot|ttf|otf|png|jpe?g|svg|webp|avif|gif)[^.]*?(\?.*)?/i);
142
- }
143
- };
144
- }
145
- }
131
+ if (cssLoader) {
132
+ return rules.map(rule => {
133
+ if (rule && typeof rule === 'object' && "use" in rule && Array.isArray(rule.use)) {
134
+ return {
135
+ ...rule,
136
+ use: rule.use.map(useElement => {
137
+ if (useElement && typeof useElement === 'object' && useElement.loader === cssLoader && typeof useElement.options === 'object') {
138
+ return {
139
+ ...useElement,
140
+ options: {
141
+ ...useElement.options,
142
+ url: {
143
+ filter(url) {
144
+ return !url.startsWith("/") && !!url.match(/\.(woff|woff2|eot|ttf|otf|png|jpe?g|svg|webp|avif|gif)[^.]*?(\?.*)?/i);
145
+ }
146
+ }
147
+ }
148
+ };
149
+ }
150
+ return useElement;
151
+ })
152
+ };
146
153
  }
147
- }
154
+ return rule;
155
+ });
148
156
  }
157
+ return rules;
149
158
  }
150
- function injectPostcssConfigOverrides(webpackConfig, variables, postcssFunctionsConfig, verbose) {
151
- if (webpackConfig.module?.rules) {
152
- const postcssConfig = (0, static_configs_1.postcssConfigBuilder)(verbose, name => variables(name) ?? (name === 'ENV' ? '' : undefined), postcssFunctionsConfig);
153
- return webpackConfig.module.rules.map(rule => {
154
- if (isTruthy(rule) && typeof rule === 'object') {
155
- if (Array.isArray(rule.use)) {
156
- for (const useItem of rule.use) {
157
- if (isTruthy(useItem) && typeof useItem === 'object' && typeof useItem.options === 'object') {
158
- if (useItem.options["sourceMap"] === false) {
159
- useItem.options["sourceMap"] = true;
159
+ function injectPostcssConfigOverrides(rules, variables, postcssFunctionsConfig, verbose) {
160
+ const postcssConfig = (0, static_configs_1.postcssConfigBuilder)(verbose, name => variables(name) ?? (name === 'ENV' ? '' : undefined), postcssFunctionsConfig);
161
+ return rules.map(rule => {
162
+ if (rule && typeof rule === 'object') {
163
+ if (Array.isArray(rule.use)) {
164
+ for (const useItem of rule.use) {
165
+ if (useItem && typeof useItem === 'object' && typeof useItem.options === 'object') {
166
+ if (useItem.options["sourceMap"] === false) {
167
+ useItem.options["sourceMap"] = true;
168
+ }
169
+ if (useItem.loader?.includes('postcss-loader')) {
170
+ if (useItem.options["postcssOptions"]) {
171
+ useItem.options["postcssOptions"] = { ...useItem.options["postcssOptions"], ...postcssConfig, sourceMap: true };
160
172
  }
161
- if (useItem.loader?.includes('postcss-loader')) {
162
- if (useItem.options["postcssOptions"]) {
163
- useItem.options["postcssOptions"] = { ...useItem.options["postcssOptions"], ...postcssConfig, sourceMap: true };
164
- }
165
- else {
166
- useItem.options["postcssOptions"] = { ...postcssConfig, sourceMap: true };
167
- }
173
+ else {
174
+ useItem.options["postcssOptions"] = { ...postcssConfig, sourceMap: true };
168
175
  }
169
176
  }
170
177
  }
171
- if (rule.test instanceof RegExp && (rule.test.test("index.ts") || rule.test.test("index.mts"))) { // Then this is the javascript and typescript rule
172
- rule.test = /\.m?[jt]sx?$/; // This hacks in support for mjs and mts files
173
- }
174
178
  }
175
- if (rule.type === "asset/inline" && rule.test instanceof RegExp && rule.issuer instanceof RegExp && rule.test.test("test.svg") && rule.issuer.test("test.pcss")) {
176
- rule.type = "asset/resource";
177
- rule.generator = { filename: "images/[name].[hash:8][ext]" };
179
+ if (rule.test instanceof RegExp && (rule.test.test("index.ts") || rule.test.test("index.mts"))) { // Then this is the javascript and typescript rule
180
+ rule.test = /\.m?[jt]sx?$/; // This hacks in support for mjs and mts files
178
181
  }
179
- if (typeof rule.generator === 'object') {
180
- const ruleGeneratorFilename = rule.generator["filename"];
181
- if (typeof ruleGeneratorFilename === 'string' && (ruleGeneratorFilename.startsWith("images/") || ruleGeneratorFilename.startsWith("fonts/"))) {
182
- rule.generator["filename"] = `../${ruleGeneratorFilename}`;
183
- }
182
+ }
183
+ if (rule.type === "asset/inline" && rule.test instanceof RegExp && rule.issuer instanceof RegExp && rule.test.test("test.svg") && rule.issuer.test("test.pcss")) {
184
+ rule.type = "asset/resource";
185
+ rule.generator = { filename: "images/[name].[hash:8][ext]" };
186
+ }
187
+ if (typeof rule.generator === 'object') {
188
+ const ruleGeneratorFilename = rule.generator["filename"];
189
+ if (typeof ruleGeneratorFilename === 'string' && (ruleGeneratorFilename.startsWith("images/") || ruleGeneratorFilename.startsWith("fonts/"))) {
190
+ rule.generator["filename"] = `../${ruleGeneratorFilename}`;
184
191
  }
185
192
  }
186
- return rule;
187
- });
188
- }
189
- return undefined;
193
+ }
194
+ return rule;
195
+ });
196
+ }
197
+ function injectSupportForInliningSVGsAsStrings(rules) {
198
+ const svgRLoader = require.resolve('@svgr/webpack');
199
+ return rules.map(rule => {
200
+ if (rule && typeof rule === 'object' && "use" in rule && Array.isArray(rule.use) && (rule.use.includes('@svgr/webpack') || rule.use.includes(svgRLoader))) {
201
+ const { test, issuer, ...reactSpecific } = rule;
202
+ return { test, issuer, oneOf: [
203
+ { type: 'asset/source', resourceQuery: /string/ }, // *.svg?string
204
+ reactSpecific
205
+ ] };
206
+ }
207
+ return rule;
208
+ });
190
209
  }
191
210
  function parseEntrypointsJSON(dir) {
192
211
  const entrypointsJSON = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(dir, 'entrypoints.json'), 'utf8'));
@@ -227,8 +246,13 @@ function processIndividualWebpackConfig(config, webpackConfig, sources) {
227
246
  }
228
247
  const { standaloneBlocks, variablesFilePath, verbose, externals } = config;
229
248
  let currentVariables = config.currentVariables;
230
- replaceDefaultURLProcessing(webpackConfig);
231
- const fixedRules = injectPostcssConfigOverrides(webpackConfig, name => currentVariables[name], config.postcss.functions ?? (() => ({})), verbose);
249
+ const fixedRules = [
250
+ replaceDefaultURLProcessing,
251
+ (rules) => {
252
+ return injectPostcssConfigOverrides(rules, name => currentVariables[name], config.postcss.functions ?? (() => ({})), verbose);
253
+ },
254
+ injectSupportForInliningSVGsAsStrings
255
+ ].reduce((r, a) => a(r), webpackConfig.module?.rules ?? []);
232
256
  return sources.map(([src, dest]) => {
233
257
  const srcRoots = (typeof dest !== 'string' && dest.withLegacyBlocksIn
234
258
  ? [...src.split(','), ...resolveLegacyBlockScriptsInFolder(dest.withLegacyBlocksIn)]
@@ -240,7 +264,8 @@ function processIndividualWebpackConfig(config, webpackConfig, sources) {
240
264
  return undefined;
241
265
  }
242
266
  const copyFiles = srcIsDirectory && src !== dest;
243
- const plugins = webpackConfig.plugins?.filter(isTruthy).filter(plugin => plugin.constructor.name !== 'RtlCssPlugin') ?? [];
267
+ const plugins = webpackConfig.plugins?.filter(v => !!v)
268
+ .filter(plugin => plugin.constructor.name !== 'RtlCssPlugin') ?? [];
244
269
  if (process.env["NO_TS_CHECKER"] !== "true") {
245
270
  const include = (Array.isArray(srcRoot) ? srcRoot : [srcRoot])
246
271
  .filter(sr => node_path_1.default.extname(sr).length === 0 || scriptOrStyleTest(sr, scriptExtension) === "script")
@@ -304,7 +329,7 @@ function processIndividualWebpackConfig(config, webpackConfig, sources) {
304
329
  requestToHandle(request) {
305
330
  const possibleExternal = externals[request];
306
331
  if (possibleExternal !== undefined && typeof possibleExternal !== 'string') {
307
- return possibleExternal['handle'];
332
+ return typeof possibleExternal.handle;
308
333
  }
309
334
  else {
310
335
  return request;
@@ -336,11 +361,13 @@ function processIndividualWebpackConfig(config, webpackConfig, sources) {
336
361
  const res = [];
337
362
  try {
338
363
  const blockJSON = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(dir, 'block.json'), 'utf8'));
364
+ const blockJSONChunkName = node_path_1.default.join(node_path_1.default.basename(dir), "block.json");
339
365
  for (const key of entrypointFields) {
340
366
  if (key in blockJSON) {
341
- res.push(...mapToRealEntrypoints(blockJSON[key], dir, ep => ep.startsWith("file:") ? ep.substring(5) : ep));
367
+ res.push(...mapToRealEntrypoints(blockJSON[key], dir, ep => ep.startsWith("file:") ? ep.substring(5) : ep, blockJSONChunkName));
342
368
  }
343
369
  }
370
+ res.push([blockJSONChunkName, { import: node_path_1.default.join(dir, 'block.json') }]);
344
371
  return res;
345
372
  }
346
373
  catch (e) {
@@ -373,6 +400,27 @@ function processIndividualWebpackConfig(config, webpackConfig, sources) {
373
400
  }
374
401
  }
375
402
  }
403
+ // This is used to allow for block.json dependencies to correctly account for name-deduplication
404
+ for (const [key, entry] of Object.entries(currentEntry)) {
405
+ if (typeof entry === 'object' && !Array.isArray(entry) && 'lazyDependent' in entry && typeof entry.lazyDependent === 'string') {
406
+ const target = currentEntry[entry.lazyDependent];
407
+ if (typeof target === 'object' && !Array.isArray(target)) {
408
+ let dependOn;
409
+ if (target.dependOn === undefined) {
410
+ dependOn = target.dependOn = [];
411
+ }
412
+ else if (typeof target.dependOn === 'string') {
413
+ dependOn = target.dependOn = [target.dependOn];
414
+ }
415
+ else {
416
+ dependOn = target.dependOn;
417
+ }
418
+ if (!dependOn.includes(key)) {
419
+ dependOn.push(key);
420
+ }
421
+ }
422
+ }
423
+ }
376
424
  return currentEntry;
377
425
  };
378
426
  }
@@ -427,7 +475,16 @@ function processIndividualWebpackConfig(config, webpackConfig, sources) {
427
475
  },
428
476
  module: {
429
477
  ...webpackConfig.module,
430
- rules: fixedRules
478
+ rules: [...fixedRules, {
479
+ test: /block\.json/i,
480
+ type: 'asset/resource',
481
+ generator: {
482
+ filename(pathData) {
483
+ // This mess of a name-generator accounts for WebPack's calculated name changing when block.json has dependents.
484
+ return node_path_1.default.join(node_path_1.default.basename(node_path_1.default.dirname(pathData.filename ?? pathData.runtime?.toString() ?? "[name]")), "[name][ext]");
485
+ }
486
+ }
487
+ }]
431
488
  },
432
489
  resolve: {
433
490
  ...webpackConfig.resolve,
@@ -437,7 +494,7 @@ function processIndividualWebpackConfig(config, webpackConfig, sources) {
437
494
  stats: config.stats,
438
495
  plugins: copyFiles
439
496
  ? plugins.map(plugin => plugin.constructor.name === 'CopyPlugin'
440
- ? new copy_webpack_plugin_1.default({ patterns: [{ from: standaloneBlocks ? '**/(block.json|*.(php|twig|svg))' : '**/(block.json|*.(asset\.php|svg))',
497
+ ? new copy_webpack_plugin_1.default({ patterns: [{ from: standaloneBlocks ? '**/(*.(php|twig|svg))' : '**/(*.(asset\.php|svg))',
441
498
  context: srcRoot, noErrorOnMissing: true }] })
442
499
  : plugin)
443
500
  : (srcIsDirectory
@@ -451,7 +508,7 @@ function processIndividualWebpackConfig(config, webpackConfig, sources) {
451
508
  return entry();
452
509
  }
453
510
  };
454
- }).filter((cfg) => cfg !== undefined);
511
+ }).filter(cfg => cfg !== undefined);
455
512
  }
456
513
  module.exports = function (config, webpackConfig = require("@wordpress/scripts/config/webpack.config")) {
457
514
  testForDuplicatedEntryPaths(config);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plaudit/webpack-extensions",
3
- "version": "2.28.0",
3
+ "version": "2.30.0",
4
4
  "scripts": {
5
5
  "prepublishOnly": "rm -rf build && mkdir build && tsc",
6
6
  "build": "tsc",