@atomicsmash/compiler 0.5.1 → 0.6.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.
@@ -0,0 +1,4 @@
1
+ import type { LoaderContext } from "webpack";
2
+ export declare function loader(this: LoaderContext<Record<string, never>>): Promise<void>;
3
+ export default loader;
4
+ //# sourceMappingURL=BlocksLoader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlocksLoader.d.ts","sourceRoot":"","sources":["../src/BlocksLoader.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG7C,wBAAsB,MAAM,CAC3B,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,iBAc1C;AAED,eAAe,MAAM,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { tsImport } from "tsx/esm/api";
2
+ export async function loader(
3
+ // inputSourceMap?: Record<string, unknown>,
4
+ ) {
5
+ const callback = this.async();
6
+ const blockJson = await tsImport(this.resourcePath, {
7
+ parentURL: import.meta.url,
8
+ onImport: (file) => {
9
+ this.addDependency(file.slice(7));
10
+ },
11
+ }).then((module) => module.blockJson);
12
+ callback(null, JSON.stringify(blockJson));
13
+ }
14
+ export default loader;
@@ -0,0 +1,8 @@
1
+ import type { Compiler, WebpackPluginInstance } from "webpack";
2
+ export declare class BlocksPlugin implements WebpackPluginInstance {
3
+ pluginName: string;
4
+ srcFolder: string;
5
+ constructor(srcFolder: string);
6
+ apply(compiler: Compiler): void;
7
+ }
8
+ //# sourceMappingURL=BlocksPlugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlocksPlugin.d.ts","sourceRoot":"","sources":["../src/BlocksPlugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAS/D,qBAAa,YAAa,YAAW,qBAAqB;IACzD,UAAU,SAAkB;IAC5B,SAAS,EAAE,MAAM,CAAC;gBAEN,SAAS,EAAE,MAAM;IAI7B,KAAK,CAAC,QAAQ,EAAE,QAAQ;CA4JxB"}
@@ -0,0 +1,102 @@
1
+ import { readFile, writeFile, unlink as deleteFile } from "node:fs/promises";
2
+ import { extname, resolve, relative, sep as pathSeparator } from "node:path";
3
+ import glob from "fast-glob";
4
+ import json2php from "json2php";
5
+ import { getBlockJsonScriptFields, getBlockJsonStyleFields } from "./utils.js";
6
+ const printer = json2php.make({ linebreak: "\n", indent: "\t" });
7
+ export class BlocksPlugin {
8
+ pluginName = "BlocksPlugin";
9
+ srcFolder;
10
+ constructor(srcFolder) {
11
+ this.srcFolder = srcFolder;
12
+ }
13
+ apply(compiler) {
14
+ const { webpack: { Compilation, sources }, } = compiler;
15
+ compiler.hooks.thisCompilation.tap(this.pluginName, (compilation) => {
16
+ compilation.hooks.processAssets.tapPromise({
17
+ name: "CustomBlocksCSSHandler",
18
+ stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
19
+ }, async (assets) => {
20
+ const blockJsonFiles = Object.keys(assets).filter((asset) => asset.endsWith("block.json"));
21
+ const PHPFiles = await glob([
22
+ ...blockJsonFiles.map((blockJsonPath) => {
23
+ return `${resolve(`${this.srcFolder}${pathSeparator}${blockJsonPath}`, "..", `**${pathSeparator}*.php`)}`;
24
+ }),
25
+ ]);
26
+ await Promise.all(PHPFiles.map(async (file) => {
27
+ return readFile(file, "utf-8").then((data) => {
28
+ compilation.emitAsset(relative(this.srcFolder, file), new sources.RawSource(data), {
29
+ sourceFilename: file,
30
+ });
31
+ });
32
+ }));
33
+ });
34
+ });
35
+ compiler.hooks.afterEmit.tapPromise(this.pluginName, async (compilation) => {
36
+ const blockJsonFiles = Object.keys(compilation.assets).filter((asset) => asset.endsWith("block.json"));
37
+ const blocksManifest = {};
38
+ const wordpressAssetsFilePath = `${compilation.outputOptions.path}${pathSeparator}wordpress-assets-info.json`;
39
+ const wordpressAssets = await readFile(wordpressAssetsFilePath, "utf-8").then((data) => {
40
+ return JSON.parse(data);
41
+ });
42
+ for (const blockJsonFile of blockJsonFiles) {
43
+ await readFile(`${compilation.outputOptions.path}${pathSeparator}${blockJsonFile}`, "utf-8")
44
+ .then((data) => {
45
+ return JSON.parse(data);
46
+ })
47
+ .then(async (json) => {
48
+ const blockName = blockJsonFile.replace(`${pathSeparator}block.json`, "");
49
+ for (const fields of [
50
+ getBlockJsonScriptFields(json),
51
+ getBlockJsonStyleFields(json),
52
+ ]) {
53
+ if (fields === null) {
54
+ continue;
55
+ }
56
+ for (const [fieldName, value] of Object.entries(fields)) {
57
+ const values = [value].flat(1);
58
+ const newValues = [];
59
+ for (const srcFilePath of values) {
60
+ if (!srcFilePath.startsWith("file:")) {
61
+ newValues.push(srcFilePath);
62
+ continue;
63
+ }
64
+ const fileName = srcFilePath
65
+ .replace("file:./", "")
66
+ .replace(extname(srcFilePath), "");
67
+ const hashedAsset = Object.keys(compilation.assets).find((asset) => {
68
+ return (asset.startsWith(`${blockName}${pathSeparator}${fileName}`) && !asset.endsWith(".php"));
69
+ });
70
+ if (hashedAsset) {
71
+ if (hashedAsset in wordpressAssets) {
72
+ await writeFile(`${compilation.outputOptions.path}${pathSeparator}${hashedAsset.replace(extname(hashedAsset), ".asset.php")}`, `<?php return ${printer(wordpressAssets[hashedAsset])};\n`, "utf-8");
73
+ }
74
+ newValues.push(`file:./${hashedAsset.replace(`${blockName}${pathSeparator}`, "")}`);
75
+ }
76
+ else {
77
+ newValues.push(srcFilePath);
78
+ }
79
+ }
80
+ json[fieldName] = newValues;
81
+ }
82
+ }
83
+ return {
84
+ blockName,
85
+ newBlockJson: json,
86
+ };
87
+ })
88
+ .then(async ({ blockName, newBlockJson }) => {
89
+ await writeFile(`${compilation.outputOptions.path}${pathSeparator}${blockJsonFile}`, JSON.stringify(newBlockJson, undefined, 2), "utf-8");
90
+ blocksManifest[blockName] = newBlockJson;
91
+ });
92
+ }
93
+ const phpContent = `<?php
94
+ // This file is generated. Do not modify it manually.
95
+ return ${printer(blocksManifest)};
96
+ `;
97
+ await writeFile(`${compilation.outputOptions.path}${pathSeparator}blocks${pathSeparator}blocks-manifest.php`, phpContent);
98
+ await writeFile(`${compilation.outputOptions.path}${pathSeparator}wordpress-assets-info.php`, `<?php return ${json2php(wordpressAssets)};\n`);
99
+ await deleteFile(wordpressAssetsFilePath);
100
+ });
101
+ }
102
+ }
@@ -0,0 +1,6 @@
1
+ import type { EntryObject } from "webpack";
2
+ export declare function getBlocksAssetsEntryPoints(paths: string[], entryPoints: EntryObject, srcFolder: string): Promise<{
3
+ restOfPaths: string[];
4
+ entryPoints: EntryObject;
5
+ }>;
6
+ //# sourceMappingURL=getBlocksAssetsEntryPoints.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getBlocksAssetsEntryPoints.d.ts","sourceRoot":"","sources":["../src/getBlocksAssetsEntryPoints.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAK3C,wBAAsB,0BAA0B,CAC/C,KAAK,EAAE,MAAM,EAAE,EACf,WAAW,EAAE,WAAW,EACxB,SAAS,EAAE,MAAM;;;GAkDjB"}
@@ -0,0 +1,50 @@
1
+ import { relative, resolve, extname } from "node:path";
2
+ import { tsImport } from "tsx/esm/api";
3
+ import { getBlockJsonStyleFields, getBlockJsonScriptFields } from "./utils.js";
4
+ export async function getBlocksAssetsEntryPoints(paths, entryPoints, srcFolder) {
5
+ const restOfPaths = [];
6
+ for (const path of paths) {
7
+ if (!path.endsWith("block.json.ts")) {
8
+ restOfPaths.push(path);
9
+ continue;
10
+ }
11
+ const blockJson = await tsImport(path, import.meta.url).then((module) => module.blockJson);
12
+ const fields = {
13
+ ...getBlockJsonScriptFields(blockJson),
14
+ ...getBlockJsonStyleFields(blockJson),
15
+ };
16
+ if (fields) {
17
+ for (const value of Object.values(fields).flat()) {
18
+ if (!value.startsWith("file:")) {
19
+ continue;
20
+ }
21
+ const fileLocation = resolve(path, "../", value.slice(5));
22
+ const entryName = relative(srcFolder, fileLocation);
23
+ const type = extname(fileLocation);
24
+ let fileName = entryName;
25
+ switch (type) {
26
+ case ".ts":
27
+ fileName = fileName.replace(".ts", ".[contenthash].js");
28
+ break;
29
+ case ".tsx":
30
+ fileName = fileName.replace(".tsx", ".[contenthash].js");
31
+ break;
32
+ case ".js":
33
+ fileName = fileName.replace(".js", ".[contenthash].js");
34
+ break;
35
+ case ".css":
36
+ fileName = fileName.replace(".css", ".[contenthash].css");
37
+ break;
38
+ case ".scss":
39
+ fileName = fileName.replace(".scss", ".[contenthash].css");
40
+ break;
41
+ }
42
+ entryPoints[entryName] = {
43
+ import: resolve(path, "../", value.slice(5)),
44
+ filename: fileName,
45
+ };
46
+ }
47
+ }
48
+ }
49
+ return { restOfPaths, entryPoints };
50
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAuBA,OAAO,CAAC,MAAM,CAAC;IAEd,UAAU,MAAM,CAAC;QAEhB,UAAU,UAAU;YACnB,QAAQ,EAAE,YAAY,GAAG,aAAa,GAAG,MAAM,GAAG,SAAS,CAAC;SAC5D;KACD;CACD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAyBA,OAAO,CAAC,MAAM,CAAC;IAEd,UAAU,MAAM,CAAC;QAEhB,UAAU,UAAU;YACnB,QAAQ,EAAE,YAAY,GAAG,aAAa,GAAG,MAAM,GAAG,SAAS,CAAC;SAC5D;KACD;CACD"}
package/dist/index.js CHANGED
@@ -14,6 +14,8 @@ import WebpackAssetsManifest from "webpack-assets-manifest";
14
14
  import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";
15
15
  import yargs from "yargs";
16
16
  import { hideBin } from "yargs/helpers";
17
+ import { BlocksPlugin } from "./BlocksPlugin.js";
18
+ import { getBlocksAssetsEntryPoints } from "./getBlocksAssetsEntryPoints.js";
17
19
  const argv = await yargs(hideBin(process.argv))
18
20
  .options({
19
21
  in: {
@@ -36,6 +38,16 @@ const argv = await yargs(hideBin(process.argv))
36
38
  default: false,
37
39
  describe: "See a report of the generated bundle once built.",
38
40
  },
41
+ experimentalBlocksSupport: {
42
+ boolean: true,
43
+ default: false,
44
+ describe: "Enable experimental support for WordPress blocks compilation.",
45
+ },
46
+ excludeBlocks: {
47
+ array: true,
48
+ default: ["__TEMPLATE__"],
49
+ describe: "A comma separated list of the folder names of blocks to exclude from compilation. Requires experimental blocks support.",
50
+ },
39
51
  })
40
52
  .showHelpOnFail(false, "Specify --help for available options")
41
53
  .parse();
@@ -91,16 +103,25 @@ const compiler = webpack({
91
103
  // Set the environment mode from NODE_ENV.
92
104
  mode: MODE,
93
105
  entry: await glob([
106
+ ...(argv.experimentalBlocksSupport
107
+ ? [`${srcFolder}/blocks/**/block.json.ts`]
108
+ : []),
94
109
  // Parse all direct children of the JS folder, as long as they are JS & TS files
95
110
  `${srcFolder}/js/*.{js,ts,jsx,tsx}`,
96
111
  // Parse all direct children of the CSS folder, as long as they are CSS files
97
112
  `${srcFolder}/css/*.css`,
98
113
  // Parse all nested children of the CSS folder, as long as they are non-partial SCSS files
99
114
  `${srcFolder}/css/**/[^_]*.s[ac]ss`,
100
- ]).then((paths) => {
101
- const entryPoints = {};
102
- paths.forEach((path) => {
103
- const entryName = path.replace(srcFolder, "").replace(extname(path), "");
115
+ ], {
116
+ ignore: argv.experimentalBlocksSupport
117
+ ? argv.excludeBlocks.map((blockName) => `${srcFolder}/blocks/**/${blockName}/block.json.ts`)
118
+ : [],
119
+ }).then(async (paths) => {
120
+ const { restOfPaths, entryPoints } = argv.experimentalBlocksSupport
121
+ ? await getBlocksAssetsEntryPoints(paths, {}, srcFolder)
122
+ : { restOfPaths: paths, entryPoints: {} };
123
+ restOfPaths.forEach((path) => {
124
+ const entryName = path.replace(srcFolder, "");
104
125
  entryPoints[entryName] = {
105
126
  import: path,
106
127
  // Output filenames with content hash for fingerprinting
@@ -123,7 +144,11 @@ const compiler = webpack({
123
144
  },
124
145
  resolve: {
125
146
  // support TSConfig paths as aliases
126
- plugins: [new TsconfigPathsPlugin()],
147
+ plugins: [
148
+ new TsconfigPathsPlugin({
149
+ configFile: process.cwd() + "/tsconfig.json",
150
+ }),
151
+ ],
127
152
  // resolve TS extensions
128
153
  extensions: [".tsx", ".ts", ".js", ".jsx"],
129
154
  },
@@ -139,16 +164,35 @@ const compiler = webpack({
139
164
  options: {
140
165
  // Set target to match the browserslist config
141
166
  target: browserslistToEsbuild(),
167
+ tsconfig: process.cwd() + "/tsconfig.json",
142
168
  },
143
169
  },
144
- // Exclude node_modules files from transpilation unless they are vue files and the vue-loader is present
145
170
  exclude: (file) => {
171
+ // Exclude all block.json.ts files (handled by a separate loader).
172
+ if (argv.experimentalBlocksSupport && file.includes("block.json")) {
173
+ return true;
174
+ }
175
+ // Exclude node_modules files from transpilation unless they are vue files and the vue-loader is present
146
176
  if (vueConfig.loader.length > 0) {
147
177
  return file.includes("node_modules") && !file.includes(".vue.js");
148
178
  }
149
179
  return file.includes("node_modules");
150
180
  },
151
181
  },
182
+ ...(argv.experimentalBlocksSupport
183
+ ? [
184
+ {
185
+ test: /block\.json\.ts$/,
186
+ type: "asset/resource",
187
+ generator: {
188
+ filename: (pathData) => relative(srcFolder, pathData.filename ?? "").slice(0, -3),
189
+ },
190
+ use: {
191
+ loader: resolvePath(import.meta.dirname, "./BlocksLoader.js"),
192
+ },
193
+ },
194
+ ]
195
+ : []),
152
196
  // Transpile SCSS files and run the result through postcss
153
197
  {
154
198
  test: /\.s[ac]ss$/i,
@@ -156,7 +200,7 @@ const compiler = webpack({
156
200
  type: "asset/resource",
157
201
  generator: {
158
202
  binary: false,
159
- filename: "css/[name].[contenthash].css",
203
+ filename: (pathData) => relative(srcFolder, pathData.filename ?? "").replace(".scss", ".[contenthash].css"),
160
204
  },
161
205
  use: [
162
206
  {
@@ -183,7 +227,7 @@ const compiler = webpack({
183
227
  type: "asset/resource",
184
228
  generator: {
185
229
  binary: false,
186
- filename: "css/[name].[contenthash].css",
230
+ filename: (pathData) => relative(srcFolder, pathData.filename ?? "").replace(".css", ".[contenthash].css"),
187
231
  },
188
232
  use: [
189
233
  {
@@ -207,16 +251,20 @@ const compiler = webpack({
207
251
  writeToDisk: true,
208
252
  output: `${distFolder}/assets-manifest.json`,
209
253
  customize(entry) {
254
+ if (entry.key === "assets.php" ||
255
+ entry.key === "wordpress-assets-info.php" ||
256
+ entry.key.startsWith("fonts") ||
257
+ entry.key.startsWith("images")) {
258
+ return false;
259
+ }
210
260
  if (entry.key === "spritemap.svg") {
211
261
  return { key: "icons/sprite.svg", value: entry.value };
212
262
  }
213
263
  if (entry.key.startsWith("/")) {
214
- return { key: entry.key.slice(1), value: entry.value };
264
+ entry.key = entry.key.slice(1);
215
265
  }
216
- if (entry.key === "assets.php" ||
217
- entry.key.startsWith("fonts") ||
218
- entry.key.startsWith("images")) {
219
- return false;
266
+ if (entry.key.endsWith(".js.js")) {
267
+ entry.key = entry.key.slice(0, -3);
220
268
  }
221
269
  return entry;
222
270
  },
@@ -259,9 +307,20 @@ const compiler = webpack({
259
307
  // Extract wordpress dependencies from js files and output a wordpress assets file for enqueues
260
308
  new DependencyExtractionWebpackPlugin({
261
309
  combineAssets: true,
310
+ // Blocks plugin converts this to php, but it must be output as json here.
311
+ ...(argv.experimentalBlocksSupport
312
+ ? {
313
+ outputFormat: "json",
314
+ combinedOutputFile: "wordpress-assets-info.json",
315
+ }
316
+ : {
317
+ outputFormat: "php",
318
+ combinedOutputFile: "wordpress-assets-info.php",
319
+ }),
262
320
  }),
263
321
  // Support Vue files if vue-loader is present
264
322
  ...vueConfig.plugin,
323
+ ...(argv.experimentalBlocksSupport ? [new BlocksPlugin(srcFolder)] : []),
265
324
  ],
266
325
  optimization: {
267
326
  minimizer: [
@@ -0,0 +1,5 @@
1
+ import type { BlockMetaData } from "@atomicsmash/blocks-helpers";
2
+ export type BlockJson = BlockMetaData<never, never>;
3
+ export declare function getBlockJsonScriptFields(blockJson: BlockJson): Partial<Pick<BlockJson, "viewScript" | "script" | "editorScript">> | null;
4
+ export declare function getBlockJsonStyleFields(blockJson: BlockJson): Partial<Pick<BlockJson, "editorStyle" | "style" | "viewStyle">> | null;
5
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEjE,MAAM,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAEpD,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,SAAS,6EAW5D;AAED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,SAAS,0EAW3D"}
package/dist/utils.js ADDED
@@ -0,0 +1,22 @@
1
+ export function getBlockJsonScriptFields(blockJson) {
2
+ const scriptFields = ["viewScript", "script", "editorScript"];
3
+ let result = null;
4
+ for (const field of scriptFields) {
5
+ if (Object.hasOwn(blockJson, field)) {
6
+ result ??= {};
7
+ result[field] = blockJson[field];
8
+ }
9
+ }
10
+ return result;
11
+ }
12
+ export function getBlockJsonStyleFields(blockJson) {
13
+ const styleFields = ["viewStyle", "style", "editorStyle"];
14
+ let result = null;
15
+ for (const field of styleFields) {
16
+ if (Object.hasOwn(blockJson, field)) {
17
+ result ??= {};
18
+ result[field] = blockJson[field];
19
+ }
20
+ }
21
+ return result;
22
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@atomicsmash/compiler",
3
3
  "type": "module",
4
- "version": "0.5.1",
4
+ "version": "0.6.0",
5
5
  "description": "A universal compiler for all Atomic Smash projects.",
6
6
  "keywords": [
7
7
  "cli",
@@ -51,14 +51,16 @@
51
51
  "cssnano": "^7.0.6",
52
52
  "esbuild-loader": "^4.3.0",
53
53
  "fast-glob": "^3.3.2",
54
+ "json2php": "^0.0.12",
54
55
  "postcss": "^8.4.49",
55
56
  "postcss-loader": "^8.1.1",
56
- "postcss-preset-env": "^10.1.3",
57
+ "postcss-preset-env": "^10.1.5",
57
58
  "sass": "^1.83.1",
58
59
  "sass-loader": "^16.0.4",
59
60
  "style-loader": "^4.0.0",
60
61
  "svg-spritemap-webpack-plugin": "^4.7.0",
61
62
  "tsconfig-paths-webpack-plugin": "^4.2.0",
63
+ "tsx": "^4.19.3",
62
64
  "webpack": "^5.97.1",
63
65
  "webpack-assets-manifest": "^5.2.1",
64
66
  "webpack-bundle-analyzer": "^4.10.2",