@plaudit/webpack-extensions 2.58.0 → 2.58.2

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.
@@ -321,13 +321,16 @@ class BlockJSONManagingPlugin {
321
321
  registerBlockJsonProcessor(compilation) {
322
322
  const blockDirConfigData = {};
323
323
  compilation.hooks.processAssets.tap({ name: `${this.constructor.name}_UnifiedLoaderGeneratorIntegration`, stage: webpack_1.Compilation.PROCESS_ASSETS_STAGE_DERIVED }, () => {
324
- UnifiedLoaderGenerator_1.UnifiedLoaderGenerator.semaphore.resolve(this.id, ['block.json', writer => {
324
+ UnifiedLoaderGenerator_1.UnifiedLoaderGenerator.semaphore.resolve(this.id, {
325
+ group: 'block.json',
326
+ action: writer => {
325
327
  writer
326
328
  .use("Plaudit\\Common\\ACF\\BlockManager")
327
329
  .action("init", writer => {
328
330
  writer.call("BlockManager::autoloadSubfolders", [new php_writer_1.Expr(`__DIR__.${php_writer_1.Expr.convertJsonToPHP("/" + this.blocksDest)}`)]);
329
331
  });
330
- }]);
332
+ }
333
+ });
331
334
  });
332
335
  compilation.hooks.afterProcessAssets.tap(`${this.constructor.name}_GenerateMetadata`, compilationAssets => {
333
336
  const currentBlockJSONAssets = BlockJSONManagingPlugin.blockJSONAssetSourceDirs.entries()
@@ -1,14 +1,15 @@
1
+ import { VerifiedPlauditWordpressWebpackConfig } from "../utils/common-config-helpers";
1
2
  import { type Compiler, type WebpackPluginInstance } from "webpack";
2
3
  export declare class ExtensionsConfigFileGeneratorPlugin implements WebpackPluginInstance {
4
+ private readonly config;
3
5
  private readonly extensionsPath;
4
- private readonly version;
5
6
  private readonly extensionsDest;
6
- private readonly handlePrefix;
7
7
  private static readonly semaphore;
8
- private static cache;
8
+ private static phaseTwoAttached;
9
9
  private readonly id;
10
- constructor(extensionsPath: string, version: 1 | 2, extensionsDest: string, handlePrefix: string);
10
+ constructor(config: VerifiedPlauditWordpressWebpackConfig, extensionsPath: string, extensionsDest: string);
11
11
  apply(compiler: Compiler): void;
12
+ private generateVersionTwoConfigFile;
12
13
  private makeVersionOneAfterProcessAssets;
13
14
  private stripExtensionsDest;
14
15
  }
@@ -6,25 +6,23 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.ExtensionsConfigFileGeneratorPlugin = void 0;
7
7
  const promises_1 = __importDefault(require("node:fs/promises"));
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
+ const UnifiedLoaderGenerator_1 = require("./UnifiedLoaderGenerator");
9
10
  const shared_1 = require("../shared");
10
11
  const php_serializer_1 = require("../utils/php-serializer");
12
+ const php_writer_1 = require("../utils/php-writer");
11
13
  const pseduo_semaphore_1 = require("../utils/pseduo-semaphore");
12
14
  const webpack_1 = require("webpack");
13
- const UnifiedLoaderGenerator_1 = require("./UnifiedLoaderGenerator");
14
- const php_writer_1 = require("../utils/php-writer");
15
15
  class ExtensionsConfigFileGeneratorPlugin {
16
+ config;
16
17
  extensionsPath;
17
- version;
18
18
  extensionsDest;
19
- handlePrefix;
20
19
  static semaphore = new pseduo_semaphore_1.PseudoSemaphore({ assets: [], setupFiles: [] }, "Extensions");
21
- static cache = undefined;
20
+ static phaseTwoAttached = false;
22
21
  id;
23
- constructor(extensionsPath, version, extensionsDest, handlePrefix) {
22
+ constructor(config, extensionsPath, extensionsDest) {
23
+ this.config = config;
24
24
  this.extensionsPath = extensionsPath;
25
- this.version = version;
26
25
  this.extensionsDest = extensionsDest;
27
- this.handlePrefix = handlePrefix;
28
26
  this.id = Math.random().toString();
29
27
  ExtensionsConfigFileGeneratorPlugin.semaphore.register(this.id);
30
28
  UnifiedLoaderGenerator_1.UnifiedLoaderGenerator.semaphore.register(this.id);
@@ -46,86 +44,60 @@ class ExtensionsConfigFileGeneratorPlugin {
46
44
  }
47
45
  await Promise.all(emissionPromises);
48
46
  });
49
- if (this.version === 2) {
50
- const tapName = { name: `${this.constructor.name}_ProcessBlockJSONFiles`, stage: webpack_1.Compilation.PROCESS_ASSETS_STAGE_REPORT };
47
+ if (this.config.extensionsVersion === 2) {
51
48
  compiler.hooks.compilation.tap(this.constructor.name, compilation => {
52
- ExtensionsConfigFileGeneratorPlugin.cache = undefined;
49
+ ExtensionsConfigFileGeneratorPlugin.phaseTwoAttached = false;
53
50
  ExtensionsConfigFileGeneratorPlugin.semaphore.reset(this.id);
54
51
  UnifiedLoaderGenerator_1.UnifiedLoaderGenerator.semaphore.reset(this.id);
55
- compilation.hooks.processAssets.tap({ name: `${this.constructor.name}_UnifiedLoaderGeneratorIntegration`, stage: webpack_1.Compilation.PROCESS_ASSETS_STAGE_DERIVED }, () => {
56
- UnifiedLoaderGenerator_1.UnifiedLoaderGenerator.semaphore.resolve(this.id, ['extensions-config-v2', writer => {
57
- let finalExtensionsDest = this.extensionsDest.endsWith("/") ? this.extensionsDest : this.extensionsDest + "/";
58
- if (!finalExtensionsDest.startsWith("/")) {
59
- finalExtensionsDest = "/" + finalExtensionsDest;
52
+ compilation.hooks.beforeChunkIds.tap(this.constructor.name, () => {
53
+ if (!ExtensionsConfigFileGeneratorPlugin.phaseTwoAttached) {
54
+ ExtensionsConfigFileGeneratorPlugin.phaseTwoAttached = true;
55
+ compilation.hooks.processAssets.tapPromise({ name: `${this.constructor.name}_GenerateConfigFile`, stage: webpack_1.Compilation.PROCESS_ASSETS_STAGE_REPORT }, async () => {
56
+ const combinedExtensionData = (await ExtensionsConfigFileGeneratorPlugin.semaphore.wait())
57
+ .reduce((main, { assets, setupFiles }) => {
58
+ main.assets.push(...assets);
59
+ main.setupFiles.push(...setupFiles);
60
+ return main;
61
+ }, { assets: [], setupFiles: [] });
62
+ if (combinedExtensionData.assets.length > 0 || combinedExtensionData.setupFiles.length > 0) {
63
+ UnifiedLoaderGenerator_1.UnifiedLoaderGenerator.semaphore.resolve(this.id, {
64
+ group: 'extensions-config-v2',
65
+ action: writer => {
66
+ let finalExtensionsDest = this.extensionsDest.endsWith("/") ? this.extensionsDest : this.extensionsDest + "/";
67
+ if (!finalExtensionsDest.startsWith("/")) {
68
+ finalExtensionsDest = "/" + finalExtensionsDest;
69
+ }
70
+ writer
71
+ .use("Plaudit\\Base\\API\\ThemeUtils")
72
+ .withScope(writer => writer
73
+ .assign("$filePathPrefix", new php_writer_1.Expr(`__DIR__.${php_writer_1.Expr.convertJsonToPHP(finalExtensionsDest)}`))
74
+ .call("plaudit_webpack_extensions__resolve_base_uri", [new php_writer_1.Expr("$filePathPrefix")], { assignTo: "$fileUriPrefix" })
75
+ .call("ThemeUtils::loadExtensionsV2", [
76
+ new php_writer_1.Expr(`__DIR__.${php_writer_1.Expr.convertJsonToPHP(finalExtensionsDest + "mapping.config.php")}`),
77
+ new php_writer_1.Expr(`$filePathPrefix`), new php_writer_1.Expr("$fileUriPrefix"),
78
+ `${this.config.plainEntrypointsHandlePrefix || node_path_1.default.basename(process.cwd())}_extension_`
79
+ ]));
80
+ }
81
+ });
82
+ this.generateVersionTwoConfigFile(compilation, combinedExtensionData);
60
83
  }
61
- writer
62
- .use("Plaudit\\Base\\API\\ThemeUtils")
63
- .assign("$filePathPrefix", new php_writer_1.Expr(`__DIR__.${php_writer_1.Expr.convertJsonToPHP(finalExtensionsDest)}`))
64
- .call("plaudit_webpack_extensions__resolve_base_uri", [new php_writer_1.Expr("$filePathPrefix")], { assignTo: "$fileUriPrefix" })
65
- .call("ThemeUtils::loadExtensionsV2", [
66
- new php_writer_1.Expr(`__DIR__.${php_writer_1.Expr.convertJsonToPHP(finalExtensionsDest + "mapping.config.php")}`),
67
- new php_writer_1.Expr(`$filePathPrefix`), new php_writer_1.Expr("$fileUriPrefix"),
68
- `${this.handlePrefix ? this.handlePrefix : node_path_1.default.basename(process.cwd())}_extension_`
69
- ])
70
- .call("unset", [new php_writer_1.Expr("$filePathPrefix"), new php_writer_1.Expr("$fileUriPrefix")]);
71
- }]);
84
+ else {
85
+ if (!this.config.useUnifiedLoader) {
86
+ this.generateVersionTwoConfigFile(compilation, combinedExtensionData);
87
+ }
88
+ UnifiedLoaderGenerator_1.UnifiedLoaderGenerator.semaphore.resolve(this.id, undefined);
89
+ }
90
+ });
91
+ }
72
92
  });
73
- compilation.hooks.processAssets.tapPromise(tapName, async (assets) => {
93
+ compilation.hooks.processAssets.tapPromise({ name: this.constructor.name, stage: webpack_1.Compilation.PROCESS_ASSETS_STAGE_ANALYSE, additionalAssets: true }, async (assets) => {
94
+ if (!("assets.json" in assets)) {
95
+ return;
96
+ }
74
97
  try {
75
- //TODO: Add handling of extracted CSS from JS
76
98
  //TODO: It should be possible to use EntryPoints to determine the "original" file
77
- //TODO: Use the version hash of the original file, but not its dependencies for the additional file's "rest" parameter (see BlockJSONManagingPlugin.ts for how to do so)
78
99
  //TODO: There is no reason to not use basically the exact same logic to implement support for this in plain file contexts
79
100
  //TODO: BlockJSONManagingPlugin should be rewritten from scratch to use this method instead of its 15 layers of tracking + syncing
80
- if (ExtensionsConfigFileGeneratorPlugin.cache === undefined) {
81
- ExtensionsConfigFileGeneratorPlugin.cache = { assets: [], setupFiles: [] };
82
- compilation.hooks.afterProcessAssets.tap(`${this.constructor.name}_GenerateConfigFile`, () => {
83
- const regex = /^(.+?)-((?:editor-|view-|)(?:style|script|script-module))\.(?:css|m?js)$/i;
84
- const blockExtensionsConfig = { metadata: { version: this.version }, scriptHandles: {}, styleHandles: {}, blocks: {}, setupFiles: {} };
85
- for (const assetDataSource of ExtensionsConfigFileGeneratorPlugin.cache?.assets ?? []) {
86
- const normalizedAssetData = Object.entries(assetDataSource)
87
- .map(entry => {
88
- const assetPath = this.extensionsDest && entry[0].startsWith(this.extensionsDest + "/")
89
- ? entry[0].substring(this.extensionsDest.length + 1) : entry[0];
90
- return [assetPath, entry[1]];
91
- });
92
- for (const [assetPath, assetData] of normalizedAssetData) {
93
- const match = regex.exec(assetPath);
94
- if (!match) {
95
- continue;
96
- }
97
- const blockSlug = match[1], assetType = match[2];
98
- if (blockSlug && assetType) {
99
- const key = assetType.replace(/-[sm]/gi, chars => chars.substring(1).toUpperCase());
100
- const handle = `plaudit_block-extension_${blockSlug}-${assetType}`;
101
- const isCss = assetType.endsWith("tyle");
102
- blockExtensionsConfig[isCss ? 'styleHandles' : 'scriptHandles'][handle] = {
103
- src: isCss ? assetPath.replace(/\.js$/, ".css") : assetPath,
104
- rest: isCss || key.startsWith("editor")
105
- ? [assetData.dependencies, assetData.version]
106
- : [assetData.dependencies, assetData.version, { strategy: 'defer' }]
107
- };
108
- (blockExtensionsConfig.blocks[blockSlug] ?? (blockExtensionsConfig.blocks[blockSlug] = {}))[key] = handle;
109
- }
110
- }
111
- for (const [blockSlug, asset] of ExtensionsConfigFileGeneratorPlugin.cache?.setupFiles ?? []) {
112
- blockExtensionsConfig.setupFiles[blockSlug] = asset;
113
- }
114
- }
115
- blockExtensionsConfig.scriptHandles = Object.fromEntries(Object.entries(blockExtensionsConfig.scriptHandles)
116
- .toSorted(([a], [b]) => a.localeCompare(b)));
117
- blockExtensionsConfig.styleHandles = Object.fromEntries(Object.entries(blockExtensionsConfig.styleHandles)
118
- .toSorted(([a], [b]) => a.localeCompare(b)));
119
- blockExtensionsConfig.setupFiles = Object.fromEntries(Object.entries(blockExtensionsConfig.setupFiles)
120
- .toSorted(([a], [b]) => a.localeCompare(b)));
121
- blockExtensionsConfig.blocks = Object.fromEntries(Object.entries(blockExtensionsConfig.blocks)
122
- .map(block => {
123
- return [block[0], Object.fromEntries(Object.entries(block[1]).toSorted(([a], [b]) => a.localeCompare(b)))];
124
- })
125
- .toSorted(([a], [b]) => a.localeCompare(b)));
126
- compilation.emitAsset(node_path_1.default.join(this.extensionsDest, "mapping.config.php"), new webpack_1.sources.RawSource((0, shared_1.makeEmittableConfigPHP)(blockExtensionsConfig)));
127
- });
128
- }
129
101
  const myCacheData = { assets: [], setupFiles: [] };
130
102
  for (const asset of Object.keys(compilation.assets).map(asset => this.stripExtensionsDest(asset))) {
131
103
  const blockSlug = /^(.+?)-setup.php$/i.exec(asset)?.[1];
@@ -148,12 +120,6 @@ class ExtensionsConfigFileGeneratorPlugin {
148
120
  myCacheData.assets.push(assetDataSource);
149
121
  compilation.deleteAsset("assets.json");
150
122
  ExtensionsConfigFileGeneratorPlugin.semaphore.resolve(this.id, myCacheData);
151
- ExtensionsConfigFileGeneratorPlugin.cache = (await ExtensionsConfigFileGeneratorPlugin.semaphore.wait())
152
- .reduce((main, { assets, setupFiles }) => {
153
- main.assets.push(...assets);
154
- main.setupFiles.push(...setupFiles);
155
- return main;
156
- }, { assets: [], setupFiles: [] });
157
123
  }
158
124
  catch (e) {
159
125
  ExtensionsConfigFileGeneratorPlugin.semaphore.reject(this.id);
@@ -170,16 +136,62 @@ class ExtensionsConfigFileGeneratorPlugin {
170
136
  compiler.hooks.thisCompilation.tap(this.constructor.name, compilation => {
171
137
  UnifiedLoaderGenerator_1.UnifiedLoaderGenerator.semaphore.reset(this.id);
172
138
  compilation.hooks.processAssets.tap({ name: `${this.constructor.name}_UnifiedLoaderGeneratorIntegration`, stage: webpack_1.Compilation.PROCESS_ASSETS_STAGE_DERIVED }, () => {
173
- UnifiedLoaderGenerator_1.UnifiedLoaderGenerator.semaphore.resolve(this.id, ['extensions-config-v1', writer => {
139
+ UnifiedLoaderGenerator_1.UnifiedLoaderGenerator.semaphore.resolve(this.id, { group: 'extensions-config-v1', action: writer => {
174
140
  writer
175
141
  .use("Plaudit\\Base\\API\\ThemeUtils")
176
142
  .call("ThemeUtils::installExtensionSupport", [new php_writer_1.Expr(`__DIR__.${php_writer_1.Expr.convertJsonToPHP(this.extensionsDest)}`)]);
177
- }]);
143
+ } });
178
144
  });
179
145
  compilation.hooks.afterProcessAssets.tap(`${this.constructor.name}_AfterProcessAssets`, this.makeVersionOneAfterProcessAssets(compilation));
180
146
  });
181
147
  }
182
148
  }
149
+ generateVersionTwoConfigFile(compilation, combinedExtensionData) {
150
+ const regex = /^(.+?)-((?:editor-|view-|)(?:style|script|script-module))\.(?:css|m?js)$/i;
151
+ const blockExtensionsConfig = { metadata: { version: this.config.extensionsVersion }, scriptHandles: {}, styleHandles: {}, blocks: {}, setupFiles: {} };
152
+ for (const assetDataSource of combinedExtensionData.assets) {
153
+ const normalizedAssetData = Object.entries(assetDataSource)
154
+ .map(entry => {
155
+ const assetPath = this.extensionsDest && entry[0].startsWith(this.extensionsDest + "/")
156
+ ? entry[0].substring(this.extensionsDest.length + 1) : entry[0];
157
+ return [assetPath, entry[1]];
158
+ });
159
+ for (const [assetPath, assetData] of normalizedAssetData) {
160
+ const match = regex.exec(assetPath);
161
+ if (!match) {
162
+ continue;
163
+ }
164
+ const blockSlug = match[1], assetType = match[2];
165
+ if (blockSlug && assetType) {
166
+ const key = assetType.replace(/-[sm]/gi, chars => chars.substring(1).toUpperCase());
167
+ const handle = `plaudit_block-extension_${blockSlug}-${assetType}`;
168
+ const isCss = assetType.endsWith("tyle");
169
+ blockExtensionsConfig[isCss ? 'styleHandles' : 'scriptHandles'][handle] = {
170
+ src: isCss ? assetPath.replace(/\.js$/, ".css") : assetPath,
171
+ rest: isCss || key.startsWith("editor")
172
+ ? [assetData.dependencies, assetData.version]
173
+ : [assetData.dependencies, assetData.version, { strategy: 'defer' }]
174
+ };
175
+ (blockExtensionsConfig.blocks[blockSlug] ?? (blockExtensionsConfig.blocks[blockSlug] = {}))[key] = handle;
176
+ }
177
+ }
178
+ for (const [blockSlug, asset] of combinedExtensionData.setupFiles) {
179
+ blockExtensionsConfig.setupFiles[blockSlug] = asset;
180
+ }
181
+ }
182
+ blockExtensionsConfig.scriptHandles = Object.fromEntries(Object.entries(blockExtensionsConfig.scriptHandles)
183
+ .toSorted(([a], [b]) => a.localeCompare(b)));
184
+ blockExtensionsConfig.styleHandles = Object.fromEntries(Object.entries(blockExtensionsConfig.styleHandles)
185
+ .toSorted(([a], [b]) => a.localeCompare(b)));
186
+ blockExtensionsConfig.setupFiles = Object.fromEntries(Object.entries(blockExtensionsConfig.setupFiles)
187
+ .toSorted(([a], [b]) => a.localeCompare(b)));
188
+ blockExtensionsConfig.blocks = Object.fromEntries(Object.entries(blockExtensionsConfig.blocks)
189
+ .map(block => {
190
+ return [block[0], Object.fromEntries(Object.entries(block[1]).toSorted(([a], [b]) => a.localeCompare(b)))];
191
+ })
192
+ .toSorted(([a], [b]) => a.localeCompare(b)));
193
+ compilation.emitAsset(node_path_1.default.join(this.extensionsDest, "mapping.config.php"), new webpack_1.sources.RawSource((0, shared_1.makeEmittableConfigPHP)(blockExtensionsConfig)));
194
+ }
183
195
  makeVersionOneAfterProcessAssets(compilation) {
184
196
  return compilationAssets => {
185
197
  const regex = /^(.+?)-((?:editor-|view-|)(?:style|script|script-module))\.(?:css|m?js)$/i;
@@ -8,7 +8,7 @@ export declare class PlainEntrypointsConfigFileGeneratorPlugin implements Webpac
8
8
  private readonly handlePrefix;
9
9
  private readonly useUnifiedLoader;
10
10
  private static readonly semaphore;
11
- private static cache?;
11
+ private static phaseTwoAttached;
12
12
  private readonly id;
13
13
  constructor(buildRoot: string, outputDir: string, usageLocations: UsageLocations, handlePrefix: string, useUnifiedLoader: boolean);
14
14
  apply(compiler: Compiler): void;
@@ -17,7 +17,7 @@ class PlainEntrypointsConfigFileGeneratorPlugin {
17
17
  handlePrefix;
18
18
  useUnifiedLoader;
19
19
  static semaphore = new pseduo_semaphore_1.PseudoSemaphore([], "Plain");
20
- static cache = undefined;
20
+ static phaseTwoAttached = false;
21
21
  id;
22
22
  constructor(buildRoot, outputDir, usageLocations, handlePrefix, useUnifiedLoader) {
23
23
  this.buildRoot = buildRoot;
@@ -30,24 +30,23 @@ class PlainEntrypointsConfigFileGeneratorPlugin {
30
30
  UnifiedLoaderGenerator_1.UnifiedLoaderGenerator.semaphore.register(this.id);
31
31
  }
32
32
  apply(compiler) {
33
- const tapName = { name: `${this.constructor.name}_ProcessPlainEntrypointFiles`, stage: webpack_1.Compilation.PROCESS_ASSETS_STAGE_REPORT };
34
33
  compiler.hooks.compilation.tap(this.constructor.name, compilation => {
35
- PlainEntrypointsConfigFileGeneratorPlugin.cache = undefined;
34
+ PlainEntrypointsConfigFileGeneratorPlugin.phaseTwoAttached = false;
36
35
  PlainEntrypointsConfigFileGeneratorPlugin.semaphore.reset(this.id);
37
36
  UnifiedLoaderGenerator_1.UnifiedLoaderGenerator.semaphore.reset(this.id);
38
- compilation.hooks.processAssets.tap({ name: `${this.constructor.name}_UnifiedLoaderGeneratorIntegration`, stage: webpack_1.Compilation.PROCESS_ASSETS_STAGE_DERIVED }, () => {
39
- UnifiedLoaderGenerator_1.UnifiedLoaderGenerator.semaphore.resolve(this.id, ['plain-entrypoints-v2', writer => {
40
- writer.append("require_once __DIR__.'/plain-entrypoints-loader.php';");
41
- }]);
37
+ compilation.hooks.beforeChunkIds.tap(this.constructor.name, () => {
38
+ if (!PlainEntrypointsConfigFileGeneratorPlugin.phaseTwoAttached) {
39
+ PlainEntrypointsConfigFileGeneratorPlugin.phaseTwoAttached = true;
40
+ compilation.hooks.processAssets.tapPromise({ name: `${this.constructor.name}_CompileLoader`, stage: webpack_1.Compilation.PROCESS_ASSETS_STAGE_REPORT }, async () => {
41
+ this.afterProcessAssets(compilation, (await PlainEntrypointsConfigFileGeneratorPlugin.semaphore.wait()).flat());
42
+ });
43
+ }
42
44
  });
43
- compilation.hooks.processAssets.tapPromise(tapName, async (assets) => {
45
+ compilation.hooks.processAssets.tapPromise({ name: this.constructor.name, stage: webpack_1.Compilation.PROCESS_ASSETS_STAGE_ANALYSE, additionalAssets: true }, async (assets) => {
46
+ if (!("assets.json" in assets)) {
47
+ return;
48
+ }
44
49
  try {
45
- if (PlainEntrypointsConfigFileGeneratorPlugin.cache === undefined) {
46
- PlainEntrypointsConfigFileGeneratorPlugin.cache = { assets: [] };
47
- compilation.hooks.afterProcessAssets.tap(`${this.constructor.name}_CompileLoader`, () => {
48
- this.afterProcessAssets(compilation);
49
- });
50
- }
51
50
  const rawAssetDataSource = assets["assets.json"]?.source();
52
51
  if (typeof rawAssetDataSource !== 'string') {
53
52
  PlainEntrypointsConfigFileGeneratorPlugin.semaphore.reject(this.id);
@@ -104,7 +103,9 @@ class PlainEntrypointsConfigFileGeneratorPlugin {
104
103
  }
105
104
  compilation.deleteAsset("assets.json");
106
105
  PlainEntrypointsConfigFileGeneratorPlugin.semaphore.resolve(this.id, myAssetHandles);
107
- PlainEntrypointsConfigFileGeneratorPlugin.cache.assets = (await PlainEntrypointsConfigFileGeneratorPlugin.semaphore.wait()).flat();
106
+ UnifiedLoaderGenerator_1.UnifiedLoaderGenerator.semaphore.resolve(this.id, myAssetHandles.length
107
+ ? { group: 'plain-entrypoints-v2', action: writer => writer.append("require_once __DIR__.'/plain-entrypoints-loader.php';") }
108
+ : undefined);
108
109
  }
109
110
  catch (e) {
110
111
  PlainEntrypointsConfigFileGeneratorPlugin.semaphore.reject(this.id);
@@ -114,9 +115,8 @@ class PlainEntrypointsConfigFileGeneratorPlugin {
114
115
  });
115
116
  });
116
117
  }
117
- afterProcessAssets(compilation) {
118
+ afterProcessAssets(compilation, assets) {
118
119
  const emitDir = node_path_1.default.join(this.buildRoot, this.outputDir);
119
- //TODO: Add support for editorStyles via the 'block_editor_settings_all' filter
120
120
  const handleLists = {
121
121
  register: [],
122
122
  clientView: [],
@@ -126,7 +126,7 @@ class PlainEntrypointsConfigFileGeneratorPlugin {
126
126
  customizer: [],
127
127
  analytics: []
128
128
  };
129
- const allNamedHandles = (PlainEntrypointsConfigFileGeneratorPlugin.cache?.assets ?? [])
129
+ const allNamedHandles = assets
130
130
  .flatMap(({ handles }) => handles)
131
131
  .filter((handle) => !!handle.handleName)
132
132
  .sort((a, b) => a.src.localeCompare(b.src));
@@ -137,7 +137,7 @@ class PlainEntrypointsConfigFileGeneratorPlugin {
137
137
  .filter(handle => !handle.isScript)
138
138
  .map(handle => [handle.handleName, handle.src]));
139
139
  const plainEntrypointsConfig = { scriptHandles: {}, styleHandles: {} };
140
- for (const { handles, handlePrefix } of PlainEntrypointsConfigFileGeneratorPlugin.cache?.assets ?? []) {
140
+ for (const { handles, handlePrefix } of assets) {
141
141
  for (const { src, rest, locations, isScript, handleName } of handles) {
142
142
  let finalHandleName;
143
143
  if (handleName) {
@@ -29,12 +29,13 @@ class SpecialAssetHandlingPlugin {
29
29
  SpecialAssetHandlingPlugin.hasAttachedAssetCollatorForCurrentBatch = false;
30
30
  SpecialAssetHandlingPlugin.semaphore.reset(this.id);
31
31
  UnifiedLoaderGenerator_1.UnifiedLoaderGenerator.semaphore.reset(this.id);
32
- compilation.hooks.processAssets.tap({ name: `${this.constructor.name}_UnifiedLoaderGeneratorIntegration`, stage: webpack_1.Compilation.PROCESS_ASSETS_STAGE_DERIVED }, () => {
33
- UnifiedLoaderGenerator_1.UnifiedLoaderGenerator.semaphore.resolve(this.id, ['special-assets', writer => {
34
- writer.append("require_once __DIR__.'/special-assets.php';");
35
- }]);
32
+ compilation.hooks.beforeChunkIds.tap(this.constructor.name, () => {
33
+ if (!SpecialAssetHandlingPlugin.hasAttachedAssetCollatorForCurrentBatch) {
34
+ SpecialAssetHandlingPlugin.hasAttachedAssetCollatorForCurrentBatch = true;
35
+ compilation.hooks.processAssets.tapPromise({ name: `${this.constructor.name}_CompileLoader`, stage: webpack_1.Compilation.PROCESS_ASSETS_STAGE_REPORT }, () => this.collateAssets(compilation));
36
+ }
36
37
  });
37
- compilation.hooks.processAssets.tapPromise(this.constructor.name, async (assets) => {
38
+ compilation.hooks.processAssets.tapPromise({ name: this.constructor.name, stage: webpack_1.Compilation.PROCESS_ASSETS_STAGE_ANALYSE }, async (assets) => {
38
39
  try {
39
40
  const specialAssetData = {};
40
41
  for (const pathname of Object.keys(assets)) {
@@ -63,12 +64,9 @@ class SpecialAssetHandlingPlugin {
63
64
  }
64
65
  }
65
66
  SpecialAssetHandlingPlugin.semaphore.resolve(this.id, specialAssetData);
66
- if (!SpecialAssetHandlingPlugin.hasAttachedAssetCollatorForCurrentBatch) {
67
- SpecialAssetHandlingPlugin.hasAttachedAssetCollatorForCurrentBatch = true;
68
- const collatedSpecialAssetData = (await SpecialAssetHandlingPlugin.semaphore.wait())
69
- .reduce((collector, current) => ({ ...collector, ...current }), {});
70
- this.collateAssets(compilation, collatedSpecialAssetData);
71
- }
67
+ UnifiedLoaderGenerator_1.UnifiedLoaderGenerator.semaphore.resolve(this.id, Object.keys(specialAssetData).length > 0
68
+ ? { group: 'special-assets', action: writer => writer.append("require_once __DIR__.'/special-assets.php';") }
69
+ : undefined);
72
70
  }
73
71
  catch (e) {
74
72
  SpecialAssetHandlingPlugin.semaphore.reject(this.id);
@@ -77,14 +75,18 @@ class SpecialAssetHandlingPlugin {
77
75
  });
78
76
  });
79
77
  }
80
- collateAssets(compilation, collatedSpecialAssetData) {
78
+ async collateAssets(compilation) {
79
+ const collatedSpecialAssetData = (await SpecialAssetHandlingPlugin.semaphore.wait())
80
+ .reduce((collector, current) => ({ ...collector, ...current }), {});
81
81
  const preloadedAssets = Object.entries(collatedSpecialAssetData)
82
82
  .filter(([_, { preload }]) => preload)
83
83
  .sort(([a], [b]) => a.localeCompare(b));
84
84
  const outputFile = this.outputDir ? "special-assets.php" : node_path_1.default.join(node_path_1.default.dirname(node_path_1.default.dirname(SpecialAssetHandlingPlugin.validPathname)), "special-assets.php");
85
85
  const writer = new php_writer_1.PHPWriter();
86
86
  if (!preloadedAssets.length) {
87
- writer.emitAsset(compilation, outputFile);
87
+ if (!this.useUnifiedLoader) {
88
+ writer.emitAsset(compilation, outputFile);
89
+ }
88
90
  return;
89
91
  }
90
92
  if (!this.useUnifiedLoader) {
@@ -1,10 +1,16 @@
1
1
  import { PseudoSemaphore } from "../utils/pseduo-semaphore";
2
2
  import { PHPWriter } from "../utils/php-writer";
3
3
  import { Compiler, WebpackPluginInstance } from "webpack";
4
+ type LoaderInfo = {
5
+ group: string;
6
+ action(writer: PHPWriter): void;
7
+ priority?: number;
8
+ };
4
9
  export declare class UnifiedLoaderGenerator implements WebpackPluginInstance {
5
- static readonly semaphore: PseudoSemaphore<[string, (writer: PHPWriter) => void] | undefined>;
10
+ static readonly semaphore: PseudoSemaphore<LoaderInfo | undefined>;
6
11
  private static attached;
7
12
  private readonly id;
8
13
  constructor();
9
14
  apply(compiler: Compiler): void;
10
15
  }
16
+ export {};
@@ -23,8 +23,15 @@ class UnifiedLoaderGenerator {
23
23
  UnifiedLoaderGenerator.semaphore.resolve(this.id, undefined);
24
24
  if (!UnifiedLoaderGenerator.attached) {
25
25
  UnifiedLoaderGenerator.attached = true;
26
- const writerCallbacks = Object.values(Object.fromEntries((await UnifiedLoaderGenerator.semaphore.wait())
27
- .filter(a => a !== undefined))); // A simple deduplication trick
26
+ const alreadyAllocatedGroups = new Set();
27
+ const writerCallbacks = (await UnifiedLoaderGenerator.semaphore.wait())
28
+ .filter((a) => a !== undefined)
29
+ .sort((a, b) => {
30
+ const ap = a.priority ?? 0, bp = b.priority ?? 0;
31
+ return ap === bp ? a.group.localeCompare(b.group) : bp - ap;
32
+ })
33
+ .filter(a => !alreadyAllocatedGroups.has(a.group) && alreadyAllocatedGroups.add(a.group))
34
+ .map(a => a.action);
28
35
  if (writerCallbacks.length > 0) {
29
36
  compilation.hooks.afterProcessAssets.tap(this.constructor.name, () => {
30
37
  const writer = new php_writer_1.PHPWriter();
@@ -115,6 +115,7 @@ function resolveEntryFromDirectory(commonConfig, srcRoot, dest) {
115
115
  if (!dirent.isDirectory()) {
116
116
  continue;
117
117
  }
118
+ //TODO: Instead of doing all of this in a precalc phase, add block.json "entrypoints" with their library type set to "block-json"
118
119
  const dir = joinPossiblyAbsolutePaths(srcRoot, dirent.name);
119
120
  loadingEntrypoints.push(new Promise(async (resolve) => {
120
121
  const rawEntrypoints = [];
@@ -24,11 +24,13 @@ export declare class PHPWriter {
24
24
  private printingInPHP;
25
25
  private fileNamespace;
26
26
  private useList;
27
+ private scopeStack;
27
28
  constructor(inlineFirstLine?: boolean);
28
29
  indent(): this;
29
30
  outdent(): this;
30
31
  append(...lines: (string | Expr)[]): this;
31
32
  assign(variable: string | string[], expression: unknown | Expr, opts?: {
33
+ chain?: boolean;
32
34
  return?: boolean;
33
35
  }): this;
34
36
  return(line: string | Expr): this;
@@ -39,7 +41,7 @@ export declare class PHPWriter {
39
41
  linebreak(): this;
40
42
  call(func: string, args: unknown[], opts?: {
41
43
  chain?: boolean;
42
- assignTo?: string;
44
+ assignTo?: string | string[];
43
45
  return?: boolean;
44
46
  }): this;
45
47
  action(name: string | Expr, contents: (writer: this) => void, args?: ActionOrFilterArgs): this;
@@ -54,6 +56,24 @@ export declare class PHPWriter {
54
56
  openPHP(): this;
55
57
  namespace(namespace: string): this;
56
58
  use(...uses: string[]): this;
59
+ /**
60
+ * Starts a scope that, when paired with {@link #closeScope()}, allows for automating unset calls.
61
+ * All scopes started MUST be paired with either an {@link #closeScope()} or {@link #popScope()} call.
62
+ */
63
+ openScope(): this;
64
+ /**
65
+ * Pops the top scope from the stack AND unsets all variables that were assigned within it
66
+ */
67
+ closeScope(): this;
68
+ /**
69
+ * Pops the top scope from the stack WITHOUT calling unset for the variables that were assigned within it.
70
+ * This is used to allow function bodies to be safely created without disrupting their containing scope.
71
+ */
72
+ popScope(): this;
73
+ withScope(code: (writer: this) => void, opts?: {
74
+ actionDescription?: string;
75
+ usePopScopeInstead?: boolean;
76
+ }): this;
57
77
  toString(): string;
58
78
  emitAsset(compilation: Compilation, file: string, assetInfo?: AssetInfo): void;
59
79
  }
@@ -27,6 +27,7 @@ class PHPWriter {
27
27
  printingInPHP = true;
28
28
  fileNamespace = "";
29
29
  useList = [];
30
+ scopeStack = [];
30
31
  constructor(inlineFirstLine = false) {
31
32
  this.inlineFirstLine = inlineFirstLine;
32
33
  }
@@ -45,12 +46,24 @@ class PHPWriter {
45
46
  return this;
46
47
  }
47
48
  assign(variable, expression, opts = {}) {
48
- const lineComponents = (typeof variable === 'string' ? [variable] : variable).map(v => `${v} =`);
49
+ let lineComponents;
50
+ if (typeof variable === 'string') {
51
+ if (this.scopeStack.length > 0) {
52
+ this.scopeStack[this.scopeStack.length - 1].push(variable);
53
+ }
54
+ lineComponents = [`${variable} =`];
55
+ }
56
+ else {
57
+ if (this.scopeStack.length > 0) {
58
+ this.scopeStack[this.scopeStack.length - 1].push(...variable);
59
+ }
60
+ lineComponents = variable.map(v => `${v} =`);
61
+ }
49
62
  if (opts.return) {
50
63
  lineComponents.splice(0, 0, "return");
51
64
  }
52
65
  lineComponents.push(Expr.convertJsonToPHP(expression));
53
- return this.append(lineComponents.join(" ") + ';');
66
+ return this.append(!opts.chain ? lineComponents.join(" ") + ';' : lineComponents.join(" "));
54
67
  }
55
68
  return(line) {
56
69
  return this.append(`return ${line};`);
@@ -72,15 +85,16 @@ class PHPWriter {
72
85
  return this;
73
86
  }
74
87
  call(func, args, opts = {}) {
88
+ const functionCall = `${func}(${args.map(Expr.convertJsonToPHP).join(", ")})`;
89
+ if (opts.assignTo) {
90
+ return this.assign(opts.assignTo, new Expr(functionCall), opts);
91
+ }
75
92
  const lineComponents = [];
76
93
  if (opts.return) {
77
94
  lineComponents.push("return");
78
95
  }
79
- if (opts.assignTo) {
80
- lineComponents.push(`${opts.assignTo} =`);
81
- }
82
- lineComponents.push(`${func}(${args.map(Expr.convertJsonToPHP).join(", ")})${opts.chain === true ? "" : ";"}`);
83
- return this.append(lineComponents.join(" "));
96
+ lineComponents.push(functionCall);
97
+ return this.append(!opts.chain ? lineComponents.join(" ") + ';' : lineComponents.join(" "));
84
98
  }
85
99
  action(name, contents, args = {}) {
86
100
  return this.actionOrFilter('action', name, contents, args);
@@ -98,7 +112,9 @@ class PHPWriter {
98
112
  declarationComponents.push("{");
99
113
  this.openPHP().append(`add_${type}(${declarationComponents.join(" ")}`);
100
114
  this.indent();
101
- contents(this);
115
+ // We start a new scope here just in case this was called within an existing scope
116
+ // We pop the scope instead of closing it because we don't actually want to unset any variables that were created within the function
117
+ this.withScope(contents, { actionDescription: `closing the function call for the ${name} ${type}.`, usePopScopeInstead: true });
102
118
  this.openPHP().outdent();
103
119
  const actionOrFilterArgs = ["}"];
104
120
  const accepted_args = Math.max(functionArgParameters.length, 1); // This avoids us unnecessarily setting the accepted_args value to 0 for actions
@@ -150,7 +166,9 @@ class PHPWriter {
150
166
  declarationComponents.push("{");
151
167
  this.append(declarationComponents.join(' '));
152
168
  this.indent();
153
- body(this);
169
+ // We start a new scope here just in case this was called within an existing scope
170
+ // We pop the scope instead of closing it because we don't actually want to unset any variables that were created within the function
171
+ this.withScope(body, { actionDescription: `closing the "${name}" function`, usePopScopeInstead: true });
154
172
  this.openPHP().outdent().append("}");
155
173
  if (args.includeExistenceCheck) {
156
174
  return this.endIf();
@@ -183,7 +201,48 @@ class PHPWriter {
183
201
  }
184
202
  return this;
185
203
  }
204
+ /**
205
+ * Starts a scope that, when paired with {@link #closeScope()}, allows for automating unset calls.
206
+ * All scopes started MUST be paired with either an {@link #closeScope()} or {@link #popScope()} call.
207
+ */
208
+ openScope() {
209
+ this.scopeStack.push([]);
210
+ return this;
211
+ }
212
+ /**
213
+ * Pops the top scope from the stack AND unsets all variables that were assigned within it
214
+ */
215
+ closeScope() {
216
+ const scope = this.scopeStack.pop();
217
+ return scope?.length ? this.call("unset", scope.map(v => new Expr(v))) : this;
218
+ }
219
+ /**
220
+ * Pops the top scope from the stack WITHOUT calling unset for the variables that were assigned within it.
221
+ * This is used to allow function bodies to be safely created without disrupting their containing scope.
222
+ */
223
+ popScope() {
224
+ this.scopeStack.pop();
225
+ return this;
226
+ }
227
+ withScope(code, opts = {}) {
228
+ const expectedScopeCount = this.scopeStack.length;
229
+ this.openScope();
230
+ code(this);
231
+ if (opts.usePopScopeInstead) {
232
+ this.popScope();
233
+ }
234
+ else {
235
+ this.closeScope();
236
+ }
237
+ if (expectedScopeCount !== this.scopeStack.length) {
238
+ console.trace(`Encountered an unexpected number of scopes (saw: ${this.scopeStack.length}, expected: ${expectedScopeCount})`, `while ${opts.actionDescription ?? "ending an encapsulating scope"}.`);
239
+ }
240
+ return this;
241
+ }
186
242
  toString() {
243
+ if (this.scopeStack.length) {
244
+ console.trace("toString() was called on a PHPWriter that has a dangling scope");
245
+ }
187
246
  const fileContents = [];
188
247
  let canInline = true;
189
248
  if (this.fileNamespace) {
@@ -497,7 +497,7 @@ function makeBlocksWebpackConfig(config, commonConfig, webpackConfig, dest, src,
497
497
  return (0, common_config_helpers_1.commonMakeWebpackConfig)(config, commonConfig, webpackConfig, true, dest, src, srcRoot, (0, common_config_helpers_1.resolveEntryFromDirectory)(commonConfig, srcRoot, dest), plugins);
498
498
  }
499
499
  function makeExtensionsWebpackConfig(config, commonConfig, webpackConfig, dest, src, srcRoot, plugins) {
500
- plugins.push(new ExtensionsConfigFileGeneratorPlugin_1.ExtensionsConfigFileGeneratorPlugin(srcRoot, config.extensionsVersion, dest.destination, config.plainEntrypointsHandlePrefix));
500
+ plugins.push(new ExtensionsConfigFileGeneratorPlugin_1.ExtensionsConfigFileGeneratorPlugin(config, srcRoot, dest.destination));
501
501
  const entry = async () => {
502
502
  const rawEntrypoints = [];
503
503
  for await (const dirent of await promises_1.default.opendir(srcRoot)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plaudit/webpack-extensions",
3
- "version": "2.58.0",
3
+ "version": "2.58.2",
4
4
  "license": "UNLICENSED",
5
5
  "files": [
6
6
  "/build"
@@ -18,7 +18,7 @@
18
18
  "devDependencies": {
19
19
  "@plaudit/gutenberg-api-extensions": "^2.75.0",
20
20
  "@types/browser-sync-webpack-plugin": "^2.2.5",
21
- "@types/node": "^22.17.1",
21
+ "@types/node": "^24.3.0",
22
22
  "@types/postcss-functions": "^4.0.4",
23
23
  "@types/tapable": "^2.2.7",
24
24
  "@types/webpack": "^5.28.5",
@@ -63,7 +63,7 @@
63
63
  "postcss-url": "^10.1.3",
64
64
  "react": "^18.3.1",
65
65
  "react-dom": "^18.3.1",
66
- "webpack": "^5.101.1",
66
+ "webpack": "^5.101.2",
67
67
  "webpack-remove-empty-scripts": "^1.1.1",
68
68
  "xml-formatter": "^3.6.6"
69
69
  },