@broxium/compiler 1.5.0 → 1.5.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.
- package/dist/index.d.mts +69 -1
- package/dist/index.d.ts +69 -1
- package/dist/index.js +419 -16
- package/dist/index.mjs +412 -15
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -13,12 +13,17 @@ interface CompileInput {
|
|
|
13
13
|
interface CompileOutput {
|
|
14
14
|
serverJsPath: string;
|
|
15
15
|
clientJsPath: string;
|
|
16
|
+
/** Browser (canvas) bundle — uses window.__brodox_react globals, safe to load via Blob URL. */
|
|
17
|
+
browserJsPath: string;
|
|
16
18
|
/** Compiled CSS bundle path, or null if no CSS files were imported. */
|
|
17
19
|
cssPath: string | null;
|
|
18
20
|
serverJsName: string;
|
|
19
21
|
clientJsName: string;
|
|
22
|
+
browserJsName: string;
|
|
20
23
|
/** CSS bundle filename, or null if no CSS files were imported. */
|
|
21
24
|
cssName: string | null;
|
|
25
|
+
/** 6-char base36 FNV-1a scope ID used to prefix CSS selectors ([data-bc=ID]). */
|
|
26
|
+
scopeId: string;
|
|
22
27
|
compiledAt: Date;
|
|
23
28
|
}
|
|
24
29
|
|
|
@@ -26,4 +31,67 @@ declare class BrodoxCompiler {
|
|
|
26
31
|
compile(input: CompileInput): Promise<CompileOutput>;
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
|
|
34
|
+
declare function computeScopeId(slug: string, version: string): string;
|
|
35
|
+
declare function scopeCss(css: string, scopeId: string): string;
|
|
36
|
+
declare function minifyScopedCss(css: string, scopeId: string): Promise<string>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Brodox Page-Level CSS Strategy Engine
|
|
40
|
+
*
|
|
41
|
+
* Decides how the merged CSS for an entire page is delivered to the browser.
|
|
42
|
+
* CSS is scoped per-component by the compiler ([data-bc=XXXXXX] selectors).
|
|
43
|
+
* After publish, all component CSS files are merged and this function chooses
|
|
44
|
+
* the optimal delivery strategy for the combined result.
|
|
45
|
+
*
|
|
46
|
+
* 'none' → no CSS at all → nothing emitted
|
|
47
|
+
* 'inline' → total ≤ INLINE_MAX_BYTES → single <style> in <head>
|
|
48
|
+
* 'separate' → INLINE_MAX < total ≤ CHUNK_MIN → one external <link> file
|
|
49
|
+
* 'chunks' → total > CHUNK_MIN_BYTES → N × CHUNK_SIZE_BYTES <link> files
|
|
50
|
+
* e.g. 95 KB → 3 × 25 KB + 1 × 20 KB
|
|
51
|
+
*
|
|
52
|
+
* Rationale for thresholds
|
|
53
|
+
* ─────────────────────────
|
|
54
|
+
* INLINE_MAX_BYTES 4 096 HTTP request overhead ≈ 100–300 ms RTT. Inlining
|
|
55
|
+
* 4 KB of CSS costs < 4 ms parse time — always faster
|
|
56
|
+
* than an extra round-trip for a small external file.
|
|
57
|
+
*
|
|
58
|
+
* CHUNK_MIN_BYTES 51 200 A single 50 KB external file is acceptable for one
|
|
59
|
+
* HTTP/2 request. Above 50 KB, splitting into parallel
|
|
60
|
+
* 25 KB chunks can start rendering sooner.
|
|
61
|
+
*
|
|
62
|
+
* CHUNK_SIZE_BYTES 25 600 25 KB per chunk — stays within HTTP/2's default
|
|
63
|
+
* initial window size and transfers in 1–2 TCP segments
|
|
64
|
+
* on most connections.
|
|
65
|
+
*/
|
|
66
|
+
declare const PAGE_CSS_THRESHOLDS: {
|
|
67
|
+
/** Embed as <style> when total page CSS ≤ this value (bytes). */
|
|
68
|
+
readonly INLINE_MAX_BYTES: 4096;
|
|
69
|
+
/** Switch to multi-chunk delivery when total page CSS > this value (bytes). */
|
|
70
|
+
readonly CHUNK_MIN_BYTES: 51200;
|
|
71
|
+
/** Maximum bytes per CSS chunk file. */
|
|
72
|
+
readonly CHUNK_SIZE_BYTES: 25600;
|
|
73
|
+
};
|
|
74
|
+
type PageCssStrategy = 'none' | 'inline' | 'separate' | 'chunks';
|
|
75
|
+
interface PageCssDecision {
|
|
76
|
+
strategy: PageCssStrategy;
|
|
77
|
+
/** 'inline': the full merged CSS to embed as <style>. */
|
|
78
|
+
inlineCss?: string;
|
|
79
|
+
/**
|
|
80
|
+
* 'separate': one-element array with the full merged CSS.
|
|
81
|
+
* 'chunks': N-element array, each ≤ CHUNK_SIZE_BYTES, split at rule boundaries.
|
|
82
|
+
* Undefined for 'none' and 'inline'.
|
|
83
|
+
*/
|
|
84
|
+
cssChunks?: string[];
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Choose the optimal CSS delivery strategy for the merged page CSS.
|
|
88
|
+
* Call this with the concatenation of all scoped component CSS for the page.
|
|
89
|
+
*/
|
|
90
|
+
declare function decidePageCssStrategy(mergedCss: string): PageCssDecision;
|
|
91
|
+
/**
|
|
92
|
+
* Split a CSS string into chunks of at most chunkSize bytes each.
|
|
93
|
+
* Splits are always made at the nearest `}` boundary so no rule is cut mid-way.
|
|
94
|
+
*/
|
|
95
|
+
declare function splitCssIntoChunks(css: string, chunkSize: number): string[];
|
|
96
|
+
|
|
97
|
+
export { BrodoxCompiler, type CompileInput, type CompileOutput, PAGE_CSS_THRESHOLDS, type PageCssDecision, type PageCssStrategy, computeScopeId, decidePageCssStrategy, minifyScopedCss, scopeCss, splitCssIntoChunks };
|
package/dist/index.d.ts
CHANGED
|
@@ -13,12 +13,17 @@ interface CompileInput {
|
|
|
13
13
|
interface CompileOutput {
|
|
14
14
|
serverJsPath: string;
|
|
15
15
|
clientJsPath: string;
|
|
16
|
+
/** Browser (canvas) bundle — uses window.__brodox_react globals, safe to load via Blob URL. */
|
|
17
|
+
browserJsPath: string;
|
|
16
18
|
/** Compiled CSS bundle path, or null if no CSS files were imported. */
|
|
17
19
|
cssPath: string | null;
|
|
18
20
|
serverJsName: string;
|
|
19
21
|
clientJsName: string;
|
|
22
|
+
browserJsName: string;
|
|
20
23
|
/** CSS bundle filename, or null if no CSS files were imported. */
|
|
21
24
|
cssName: string | null;
|
|
25
|
+
/** 6-char base36 FNV-1a scope ID used to prefix CSS selectors ([data-bc=ID]). */
|
|
26
|
+
scopeId: string;
|
|
22
27
|
compiledAt: Date;
|
|
23
28
|
}
|
|
24
29
|
|
|
@@ -26,4 +31,67 @@ declare class BrodoxCompiler {
|
|
|
26
31
|
compile(input: CompileInput): Promise<CompileOutput>;
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
|
|
34
|
+
declare function computeScopeId(slug: string, version: string): string;
|
|
35
|
+
declare function scopeCss(css: string, scopeId: string): string;
|
|
36
|
+
declare function minifyScopedCss(css: string, scopeId: string): Promise<string>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Brodox Page-Level CSS Strategy Engine
|
|
40
|
+
*
|
|
41
|
+
* Decides how the merged CSS for an entire page is delivered to the browser.
|
|
42
|
+
* CSS is scoped per-component by the compiler ([data-bc=XXXXXX] selectors).
|
|
43
|
+
* After publish, all component CSS files are merged and this function chooses
|
|
44
|
+
* the optimal delivery strategy for the combined result.
|
|
45
|
+
*
|
|
46
|
+
* 'none' → no CSS at all → nothing emitted
|
|
47
|
+
* 'inline' → total ≤ INLINE_MAX_BYTES → single <style> in <head>
|
|
48
|
+
* 'separate' → INLINE_MAX < total ≤ CHUNK_MIN → one external <link> file
|
|
49
|
+
* 'chunks' → total > CHUNK_MIN_BYTES → N × CHUNK_SIZE_BYTES <link> files
|
|
50
|
+
* e.g. 95 KB → 3 × 25 KB + 1 × 20 KB
|
|
51
|
+
*
|
|
52
|
+
* Rationale for thresholds
|
|
53
|
+
* ─────────────────────────
|
|
54
|
+
* INLINE_MAX_BYTES 4 096 HTTP request overhead ≈ 100–300 ms RTT. Inlining
|
|
55
|
+
* 4 KB of CSS costs < 4 ms parse time — always faster
|
|
56
|
+
* than an extra round-trip for a small external file.
|
|
57
|
+
*
|
|
58
|
+
* CHUNK_MIN_BYTES 51 200 A single 50 KB external file is acceptable for one
|
|
59
|
+
* HTTP/2 request. Above 50 KB, splitting into parallel
|
|
60
|
+
* 25 KB chunks can start rendering sooner.
|
|
61
|
+
*
|
|
62
|
+
* CHUNK_SIZE_BYTES 25 600 25 KB per chunk — stays within HTTP/2's default
|
|
63
|
+
* initial window size and transfers in 1–2 TCP segments
|
|
64
|
+
* on most connections.
|
|
65
|
+
*/
|
|
66
|
+
declare const PAGE_CSS_THRESHOLDS: {
|
|
67
|
+
/** Embed as <style> when total page CSS ≤ this value (bytes). */
|
|
68
|
+
readonly INLINE_MAX_BYTES: 4096;
|
|
69
|
+
/** Switch to multi-chunk delivery when total page CSS > this value (bytes). */
|
|
70
|
+
readonly CHUNK_MIN_BYTES: 51200;
|
|
71
|
+
/** Maximum bytes per CSS chunk file. */
|
|
72
|
+
readonly CHUNK_SIZE_BYTES: 25600;
|
|
73
|
+
};
|
|
74
|
+
type PageCssStrategy = 'none' | 'inline' | 'separate' | 'chunks';
|
|
75
|
+
interface PageCssDecision {
|
|
76
|
+
strategy: PageCssStrategy;
|
|
77
|
+
/** 'inline': the full merged CSS to embed as <style>. */
|
|
78
|
+
inlineCss?: string;
|
|
79
|
+
/**
|
|
80
|
+
* 'separate': one-element array with the full merged CSS.
|
|
81
|
+
* 'chunks': N-element array, each ≤ CHUNK_SIZE_BYTES, split at rule boundaries.
|
|
82
|
+
* Undefined for 'none' and 'inline'.
|
|
83
|
+
*/
|
|
84
|
+
cssChunks?: string[];
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Choose the optimal CSS delivery strategy for the merged page CSS.
|
|
88
|
+
* Call this with the concatenation of all scoped component CSS for the page.
|
|
89
|
+
*/
|
|
90
|
+
declare function decidePageCssStrategy(mergedCss: string): PageCssDecision;
|
|
91
|
+
/**
|
|
92
|
+
* Split a CSS string into chunks of at most chunkSize bytes each.
|
|
93
|
+
* Splits are always made at the nearest `}` boundary so no rule is cut mid-way.
|
|
94
|
+
*/
|
|
95
|
+
declare function splitCssIntoChunks(css: string, chunkSize: number): string[];
|
|
96
|
+
|
|
97
|
+
export { BrodoxCompiler, type CompileInput, type CompileOutput, PAGE_CSS_THRESHOLDS, type PageCssDecision, type PageCssStrategy, computeScopeId, decidePageCssStrategy, minifyScopedCss, scopeCss, splitCssIntoChunks };
|
package/dist/index.js
CHANGED
|
@@ -30,12 +30,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
-
BrodoxCompiler: () => BrodoxCompiler
|
|
33
|
+
BrodoxCompiler: () => BrodoxCompiler,
|
|
34
|
+
PAGE_CSS_THRESHOLDS: () => PAGE_CSS_THRESHOLDS,
|
|
35
|
+
computeScopeId: () => computeScopeId,
|
|
36
|
+
decidePageCssStrategy: () => decidePageCssStrategy,
|
|
37
|
+
minifyScopedCss: () => minifyScopedCss,
|
|
38
|
+
scopeCss: () => scopeCss,
|
|
39
|
+
splitCssIntoChunks: () => splitCssIntoChunks
|
|
34
40
|
});
|
|
35
41
|
module.exports = __toCommonJS(index_exports);
|
|
36
42
|
|
|
37
43
|
// src/compiler.ts
|
|
38
|
-
var
|
|
44
|
+
var esbuild2 = __toESM(require("esbuild"));
|
|
39
45
|
var import_promises3 = __toESM(require("fs/promises"));
|
|
40
46
|
var import_node_path3 = __toESM(require("path"));
|
|
41
47
|
var import_node_os = __toESM(require("os"));
|
|
@@ -154,11 +160,13 @@ export function useRouter() {
|
|
|
154
160
|
|
|
155
161
|
export function useParams() { return {}; }
|
|
156
162
|
|
|
157
|
-
export function BrodoxHead({ title, description }) {
|
|
163
|
+
export function BrodoxHead({ title, description, cssContent }) {
|
|
158
164
|
if (title && typeof globalThis.__brodoxCollectHead === 'function')
|
|
159
165
|
globalThis.__brodoxCollectHead({ type: 'title', props: { content: title } });
|
|
160
166
|
if (description && typeof globalThis.__brodoxCollectHead === 'function')
|
|
161
167
|
globalThis.__brodoxCollectHead({ type: 'meta', props: { name: 'description', content: description } });
|
|
168
|
+
if (cssContent && typeof globalThis.__brodoxCollectHead === 'function')
|
|
169
|
+
globalThis.__brodoxCollectHead({ type: 'style', props: { content: cssContent } });
|
|
162
170
|
return null;
|
|
163
171
|
}
|
|
164
172
|
|
|
@@ -234,6 +242,311 @@ export var ClientRender = Client;
|
|
|
234
242
|
};
|
|
235
243
|
}
|
|
236
244
|
|
|
245
|
+
// src/plugins/canvasBuildPlugin.ts
|
|
246
|
+
function canvasReactPlugin() {
|
|
247
|
+
return {
|
|
248
|
+
name: "brodox-canvas-react",
|
|
249
|
+
setup(build2) {
|
|
250
|
+
build2.onResolve({ filter: /^react\/jsx-runtime$/ }, () => ({
|
|
251
|
+
path: "react/jsx-runtime",
|
|
252
|
+
namespace: "brodox-canvas-jsx"
|
|
253
|
+
}));
|
|
254
|
+
build2.onLoad({ filter: /.*/, namespace: "brodox-canvas-jsx" }, () => ({
|
|
255
|
+
loader: "js",
|
|
256
|
+
// Use window.__brodox_jsx (ReactJSXRuntime) — the real jsx/jsxs functions
|
|
257
|
+
// behave differently from createElement for children handling.
|
|
258
|
+
contents: `
|
|
259
|
+
var _jx = window.__brodox_jsx;
|
|
260
|
+
export var jsx = _jx.jsx;
|
|
261
|
+
export var jsxs = _jx.jsxs;
|
|
262
|
+
export var Fragment = _jx.Fragment;
|
|
263
|
+
`.trim()
|
|
264
|
+
}));
|
|
265
|
+
build2.onResolve({ filter: /^react\/jsx-dev-runtime$/ }, () => ({
|
|
266
|
+
path: "react/jsx-dev-runtime",
|
|
267
|
+
namespace: "brodox-canvas-jsx-dev"
|
|
268
|
+
}));
|
|
269
|
+
build2.onLoad({ filter: /.*/, namespace: "brodox-canvas-jsx-dev" }, () => ({
|
|
270
|
+
loader: "js",
|
|
271
|
+
contents: `
|
|
272
|
+
var _jx = window.__brodox_jsx;
|
|
273
|
+
export var jsxDEV = _jx.jsx || _jx.jsxDEV;
|
|
274
|
+
export var Fragment = _jx.Fragment;
|
|
275
|
+
`.trim()
|
|
276
|
+
}));
|
|
277
|
+
build2.onResolve({ filter: /^react$/ }, () => ({
|
|
278
|
+
path: "react",
|
|
279
|
+
namespace: "brodox-canvas-react"
|
|
280
|
+
}));
|
|
281
|
+
build2.onLoad({ filter: /.*/, namespace: "brodox-canvas-react" }, () => ({
|
|
282
|
+
loader: "js",
|
|
283
|
+
contents: `
|
|
284
|
+
var _r = window.__brodox_react;
|
|
285
|
+
export default _r;
|
|
286
|
+
export var Children = _r.Children;
|
|
287
|
+
export var createElement = _r.createElement;
|
|
288
|
+
export var cloneElement = _r.cloneElement;
|
|
289
|
+
export var createContext = _r.createContext;
|
|
290
|
+
export var createRef = _r.createRef;
|
|
291
|
+
export var forwardRef = _r.forwardRef;
|
|
292
|
+
export var Fragment = _r.Fragment;
|
|
293
|
+
export var isValidElement = _r.isValidElement;
|
|
294
|
+
export var lazy = _r.lazy;
|
|
295
|
+
export var memo = _r.memo;
|
|
296
|
+
export var Suspense = _r.Suspense;
|
|
297
|
+
export var useCallback = _r.useCallback;
|
|
298
|
+
export var useContext = _r.useContext;
|
|
299
|
+
export var useDebugValue = _r.useDebugValue;
|
|
300
|
+
export var useEffect = _r.useEffect;
|
|
301
|
+
export var useId = _r.useId;
|
|
302
|
+
export var useImperativeHandle = _r.useImperativeHandle;
|
|
303
|
+
export var useLayoutEffect = _r.useLayoutEffect;
|
|
304
|
+
export var useMemo = _r.useMemo;
|
|
305
|
+
export var useReducer = _r.useReducer;
|
|
306
|
+
export var useRef = _r.useRef;
|
|
307
|
+
export var useState = _r.useState;
|
|
308
|
+
export var useSyncExternalStore= _r.useSyncExternalStore;
|
|
309
|
+
export var useTransition = _r.useTransition;
|
|
310
|
+
export var startTransition = _r.startTransition;
|
|
311
|
+
`.trim()
|
|
312
|
+
}));
|
|
313
|
+
build2.onResolve({ filter: /^react-dom(\/.*)?$/ }, () => ({
|
|
314
|
+
path: "react-dom",
|
|
315
|
+
namespace: "brodox-canvas-react-dom"
|
|
316
|
+
}));
|
|
317
|
+
build2.onLoad({ filter: /.*/, namespace: "brodox-canvas-react-dom" }, () => ({
|
|
318
|
+
loader: "js",
|
|
319
|
+
contents: `
|
|
320
|
+
var _rd = window.__brodox_react_dom;
|
|
321
|
+
export default _rd;
|
|
322
|
+
export var createPortal = _rd && _rd.createPortal;
|
|
323
|
+
export var flushSync = _rd && _rd.flushSync;
|
|
324
|
+
`.trim()
|
|
325
|
+
}));
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function canvasRuntimePlugin() {
|
|
330
|
+
return {
|
|
331
|
+
name: "brodox-runtime-canvas-stub",
|
|
332
|
+
setup(build2) {
|
|
333
|
+
build2.onResolve({ filter: /^@broxium\/runtime$/ }, () => ({
|
|
334
|
+
path: "@broxium/runtime",
|
|
335
|
+
namespace: "brodox-runtime-canvas"
|
|
336
|
+
}));
|
|
337
|
+
build2.onLoad({ filter: /.*/, namespace: "brodox-runtime-canvas" }, () => ({
|
|
338
|
+
loader: "js",
|
|
339
|
+
contents: `
|
|
340
|
+
// brodox-runtime-canvas:@broxium/runtime
|
|
341
|
+
var React = window.__brodox_react;
|
|
342
|
+
var h = React.createElement;
|
|
343
|
+
var F = React.Fragment;
|
|
344
|
+
|
|
345
|
+
export function BrodoxHead() { return null; }
|
|
346
|
+
export function BrodoxFont() { return null; }
|
|
347
|
+
|
|
348
|
+
export function BrodoxLink({ href, children, className, style, target }) {
|
|
349
|
+
return h('a', { href, className, style, target }, children);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export function BrodoxImage({ src, alt, width, height, fill, className, style }) {
|
|
353
|
+
var imgStyle = fill
|
|
354
|
+
? Object.assign({ width: '100%', height: '100%', objectFit: 'cover' }, style || {})
|
|
355
|
+
: (style || {});
|
|
356
|
+
return h('img', { src: src, alt: alt || '', width: fill ? undefined : width, height: fill ? undefined : height, className: className, style: imgStyle });
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export function BrodoxRouter({ children }) { return h(F, null, children); }
|
|
360
|
+
|
|
361
|
+
export function useRouter() {
|
|
362
|
+
return { pathname: '/', params: {}, navigate: function() {}, back: function() {}, forward: function() {}, prefetch: function() {} };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export function useParams() { return {}; }
|
|
366
|
+
|
|
367
|
+
// In the canvas, Client and Server are both transparent: render children as-is.
|
|
368
|
+
export function Client({ children }) { return h(F, null, children); }
|
|
369
|
+
export function Server({ children }) { return h(F, null, children); }
|
|
370
|
+
export var ClientRender = Client;
|
|
371
|
+
`.trim()
|
|
372
|
+
}));
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// src/utils/scopeCss.ts
|
|
378
|
+
var esbuild = __toESM(require("esbuild"));
|
|
379
|
+
function computeScopeId(slug, version) {
|
|
380
|
+
let hash = 2166136261;
|
|
381
|
+
const input = `${slug}@${version}`;
|
|
382
|
+
for (let i = 0; i < input.length; i++) {
|
|
383
|
+
hash ^= input.charCodeAt(i);
|
|
384
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
385
|
+
}
|
|
386
|
+
return hash.toString(36).slice(0, 6).padStart(6, "0");
|
|
387
|
+
}
|
|
388
|
+
function scopeSelectors(selectorText, scope) {
|
|
389
|
+
return selectorText.split(",").map((s) => {
|
|
390
|
+
const t = s.trim();
|
|
391
|
+
if (!t) return t;
|
|
392
|
+
if (t === ":root") return scope;
|
|
393
|
+
if (t === "html" || t === "body") return t;
|
|
394
|
+
return `${scope} ${t}`;
|
|
395
|
+
}).join(",\n");
|
|
396
|
+
}
|
|
397
|
+
function scopeCss(css, scopeId) {
|
|
398
|
+
const scope = `[data-bc=${scopeId}]`;
|
|
399
|
+
return scopeBlock(css, scope);
|
|
400
|
+
}
|
|
401
|
+
function scopeBlock(css, scope) {
|
|
402
|
+
let i = 0;
|
|
403
|
+
let out = "";
|
|
404
|
+
const len = css.length;
|
|
405
|
+
while (i < len) {
|
|
406
|
+
const wsStart = i;
|
|
407
|
+
while (i < len && /\s/.test(css[i])) i++;
|
|
408
|
+
out += css.slice(wsStart, i);
|
|
409
|
+
if (i >= len) break;
|
|
410
|
+
if (css[i] === "/" && css[i + 1] === "*") {
|
|
411
|
+
const end = css.indexOf("*/", i + 2);
|
|
412
|
+
if (end === -1) {
|
|
413
|
+
out += css.slice(i);
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
out += css.slice(i, end + 2);
|
|
417
|
+
i = end + 2;
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (css[i] === "@") {
|
|
421
|
+
let j = i + 1;
|
|
422
|
+
while (j < len && /[a-zA-Z-]/.test(css[j])) j++;
|
|
423
|
+
const atName = css.slice(i + 1, j).toLowerCase();
|
|
424
|
+
if (atName === "charset" || atName === "import") {
|
|
425
|
+
const semi = css.indexOf(";", j);
|
|
426
|
+
if (semi === -1) {
|
|
427
|
+
out += css.slice(i);
|
|
428
|
+
i = len;
|
|
429
|
+
} else {
|
|
430
|
+
out += css.slice(i, semi + 1);
|
|
431
|
+
i = semi + 1;
|
|
432
|
+
}
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
if (atName === "keyframes" || atName === "-webkit-keyframes" || atName === "-moz-keyframes" || atName === "font-face") {
|
|
436
|
+
while (j < len && css[j] !== "{") j++;
|
|
437
|
+
if (j >= len) {
|
|
438
|
+
out += css.slice(i);
|
|
439
|
+
i = len;
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
const { block, end: blockEnd } = readBlock(css, j);
|
|
443
|
+
out += css.slice(i, j) + block;
|
|
444
|
+
i = blockEnd;
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (atName === "media" || atName === "supports" || atName === "layer" || atName === "container") {
|
|
448
|
+
while (j < len && css[j] !== "{") j++;
|
|
449
|
+
if (j >= len) {
|
|
450
|
+
out += css.slice(i);
|
|
451
|
+
i = len;
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
const condition = css.slice(i, j);
|
|
455
|
+
const { body, end: blockEnd } = readBlockBody(css, j);
|
|
456
|
+
const scopedBody = scopeBlock(body, scope);
|
|
457
|
+
out += `${condition}{
|
|
458
|
+
${scopedBody}}
|
|
459
|
+
`;
|
|
460
|
+
i = blockEnd;
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
while (j < len && css[j] !== "{" && css[j] !== ";") j++;
|
|
464
|
+
if (j >= len) {
|
|
465
|
+
out += css.slice(i);
|
|
466
|
+
i = len;
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
if (css[j] === ";") {
|
|
470
|
+
out += css.slice(i, j + 1);
|
|
471
|
+
i = j + 1;
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
const { block: unknownBlock, end: unknownEnd } = readBlock(css, j);
|
|
475
|
+
out += css.slice(i, j) + unknownBlock;
|
|
476
|
+
i = unknownEnd;
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
let selectorEnd = i;
|
|
480
|
+
while (selectorEnd < len) {
|
|
481
|
+
if (css[selectorEnd] === "{") break;
|
|
482
|
+
if (css[selectorEnd] === "/" && css[selectorEnd + 1] === "*") {
|
|
483
|
+
const end = css.indexOf("*/", selectorEnd + 2);
|
|
484
|
+
selectorEnd = end === -1 ? len : end + 2;
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
selectorEnd++;
|
|
488
|
+
}
|
|
489
|
+
if (selectorEnd >= len) {
|
|
490
|
+
out += css.slice(i);
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
const rawSelector = css.slice(i, selectorEnd);
|
|
494
|
+
const trimmedSelector = rawSelector.trim();
|
|
495
|
+
if (!trimmedSelector) {
|
|
496
|
+
i = selectorEnd;
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
const cleanSelector = trimmedSelector.replace(/\/\*.*?\*\//gs, "").trim();
|
|
500
|
+
const scoped = scopeSelectors(cleanSelector, scope);
|
|
501
|
+
const { body: declBody, end: declEnd } = readBlockBody(css, selectorEnd);
|
|
502
|
+
out += `${scoped} {
|
|
503
|
+
${declBody}}
|
|
504
|
+
`;
|
|
505
|
+
i = declEnd;
|
|
506
|
+
}
|
|
507
|
+
return out;
|
|
508
|
+
}
|
|
509
|
+
function readBlock(css, openBrace) {
|
|
510
|
+
let depth = 0;
|
|
511
|
+
let i = openBrace;
|
|
512
|
+
const len = css.length;
|
|
513
|
+
while (i < len) {
|
|
514
|
+
if (css[i] === "{") depth++;
|
|
515
|
+
else if (css[i] === "}") {
|
|
516
|
+
depth--;
|
|
517
|
+
if (depth === 0) {
|
|
518
|
+
return { block: css.slice(openBrace, i + 1), end: i + 1 };
|
|
519
|
+
}
|
|
520
|
+
} else if (css[i] === "/" && css[i + 1] === "*") {
|
|
521
|
+
const end = css.indexOf("*/", i + 2);
|
|
522
|
+
if (end === -1) {
|
|
523
|
+
i = len;
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
i = end + 1;
|
|
527
|
+
}
|
|
528
|
+
i++;
|
|
529
|
+
}
|
|
530
|
+
return { block: css.slice(openBrace), end: len };
|
|
531
|
+
}
|
|
532
|
+
function readBlockBody(css, openBrace) {
|
|
533
|
+
const { block, end } = readBlock(css, openBrace);
|
|
534
|
+
const body = block.slice(1, -1);
|
|
535
|
+
return { body, end };
|
|
536
|
+
}
|
|
537
|
+
async function minifyScopedCss(css, scopeId) {
|
|
538
|
+
const scoped = scopeCss(css, scopeId);
|
|
539
|
+
try {
|
|
540
|
+
const result = await esbuild.transform(scoped, {
|
|
541
|
+
loader: "css",
|
|
542
|
+
minify: true
|
|
543
|
+
});
|
|
544
|
+
return result.code;
|
|
545
|
+
} catch {
|
|
546
|
+
return scoped;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
237
550
|
// src/compiler.ts
|
|
238
551
|
var ENTRY_PRIORITY = [
|
|
239
552
|
"App.tsx",
|
|
@@ -280,6 +593,21 @@ function findReactNodeModules() {
|
|
|
280
593
|
}
|
|
281
594
|
return [];
|
|
282
595
|
}
|
|
596
|
+
function cssTextPlugin(scopeId) {
|
|
597
|
+
return {
|
|
598
|
+
name: "brodox-css-text",
|
|
599
|
+
setup(build2) {
|
|
600
|
+
build2.onLoad({ filter: /\.css$/ }, async (args) => {
|
|
601
|
+
const content = await import_promises3.default.readFile(args.path, "utf8");
|
|
602
|
+
const minified = await minifyScopedCss(content, scopeId);
|
|
603
|
+
return {
|
|
604
|
+
contents: `module.exports = ${JSON.stringify(minified)};`,
|
|
605
|
+
loader: "js"
|
|
606
|
+
};
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
}
|
|
283
611
|
var BrodoxCompiler = class {
|
|
284
612
|
async compile(input) {
|
|
285
613
|
const tmpDir = import_node_path3.default.join(import_node_os.default.tmpdir(), `brodox-compile-${input.slug}-${(0, import_node_crypto.randomUUID)()}`);
|
|
@@ -302,14 +630,17 @@ var BrodoxCompiler = class {
|
|
|
302
630
|
if (!entryPoint) {
|
|
303
631
|
throw new Error(`No entry file found in component files for ${input.slug}`);
|
|
304
632
|
}
|
|
633
|
+
const scopeId = computeScopeId(input.slug, input.version);
|
|
305
634
|
const safeName = `${input.slug}-v${input.version}`;
|
|
306
635
|
const serverJsName = `${safeName}.server.esm.js`;
|
|
307
636
|
const clientJsName = `${safeName}.client.esm.js`;
|
|
637
|
+
const browserJsName = `${safeName}.browser.js`;
|
|
308
638
|
const serverJsPath = import_node_path3.default.join(input.outputDir, serverJsName);
|
|
309
639
|
const clientJsPath = import_node_path3.default.join(input.outputDir, clientJsName);
|
|
640
|
+
const browserJsPath = import_node_path3.default.join(input.outputDir, browserJsName);
|
|
310
641
|
await import_promises3.default.mkdir(input.outputDir, { recursive: true });
|
|
311
642
|
const serverNodePaths = [...input.nodePaths ?? [], ...findReactNodeModules()];
|
|
312
|
-
await
|
|
643
|
+
await esbuild2.build({
|
|
313
644
|
entryPoints: [entryPoint],
|
|
314
645
|
bundle: true,
|
|
315
646
|
format: "esm",
|
|
@@ -319,13 +650,11 @@ var BrodoxCompiler = class {
|
|
|
319
650
|
nodePaths: serverNodePaths,
|
|
320
651
|
external: [],
|
|
321
652
|
// no externals — fully self-contained
|
|
322
|
-
plugins: [clientStubPlugin(), runtimeServerStubPlugin(serverNodePaths)],
|
|
653
|
+
plugins: [clientStubPlugin(), runtimeServerStubPlugin(serverNodePaths), cssTextPlugin(scopeId)],
|
|
323
654
|
outfile: serverJsPath,
|
|
324
655
|
minify: false,
|
|
325
656
|
sourcemap: false,
|
|
326
|
-
define: { "process.env.NODE_ENV": '"production"' }
|
|
327
|
-
loader: { ".css": "text" }
|
|
328
|
-
// CSS imports return empty string in server bundle
|
|
657
|
+
define: { "process.env.NODE_ENV": '"production"' }
|
|
329
658
|
});
|
|
330
659
|
const clientComponents = [];
|
|
331
660
|
for (const file of input.files) {
|
|
@@ -350,7 +679,7 @@ ${registryEntries}
|
|
|
350
679
|
await import_promises3.default.writeFile(registryEntryPath, registryWrapper, "utf8");
|
|
351
680
|
clientEntryPoint = registryEntryPath;
|
|
352
681
|
}
|
|
353
|
-
await
|
|
682
|
+
await esbuild2.build({
|
|
354
683
|
entryPoints: [clientEntryPoint],
|
|
355
684
|
bundle: true,
|
|
356
685
|
format: "esm",
|
|
@@ -358,21 +687,35 @@ ${registryEntries}
|
|
|
358
687
|
target: ["es2020", "chrome90", "firefox88", "safari14"],
|
|
359
688
|
jsx: "automatic",
|
|
360
689
|
external: CLIENT_EXTERNALS,
|
|
361
|
-
plugins: [serverStubPlugin()],
|
|
690
|
+
plugins: [serverStubPlugin(), cssTextPlugin(scopeId)],
|
|
362
691
|
outfile: clientJsPath,
|
|
363
692
|
minify: true,
|
|
364
693
|
sourcemap: false,
|
|
365
694
|
define: { "process.env.NODE_ENV": '"production"' },
|
|
366
|
-
banner: { js: 'import React from "react";' }
|
|
367
|
-
|
|
368
|
-
|
|
695
|
+
banner: { js: 'import React from "react";' }
|
|
696
|
+
});
|
|
697
|
+
await esbuild2.build({
|
|
698
|
+
entryPoints: [clientEntryPoint],
|
|
699
|
+
bundle: true,
|
|
700
|
+
format: "esm",
|
|
701
|
+
platform: "browser",
|
|
702
|
+
target: ["es2020", "chrome90", "firefox88", "safari14"],
|
|
703
|
+
jsx: "automatic",
|
|
704
|
+
external: [],
|
|
705
|
+
// no externals — everything inlined or replaced by plugins
|
|
706
|
+
plugins: [canvasReactPlugin(), canvasRuntimePlugin(), cssTextPlugin(scopeId)],
|
|
707
|
+
outfile: browserJsPath,
|
|
708
|
+
minify: true,
|
|
709
|
+
sourcemap: false,
|
|
710
|
+
define: { "process.env.NODE_ENV": '"production"' }
|
|
711
|
+
// no banner — canvasReactPlugin handles the React reference via window global
|
|
369
712
|
});
|
|
370
713
|
const hasCss = input.files.some((f) => /\.css$/.test(f.path));
|
|
371
714
|
const cssName = hasCss ? `${safeName}.css` : null;
|
|
372
715
|
const cssPath = hasCss ? import_node_path3.default.join(input.outputDir, cssName) : null;
|
|
373
716
|
if (hasCss && cssPath) {
|
|
374
717
|
try {
|
|
375
|
-
await
|
|
718
|
+
await esbuild2.build({
|
|
376
719
|
entryPoints: [entryPoint],
|
|
377
720
|
bundle: true,
|
|
378
721
|
format: "esm",
|
|
@@ -382,7 +725,8 @@ ${registryEntries}
|
|
|
382
725
|
plugins: [serverStubPlugin()],
|
|
383
726
|
outfile: cssPath.replace(/\.css$/, ".css.tmp.js"),
|
|
384
727
|
// esbuild needs a JS outfile
|
|
385
|
-
minify:
|
|
728
|
+
minify: false,
|
|
729
|
+
// we'll minify after scoping
|
|
386
730
|
sourcemap: false,
|
|
387
731
|
define: { "process.env.NODE_ENV": '"production"' },
|
|
388
732
|
loader: { ".css": "css" }
|
|
@@ -393,6 +737,12 @@ ${registryEntries}
|
|
|
393
737
|
} catch {
|
|
394
738
|
}
|
|
395
739
|
await import_promises3.default.rm(cssPath.replace(/\.css$/, ".css.tmp.js"), { force: true });
|
|
740
|
+
try {
|
|
741
|
+
const rawCss = await import_promises3.default.readFile(cssPath, "utf8");
|
|
742
|
+
const minifiedScoped = await minifyScopedCss(rawCss, scopeId);
|
|
743
|
+
await import_promises3.default.writeFile(cssPath, minifiedScoped, "utf8");
|
|
744
|
+
} catch {
|
|
745
|
+
}
|
|
396
746
|
} catch {
|
|
397
747
|
}
|
|
398
748
|
}
|
|
@@ -400,15 +750,68 @@ ${registryEntries}
|
|
|
400
750
|
return {
|
|
401
751
|
serverJsPath,
|
|
402
752
|
clientJsPath,
|
|
753
|
+
browserJsPath,
|
|
403
754
|
cssPath,
|
|
404
755
|
serverJsName,
|
|
405
756
|
clientJsName,
|
|
757
|
+
browserJsName,
|
|
406
758
|
cssName,
|
|
759
|
+
scopeId,
|
|
407
760
|
compiledAt: /* @__PURE__ */ new Date()
|
|
408
761
|
};
|
|
409
762
|
}
|
|
410
763
|
};
|
|
764
|
+
|
|
765
|
+
// src/utils/cssStrategy.ts
|
|
766
|
+
var PAGE_CSS_THRESHOLDS = {
|
|
767
|
+
/** Embed as <style> when total page CSS ≤ this value (bytes). */
|
|
768
|
+
INLINE_MAX_BYTES: 4096,
|
|
769
|
+
/** Switch to multi-chunk delivery when total page CSS > this value (bytes). */
|
|
770
|
+
CHUNK_MIN_BYTES: 51200,
|
|
771
|
+
/** Maximum bytes per CSS chunk file. */
|
|
772
|
+
CHUNK_SIZE_BYTES: 25600
|
|
773
|
+
};
|
|
774
|
+
function decidePageCssStrategy(mergedCss) {
|
|
775
|
+
const size = Buffer.byteLength(mergedCss, "utf8");
|
|
776
|
+
if (size === 0) return { strategy: "none" };
|
|
777
|
+
if (size <= PAGE_CSS_THRESHOLDS.INLINE_MAX_BYTES) {
|
|
778
|
+
return { strategy: "inline", inlineCss: mergedCss };
|
|
779
|
+
}
|
|
780
|
+
if (size > PAGE_CSS_THRESHOLDS.CHUNK_MIN_BYTES) {
|
|
781
|
+
return {
|
|
782
|
+
strategy: "chunks",
|
|
783
|
+
cssChunks: splitCssIntoChunks(mergedCss, PAGE_CSS_THRESHOLDS.CHUNK_SIZE_BYTES)
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
return { strategy: "separate", cssChunks: [mergedCss] };
|
|
787
|
+
}
|
|
788
|
+
function splitCssIntoChunks(css, chunkSize) {
|
|
789
|
+
const chunks = [];
|
|
790
|
+
let remaining = css.trim();
|
|
791
|
+
while (remaining.length > 0) {
|
|
792
|
+
const buf = Buffer.from(remaining, "utf8");
|
|
793
|
+
if (buf.byteLength <= chunkSize) {
|
|
794
|
+
chunks.push(remaining);
|
|
795
|
+
break;
|
|
796
|
+
}
|
|
797
|
+
const candidate = buf.slice(0, chunkSize).toString("utf8");
|
|
798
|
+
const lastBrace = candidate.lastIndexOf("}");
|
|
799
|
+
if (lastBrace < 0) {
|
|
800
|
+
chunks.push(remaining);
|
|
801
|
+
break;
|
|
802
|
+
}
|
|
803
|
+
chunks.push(remaining.slice(0, lastBrace + 1));
|
|
804
|
+
remaining = remaining.slice(lastBrace + 1).trim();
|
|
805
|
+
}
|
|
806
|
+
return chunks.filter((c) => c.length > 0);
|
|
807
|
+
}
|
|
411
808
|
// Annotate the CommonJS export names for ESM import in node:
|
|
412
809
|
0 && (module.exports = {
|
|
413
|
-
BrodoxCompiler
|
|
810
|
+
BrodoxCompiler,
|
|
811
|
+
PAGE_CSS_THRESHOLDS,
|
|
812
|
+
computeScopeId,
|
|
813
|
+
decidePageCssStrategy,
|
|
814
|
+
minifyScopedCss,
|
|
815
|
+
scopeCss,
|
|
816
|
+
splitCssIntoChunks
|
|
414
817
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -6,7 +6,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
6
6
|
});
|
|
7
7
|
|
|
8
8
|
// src/compiler.ts
|
|
9
|
-
import * as
|
|
9
|
+
import * as esbuild2 from "esbuild";
|
|
10
10
|
import fs3 from "fs/promises";
|
|
11
11
|
import path3 from "path";
|
|
12
12
|
import os from "os";
|
|
@@ -125,11 +125,13 @@ export function useRouter() {
|
|
|
125
125
|
|
|
126
126
|
export function useParams() { return {}; }
|
|
127
127
|
|
|
128
|
-
export function BrodoxHead({ title, description }) {
|
|
128
|
+
export function BrodoxHead({ title, description, cssContent }) {
|
|
129
129
|
if (title && typeof globalThis.__brodoxCollectHead === 'function')
|
|
130
130
|
globalThis.__brodoxCollectHead({ type: 'title', props: { content: title } });
|
|
131
131
|
if (description && typeof globalThis.__brodoxCollectHead === 'function')
|
|
132
132
|
globalThis.__brodoxCollectHead({ type: 'meta', props: { name: 'description', content: description } });
|
|
133
|
+
if (cssContent && typeof globalThis.__brodoxCollectHead === 'function')
|
|
134
|
+
globalThis.__brodoxCollectHead({ type: 'style', props: { content: cssContent } });
|
|
133
135
|
return null;
|
|
134
136
|
}
|
|
135
137
|
|
|
@@ -205,6 +207,311 @@ export var ClientRender = Client;
|
|
|
205
207
|
};
|
|
206
208
|
}
|
|
207
209
|
|
|
210
|
+
// src/plugins/canvasBuildPlugin.ts
|
|
211
|
+
function canvasReactPlugin() {
|
|
212
|
+
return {
|
|
213
|
+
name: "brodox-canvas-react",
|
|
214
|
+
setup(build2) {
|
|
215
|
+
build2.onResolve({ filter: /^react\/jsx-runtime$/ }, () => ({
|
|
216
|
+
path: "react/jsx-runtime",
|
|
217
|
+
namespace: "brodox-canvas-jsx"
|
|
218
|
+
}));
|
|
219
|
+
build2.onLoad({ filter: /.*/, namespace: "brodox-canvas-jsx" }, () => ({
|
|
220
|
+
loader: "js",
|
|
221
|
+
// Use window.__brodox_jsx (ReactJSXRuntime) — the real jsx/jsxs functions
|
|
222
|
+
// behave differently from createElement for children handling.
|
|
223
|
+
contents: `
|
|
224
|
+
var _jx = window.__brodox_jsx;
|
|
225
|
+
export var jsx = _jx.jsx;
|
|
226
|
+
export var jsxs = _jx.jsxs;
|
|
227
|
+
export var Fragment = _jx.Fragment;
|
|
228
|
+
`.trim()
|
|
229
|
+
}));
|
|
230
|
+
build2.onResolve({ filter: /^react\/jsx-dev-runtime$/ }, () => ({
|
|
231
|
+
path: "react/jsx-dev-runtime",
|
|
232
|
+
namespace: "brodox-canvas-jsx-dev"
|
|
233
|
+
}));
|
|
234
|
+
build2.onLoad({ filter: /.*/, namespace: "brodox-canvas-jsx-dev" }, () => ({
|
|
235
|
+
loader: "js",
|
|
236
|
+
contents: `
|
|
237
|
+
var _jx = window.__brodox_jsx;
|
|
238
|
+
export var jsxDEV = _jx.jsx || _jx.jsxDEV;
|
|
239
|
+
export var Fragment = _jx.Fragment;
|
|
240
|
+
`.trim()
|
|
241
|
+
}));
|
|
242
|
+
build2.onResolve({ filter: /^react$/ }, () => ({
|
|
243
|
+
path: "react",
|
|
244
|
+
namespace: "brodox-canvas-react"
|
|
245
|
+
}));
|
|
246
|
+
build2.onLoad({ filter: /.*/, namespace: "brodox-canvas-react" }, () => ({
|
|
247
|
+
loader: "js",
|
|
248
|
+
contents: `
|
|
249
|
+
var _r = window.__brodox_react;
|
|
250
|
+
export default _r;
|
|
251
|
+
export var Children = _r.Children;
|
|
252
|
+
export var createElement = _r.createElement;
|
|
253
|
+
export var cloneElement = _r.cloneElement;
|
|
254
|
+
export var createContext = _r.createContext;
|
|
255
|
+
export var createRef = _r.createRef;
|
|
256
|
+
export var forwardRef = _r.forwardRef;
|
|
257
|
+
export var Fragment = _r.Fragment;
|
|
258
|
+
export var isValidElement = _r.isValidElement;
|
|
259
|
+
export var lazy = _r.lazy;
|
|
260
|
+
export var memo = _r.memo;
|
|
261
|
+
export var Suspense = _r.Suspense;
|
|
262
|
+
export var useCallback = _r.useCallback;
|
|
263
|
+
export var useContext = _r.useContext;
|
|
264
|
+
export var useDebugValue = _r.useDebugValue;
|
|
265
|
+
export var useEffect = _r.useEffect;
|
|
266
|
+
export var useId = _r.useId;
|
|
267
|
+
export var useImperativeHandle = _r.useImperativeHandle;
|
|
268
|
+
export var useLayoutEffect = _r.useLayoutEffect;
|
|
269
|
+
export var useMemo = _r.useMemo;
|
|
270
|
+
export var useReducer = _r.useReducer;
|
|
271
|
+
export var useRef = _r.useRef;
|
|
272
|
+
export var useState = _r.useState;
|
|
273
|
+
export var useSyncExternalStore= _r.useSyncExternalStore;
|
|
274
|
+
export var useTransition = _r.useTransition;
|
|
275
|
+
export var startTransition = _r.startTransition;
|
|
276
|
+
`.trim()
|
|
277
|
+
}));
|
|
278
|
+
build2.onResolve({ filter: /^react-dom(\/.*)?$/ }, () => ({
|
|
279
|
+
path: "react-dom",
|
|
280
|
+
namespace: "brodox-canvas-react-dom"
|
|
281
|
+
}));
|
|
282
|
+
build2.onLoad({ filter: /.*/, namespace: "brodox-canvas-react-dom" }, () => ({
|
|
283
|
+
loader: "js",
|
|
284
|
+
contents: `
|
|
285
|
+
var _rd = window.__brodox_react_dom;
|
|
286
|
+
export default _rd;
|
|
287
|
+
export var createPortal = _rd && _rd.createPortal;
|
|
288
|
+
export var flushSync = _rd && _rd.flushSync;
|
|
289
|
+
`.trim()
|
|
290
|
+
}));
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
function canvasRuntimePlugin() {
|
|
295
|
+
return {
|
|
296
|
+
name: "brodox-runtime-canvas-stub",
|
|
297
|
+
setup(build2) {
|
|
298
|
+
build2.onResolve({ filter: /^@broxium\/runtime$/ }, () => ({
|
|
299
|
+
path: "@broxium/runtime",
|
|
300
|
+
namespace: "brodox-runtime-canvas"
|
|
301
|
+
}));
|
|
302
|
+
build2.onLoad({ filter: /.*/, namespace: "brodox-runtime-canvas" }, () => ({
|
|
303
|
+
loader: "js",
|
|
304
|
+
contents: `
|
|
305
|
+
// brodox-runtime-canvas:@broxium/runtime
|
|
306
|
+
var React = window.__brodox_react;
|
|
307
|
+
var h = React.createElement;
|
|
308
|
+
var F = React.Fragment;
|
|
309
|
+
|
|
310
|
+
export function BrodoxHead() { return null; }
|
|
311
|
+
export function BrodoxFont() { return null; }
|
|
312
|
+
|
|
313
|
+
export function BrodoxLink({ href, children, className, style, target }) {
|
|
314
|
+
return h('a', { href, className, style, target }, children);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function BrodoxImage({ src, alt, width, height, fill, className, style }) {
|
|
318
|
+
var imgStyle = fill
|
|
319
|
+
? Object.assign({ width: '100%', height: '100%', objectFit: 'cover' }, style || {})
|
|
320
|
+
: (style || {});
|
|
321
|
+
return h('img', { src: src, alt: alt || '', width: fill ? undefined : width, height: fill ? undefined : height, className: className, style: imgStyle });
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export function BrodoxRouter({ children }) { return h(F, null, children); }
|
|
325
|
+
|
|
326
|
+
export function useRouter() {
|
|
327
|
+
return { pathname: '/', params: {}, navigate: function() {}, back: function() {}, forward: function() {}, prefetch: function() {} };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function useParams() { return {}; }
|
|
331
|
+
|
|
332
|
+
// In the canvas, Client and Server are both transparent: render children as-is.
|
|
333
|
+
export function Client({ children }) { return h(F, null, children); }
|
|
334
|
+
export function Server({ children }) { return h(F, null, children); }
|
|
335
|
+
export var ClientRender = Client;
|
|
336
|
+
`.trim()
|
|
337
|
+
}));
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// src/utils/scopeCss.ts
|
|
343
|
+
import * as esbuild from "esbuild";
|
|
344
|
+
function computeScopeId(slug, version) {
|
|
345
|
+
let hash = 2166136261;
|
|
346
|
+
const input = `${slug}@${version}`;
|
|
347
|
+
for (let i = 0; i < input.length; i++) {
|
|
348
|
+
hash ^= input.charCodeAt(i);
|
|
349
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
350
|
+
}
|
|
351
|
+
return hash.toString(36).slice(0, 6).padStart(6, "0");
|
|
352
|
+
}
|
|
353
|
+
function scopeSelectors(selectorText, scope) {
|
|
354
|
+
return selectorText.split(",").map((s) => {
|
|
355
|
+
const t = s.trim();
|
|
356
|
+
if (!t) return t;
|
|
357
|
+
if (t === ":root") return scope;
|
|
358
|
+
if (t === "html" || t === "body") return t;
|
|
359
|
+
return `${scope} ${t}`;
|
|
360
|
+
}).join(",\n");
|
|
361
|
+
}
|
|
362
|
+
function scopeCss(css, scopeId) {
|
|
363
|
+
const scope = `[data-bc=${scopeId}]`;
|
|
364
|
+
return scopeBlock(css, scope);
|
|
365
|
+
}
|
|
366
|
+
function scopeBlock(css, scope) {
|
|
367
|
+
let i = 0;
|
|
368
|
+
let out = "";
|
|
369
|
+
const len = css.length;
|
|
370
|
+
while (i < len) {
|
|
371
|
+
const wsStart = i;
|
|
372
|
+
while (i < len && /\s/.test(css[i])) i++;
|
|
373
|
+
out += css.slice(wsStart, i);
|
|
374
|
+
if (i >= len) break;
|
|
375
|
+
if (css[i] === "/" && css[i + 1] === "*") {
|
|
376
|
+
const end = css.indexOf("*/", i + 2);
|
|
377
|
+
if (end === -1) {
|
|
378
|
+
out += css.slice(i);
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
out += css.slice(i, end + 2);
|
|
382
|
+
i = end + 2;
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (css[i] === "@") {
|
|
386
|
+
let j = i + 1;
|
|
387
|
+
while (j < len && /[a-zA-Z-]/.test(css[j])) j++;
|
|
388
|
+
const atName = css.slice(i + 1, j).toLowerCase();
|
|
389
|
+
if (atName === "charset" || atName === "import") {
|
|
390
|
+
const semi = css.indexOf(";", j);
|
|
391
|
+
if (semi === -1) {
|
|
392
|
+
out += css.slice(i);
|
|
393
|
+
i = len;
|
|
394
|
+
} else {
|
|
395
|
+
out += css.slice(i, semi + 1);
|
|
396
|
+
i = semi + 1;
|
|
397
|
+
}
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
if (atName === "keyframes" || atName === "-webkit-keyframes" || atName === "-moz-keyframes" || atName === "font-face") {
|
|
401
|
+
while (j < len && css[j] !== "{") j++;
|
|
402
|
+
if (j >= len) {
|
|
403
|
+
out += css.slice(i);
|
|
404
|
+
i = len;
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
const { block, end: blockEnd } = readBlock(css, j);
|
|
408
|
+
out += css.slice(i, j) + block;
|
|
409
|
+
i = blockEnd;
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
if (atName === "media" || atName === "supports" || atName === "layer" || atName === "container") {
|
|
413
|
+
while (j < len && css[j] !== "{") j++;
|
|
414
|
+
if (j >= len) {
|
|
415
|
+
out += css.slice(i);
|
|
416
|
+
i = len;
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
const condition = css.slice(i, j);
|
|
420
|
+
const { body, end: blockEnd } = readBlockBody(css, j);
|
|
421
|
+
const scopedBody = scopeBlock(body, scope);
|
|
422
|
+
out += `${condition}{
|
|
423
|
+
${scopedBody}}
|
|
424
|
+
`;
|
|
425
|
+
i = blockEnd;
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
while (j < len && css[j] !== "{" && css[j] !== ";") j++;
|
|
429
|
+
if (j >= len) {
|
|
430
|
+
out += css.slice(i);
|
|
431
|
+
i = len;
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
if (css[j] === ";") {
|
|
435
|
+
out += css.slice(i, j + 1);
|
|
436
|
+
i = j + 1;
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
const { block: unknownBlock, end: unknownEnd } = readBlock(css, j);
|
|
440
|
+
out += css.slice(i, j) + unknownBlock;
|
|
441
|
+
i = unknownEnd;
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
let selectorEnd = i;
|
|
445
|
+
while (selectorEnd < len) {
|
|
446
|
+
if (css[selectorEnd] === "{") break;
|
|
447
|
+
if (css[selectorEnd] === "/" && css[selectorEnd + 1] === "*") {
|
|
448
|
+
const end = css.indexOf("*/", selectorEnd + 2);
|
|
449
|
+
selectorEnd = end === -1 ? len : end + 2;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
selectorEnd++;
|
|
453
|
+
}
|
|
454
|
+
if (selectorEnd >= len) {
|
|
455
|
+
out += css.slice(i);
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
const rawSelector = css.slice(i, selectorEnd);
|
|
459
|
+
const trimmedSelector = rawSelector.trim();
|
|
460
|
+
if (!trimmedSelector) {
|
|
461
|
+
i = selectorEnd;
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
const cleanSelector = trimmedSelector.replace(/\/\*.*?\*\//gs, "").trim();
|
|
465
|
+
const scoped = scopeSelectors(cleanSelector, scope);
|
|
466
|
+
const { body: declBody, end: declEnd } = readBlockBody(css, selectorEnd);
|
|
467
|
+
out += `${scoped} {
|
|
468
|
+
${declBody}}
|
|
469
|
+
`;
|
|
470
|
+
i = declEnd;
|
|
471
|
+
}
|
|
472
|
+
return out;
|
|
473
|
+
}
|
|
474
|
+
function readBlock(css, openBrace) {
|
|
475
|
+
let depth = 0;
|
|
476
|
+
let i = openBrace;
|
|
477
|
+
const len = css.length;
|
|
478
|
+
while (i < len) {
|
|
479
|
+
if (css[i] === "{") depth++;
|
|
480
|
+
else if (css[i] === "}") {
|
|
481
|
+
depth--;
|
|
482
|
+
if (depth === 0) {
|
|
483
|
+
return { block: css.slice(openBrace, i + 1), end: i + 1 };
|
|
484
|
+
}
|
|
485
|
+
} else if (css[i] === "/" && css[i + 1] === "*") {
|
|
486
|
+
const end = css.indexOf("*/", i + 2);
|
|
487
|
+
if (end === -1) {
|
|
488
|
+
i = len;
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
i = end + 1;
|
|
492
|
+
}
|
|
493
|
+
i++;
|
|
494
|
+
}
|
|
495
|
+
return { block: css.slice(openBrace), end: len };
|
|
496
|
+
}
|
|
497
|
+
function readBlockBody(css, openBrace) {
|
|
498
|
+
const { block, end } = readBlock(css, openBrace);
|
|
499
|
+
const body = block.slice(1, -1);
|
|
500
|
+
return { body, end };
|
|
501
|
+
}
|
|
502
|
+
async function minifyScopedCss(css, scopeId) {
|
|
503
|
+
const scoped = scopeCss(css, scopeId);
|
|
504
|
+
try {
|
|
505
|
+
const result = await esbuild.transform(scoped, {
|
|
506
|
+
loader: "css",
|
|
507
|
+
minify: true
|
|
508
|
+
});
|
|
509
|
+
return result.code;
|
|
510
|
+
} catch {
|
|
511
|
+
return scoped;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
208
515
|
// src/compiler.ts
|
|
209
516
|
var ENTRY_PRIORITY = [
|
|
210
517
|
"App.tsx",
|
|
@@ -251,6 +558,21 @@ function findReactNodeModules() {
|
|
|
251
558
|
}
|
|
252
559
|
return [];
|
|
253
560
|
}
|
|
561
|
+
function cssTextPlugin(scopeId) {
|
|
562
|
+
return {
|
|
563
|
+
name: "brodox-css-text",
|
|
564
|
+
setup(build2) {
|
|
565
|
+
build2.onLoad({ filter: /\.css$/ }, async (args) => {
|
|
566
|
+
const content = await fs3.readFile(args.path, "utf8");
|
|
567
|
+
const minified = await minifyScopedCss(content, scopeId);
|
|
568
|
+
return {
|
|
569
|
+
contents: `module.exports = ${JSON.stringify(minified)};`,
|
|
570
|
+
loader: "js"
|
|
571
|
+
};
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
}
|
|
254
576
|
var BrodoxCompiler = class {
|
|
255
577
|
async compile(input) {
|
|
256
578
|
const tmpDir = path3.join(os.tmpdir(), `brodox-compile-${input.slug}-${randomUUID()}`);
|
|
@@ -273,14 +595,17 @@ var BrodoxCompiler = class {
|
|
|
273
595
|
if (!entryPoint) {
|
|
274
596
|
throw new Error(`No entry file found in component files for ${input.slug}`);
|
|
275
597
|
}
|
|
598
|
+
const scopeId = computeScopeId(input.slug, input.version);
|
|
276
599
|
const safeName = `${input.slug}-v${input.version}`;
|
|
277
600
|
const serverJsName = `${safeName}.server.esm.js`;
|
|
278
601
|
const clientJsName = `${safeName}.client.esm.js`;
|
|
602
|
+
const browserJsName = `${safeName}.browser.js`;
|
|
279
603
|
const serverJsPath = path3.join(input.outputDir, serverJsName);
|
|
280
604
|
const clientJsPath = path3.join(input.outputDir, clientJsName);
|
|
605
|
+
const browserJsPath = path3.join(input.outputDir, browserJsName);
|
|
281
606
|
await fs3.mkdir(input.outputDir, { recursive: true });
|
|
282
607
|
const serverNodePaths = [...input.nodePaths ?? [], ...findReactNodeModules()];
|
|
283
|
-
await
|
|
608
|
+
await esbuild2.build({
|
|
284
609
|
entryPoints: [entryPoint],
|
|
285
610
|
bundle: true,
|
|
286
611
|
format: "esm",
|
|
@@ -290,13 +615,11 @@ var BrodoxCompiler = class {
|
|
|
290
615
|
nodePaths: serverNodePaths,
|
|
291
616
|
external: [],
|
|
292
617
|
// no externals — fully self-contained
|
|
293
|
-
plugins: [clientStubPlugin(), runtimeServerStubPlugin(serverNodePaths)],
|
|
618
|
+
plugins: [clientStubPlugin(), runtimeServerStubPlugin(serverNodePaths), cssTextPlugin(scopeId)],
|
|
294
619
|
outfile: serverJsPath,
|
|
295
620
|
minify: false,
|
|
296
621
|
sourcemap: false,
|
|
297
|
-
define: { "process.env.NODE_ENV": '"production"' }
|
|
298
|
-
loader: { ".css": "text" }
|
|
299
|
-
// CSS imports return empty string in server bundle
|
|
622
|
+
define: { "process.env.NODE_ENV": '"production"' }
|
|
300
623
|
});
|
|
301
624
|
const clientComponents = [];
|
|
302
625
|
for (const file of input.files) {
|
|
@@ -321,7 +644,7 @@ ${registryEntries}
|
|
|
321
644
|
await fs3.writeFile(registryEntryPath, registryWrapper, "utf8");
|
|
322
645
|
clientEntryPoint = registryEntryPath;
|
|
323
646
|
}
|
|
324
|
-
await
|
|
647
|
+
await esbuild2.build({
|
|
325
648
|
entryPoints: [clientEntryPoint],
|
|
326
649
|
bundle: true,
|
|
327
650
|
format: "esm",
|
|
@@ -329,21 +652,35 @@ ${registryEntries}
|
|
|
329
652
|
target: ["es2020", "chrome90", "firefox88", "safari14"],
|
|
330
653
|
jsx: "automatic",
|
|
331
654
|
external: CLIENT_EXTERNALS,
|
|
332
|
-
plugins: [serverStubPlugin()],
|
|
655
|
+
plugins: [serverStubPlugin(), cssTextPlugin(scopeId)],
|
|
333
656
|
outfile: clientJsPath,
|
|
334
657
|
minify: true,
|
|
335
658
|
sourcemap: false,
|
|
336
659
|
define: { "process.env.NODE_ENV": '"production"' },
|
|
337
|
-
banner: { js: 'import React from "react";' }
|
|
338
|
-
|
|
339
|
-
|
|
660
|
+
banner: { js: 'import React from "react";' }
|
|
661
|
+
});
|
|
662
|
+
await esbuild2.build({
|
|
663
|
+
entryPoints: [clientEntryPoint],
|
|
664
|
+
bundle: true,
|
|
665
|
+
format: "esm",
|
|
666
|
+
platform: "browser",
|
|
667
|
+
target: ["es2020", "chrome90", "firefox88", "safari14"],
|
|
668
|
+
jsx: "automatic",
|
|
669
|
+
external: [],
|
|
670
|
+
// no externals — everything inlined or replaced by plugins
|
|
671
|
+
plugins: [canvasReactPlugin(), canvasRuntimePlugin(), cssTextPlugin(scopeId)],
|
|
672
|
+
outfile: browserJsPath,
|
|
673
|
+
minify: true,
|
|
674
|
+
sourcemap: false,
|
|
675
|
+
define: { "process.env.NODE_ENV": '"production"' }
|
|
676
|
+
// no banner — canvasReactPlugin handles the React reference via window global
|
|
340
677
|
});
|
|
341
678
|
const hasCss = input.files.some((f) => /\.css$/.test(f.path));
|
|
342
679
|
const cssName = hasCss ? `${safeName}.css` : null;
|
|
343
680
|
const cssPath = hasCss ? path3.join(input.outputDir, cssName) : null;
|
|
344
681
|
if (hasCss && cssPath) {
|
|
345
682
|
try {
|
|
346
|
-
await
|
|
683
|
+
await esbuild2.build({
|
|
347
684
|
entryPoints: [entryPoint],
|
|
348
685
|
bundle: true,
|
|
349
686
|
format: "esm",
|
|
@@ -353,7 +690,8 @@ ${registryEntries}
|
|
|
353
690
|
plugins: [serverStubPlugin()],
|
|
354
691
|
outfile: cssPath.replace(/\.css$/, ".css.tmp.js"),
|
|
355
692
|
// esbuild needs a JS outfile
|
|
356
|
-
minify:
|
|
693
|
+
minify: false,
|
|
694
|
+
// we'll minify after scoping
|
|
357
695
|
sourcemap: false,
|
|
358
696
|
define: { "process.env.NODE_ENV": '"production"' },
|
|
359
697
|
loader: { ".css": "css" }
|
|
@@ -364,6 +702,12 @@ ${registryEntries}
|
|
|
364
702
|
} catch {
|
|
365
703
|
}
|
|
366
704
|
await fs3.rm(cssPath.replace(/\.css$/, ".css.tmp.js"), { force: true });
|
|
705
|
+
try {
|
|
706
|
+
const rawCss = await fs3.readFile(cssPath, "utf8");
|
|
707
|
+
const minifiedScoped = await minifyScopedCss(rawCss, scopeId);
|
|
708
|
+
await fs3.writeFile(cssPath, minifiedScoped, "utf8");
|
|
709
|
+
} catch {
|
|
710
|
+
}
|
|
367
711
|
} catch {
|
|
368
712
|
}
|
|
369
713
|
}
|
|
@@ -371,14 +715,67 @@ ${registryEntries}
|
|
|
371
715
|
return {
|
|
372
716
|
serverJsPath,
|
|
373
717
|
clientJsPath,
|
|
718
|
+
browserJsPath,
|
|
374
719
|
cssPath,
|
|
375
720
|
serverJsName,
|
|
376
721
|
clientJsName,
|
|
722
|
+
browserJsName,
|
|
377
723
|
cssName,
|
|
724
|
+
scopeId,
|
|
378
725
|
compiledAt: /* @__PURE__ */ new Date()
|
|
379
726
|
};
|
|
380
727
|
}
|
|
381
728
|
};
|
|
729
|
+
|
|
730
|
+
// src/utils/cssStrategy.ts
|
|
731
|
+
var PAGE_CSS_THRESHOLDS = {
|
|
732
|
+
/** Embed as <style> when total page CSS ≤ this value (bytes). */
|
|
733
|
+
INLINE_MAX_BYTES: 4096,
|
|
734
|
+
/** Switch to multi-chunk delivery when total page CSS > this value (bytes). */
|
|
735
|
+
CHUNK_MIN_BYTES: 51200,
|
|
736
|
+
/** Maximum bytes per CSS chunk file. */
|
|
737
|
+
CHUNK_SIZE_BYTES: 25600
|
|
738
|
+
};
|
|
739
|
+
function decidePageCssStrategy(mergedCss) {
|
|
740
|
+
const size = Buffer.byteLength(mergedCss, "utf8");
|
|
741
|
+
if (size === 0) return { strategy: "none" };
|
|
742
|
+
if (size <= PAGE_CSS_THRESHOLDS.INLINE_MAX_BYTES) {
|
|
743
|
+
return { strategy: "inline", inlineCss: mergedCss };
|
|
744
|
+
}
|
|
745
|
+
if (size > PAGE_CSS_THRESHOLDS.CHUNK_MIN_BYTES) {
|
|
746
|
+
return {
|
|
747
|
+
strategy: "chunks",
|
|
748
|
+
cssChunks: splitCssIntoChunks(mergedCss, PAGE_CSS_THRESHOLDS.CHUNK_SIZE_BYTES)
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
return { strategy: "separate", cssChunks: [mergedCss] };
|
|
752
|
+
}
|
|
753
|
+
function splitCssIntoChunks(css, chunkSize) {
|
|
754
|
+
const chunks = [];
|
|
755
|
+
let remaining = css.trim();
|
|
756
|
+
while (remaining.length > 0) {
|
|
757
|
+
const buf = Buffer.from(remaining, "utf8");
|
|
758
|
+
if (buf.byteLength <= chunkSize) {
|
|
759
|
+
chunks.push(remaining);
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
762
|
+
const candidate = buf.slice(0, chunkSize).toString("utf8");
|
|
763
|
+
const lastBrace = candidate.lastIndexOf("}");
|
|
764
|
+
if (lastBrace < 0) {
|
|
765
|
+
chunks.push(remaining);
|
|
766
|
+
break;
|
|
767
|
+
}
|
|
768
|
+
chunks.push(remaining.slice(0, lastBrace + 1));
|
|
769
|
+
remaining = remaining.slice(lastBrace + 1).trim();
|
|
770
|
+
}
|
|
771
|
+
return chunks.filter((c) => c.length > 0);
|
|
772
|
+
}
|
|
382
773
|
export {
|
|
383
|
-
BrodoxCompiler
|
|
774
|
+
BrodoxCompiler,
|
|
775
|
+
PAGE_CSS_THRESHOLDS,
|
|
776
|
+
computeScopeId,
|
|
777
|
+
decidePageCssStrategy,
|
|
778
|
+
minifyScopedCss,
|
|
779
|
+
scopeCss,
|
|
780
|
+
splitCssIntoChunks
|
|
384
781
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@broxium/compiler",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"description": "Brodox component compiler — TSX to ESM server + client bundles",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
"esbuild": "^0.25.0"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
+
"@types/node": "^20.19.42",
|
|
21
22
|
"tsup": "^8.0.0",
|
|
22
|
-
"typescript": "^5.0.0"
|
|
23
|
-
"@types/node": "^20.0.0"
|
|
23
|
+
"typescript": "^5.0.0"
|
|
24
24
|
},
|
|
25
25
|
"scripts": {
|
|
26
26
|
"build": "tsup src/index.ts --format esm,cjs --dts",
|