@broxium/compiler 1.1.0 → 1.3.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/package.json +1 -1
- package/src/plugins/runtimeServerStubPlugin.ts +4 -2
- package/dist/index.d.mts +0 -25
- package/dist/index.d.ts +0 -25
- package/dist/index.js +0 -349
- package/dist/index.mjs +0 -319
package/package.json
CHANGED
|
@@ -42,7 +42,9 @@ export function runtimeServerStubPlugin(nodePaths: string[] = []): Plugin {
|
|
|
42
42
|
loader: 'js',
|
|
43
43
|
resolveDir,
|
|
44
44
|
contents: `
|
|
45
|
-
import { createElement, Fragment,
|
|
45
|
+
import { createElement, Fragment, Children } from 'react';
|
|
46
|
+
var __islandSeq = 0;
|
|
47
|
+
function __nextIslandId() { return 'bi-' + (++__islandSeq) + '-' + Math.random().toString(36).slice(2,7); }
|
|
46
48
|
|
|
47
49
|
export function BrodoxImage({ src, alt, width, height, fill, className, style, priority, quality = 75, sizes }) {
|
|
48
50
|
const maxW = width || 1920;
|
|
@@ -90,7 +92,7 @@ export function BrodoxRouter({ children }) {
|
|
|
90
92
|
* parent bundle's __registry__ after page load.
|
|
91
93
|
*/
|
|
92
94
|
export function Client({ children }) {
|
|
93
|
-
var id =
|
|
95
|
+
var id = __nextIslandId();
|
|
94
96
|
var child = Children.only(children);
|
|
95
97
|
var compType = child.type;
|
|
96
98
|
var name = typeof compType !== 'string' && compType
|
package/dist/index.d.mts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
interface CompileInput {
|
|
2
|
-
componentId: number;
|
|
3
|
-
slug: string;
|
|
4
|
-
version: string;
|
|
5
|
-
files: Array<{
|
|
6
|
-
path: string;
|
|
7
|
-
content: string;
|
|
8
|
-
}>;
|
|
9
|
-
outputDir: string;
|
|
10
|
-
/** Extra node_modules directories esbuild uses to resolve React for the server bundle. */
|
|
11
|
-
nodePaths?: string[];
|
|
12
|
-
}
|
|
13
|
-
interface CompileOutput {
|
|
14
|
-
serverJsPath: string;
|
|
15
|
-
clientJsPath: string;
|
|
16
|
-
serverJsName: string;
|
|
17
|
-
clientJsName: string;
|
|
18
|
-
compiledAt: Date;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
declare class BrodoxCompiler {
|
|
22
|
-
compile(input: CompileInput): Promise<CompileOutput>;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export { BrodoxCompiler, type CompileInput, type CompileOutput };
|
package/dist/index.d.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
interface CompileInput {
|
|
2
|
-
componentId: number;
|
|
3
|
-
slug: string;
|
|
4
|
-
version: string;
|
|
5
|
-
files: Array<{
|
|
6
|
-
path: string;
|
|
7
|
-
content: string;
|
|
8
|
-
}>;
|
|
9
|
-
outputDir: string;
|
|
10
|
-
/** Extra node_modules directories esbuild uses to resolve React for the server bundle. */
|
|
11
|
-
nodePaths?: string[];
|
|
12
|
-
}
|
|
13
|
-
interface CompileOutput {
|
|
14
|
-
serverJsPath: string;
|
|
15
|
-
clientJsPath: string;
|
|
16
|
-
serverJsName: string;
|
|
17
|
-
clientJsName: string;
|
|
18
|
-
compiledAt: Date;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
declare class BrodoxCompiler {
|
|
22
|
-
compile(input: CompileInput): Promise<CompileOutput>;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export { BrodoxCompiler, type CompileInput, type CompileOutput };
|
package/dist/index.js
DELETED
|
@@ -1,349 +0,0 @@
|
|
|
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/index.ts
|
|
31
|
-
var index_exports = {};
|
|
32
|
-
__export(index_exports, {
|
|
33
|
-
BrodoxCompiler: () => BrodoxCompiler
|
|
34
|
-
});
|
|
35
|
-
module.exports = __toCommonJS(index_exports);
|
|
36
|
-
|
|
37
|
-
// src/compiler.ts
|
|
38
|
-
var esbuild = __toESM(require("esbuild"));
|
|
39
|
-
var import_promises3 = __toESM(require("fs/promises"));
|
|
40
|
-
var import_node_path3 = __toESM(require("path"));
|
|
41
|
-
var import_node_os = __toESM(require("os"));
|
|
42
|
-
var import_node_crypto = require("crypto");
|
|
43
|
-
|
|
44
|
-
// src/plugins/clientStubPlugin.ts
|
|
45
|
-
var import_promises = __toESM(require("fs/promises"));
|
|
46
|
-
var import_node_path = __toESM(require("path"));
|
|
47
|
-
function isClientFile(content, filePath) {
|
|
48
|
-
const trimmed = content.trimStart();
|
|
49
|
-
if (trimmed.startsWith("'use client'") || trimmed.startsWith('"use client"')) return true;
|
|
50
|
-
return /\.client\.[jt]sx?$/.test(filePath);
|
|
51
|
-
}
|
|
52
|
-
function extractName(content, filePath) {
|
|
53
|
-
const m = content.match(/export\s+default\s+function\s+(\w+)/) ?? content.match(/(?:^|\n)function\s+(\w+)/) ?? content.match(/(?:^|\n)const\s+(\w+)\s*=/);
|
|
54
|
-
const base = import_node_path.default.basename(filePath, import_node_path.default.extname(filePath)).replace(/\.client$/, "");
|
|
55
|
-
return m?.[1] ?? base;
|
|
56
|
-
}
|
|
57
|
-
function clientStubPlugin() {
|
|
58
|
-
return {
|
|
59
|
-
name: "brodox-client-stub",
|
|
60
|
-
setup(build2) {
|
|
61
|
-
build2.onLoad({ filter: /\.(tsx?|jsx?)$/ }, async (args) => {
|
|
62
|
-
const content = await import_promises.default.readFile(args.path, "utf8");
|
|
63
|
-
if (isClientFile(content, args.path)) {
|
|
64
|
-
const name = extractName(content, args.path);
|
|
65
|
-
return {
|
|
66
|
-
contents: `
|
|
67
|
-
import React from 'react'
|
|
68
|
-
function ${name}() { return null }
|
|
69
|
-
${name}.displayName = "${name}"
|
|
70
|
-
export default ${name}
|
|
71
|
-
export function getServerData() { return {} }
|
|
72
|
-
`,
|
|
73
|
-
loader: "jsx"
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// src/plugins/serverStubPlugin.ts
|
|
82
|
-
var import_promises2 = __toESM(require("fs/promises"));
|
|
83
|
-
function serverStubPlugin() {
|
|
84
|
-
return {
|
|
85
|
-
name: "brodox-server-stub",
|
|
86
|
-
setup(build2) {
|
|
87
|
-
build2.onLoad({ filter: /\.(tsx?|jsx?)$/ }, async (args) => {
|
|
88
|
-
const content = await import_promises2.default.readFile(args.path, "utf8");
|
|
89
|
-
const trimmed = content.trimStart();
|
|
90
|
-
if (trimmed.startsWith("'use server'") || trimmed.startsWith('"use server"')) {
|
|
91
|
-
return {
|
|
92
|
-
contents: `export default function ServerStub() { return null }`,
|
|
93
|
-
loader: "jsx"
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// src/plugins/runtimeServerStubPlugin.ts
|
|
102
|
-
var import_node_path2 = __toESM(require("path"));
|
|
103
|
-
function runtimeServerStubPlugin(nodePaths = []) {
|
|
104
|
-
const resolveDir = nodePaths.length > 0 ? import_node_path2.default.dirname(nodePaths[0]) : process.cwd();
|
|
105
|
-
return {
|
|
106
|
-
name: "brodox-runtime-server-stub",
|
|
107
|
-
setup(build2) {
|
|
108
|
-
build2.onResolve({ filter: /^@broxium\/runtime$/ }, () => ({
|
|
109
|
-
path: "@broxium/runtime",
|
|
110
|
-
namespace: "brodox-runtime-server-stub"
|
|
111
|
-
}));
|
|
112
|
-
build2.onLoad({ filter: /.*/, namespace: "brodox-runtime-server-stub" }, () => ({
|
|
113
|
-
loader: "js",
|
|
114
|
-
resolveDir,
|
|
115
|
-
contents: `
|
|
116
|
-
import { createElement, Fragment, useId, Children } from 'react';
|
|
117
|
-
|
|
118
|
-
export function BrodoxImage({ src, alt, width, height, fill, className, style, priority, quality = 75, sizes }) {
|
|
119
|
-
const maxW = width || 1920;
|
|
120
|
-
const widths = [320, 640, 768, 1024, 1280, 1920].filter(w => w <= maxW);
|
|
121
|
-
if (!widths.length) widths.push(maxW);
|
|
122
|
-
const q = Math.min(100, Math.max(1, quality));
|
|
123
|
-
const enc = encodeURIComponent(src);
|
|
124
|
-
const optimisedSrc = '/api/image?src=' + enc + '&w=' + maxW + '&q=' + q + '&fmt=webp';
|
|
125
|
-
const srcSet = widths.map(w => '/api/image?src=' + enc + '&w=' + w + '&q=' + q + '&fmt=webp ' + w + 'w').join(', ');
|
|
126
|
-
const imgStyle = fill
|
|
127
|
-
? Object.assign({ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover' }, style || {})
|
|
128
|
-
: (style || {});
|
|
129
|
-
return createElement('img', {
|
|
130
|
-
src: optimisedSrc, srcSet, sizes, alt: alt || '',
|
|
131
|
-
width: fill ? undefined : width, height: fill ? undefined : height,
|
|
132
|
-
loading: priority ? 'eager' : 'lazy', decoding: 'async',
|
|
133
|
-
className, style: imgStyle,
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export function BrodoxLink({ href, children, className, style }) {
|
|
138
|
-
return createElement('a', { href, className, style }, children);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export function useRouter() {
|
|
142
|
-
return { pathname: '/', params: {}, navigate: function(){}, back: function(){}, forward: function(){}, prefetch: function(){} };
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export function useParams() { return {}; }
|
|
146
|
-
|
|
147
|
-
export function BrodoxHead() { return null; }
|
|
148
|
-
|
|
149
|
-
export function BrodoxFont() { return null; }
|
|
150
|
-
|
|
151
|
-
export function BrodoxRouter({ children }) {
|
|
152
|
-
return createElement(Fragment, null, children);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* <Client> \u2014 island boundary marker.
|
|
157
|
-
*
|
|
158
|
-
* During SSR emits an empty placeholder div plus a sibling
|
|
159
|
-
* <script type="application/json"> carrying the child's props.
|
|
160
|
-
* The IslandHydrator walks the live DOM and mounts the component from the
|
|
161
|
-
* parent bundle's __registry__ after page load.
|
|
162
|
-
*/
|
|
163
|
-
export function Client({ children }) {
|
|
164
|
-
var id = useId();
|
|
165
|
-
var child = Children.only(children);
|
|
166
|
-
var compType = child.type;
|
|
167
|
-
var name = typeof compType !== 'string' && compType
|
|
168
|
-
? (compType.displayName || compType.name || 'Unknown')
|
|
169
|
-
: 'Unknown';
|
|
170
|
-
var props = child.props || {};
|
|
171
|
-
var safeProps = JSON.stringify(props)
|
|
172
|
-
.replace(/</g, '\\u003c')
|
|
173
|
-
.replace(/>/g, '\\u003e')
|
|
174
|
-
.replace(/&/g, '\\u0026');
|
|
175
|
-
return createElement(Fragment, null,
|
|
176
|
-
createElement('div', {
|
|
177
|
-
'data-brodox-island': id,
|
|
178
|
-
'data-hydration': 'load',
|
|
179
|
-
'data-client-js': '',
|
|
180
|
-
'data-component-slug': '',
|
|
181
|
-
'data-version': '',
|
|
182
|
-
'data-component': name,
|
|
183
|
-
}),
|
|
184
|
-
createElement('script', {
|
|
185
|
-
type: 'application/json',
|
|
186
|
-
'data-brodox-props': id,
|
|
187
|
-
dangerouslySetInnerHTML: { __html: safeProps },
|
|
188
|
-
})
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* <Server> \u2014 semantic server-only wrapper. Transparent passthrough.
|
|
194
|
-
*/
|
|
195
|
-
export function Server({ children }) {
|
|
196
|
-
return createElement(Fragment, null, children);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/** @deprecated Use <Client> instead. */
|
|
200
|
-
export var ClientRender = Client;
|
|
201
|
-
`
|
|
202
|
-
}));
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// src/compiler.ts
|
|
208
|
-
var ENTRY_PRIORITY = [
|
|
209
|
-
"App.tsx",
|
|
210
|
-
"App.jsx",
|
|
211
|
-
"App.ts",
|
|
212
|
-
"App.js",
|
|
213
|
-
"index.tsx",
|
|
214
|
-
"index.jsx",
|
|
215
|
-
"index.ts",
|
|
216
|
-
"index.js"
|
|
217
|
-
];
|
|
218
|
-
function isClientFile2(content, filePath) {
|
|
219
|
-
const trimmed = content.trimStart();
|
|
220
|
-
if (trimmed.startsWith("'use client'") || trimmed.startsWith('"use client"')) return true;
|
|
221
|
-
return /\.client\.[jt]sx?$/.test(filePath);
|
|
222
|
-
}
|
|
223
|
-
function extractClientComponentName(content, filePath) {
|
|
224
|
-
const m = content.match(/export\s+default\s+function\s+(\w+)/) ?? content.match(/export\s+default\s+(\w+)\s*[;({]/) ?? content.match(/(?:^|\n)function\s+(\w+)/);
|
|
225
|
-
const base = import_node_path3.default.basename(filePath, import_node_path3.default.extname(filePath)).replace(/\.client$/, "");
|
|
226
|
-
return m?.[1] ?? base;
|
|
227
|
-
}
|
|
228
|
-
var CLIENT_EXTERNALS = [
|
|
229
|
-
"react",
|
|
230
|
-
"react-dom",
|
|
231
|
-
"react/jsx-runtime",
|
|
232
|
-
"react/jsx-dev-runtime",
|
|
233
|
-
"@broxium/runtime"
|
|
234
|
-
];
|
|
235
|
-
function findReactNodeModules() {
|
|
236
|
-
const fsSync = require("fs");
|
|
237
|
-
try {
|
|
238
|
-
const reactPkg = require.resolve("react/package.json");
|
|
239
|
-
return [import_node_path3.default.dirname(import_node_path3.default.dirname(reactPkg))];
|
|
240
|
-
} catch {
|
|
241
|
-
}
|
|
242
|
-
let dir = process.cwd();
|
|
243
|
-
for (let i = 0; i < 10; i++) {
|
|
244
|
-
const nm = import_node_path3.default.join(dir, "node_modules");
|
|
245
|
-
const reactPkg = import_node_path3.default.join(nm, "react", "package.json");
|
|
246
|
-
if (fsSync.existsSync(reactPkg)) return [nm];
|
|
247
|
-
const parent = import_node_path3.default.dirname(dir);
|
|
248
|
-
if (parent === dir) break;
|
|
249
|
-
dir = parent;
|
|
250
|
-
}
|
|
251
|
-
return [];
|
|
252
|
-
}
|
|
253
|
-
var BrodoxCompiler = class {
|
|
254
|
-
async compile(input) {
|
|
255
|
-
const tmpDir = import_node_path3.default.join(import_node_os.default.tmpdir(), `brodox-compile-${input.slug}-${(0, import_node_crypto.randomUUID)()}`);
|
|
256
|
-
await import_promises3.default.mkdir(tmpDir, { recursive: true });
|
|
257
|
-
for (const file of input.files) {
|
|
258
|
-
const filePath = import_node_path3.default.join(tmpDir, file.path);
|
|
259
|
-
await import_promises3.default.mkdir(import_node_path3.default.dirname(filePath), { recursive: true });
|
|
260
|
-
await import_promises3.default.writeFile(filePath, file.content, "utf8");
|
|
261
|
-
}
|
|
262
|
-
let entryPoint = null;
|
|
263
|
-
for (const candidate of ENTRY_PRIORITY) {
|
|
264
|
-
const full = import_node_path3.default.join(tmpDir, candidate);
|
|
265
|
-
try {
|
|
266
|
-
await import_promises3.default.access(full);
|
|
267
|
-
entryPoint = full;
|
|
268
|
-
break;
|
|
269
|
-
} catch {
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
if (!entryPoint) {
|
|
273
|
-
throw new Error(`No entry file found in component files for ${input.slug}`);
|
|
274
|
-
}
|
|
275
|
-
const safeName = `${input.slug}-v${input.version}`;
|
|
276
|
-
const serverJsName = `${safeName}.server.esm.js`;
|
|
277
|
-
const clientJsName = `${safeName}.client.esm.js`;
|
|
278
|
-
const serverJsPath = import_node_path3.default.join(input.outputDir, serverJsName);
|
|
279
|
-
const clientJsPath = import_node_path3.default.join(input.outputDir, clientJsName);
|
|
280
|
-
await import_promises3.default.mkdir(input.outputDir, { recursive: true });
|
|
281
|
-
const serverNodePaths = [...input.nodePaths ?? [], ...findReactNodeModules()];
|
|
282
|
-
await esbuild.build({
|
|
283
|
-
entryPoints: [entryPoint],
|
|
284
|
-
bundle: true,
|
|
285
|
-
format: "esm",
|
|
286
|
-
platform: "node",
|
|
287
|
-
target: "node20",
|
|
288
|
-
jsx: "automatic",
|
|
289
|
-
nodePaths: serverNodePaths,
|
|
290
|
-
external: [],
|
|
291
|
-
// no externals — fully self-contained
|
|
292
|
-
plugins: [clientStubPlugin(), runtimeServerStubPlugin(serverNodePaths)],
|
|
293
|
-
outfile: serverJsPath,
|
|
294
|
-
minify: false,
|
|
295
|
-
sourcemap: false,
|
|
296
|
-
define: { "process.env.NODE_ENV": '"production"' }
|
|
297
|
-
});
|
|
298
|
-
const clientComponents = [];
|
|
299
|
-
for (const file of input.files) {
|
|
300
|
-
if (isClientFile2(file.content, file.path)) {
|
|
301
|
-
const name = extractClientComponentName(file.content, file.path);
|
|
302
|
-
clientComponents.push({ name, filePath: file.path });
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
let clientEntryPoint = entryPoint;
|
|
306
|
-
if (clientComponents.length > 0) {
|
|
307
|
-
const entryRelative = import_node_path3.default.relative(tmpDir, entryPoint).replace(/\\/g, "/");
|
|
308
|
-
const importLines = clientComponents.map((c, i) => `import __reg${i}__ from './${c.filePath.replace(/\\/g, "/")}';`).join("\n");
|
|
309
|
-
const registryEntries = clientComponents.map((c, i) => ` '${c.name}': __reg${i}__`).join(",\n");
|
|
310
|
-
const registryWrapper = [
|
|
311
|
-
`export { default } from './${entryRelative}';`,
|
|
312
|
-
importLines,
|
|
313
|
-
`export const __registry__ = {
|
|
314
|
-
${registryEntries}
|
|
315
|
-
};`
|
|
316
|
-
].join("\n");
|
|
317
|
-
const registryEntryPath = import_node_path3.default.join(tmpDir, "__brodox_registry__.jsx");
|
|
318
|
-
await import_promises3.default.writeFile(registryEntryPath, registryWrapper, "utf8");
|
|
319
|
-
clientEntryPoint = registryEntryPath;
|
|
320
|
-
}
|
|
321
|
-
await esbuild.build({
|
|
322
|
-
entryPoints: [clientEntryPoint],
|
|
323
|
-
bundle: true,
|
|
324
|
-
format: "esm",
|
|
325
|
-
platform: "browser",
|
|
326
|
-
target: ["es2020", "chrome90", "firefox88", "safari14"],
|
|
327
|
-
jsx: "automatic",
|
|
328
|
-
external: CLIENT_EXTERNALS,
|
|
329
|
-
plugins: [serverStubPlugin()],
|
|
330
|
-
outfile: clientJsPath,
|
|
331
|
-
minify: true,
|
|
332
|
-
sourcemap: false,
|
|
333
|
-
define: { "process.env.NODE_ENV": '"production"' },
|
|
334
|
-
banner: { js: 'import React from "react";' }
|
|
335
|
-
});
|
|
336
|
-
await import_promises3.default.rm(tmpDir, { recursive: true, force: true });
|
|
337
|
-
return {
|
|
338
|
-
serverJsPath,
|
|
339
|
-
clientJsPath,
|
|
340
|
-
serverJsName,
|
|
341
|
-
clientJsName,
|
|
342
|
-
compiledAt: /* @__PURE__ */ new Date()
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
};
|
|
346
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
347
|
-
0 && (module.exports = {
|
|
348
|
-
BrodoxCompiler
|
|
349
|
-
});
|
package/dist/index.mjs
DELETED
|
@@ -1,319 +0,0 @@
|
|
|
1
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
-
}) : x)(function(x) {
|
|
4
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
-
});
|
|
7
|
-
|
|
8
|
-
// src/compiler.ts
|
|
9
|
-
import * as esbuild from "esbuild";
|
|
10
|
-
import fs3 from "fs/promises";
|
|
11
|
-
import path3 from "path";
|
|
12
|
-
import os from "os";
|
|
13
|
-
import { randomUUID } from "crypto";
|
|
14
|
-
|
|
15
|
-
// src/plugins/clientStubPlugin.ts
|
|
16
|
-
import fs from "fs/promises";
|
|
17
|
-
import path from "path";
|
|
18
|
-
function isClientFile(content, filePath) {
|
|
19
|
-
const trimmed = content.trimStart();
|
|
20
|
-
if (trimmed.startsWith("'use client'") || trimmed.startsWith('"use client"')) return true;
|
|
21
|
-
return /\.client\.[jt]sx?$/.test(filePath);
|
|
22
|
-
}
|
|
23
|
-
function extractName(content, filePath) {
|
|
24
|
-
const m = content.match(/export\s+default\s+function\s+(\w+)/) ?? content.match(/(?:^|\n)function\s+(\w+)/) ?? content.match(/(?:^|\n)const\s+(\w+)\s*=/);
|
|
25
|
-
const base = path.basename(filePath, path.extname(filePath)).replace(/\.client$/, "");
|
|
26
|
-
return m?.[1] ?? base;
|
|
27
|
-
}
|
|
28
|
-
function clientStubPlugin() {
|
|
29
|
-
return {
|
|
30
|
-
name: "brodox-client-stub",
|
|
31
|
-
setup(build2) {
|
|
32
|
-
build2.onLoad({ filter: /\.(tsx?|jsx?)$/ }, async (args) => {
|
|
33
|
-
const content = await fs.readFile(args.path, "utf8");
|
|
34
|
-
if (isClientFile(content, args.path)) {
|
|
35
|
-
const name = extractName(content, args.path);
|
|
36
|
-
return {
|
|
37
|
-
contents: `
|
|
38
|
-
import React from 'react'
|
|
39
|
-
function ${name}() { return null }
|
|
40
|
-
${name}.displayName = "${name}"
|
|
41
|
-
export default ${name}
|
|
42
|
-
export function getServerData() { return {} }
|
|
43
|
-
`,
|
|
44
|
-
loader: "jsx"
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// src/plugins/serverStubPlugin.ts
|
|
53
|
-
import fs2 from "fs/promises";
|
|
54
|
-
function serverStubPlugin() {
|
|
55
|
-
return {
|
|
56
|
-
name: "brodox-server-stub",
|
|
57
|
-
setup(build2) {
|
|
58
|
-
build2.onLoad({ filter: /\.(tsx?|jsx?)$/ }, async (args) => {
|
|
59
|
-
const content = await fs2.readFile(args.path, "utf8");
|
|
60
|
-
const trimmed = content.trimStart();
|
|
61
|
-
if (trimmed.startsWith("'use server'") || trimmed.startsWith('"use server"')) {
|
|
62
|
-
return {
|
|
63
|
-
contents: `export default function ServerStub() { return null }`,
|
|
64
|
-
loader: "jsx"
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// src/plugins/runtimeServerStubPlugin.ts
|
|
73
|
-
import path2 from "path";
|
|
74
|
-
function runtimeServerStubPlugin(nodePaths = []) {
|
|
75
|
-
const resolveDir = nodePaths.length > 0 ? path2.dirname(nodePaths[0]) : process.cwd();
|
|
76
|
-
return {
|
|
77
|
-
name: "brodox-runtime-server-stub",
|
|
78
|
-
setup(build2) {
|
|
79
|
-
build2.onResolve({ filter: /^@broxium\/runtime$/ }, () => ({
|
|
80
|
-
path: "@broxium/runtime",
|
|
81
|
-
namespace: "brodox-runtime-server-stub"
|
|
82
|
-
}));
|
|
83
|
-
build2.onLoad({ filter: /.*/, namespace: "brodox-runtime-server-stub" }, () => ({
|
|
84
|
-
loader: "js",
|
|
85
|
-
resolveDir,
|
|
86
|
-
contents: `
|
|
87
|
-
import { createElement, Fragment, useId, Children } from 'react';
|
|
88
|
-
|
|
89
|
-
export function BrodoxImage({ src, alt, width, height, fill, className, style, priority, quality = 75, sizes }) {
|
|
90
|
-
const maxW = width || 1920;
|
|
91
|
-
const widths = [320, 640, 768, 1024, 1280, 1920].filter(w => w <= maxW);
|
|
92
|
-
if (!widths.length) widths.push(maxW);
|
|
93
|
-
const q = Math.min(100, Math.max(1, quality));
|
|
94
|
-
const enc = encodeURIComponent(src);
|
|
95
|
-
const optimisedSrc = '/api/image?src=' + enc + '&w=' + maxW + '&q=' + q + '&fmt=webp';
|
|
96
|
-
const srcSet = widths.map(w => '/api/image?src=' + enc + '&w=' + w + '&q=' + q + '&fmt=webp ' + w + 'w').join(', ');
|
|
97
|
-
const imgStyle = fill
|
|
98
|
-
? Object.assign({ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover' }, style || {})
|
|
99
|
-
: (style || {});
|
|
100
|
-
return createElement('img', {
|
|
101
|
-
src: optimisedSrc, srcSet, sizes, alt: alt || '',
|
|
102
|
-
width: fill ? undefined : width, height: fill ? undefined : height,
|
|
103
|
-
loading: priority ? 'eager' : 'lazy', decoding: 'async',
|
|
104
|
-
className, style: imgStyle,
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export function BrodoxLink({ href, children, className, style }) {
|
|
109
|
-
return createElement('a', { href, className, style }, children);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export function useRouter() {
|
|
113
|
-
return { pathname: '/', params: {}, navigate: function(){}, back: function(){}, forward: function(){}, prefetch: function(){} };
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export function useParams() { return {}; }
|
|
117
|
-
|
|
118
|
-
export function BrodoxHead() { return null; }
|
|
119
|
-
|
|
120
|
-
export function BrodoxFont() { return null; }
|
|
121
|
-
|
|
122
|
-
export function BrodoxRouter({ children }) {
|
|
123
|
-
return createElement(Fragment, null, children);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* <Client> \u2014 island boundary marker.
|
|
128
|
-
*
|
|
129
|
-
* During SSR emits an empty placeholder div plus a sibling
|
|
130
|
-
* <script type="application/json"> carrying the child's props.
|
|
131
|
-
* The IslandHydrator walks the live DOM and mounts the component from the
|
|
132
|
-
* parent bundle's __registry__ after page load.
|
|
133
|
-
*/
|
|
134
|
-
export function Client({ children }) {
|
|
135
|
-
var id = useId();
|
|
136
|
-
var child = Children.only(children);
|
|
137
|
-
var compType = child.type;
|
|
138
|
-
var name = typeof compType !== 'string' && compType
|
|
139
|
-
? (compType.displayName || compType.name || 'Unknown')
|
|
140
|
-
: 'Unknown';
|
|
141
|
-
var props = child.props || {};
|
|
142
|
-
var safeProps = JSON.stringify(props)
|
|
143
|
-
.replace(/</g, '\\u003c')
|
|
144
|
-
.replace(/>/g, '\\u003e')
|
|
145
|
-
.replace(/&/g, '\\u0026');
|
|
146
|
-
return createElement(Fragment, null,
|
|
147
|
-
createElement('div', {
|
|
148
|
-
'data-brodox-island': id,
|
|
149
|
-
'data-hydration': 'load',
|
|
150
|
-
'data-client-js': '',
|
|
151
|
-
'data-component-slug': '',
|
|
152
|
-
'data-version': '',
|
|
153
|
-
'data-component': name,
|
|
154
|
-
}),
|
|
155
|
-
createElement('script', {
|
|
156
|
-
type: 'application/json',
|
|
157
|
-
'data-brodox-props': id,
|
|
158
|
-
dangerouslySetInnerHTML: { __html: safeProps },
|
|
159
|
-
})
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* <Server> \u2014 semantic server-only wrapper. Transparent passthrough.
|
|
165
|
-
*/
|
|
166
|
-
export function Server({ children }) {
|
|
167
|
-
return createElement(Fragment, null, children);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/** @deprecated Use <Client> instead. */
|
|
171
|
-
export var ClientRender = Client;
|
|
172
|
-
`
|
|
173
|
-
}));
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// src/compiler.ts
|
|
179
|
-
var ENTRY_PRIORITY = [
|
|
180
|
-
"App.tsx",
|
|
181
|
-
"App.jsx",
|
|
182
|
-
"App.ts",
|
|
183
|
-
"App.js",
|
|
184
|
-
"index.tsx",
|
|
185
|
-
"index.jsx",
|
|
186
|
-
"index.ts",
|
|
187
|
-
"index.js"
|
|
188
|
-
];
|
|
189
|
-
function isClientFile2(content, filePath) {
|
|
190
|
-
const trimmed = content.trimStart();
|
|
191
|
-
if (trimmed.startsWith("'use client'") || trimmed.startsWith('"use client"')) return true;
|
|
192
|
-
return /\.client\.[jt]sx?$/.test(filePath);
|
|
193
|
-
}
|
|
194
|
-
function extractClientComponentName(content, filePath) {
|
|
195
|
-
const m = content.match(/export\s+default\s+function\s+(\w+)/) ?? content.match(/export\s+default\s+(\w+)\s*[;({]/) ?? content.match(/(?:^|\n)function\s+(\w+)/);
|
|
196
|
-
const base = path3.basename(filePath, path3.extname(filePath)).replace(/\.client$/, "");
|
|
197
|
-
return m?.[1] ?? base;
|
|
198
|
-
}
|
|
199
|
-
var CLIENT_EXTERNALS = [
|
|
200
|
-
"react",
|
|
201
|
-
"react-dom",
|
|
202
|
-
"react/jsx-runtime",
|
|
203
|
-
"react/jsx-dev-runtime",
|
|
204
|
-
"@broxium/runtime"
|
|
205
|
-
];
|
|
206
|
-
function findReactNodeModules() {
|
|
207
|
-
const fsSync = __require("fs");
|
|
208
|
-
try {
|
|
209
|
-
const reactPkg = __require.resolve("react/package.json");
|
|
210
|
-
return [path3.dirname(path3.dirname(reactPkg))];
|
|
211
|
-
} catch {
|
|
212
|
-
}
|
|
213
|
-
let dir = process.cwd();
|
|
214
|
-
for (let i = 0; i < 10; i++) {
|
|
215
|
-
const nm = path3.join(dir, "node_modules");
|
|
216
|
-
const reactPkg = path3.join(nm, "react", "package.json");
|
|
217
|
-
if (fsSync.existsSync(reactPkg)) return [nm];
|
|
218
|
-
const parent = path3.dirname(dir);
|
|
219
|
-
if (parent === dir) break;
|
|
220
|
-
dir = parent;
|
|
221
|
-
}
|
|
222
|
-
return [];
|
|
223
|
-
}
|
|
224
|
-
var BrodoxCompiler = class {
|
|
225
|
-
async compile(input) {
|
|
226
|
-
const tmpDir = path3.join(os.tmpdir(), `brodox-compile-${input.slug}-${randomUUID()}`);
|
|
227
|
-
await fs3.mkdir(tmpDir, { recursive: true });
|
|
228
|
-
for (const file of input.files) {
|
|
229
|
-
const filePath = path3.join(tmpDir, file.path);
|
|
230
|
-
await fs3.mkdir(path3.dirname(filePath), { recursive: true });
|
|
231
|
-
await fs3.writeFile(filePath, file.content, "utf8");
|
|
232
|
-
}
|
|
233
|
-
let entryPoint = null;
|
|
234
|
-
for (const candidate of ENTRY_PRIORITY) {
|
|
235
|
-
const full = path3.join(tmpDir, candidate);
|
|
236
|
-
try {
|
|
237
|
-
await fs3.access(full);
|
|
238
|
-
entryPoint = full;
|
|
239
|
-
break;
|
|
240
|
-
} catch {
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
if (!entryPoint) {
|
|
244
|
-
throw new Error(`No entry file found in component files for ${input.slug}`);
|
|
245
|
-
}
|
|
246
|
-
const safeName = `${input.slug}-v${input.version}`;
|
|
247
|
-
const serverJsName = `${safeName}.server.esm.js`;
|
|
248
|
-
const clientJsName = `${safeName}.client.esm.js`;
|
|
249
|
-
const serverJsPath = path3.join(input.outputDir, serverJsName);
|
|
250
|
-
const clientJsPath = path3.join(input.outputDir, clientJsName);
|
|
251
|
-
await fs3.mkdir(input.outputDir, { recursive: true });
|
|
252
|
-
const serverNodePaths = [...input.nodePaths ?? [], ...findReactNodeModules()];
|
|
253
|
-
await esbuild.build({
|
|
254
|
-
entryPoints: [entryPoint],
|
|
255
|
-
bundle: true,
|
|
256
|
-
format: "esm",
|
|
257
|
-
platform: "node",
|
|
258
|
-
target: "node20",
|
|
259
|
-
jsx: "automatic",
|
|
260
|
-
nodePaths: serverNodePaths,
|
|
261
|
-
external: [],
|
|
262
|
-
// no externals — fully self-contained
|
|
263
|
-
plugins: [clientStubPlugin(), runtimeServerStubPlugin(serverNodePaths)],
|
|
264
|
-
outfile: serverJsPath,
|
|
265
|
-
minify: false,
|
|
266
|
-
sourcemap: false,
|
|
267
|
-
define: { "process.env.NODE_ENV": '"production"' }
|
|
268
|
-
});
|
|
269
|
-
const clientComponents = [];
|
|
270
|
-
for (const file of input.files) {
|
|
271
|
-
if (isClientFile2(file.content, file.path)) {
|
|
272
|
-
const name = extractClientComponentName(file.content, file.path);
|
|
273
|
-
clientComponents.push({ name, filePath: file.path });
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
let clientEntryPoint = entryPoint;
|
|
277
|
-
if (clientComponents.length > 0) {
|
|
278
|
-
const entryRelative = path3.relative(tmpDir, entryPoint).replace(/\\/g, "/");
|
|
279
|
-
const importLines = clientComponents.map((c, i) => `import __reg${i}__ from './${c.filePath.replace(/\\/g, "/")}';`).join("\n");
|
|
280
|
-
const registryEntries = clientComponents.map((c, i) => ` '${c.name}': __reg${i}__`).join(",\n");
|
|
281
|
-
const registryWrapper = [
|
|
282
|
-
`export { default } from './${entryRelative}';`,
|
|
283
|
-
importLines,
|
|
284
|
-
`export const __registry__ = {
|
|
285
|
-
${registryEntries}
|
|
286
|
-
};`
|
|
287
|
-
].join("\n");
|
|
288
|
-
const registryEntryPath = path3.join(tmpDir, "__brodox_registry__.jsx");
|
|
289
|
-
await fs3.writeFile(registryEntryPath, registryWrapper, "utf8");
|
|
290
|
-
clientEntryPoint = registryEntryPath;
|
|
291
|
-
}
|
|
292
|
-
await esbuild.build({
|
|
293
|
-
entryPoints: [clientEntryPoint],
|
|
294
|
-
bundle: true,
|
|
295
|
-
format: "esm",
|
|
296
|
-
platform: "browser",
|
|
297
|
-
target: ["es2020", "chrome90", "firefox88", "safari14"],
|
|
298
|
-
jsx: "automatic",
|
|
299
|
-
external: CLIENT_EXTERNALS,
|
|
300
|
-
plugins: [serverStubPlugin()],
|
|
301
|
-
outfile: clientJsPath,
|
|
302
|
-
minify: true,
|
|
303
|
-
sourcemap: false,
|
|
304
|
-
define: { "process.env.NODE_ENV": '"production"' },
|
|
305
|
-
banner: { js: 'import React from "react";' }
|
|
306
|
-
});
|
|
307
|
-
await fs3.rm(tmpDir, { recursive: true, force: true });
|
|
308
|
-
return {
|
|
309
|
-
serverJsPath,
|
|
310
|
-
clientJsPath,
|
|
311
|
-
serverJsName,
|
|
312
|
-
clientJsName,
|
|
313
|
-
compiledAt: /* @__PURE__ */ new Date()
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
};
|
|
317
|
-
export {
|
|
318
|
-
BrodoxCompiler
|
|
319
|
-
};
|