@kcroyweb/vite-plugin-wp-helper 0.1.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.
package/LICENSE.md ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 KC Roy Web Services
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # @kcroyweb/vite-plugin-wp-helper
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@kcroyweb/vite-plugin-wp-helper.svg)](https://www.npmjs.com/package/@kcroyweb/vite-plugin-wp-helper)
4
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
5
+ [![Coverage](https://git.kcroywebservices.com/packages/vite-plugin-wp-helper/badges/main/coverage.svg)](https://git.kcroywebservices.com/packages/vite-plugin-wp-helper/-/commits/main)
6
+
7
+ Vite plugin to make WordPress block development possible with a regular JS/TS setup within vite.
8
+
9
+ ## Installation
10
+
11
+ You can install the package using npm, pnpm, or yarn:
12
+
13
+ ```bash
14
+ # npm
15
+ npm install @kcroyweb/vite-plugin-wp-helper --save-dev
16
+
17
+ # pnpm
18
+ pnpm add -D @kcroyweb/vite-plugin-wp-helper
19
+
20
+ # yarn
21
+ yarn add @kcroyweb/vite-plugin-wp-helper --dev
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ Add the plugin to your `vite.config.js` or `vite.config.ts` file:
27
+
28
+ ```javascript
29
+ // vite.config.js
30
+ import { defineConfig } from 'vite';
31
+ import wpHelper from '@kcroyweb/vite-plugin-wp-helper';
32
+
33
+ export default defineConfig({
34
+ plugins: [
35
+ wpHelper({
36
+ srcBlockDir: 'src/blocks',
37
+ }),
38
+ ],
39
+ });
40
+ ```
41
+
42
+ ## How it Works
43
+
44
+ This plugin is designed to simplify the build process for WordPress blocks within a Vite environment. It automates several tasks that are typically handled by `@wordpress/scripts`.
45
+
46
+ During the build process (`vite build`), the plugin will:
47
+
48
+ 1. Scan the `srcBlockDir` directory for any `block.json` files.
49
+ 2. For each `block.json` found, it will build the corresponding `index.js`/`ts`, `editor.js`/`ts`, and `view.js`/`ts` entry points using Vite.
50
+ 3. It automatically handles externalizing WordPress dependencies (like `@wordpress/blocks`, `react`, etc.) and maps them to the correct `wp` global variables.
51
+ 4. It generates a `.asset.php` file for each JavaScript entry point, containing the dependency list and a version hash, which is required for enqueuing scripts in WordPress.
52
+ 5. It processes `style.css` and `editor.css` files with PostCSS and places the output alongside the block's other assets.
53
+ 6. The final compiled block assets and the updated `block.json` file are placed in the output directory.
54
+
55
+ ## Options
56
+
57
+ ### `srcBlockDir`
58
+
59
+ - **Type:** `string`
60
+ - **Required:** `true`
61
+
62
+ The source directory where your WordPress blocks are located. The plugin will recursively search this directory for `block.json` files.
63
+
64
+ ### `outDir`
65
+
66
+ - **Type:** `string`
67
+ - **Default:** `'dist'`
68
+
69
+ The output directory where the compiled block assets will be placed.
70
+
71
+ ## License
72
+
73
+ This project is licensed under the ISC License.
@@ -0,0 +1,7 @@
1
+ export default function wpHelper(options: {
2
+ srcBlockDir: string;
3
+ outDir?: string;
4
+ }): {
5
+ name: string;
6
+ buildStart(): Promise<void>;
7
+ };
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/wp-helper.ts
31
+ var wp_helper_exports = {};
32
+ __export(wp_helper_exports, {
33
+ default: () => wpHelper
34
+ });
35
+ module.exports = __toCommonJS(wp_helper_exports);
36
+ var import_node_path = require("path");
37
+ var import_node_fs = require("fs");
38
+ var import_vite = require("vite");
39
+ var import_postcss = __toESM(require("postcss"));
40
+ var import_postcss_load_config = __toESM(require("postcss-load-config"));
41
+ function wpHelper(options) {
42
+ if (!options.srcBlockDir) {
43
+ throw new Error("[vite-plugin-wp-helper] srcBlockDir is required");
44
+ }
45
+ const wpHandles = {
46
+ react: "wp-react",
47
+ "react-dom": "wp-react-dom",
48
+ "@wordpress/blocks": "wp-blocks",
49
+ "@wordpress/element": "wp-element",
50
+ "@wordpress/components": "wp-components",
51
+ "@wordpress/data": "wp-data",
52
+ "@wordpress/block-editor": "wp-block-editor",
53
+ "@wordpress/i18n": "wp-i18n",
54
+ "@wordpress/api-fetch": "wp-api-fetch",
55
+ "@wordpress/compose": "wp-compose",
56
+ "@wordpress/hooks": "wp-hooks"
57
+ };
58
+ const outDir = options.outDir ? (0, import_node_path.resolve)(options.outDir) : (0, import_node_path.resolve)("dist");
59
+ const findBlockJsonFiles = (dir, list = []) => {
60
+ if (!(0, import_node_fs.existsSync)(dir)) return list;
61
+ for (const file of (0, import_node_fs.readdirSync)(dir)) {
62
+ const full = (0, import_node_path.join)(dir, file);
63
+ const stat = (0, import_node_fs.statSync)(full);
64
+ if (stat.isDirectory()) findBlockJsonFiles(full, list);
65
+ else if (file === "block.json") list.push(full);
66
+ }
67
+ return list;
68
+ };
69
+ const extractDependencies = (chunk) => {
70
+ const deps = /* @__PURE__ */ new Set();
71
+ const scan = (v) => {
72
+ for (const [pkg, handle] of Object.entries(wpHandles)) {
73
+ if (v.includes(pkg)) deps.add(handle);
74
+ }
75
+ };
76
+ if (chunk.imports) chunk.imports.forEach(scan);
77
+ if (chunk.code) scan(chunk.code);
78
+ if (chunk.modules) Object.keys(chunk.modules).forEach(scan);
79
+ return [...deps].sort();
80
+ };
81
+ const generateAssetPhp = (deps) => `<?php
82
+ return array(
83
+ 'dependencies' => array(${deps.map((d) => `'${d}'`).join(", ")}),
84
+ 'version' => '${Date.now()}',
85
+ );
86
+ `;
87
+ async function buildCss(input, output) {
88
+ const css = (0, import_node_fs.readFileSync)(input, "utf8");
89
+ const { plugins, options: options2 } = await (0, import_postcss_load_config.default)({}, process.cwd());
90
+ const result = await (0, import_postcss.default)(plugins).process(css, {
91
+ ...options2,
92
+ from: input,
93
+ to: output
94
+ });
95
+ (0, import_node_fs.writeFileSync)(output, result.css);
96
+ if (result.map) {
97
+ (0, import_node_fs.writeFileSync)(output + ".map", result.map.toString());
98
+ }
99
+ }
100
+ return {
101
+ name: "vite-plugin-wp-helper",
102
+ async buildStart() {
103
+ const blockJsonFiles = findBlockJsonFiles(options.srcBlockDir);
104
+ for (const blockJsonPath of blockJsonFiles) {
105
+ const blockDir = (0, import_node_path.dirname)(blockJsonPath);
106
+ const blockName = (0, import_node_path.basename)(blockDir);
107
+ const blockOutDir = (0, import_node_path.join)(outDir, "blocks", blockName);
108
+ (0, import_node_fs.mkdirSync)(blockOutDir, { recursive: true });
109
+ const rawBlockJson = JSON.parse((0, import_node_fs.readFileSync)(blockJsonPath, "utf8"));
110
+ const jsEntries = {
111
+ index: ["index.ts", "index.js"],
112
+ editor: ["editor.ts", "editor.js"],
113
+ view: ["view.ts", "view.js"]
114
+ };
115
+ for (const [key, files] of Object.entries(jsEntries)) {
116
+ const entry = files.map((f) => (0, import_node_path.join)(blockDir, f)).find((p) => (0, import_node_fs.existsSync)(p));
117
+ if (!entry) continue;
118
+ const outFile = `${key}.js`;
119
+ const result = await (0, import_vite.build)({
120
+ configFile: false,
121
+ optimizeDeps: { exclude: Object.keys(wpHandles) },
122
+ build: {
123
+ minify: false,
124
+ outDir: blockOutDir,
125
+ emptyOutDir: false,
126
+ rollupOptions: {
127
+ input: entry,
128
+ external: Object.keys(wpHandles),
129
+ output: {
130
+ format: "iife",
131
+ entryFileNames: outFile,
132
+ globals: {
133
+ react: "React",
134
+ "react-dom": "ReactDOM",
135
+ "@wordpress/blocks": "wp.blocks",
136
+ "@wordpress/block-editor": "wp.blockEditor",
137
+ "@wordpress/element": "wp.element",
138
+ "@wordpress/i18n": "wp.i18n",
139
+ "@wordpress/components": "wp.components",
140
+ "@wordpress/data": "wp.data",
141
+ "@wordpress/api-fetch": "wp.apiFetch",
142
+ "@wordpress/compose": "wp.compose",
143
+ "@wordpress/hooks": "wp.hooks"
144
+ }
145
+ }
146
+ }
147
+ }
148
+ });
149
+ const chunk = result.output?.find((c) => c.isEntry);
150
+ if (chunk) {
151
+ const deps = extractDependencies(chunk);
152
+ (0, import_node_fs.writeFileSync)(
153
+ (0, import_node_path.join)(blockOutDir, outFile.replace(".js", ".asset.php")),
154
+ generateAssetPhp(deps)
155
+ );
156
+ }
157
+ if (key === "index") rawBlockJson.editorScript = `file:./${outFile}`;
158
+ if (key === "view") rawBlockJson.viewScript = `file:./${outFile}`;
159
+ }
160
+ const cssFiles = {
161
+ style: (0, import_node_path.join)(blockDir, "style.css"),
162
+ editorStyle: (0, import_node_path.join)(blockDir, "editor.css")
163
+ };
164
+ if ((0, import_node_fs.existsSync)(cssFiles.style)) {
165
+ await buildCss(cssFiles.style, (0, import_node_path.join)(blockOutDir, "style.css"));
166
+ rawBlockJson.viewStyle = "file:./style.css";
167
+ }
168
+ if ((0, import_node_fs.existsSync)(cssFiles.editorStyle)) {
169
+ await buildCss(cssFiles.editorStyle, (0, import_node_path.join)(blockOutDir, "editor.css"));
170
+ rawBlockJson.editorStyle = "file:./editor.css";
171
+ }
172
+ (0, import_node_fs.writeFileSync)(
173
+ (0, import_node_path.join)(blockOutDir, "block.json"),
174
+ JSON.stringify(rawBlockJson, null, 2)
175
+ );
176
+ console.log(`[wp-helper] Built block: ${blockName}`);
177
+ }
178
+ }
179
+ };
180
+ }
@@ -0,0 +1,156 @@
1
+ // src/wp-helper.ts
2
+ import { resolve, dirname, join, basename } from "path";
3
+ import {
4
+ existsSync,
5
+ readFileSync,
6
+ writeFileSync,
7
+ mkdirSync,
8
+ readdirSync,
9
+ statSync
10
+ } from "fs";
11
+ import { build as viteBuild } from "vite";
12
+ import postcss from "postcss";
13
+ import loadPostcssConfig from "postcss-load-config";
14
+ function wpHelper(options) {
15
+ if (!options.srcBlockDir) {
16
+ throw new Error("[vite-plugin-wp-helper] srcBlockDir is required");
17
+ }
18
+ const wpHandles = {
19
+ react: "wp-react",
20
+ "react-dom": "wp-react-dom",
21
+ "@wordpress/blocks": "wp-blocks",
22
+ "@wordpress/element": "wp-element",
23
+ "@wordpress/components": "wp-components",
24
+ "@wordpress/data": "wp-data",
25
+ "@wordpress/block-editor": "wp-block-editor",
26
+ "@wordpress/i18n": "wp-i18n",
27
+ "@wordpress/api-fetch": "wp-api-fetch",
28
+ "@wordpress/compose": "wp-compose",
29
+ "@wordpress/hooks": "wp-hooks"
30
+ };
31
+ const outDir = options.outDir ? resolve(options.outDir) : resolve("dist");
32
+ const findBlockJsonFiles = (dir, list = []) => {
33
+ if (!existsSync(dir)) return list;
34
+ for (const file of readdirSync(dir)) {
35
+ const full = join(dir, file);
36
+ const stat = statSync(full);
37
+ if (stat.isDirectory()) findBlockJsonFiles(full, list);
38
+ else if (file === "block.json") list.push(full);
39
+ }
40
+ return list;
41
+ };
42
+ const extractDependencies = (chunk) => {
43
+ const deps = /* @__PURE__ */ new Set();
44
+ const scan = (v) => {
45
+ for (const [pkg, handle] of Object.entries(wpHandles)) {
46
+ if (v.includes(pkg)) deps.add(handle);
47
+ }
48
+ };
49
+ if (chunk.imports) chunk.imports.forEach(scan);
50
+ if (chunk.code) scan(chunk.code);
51
+ if (chunk.modules) Object.keys(chunk.modules).forEach(scan);
52
+ return [...deps].sort();
53
+ };
54
+ const generateAssetPhp = (deps) => `<?php
55
+ return array(
56
+ 'dependencies' => array(${deps.map((d) => `'${d}'`).join(", ")}),
57
+ 'version' => '${Date.now()}',
58
+ );
59
+ `;
60
+ async function buildCss(input, output) {
61
+ const css = readFileSync(input, "utf8");
62
+ const { plugins, options: options2 } = await loadPostcssConfig({}, process.cwd());
63
+ const result = await postcss(plugins).process(css, {
64
+ ...options2,
65
+ from: input,
66
+ to: output
67
+ });
68
+ writeFileSync(output, result.css);
69
+ if (result.map) {
70
+ writeFileSync(output + ".map", result.map.toString());
71
+ }
72
+ }
73
+ return {
74
+ name: "vite-plugin-wp-helper",
75
+ async buildStart() {
76
+ const blockJsonFiles = findBlockJsonFiles(options.srcBlockDir);
77
+ for (const blockJsonPath of blockJsonFiles) {
78
+ const blockDir = dirname(blockJsonPath);
79
+ const blockName = basename(blockDir);
80
+ const blockOutDir = join(outDir, "blocks", blockName);
81
+ mkdirSync(blockOutDir, { recursive: true });
82
+ const rawBlockJson = JSON.parse(readFileSync(blockJsonPath, "utf8"));
83
+ const jsEntries = {
84
+ index: ["index.ts", "index.js"],
85
+ editor: ["editor.ts", "editor.js"],
86
+ view: ["view.ts", "view.js"]
87
+ };
88
+ for (const [key, files] of Object.entries(jsEntries)) {
89
+ const entry = files.map((f) => join(blockDir, f)).find((p) => existsSync(p));
90
+ if (!entry) continue;
91
+ const outFile = `${key}.js`;
92
+ const result = await viteBuild({
93
+ configFile: false,
94
+ optimizeDeps: { exclude: Object.keys(wpHandles) },
95
+ build: {
96
+ minify: false,
97
+ outDir: blockOutDir,
98
+ emptyOutDir: false,
99
+ rollupOptions: {
100
+ input: entry,
101
+ external: Object.keys(wpHandles),
102
+ output: {
103
+ format: "iife",
104
+ entryFileNames: outFile,
105
+ globals: {
106
+ react: "React",
107
+ "react-dom": "ReactDOM",
108
+ "@wordpress/blocks": "wp.blocks",
109
+ "@wordpress/block-editor": "wp.blockEditor",
110
+ "@wordpress/element": "wp.element",
111
+ "@wordpress/i18n": "wp.i18n",
112
+ "@wordpress/components": "wp.components",
113
+ "@wordpress/data": "wp.data",
114
+ "@wordpress/api-fetch": "wp.apiFetch",
115
+ "@wordpress/compose": "wp.compose",
116
+ "@wordpress/hooks": "wp.hooks"
117
+ }
118
+ }
119
+ }
120
+ }
121
+ });
122
+ const chunk = result.output?.find((c) => c.isEntry);
123
+ if (chunk) {
124
+ const deps = extractDependencies(chunk);
125
+ writeFileSync(
126
+ join(blockOutDir, outFile.replace(".js", ".asset.php")),
127
+ generateAssetPhp(deps)
128
+ );
129
+ }
130
+ if (key === "index") rawBlockJson.editorScript = `file:./${outFile}`;
131
+ if (key === "view") rawBlockJson.viewScript = `file:./${outFile}`;
132
+ }
133
+ const cssFiles = {
134
+ style: join(blockDir, "style.css"),
135
+ editorStyle: join(blockDir, "editor.css")
136
+ };
137
+ if (existsSync(cssFiles.style)) {
138
+ await buildCss(cssFiles.style, join(blockOutDir, "style.css"));
139
+ rawBlockJson.viewStyle = "file:./style.css";
140
+ }
141
+ if (existsSync(cssFiles.editorStyle)) {
142
+ await buildCss(cssFiles.editorStyle, join(blockOutDir, "editor.css"));
143
+ rawBlockJson.editorStyle = "file:./editor.css";
144
+ }
145
+ writeFileSync(
146
+ join(blockOutDir, "block.json"),
147
+ JSON.stringify(rawBlockJson, null, 2)
148
+ );
149
+ console.log(`[wp-helper] Built block: ${blockName}`);
150
+ }
151
+ }
152
+ };
153
+ }
154
+ export {
155
+ wpHelper as default
156
+ };
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@kcroyweb/vite-plugin-wp-helper",
3
+ "version": "0.1.0",
4
+ "description": "Vite plugin to make WordPress block development possible with a regular JS/TS setup within vite.",
5
+ "main": "dist/wp-helper.js",
6
+ "types": "dist/wp-helper.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsup src/wp-helper.ts --format cjs,esm --external postcss --external postcss-load-config --external vite && tsc --emitDeclarationOnly --declaration --declarationDir dist --project tsconfig.json",
12
+ "test": "vitest",
13
+ "coverage": "vitest run --coverage"
14
+ },
15
+ "keywords": [
16
+ "vite",
17
+ "wordpress",
18
+ "vite-plugin"
19
+ ],
20
+ "author": "",
21
+ "license": "ISC",
22
+ "homepage": "https://git.kcroywebservices.com/packages/vite-plugin-wp-helper#readme",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git@git.kcroywebservices.com:packages/vite-plugin-wp-helper.git"
26
+ },
27
+ "bugs": {
28
+ "url": "https://git.kcroywebservices.com/packages/vite-plugin-wp-helper/-/issues"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "engines": {
34
+ "node": ">=18"
35
+ },
36
+ "peerDependencies": {
37
+ "vite": "^5.0.0"
38
+ },
39
+ "packageManager": "pnpm@10.28.1",
40
+ "devDependencies": {
41
+ "@types/node": "^25.0.10",
42
+ "@vitest/coverage-v8": "^4.0.18",
43
+ "jsdom": "^27.4.0",
44
+ "postcss": "^8.5.6",
45
+ "postcss-load-config": "^6.0.1",
46
+ "tsup": "^8.5.1",
47
+ "typescript": "^5.9.3",
48
+ "vite": "^7.3.1",
49
+ "vitest": "^4.0.18"
50
+ }
51
+ }