@broxium/compiler 1.3.2 → 1.5.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/README.md +125 -6
- package/dist/index.d.mts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +74 -11
- package/dist/index.mjs +74 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -123,6 +123,60 @@ Source files are written to a temporary directory under `os.tmpdir()` before com
|
|
|
123
123
|
|
|
124
124
|
---
|
|
125
125
|
|
|
126
|
+
## `config.json` format
|
|
127
|
+
|
|
128
|
+
Every component must include a `config.json` file alongside `App.tsx`. This file defines the props exposed in the website builder's property inspector panel.
|
|
129
|
+
|
|
130
|
+
**The format is a flat object** — prop name as key, field definition as value. Do not use a `name`/`slug`/`props` wrapper.
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"title": {
|
|
135
|
+
"label": "Title",
|
|
136
|
+
"type": "text",
|
|
137
|
+
"default": "Hello World"
|
|
138
|
+
},
|
|
139
|
+
"count": {
|
|
140
|
+
"label": "Item Count",
|
|
141
|
+
"type": "number",
|
|
142
|
+
"default": 3
|
|
143
|
+
},
|
|
144
|
+
"theme": {
|
|
145
|
+
"label": "Theme",
|
|
146
|
+
"type": "select",
|
|
147
|
+
"options": ["light", "dark"],
|
|
148
|
+
"default": "light"
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Supported field types
|
|
154
|
+
|
|
155
|
+
| `type` | Builder input | Extra keys |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| `text` | Text input | — |
|
|
158
|
+
| `textarea` | Multiline textarea | — |
|
|
159
|
+
| `url` | URL input (validated) | — |
|
|
160
|
+
| `number` | Number input | — |
|
|
161
|
+
| `select` | Dropdown | `options: string[]` |
|
|
162
|
+
| `color` | Color picker + hex | — |
|
|
163
|
+
| `boolean` | Checkbox | — |
|
|
164
|
+
| `range` | Slider | `min`, `max` |
|
|
165
|
+
| `brodox-*` | Custom Brodox field | varies |
|
|
166
|
+
|
|
167
|
+
### Optional field keys
|
|
168
|
+
|
|
169
|
+
| Key | Type | Description |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| `label` | `string` | Display name in the inspector panel |
|
|
172
|
+
| `type` | `string` | Input type (required) |
|
|
173
|
+
| `default` | `any` | Initial value when component is first dropped |
|
|
174
|
+
| `render` | `"client"` \| `"server"` | Badge shown in inspector (cosmetic only) |
|
|
175
|
+
|
|
176
|
+
> **Common crash:** If `config.json` is not a flat object (e.g. has a top-level `name` or `props` array key), the builder will crash with `Cannot read properties of undefined (reading 'startsWith')` when the component is dragged onto the canvas.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
126
180
|
## `'use client'` and `'use server'` handling
|
|
127
181
|
|
|
128
182
|
The two esbuild builds use custom plugins to handle React-style directives:
|
|
@@ -153,11 +207,44 @@ Server-only code never runs in the browser.
|
|
|
153
207
|
|
|
154
208
|
The directive on the **entry file** (`App.tsx`) determines the `renderMode` stored in the Page Manifest:
|
|
155
209
|
|
|
156
|
-
| Entry file starts with | `renderMode` | Server renders | Client hydrates |
|
|
157
|
-
|
|
158
|
-
| `'use client'` | `client` | No | Yes |
|
|
159
|
-
| `'use server'` | `server` | Yes | No |
|
|
160
|
-
| _(nothing)_ | `both` | Yes | Yes |
|
|
210
|
+
| Entry file starts with | `renderMode` | Server renders | Client hydrates | Use when |
|
|
211
|
+
|---|---|---|---|---|
|
|
212
|
+
| `'use client'` | `client` | No | Yes (full) | `useState`, `useEffect`, event handlers, browser APIs |
|
|
213
|
+
| `'use server'` | `server` | Yes (full) | No | Display-only, no interactivity needed |
|
|
214
|
+
| _(nothing)_ | `both` | Yes | Yes (islands only) | Static shell with `<Client>` islands for interactive parts |
|
|
215
|
+
|
|
216
|
+
> **Note:** These directives are **not** the same as Next.js. `'use server'` here means "exclude from client bundle" — it does not create a server action.
|
|
217
|
+
|
|
218
|
+
### Common mistake — missing `'use client'`
|
|
219
|
+
|
|
220
|
+
Using `useState`, `useEffect`, or any hook at the top level of the entry file **without** `'use client'` causes the web engine to crash during SSR:
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
Cannot read properties of null (reading 'useState')
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Fix: add `'use client'` as the very first line of `App.tsx`.
|
|
227
|
+
|
|
228
|
+
```jsx
|
|
229
|
+
'use client';
|
|
230
|
+
|
|
231
|
+
import { useState } from 'react';
|
|
232
|
+
|
|
233
|
+
export default function App() {
|
|
234
|
+
const [count, setCount] = useState(0);
|
|
235
|
+
// ...
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Sub-file directives
|
|
240
|
+
|
|
241
|
+
You can split a multi-file component by marking individual files:
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
App.tsx ← no directive (both: SSR shell + client hydration)
|
|
245
|
+
├── Counter.tsx ← 'use client' (stubbed on server, runs in browser)
|
|
246
|
+
└── DataTable.tsx← 'use server' (stubbed in browser, runs on server)
|
|
247
|
+
```
|
|
161
248
|
|
|
162
249
|
---
|
|
163
250
|
|
|
@@ -238,14 +325,46 @@ When `BundleService` catches a compilation error, it blocks the component from b
|
|
|
238
325
|
|
|
239
326
|
---
|
|
240
327
|
|
|
328
|
+
## `runtimeServerStubPlugin` — @broxium/runtime server stubs
|
|
329
|
+
|
|
330
|
+
During server bundle compilation, all `@broxium/runtime` imports are replaced
|
|
331
|
+
by inline server-safe stubs so the server bundle has zero external dependencies.
|
|
332
|
+
|
|
333
|
+
Key stub behaviours:
|
|
334
|
+
|
|
335
|
+
| Export | Server stub renders |
|
|
336
|
+
|---|---|
|
|
337
|
+
| `BrodoxLink` | `<a data-brodox-link href={href}>` — the `data-brodox-link` attribute is required for the shell's click interceptor |
|
|
338
|
+
| `BrodoxImage` | `<img src="/api/image?...">` with srcset, or raw `src` if `direct={true}` |
|
|
339
|
+
| `Client` | Empty placeholder div + sibling `<script type="application/json">` with props |
|
|
340
|
+
| `Server` | Transparent passthrough (Fragment) |
|
|
341
|
+
| `useRouter`, `useParams` | Static no-ops (return empty objects) |
|
|
342
|
+
| `BrodoxHead`, `BrodoxFont` | null |
|
|
343
|
+
|
|
344
|
+
**Important:** If you compile a component and the rendered `<a>` tags do not
|
|
345
|
+
have `data-brodox-link`, the shell will not intercept clicks on those links and
|
|
346
|
+
the browser will do a full page reload. This was fixed in v1.3.2 — ensure all
|
|
347
|
+
projects use `@broxium/compiler@^1.3.2` or later. Existing pre-1.3.2 server
|
|
348
|
+
bundles must be patched manually or recompiled.
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
241
352
|
## Package info
|
|
242
353
|
|
|
243
354
|
| | |
|
|
244
355
|
|---|---|
|
|
245
356
|
| Package | `@broxium/compiler` |
|
|
246
|
-
| Version | `1.
|
|
357
|
+
| Version | `1.3.3` |
|
|
247
358
|
| Formats | ESM (`dist/index.mjs`), CJS (`dist/index.js`) |
|
|
248
359
|
| Types | `dist/index.d.ts` |
|
|
249
360
|
| Runtime dependency | `esbuild ^0.25` |
|
|
250
361
|
| Node.js requirement | 20+ |
|
|
251
362
|
| Side effects | Writes files to `outputDir`, creates/removes temp directory |
|
|
363
|
+
|
|
364
|
+
## Changelog
|
|
365
|
+
|
|
366
|
+
| Version | Change |
|
|
367
|
+
|---|---|
|
|
368
|
+
| 1.3.3 | `BrodoxImage` stub: added `direct` prop support |
|
|
369
|
+
| 1.3.2 | `BrodoxLink` stub: added `data-brodox-link` attribute |
|
|
370
|
+
| 1.3.1 | Initial public release |
|
package/dist/index.d.mts
CHANGED
|
@@ -13,8 +13,12 @@ interface CompileInput {
|
|
|
13
13
|
interface CompileOutput {
|
|
14
14
|
serverJsPath: string;
|
|
15
15
|
clientJsPath: string;
|
|
16
|
+
/** Compiled CSS bundle path, or null if no CSS files were imported. */
|
|
17
|
+
cssPath: string | null;
|
|
16
18
|
serverJsName: string;
|
|
17
19
|
clientJsName: string;
|
|
20
|
+
/** CSS bundle filename, or null if no CSS files were imported. */
|
|
21
|
+
cssName: string | null;
|
|
18
22
|
compiledAt: Date;
|
|
19
23
|
}
|
|
20
24
|
|
package/dist/index.d.ts
CHANGED
|
@@ -13,8 +13,12 @@ interface CompileInput {
|
|
|
13
13
|
interface CompileOutput {
|
|
14
14
|
serverJsPath: string;
|
|
15
15
|
clientJsPath: string;
|
|
16
|
+
/** Compiled CSS bundle path, or null if no CSS files were imported. */
|
|
17
|
+
cssPath: string | null;
|
|
16
18
|
serverJsName: string;
|
|
17
19
|
clientJsName: string;
|
|
20
|
+
/** CSS bundle filename, or null if no CSS files were imported. */
|
|
21
|
+
cssName: string | null;
|
|
18
22
|
compiledAt: Date;
|
|
19
23
|
}
|
|
20
24
|
|
package/dist/index.js
CHANGED
|
@@ -117,7 +117,18 @@ import { createElement, Fragment, Children } from 'react';
|
|
|
117
117
|
var __islandSeq = 0;
|
|
118
118
|
function __nextIslandId() { return 'bi-' + (++__islandSeq) + '-' + Math.random().toString(36).slice(2,7); }
|
|
119
119
|
|
|
120
|
-
export function BrodoxImage({ src, alt, width, height, fill, className, style, priority, quality = 75, sizes }) {
|
|
120
|
+
export function BrodoxImage({ src, alt, width, height, fill, className, style, priority, quality = 75, sizes, direct = false }) {
|
|
121
|
+
const imgStyle = fill
|
|
122
|
+
? Object.assign({ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover' }, style || {})
|
|
123
|
+
: (style || {});
|
|
124
|
+
if (direct) {
|
|
125
|
+
return createElement('img', {
|
|
126
|
+
src, alt: alt || '',
|
|
127
|
+
width: fill ? undefined : width, height: fill ? undefined : height,
|
|
128
|
+
loading: priority ? 'eager' : 'lazy', decoding: 'async',
|
|
129
|
+
className, style: imgStyle,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
121
132
|
const maxW = width || 1920;
|
|
122
133
|
const widths = [320, 640, 768, 1024, 1280, 1920].filter(w => w <= maxW);
|
|
123
134
|
if (!widths.length) widths.push(maxW);
|
|
@@ -125,9 +136,6 @@ export function BrodoxImage({ src, alt, width, height, fill, className, style, p
|
|
|
125
136
|
const enc = encodeURIComponent(src);
|
|
126
137
|
const optimisedSrc = '/api/image?src=' + enc + '&w=' + maxW + '&q=' + q + '&fmt=webp';
|
|
127
138
|
const srcSet = widths.map(w => '/api/image?src=' + enc + '&w=' + w + '&q=' + q + '&fmt=webp ' + w + 'w').join(', ');
|
|
128
|
-
const imgStyle = fill
|
|
129
|
-
? Object.assign({ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover' }, style || {})
|
|
130
|
-
: (style || {});
|
|
131
139
|
return createElement('img', {
|
|
132
140
|
src: optimisedSrc, srcSet, sizes, alt: alt || '',
|
|
133
141
|
width: fill ? undefined : width, height: fill ? undefined : height,
|
|
@@ -146,9 +154,29 @@ export function useRouter() {
|
|
|
146
154
|
|
|
147
155
|
export function useParams() { return {}; }
|
|
148
156
|
|
|
149
|
-
export function BrodoxHead(
|
|
157
|
+
export function BrodoxHead({ title, description }) {
|
|
158
|
+
if (title && typeof globalThis.__brodoxCollectHead === 'function')
|
|
159
|
+
globalThis.__brodoxCollectHead({ type: 'title', props: { content: title } });
|
|
160
|
+
if (description && typeof globalThis.__brodoxCollectHead === 'function')
|
|
161
|
+
globalThis.__brodoxCollectHead({ type: 'meta', props: { name: 'description', content: description } });
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
150
164
|
|
|
151
|
-
export function BrodoxFont(
|
|
165
|
+
export function BrodoxFont({ href, family, weights, display }) {
|
|
166
|
+
weights = weights || [400, 700];
|
|
167
|
+
display = display || 'swap';
|
|
168
|
+
var url = href;
|
|
169
|
+
if (!url && family) {
|
|
170
|
+
url = 'https://fonts.googleapis.com/css2?family='
|
|
171
|
+
+ encodeURIComponent(family) + ':wght@' + weights.join(';') + '&display=' + display;
|
|
172
|
+
}
|
|
173
|
+
if (url && typeof globalThis.__brodoxCollectHead === 'function') {
|
|
174
|
+
globalThis.__brodoxCollectHead({ type: 'link', props: { rel: 'preconnect', href: 'https://fonts.googleapis.com' } });
|
|
175
|
+
globalThis.__brodoxCollectHead({ type: 'link', props: { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossOrigin: 'anonymous' } });
|
|
176
|
+
globalThis.__brodoxCollectHead({ type: 'link', props: { rel: 'stylesheet', href: url } });
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
152
180
|
|
|
153
181
|
export function BrodoxRouter({ children }) {
|
|
154
182
|
return createElement(Fragment, null, children);
|
|
@@ -162,7 +190,7 @@ export function BrodoxRouter({ children }) {
|
|
|
162
190
|
* The IslandHydrator walks the live DOM and mounts the component from the
|
|
163
191
|
* parent bundle's __registry__ after page load.
|
|
164
192
|
*/
|
|
165
|
-
export function Client({ children }) {
|
|
193
|
+
export function Client({ children, hydration = 'load' }) {
|
|
166
194
|
var id = __nextIslandId();
|
|
167
195
|
var child = Children.only(children);
|
|
168
196
|
var compType = child.type;
|
|
@@ -177,13 +205,13 @@ export function Client({ children }) {
|
|
|
177
205
|
return createElement(Fragment, null,
|
|
178
206
|
createElement('div', {
|
|
179
207
|
'data-brodox-island': id,
|
|
180
|
-
'data-hydration':
|
|
208
|
+
'data-hydration': hydration,
|
|
181
209
|
'data-client-js': '',
|
|
182
210
|
'data-component-slug': '',
|
|
183
211
|
'data-version': '',
|
|
184
212
|
'data-component': name,
|
|
185
213
|
}),
|
|
186
|
-
createElement('script', {
|
|
214
|
+
hydration === 'none' ? null : createElement('script', {
|
|
187
215
|
type: 'application/json',
|
|
188
216
|
'data-brodox-props': id,
|
|
189
217
|
dangerouslySetInnerHTML: { __html: safeProps },
|
|
@@ -295,7 +323,9 @@ var BrodoxCompiler = class {
|
|
|
295
323
|
outfile: serverJsPath,
|
|
296
324
|
minify: false,
|
|
297
325
|
sourcemap: false,
|
|
298
|
-
define: { "process.env.NODE_ENV": '"production"' }
|
|
326
|
+
define: { "process.env.NODE_ENV": '"production"' },
|
|
327
|
+
loader: { ".css": "text" }
|
|
328
|
+
// CSS imports return empty string in server bundle
|
|
299
329
|
});
|
|
300
330
|
const clientComponents = [];
|
|
301
331
|
for (const file of input.files) {
|
|
@@ -333,14 +363,47 @@ ${registryEntries}
|
|
|
333
363
|
minify: true,
|
|
334
364
|
sourcemap: false,
|
|
335
365
|
define: { "process.env.NODE_ENV": '"production"' },
|
|
336
|
-
banner: { js: 'import React from "react";' }
|
|
366
|
+
banner: { js: 'import React from "react";' },
|
|
367
|
+
loader: { ".css": "text" }
|
|
368
|
+
// CSS imports return the CSS string (injected via BrodoxHead or style tag)
|
|
337
369
|
});
|
|
370
|
+
const hasCss = input.files.some((f) => /\.css$/.test(f.path));
|
|
371
|
+
const cssName = hasCss ? `${safeName}.css` : null;
|
|
372
|
+
const cssPath = hasCss ? import_node_path3.default.join(input.outputDir, cssName) : null;
|
|
373
|
+
if (hasCss && cssPath) {
|
|
374
|
+
try {
|
|
375
|
+
await esbuild.build({
|
|
376
|
+
entryPoints: [entryPoint],
|
|
377
|
+
bundle: true,
|
|
378
|
+
format: "esm",
|
|
379
|
+
platform: "browser",
|
|
380
|
+
jsx: "automatic",
|
|
381
|
+
external: CLIENT_EXTERNALS,
|
|
382
|
+
plugins: [serverStubPlugin()],
|
|
383
|
+
outfile: cssPath.replace(/\.css$/, ".css.tmp.js"),
|
|
384
|
+
// esbuild needs a JS outfile
|
|
385
|
+
minify: true,
|
|
386
|
+
sourcemap: false,
|
|
387
|
+
define: { "process.env.NODE_ENV": '"production"' },
|
|
388
|
+
loader: { ".css": "css" }
|
|
389
|
+
});
|
|
390
|
+
const defaultCssOut = cssPath.replace(/\.css$/, ".css.tmp.css");
|
|
391
|
+
try {
|
|
392
|
+
await import_promises3.default.rename(defaultCssOut, cssPath);
|
|
393
|
+
} catch {
|
|
394
|
+
}
|
|
395
|
+
await import_promises3.default.rm(cssPath.replace(/\.css$/, ".css.tmp.js"), { force: true });
|
|
396
|
+
} catch {
|
|
397
|
+
}
|
|
398
|
+
}
|
|
338
399
|
await import_promises3.default.rm(tmpDir, { recursive: true, force: true });
|
|
339
400
|
return {
|
|
340
401
|
serverJsPath,
|
|
341
402
|
clientJsPath,
|
|
403
|
+
cssPath,
|
|
342
404
|
serverJsName,
|
|
343
405
|
clientJsName,
|
|
406
|
+
cssName,
|
|
344
407
|
compiledAt: /* @__PURE__ */ new Date()
|
|
345
408
|
};
|
|
346
409
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -88,7 +88,18 @@ import { createElement, Fragment, Children } from 'react';
|
|
|
88
88
|
var __islandSeq = 0;
|
|
89
89
|
function __nextIslandId() { return 'bi-' + (++__islandSeq) + '-' + Math.random().toString(36).slice(2,7); }
|
|
90
90
|
|
|
91
|
-
export function BrodoxImage({ src, alt, width, height, fill, className, style, priority, quality = 75, sizes }) {
|
|
91
|
+
export function BrodoxImage({ src, alt, width, height, fill, className, style, priority, quality = 75, sizes, direct = false }) {
|
|
92
|
+
const imgStyle = fill
|
|
93
|
+
? Object.assign({ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover' }, style || {})
|
|
94
|
+
: (style || {});
|
|
95
|
+
if (direct) {
|
|
96
|
+
return createElement('img', {
|
|
97
|
+
src, alt: alt || '',
|
|
98
|
+
width: fill ? undefined : width, height: fill ? undefined : height,
|
|
99
|
+
loading: priority ? 'eager' : 'lazy', decoding: 'async',
|
|
100
|
+
className, style: imgStyle,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
92
103
|
const maxW = width || 1920;
|
|
93
104
|
const widths = [320, 640, 768, 1024, 1280, 1920].filter(w => w <= maxW);
|
|
94
105
|
if (!widths.length) widths.push(maxW);
|
|
@@ -96,9 +107,6 @@ export function BrodoxImage({ src, alt, width, height, fill, className, style, p
|
|
|
96
107
|
const enc = encodeURIComponent(src);
|
|
97
108
|
const optimisedSrc = '/api/image?src=' + enc + '&w=' + maxW + '&q=' + q + '&fmt=webp';
|
|
98
109
|
const srcSet = widths.map(w => '/api/image?src=' + enc + '&w=' + w + '&q=' + q + '&fmt=webp ' + w + 'w').join(', ');
|
|
99
|
-
const imgStyle = fill
|
|
100
|
-
? Object.assign({ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover' }, style || {})
|
|
101
|
-
: (style || {});
|
|
102
110
|
return createElement('img', {
|
|
103
111
|
src: optimisedSrc, srcSet, sizes, alt: alt || '',
|
|
104
112
|
width: fill ? undefined : width, height: fill ? undefined : height,
|
|
@@ -117,9 +125,29 @@ export function useRouter() {
|
|
|
117
125
|
|
|
118
126
|
export function useParams() { return {}; }
|
|
119
127
|
|
|
120
|
-
export function BrodoxHead(
|
|
128
|
+
export function BrodoxHead({ title, description }) {
|
|
129
|
+
if (title && typeof globalThis.__brodoxCollectHead === 'function')
|
|
130
|
+
globalThis.__brodoxCollectHead({ type: 'title', props: { content: title } });
|
|
131
|
+
if (description && typeof globalThis.__brodoxCollectHead === 'function')
|
|
132
|
+
globalThis.__brodoxCollectHead({ type: 'meta', props: { name: 'description', content: description } });
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
121
135
|
|
|
122
|
-
export function BrodoxFont(
|
|
136
|
+
export function BrodoxFont({ href, family, weights, display }) {
|
|
137
|
+
weights = weights || [400, 700];
|
|
138
|
+
display = display || 'swap';
|
|
139
|
+
var url = href;
|
|
140
|
+
if (!url && family) {
|
|
141
|
+
url = 'https://fonts.googleapis.com/css2?family='
|
|
142
|
+
+ encodeURIComponent(family) + ':wght@' + weights.join(';') + '&display=' + display;
|
|
143
|
+
}
|
|
144
|
+
if (url && typeof globalThis.__brodoxCollectHead === 'function') {
|
|
145
|
+
globalThis.__brodoxCollectHead({ type: 'link', props: { rel: 'preconnect', href: 'https://fonts.googleapis.com' } });
|
|
146
|
+
globalThis.__brodoxCollectHead({ type: 'link', props: { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossOrigin: 'anonymous' } });
|
|
147
|
+
globalThis.__brodoxCollectHead({ type: 'link', props: { rel: 'stylesheet', href: url } });
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
123
151
|
|
|
124
152
|
export function BrodoxRouter({ children }) {
|
|
125
153
|
return createElement(Fragment, null, children);
|
|
@@ -133,7 +161,7 @@ export function BrodoxRouter({ children }) {
|
|
|
133
161
|
* The IslandHydrator walks the live DOM and mounts the component from the
|
|
134
162
|
* parent bundle's __registry__ after page load.
|
|
135
163
|
*/
|
|
136
|
-
export function Client({ children }) {
|
|
164
|
+
export function Client({ children, hydration = 'load' }) {
|
|
137
165
|
var id = __nextIslandId();
|
|
138
166
|
var child = Children.only(children);
|
|
139
167
|
var compType = child.type;
|
|
@@ -148,13 +176,13 @@ export function Client({ children }) {
|
|
|
148
176
|
return createElement(Fragment, null,
|
|
149
177
|
createElement('div', {
|
|
150
178
|
'data-brodox-island': id,
|
|
151
|
-
'data-hydration':
|
|
179
|
+
'data-hydration': hydration,
|
|
152
180
|
'data-client-js': '',
|
|
153
181
|
'data-component-slug': '',
|
|
154
182
|
'data-version': '',
|
|
155
183
|
'data-component': name,
|
|
156
184
|
}),
|
|
157
|
-
createElement('script', {
|
|
185
|
+
hydration === 'none' ? null : createElement('script', {
|
|
158
186
|
type: 'application/json',
|
|
159
187
|
'data-brodox-props': id,
|
|
160
188
|
dangerouslySetInnerHTML: { __html: safeProps },
|
|
@@ -266,7 +294,9 @@ var BrodoxCompiler = class {
|
|
|
266
294
|
outfile: serverJsPath,
|
|
267
295
|
minify: false,
|
|
268
296
|
sourcemap: false,
|
|
269
|
-
define: { "process.env.NODE_ENV": '"production"' }
|
|
297
|
+
define: { "process.env.NODE_ENV": '"production"' },
|
|
298
|
+
loader: { ".css": "text" }
|
|
299
|
+
// CSS imports return empty string in server bundle
|
|
270
300
|
});
|
|
271
301
|
const clientComponents = [];
|
|
272
302
|
for (const file of input.files) {
|
|
@@ -304,14 +334,47 @@ ${registryEntries}
|
|
|
304
334
|
minify: true,
|
|
305
335
|
sourcemap: false,
|
|
306
336
|
define: { "process.env.NODE_ENV": '"production"' },
|
|
307
|
-
banner: { js: 'import React from "react";' }
|
|
337
|
+
banner: { js: 'import React from "react";' },
|
|
338
|
+
loader: { ".css": "text" }
|
|
339
|
+
// CSS imports return the CSS string (injected via BrodoxHead or style tag)
|
|
308
340
|
});
|
|
341
|
+
const hasCss = input.files.some((f) => /\.css$/.test(f.path));
|
|
342
|
+
const cssName = hasCss ? `${safeName}.css` : null;
|
|
343
|
+
const cssPath = hasCss ? path3.join(input.outputDir, cssName) : null;
|
|
344
|
+
if (hasCss && cssPath) {
|
|
345
|
+
try {
|
|
346
|
+
await esbuild.build({
|
|
347
|
+
entryPoints: [entryPoint],
|
|
348
|
+
bundle: true,
|
|
349
|
+
format: "esm",
|
|
350
|
+
platform: "browser",
|
|
351
|
+
jsx: "automatic",
|
|
352
|
+
external: CLIENT_EXTERNALS,
|
|
353
|
+
plugins: [serverStubPlugin()],
|
|
354
|
+
outfile: cssPath.replace(/\.css$/, ".css.tmp.js"),
|
|
355
|
+
// esbuild needs a JS outfile
|
|
356
|
+
minify: true,
|
|
357
|
+
sourcemap: false,
|
|
358
|
+
define: { "process.env.NODE_ENV": '"production"' },
|
|
359
|
+
loader: { ".css": "css" }
|
|
360
|
+
});
|
|
361
|
+
const defaultCssOut = cssPath.replace(/\.css$/, ".css.tmp.css");
|
|
362
|
+
try {
|
|
363
|
+
await fs3.rename(defaultCssOut, cssPath);
|
|
364
|
+
} catch {
|
|
365
|
+
}
|
|
366
|
+
await fs3.rm(cssPath.replace(/\.css$/, ".css.tmp.js"), { force: true });
|
|
367
|
+
} catch {
|
|
368
|
+
}
|
|
369
|
+
}
|
|
309
370
|
await fs3.rm(tmpDir, { recursive: true, force: true });
|
|
310
371
|
return {
|
|
311
372
|
serverJsPath,
|
|
312
373
|
clientJsPath,
|
|
374
|
+
cssPath,
|
|
313
375
|
serverJsName,
|
|
314
376
|
clientJsName,
|
|
377
|
+
cssName,
|
|
315
378
|
compiledAt: /* @__PURE__ */ new Date()
|
|
316
379
|
};
|
|
317
380
|
}
|