@zubra/server 5.4.0 → 5.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/package.json +12 -3
- package/src/core.js +520 -0
- package/src/edge.d.ts +44 -0
- package/src/edge.js +85 -0
- package/src/hono-edge.d.ts +3 -0
- package/src/hono-edge.js +12 -0
- package/src/hono-middleware.js +13 -0
- package/src/hono.js +4 -10
- package/src/index.js +40 -476
package/src/edge.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// @zubra/server without a disk: for Cloudflare Workers, Deno and anything else that has no
|
|
2
|
+
// components folder to read. You give it the compiler's WebAssembly module and the components'
|
|
3
|
+
// sources (a bundler's text imports, or strings), and it does what `createZubra` does for Node:
|
|
4
|
+
//
|
|
5
|
+
// import wasm from '@zubra/compiler/zubra.wasm';
|
|
6
|
+
// import priceTag from './components/price-tag.zb'; // a Text rule in wrangler.jsonc
|
|
7
|
+
// import { createZubra } from '@zubra/server/edge';
|
|
8
|
+
//
|
|
9
|
+
// const zubra = createZubra({ wasm, components: { 'price-tag': priceTag } });
|
|
10
|
+
// export default { fetch: async (request) => (await zubra.fetch(request)) ?? new Response(zubra.page().finish(...)) };
|
|
11
|
+
//
|
|
12
|
+
// The runtime (`@zubra/runtime`) is not served from here: there is no disk to serve it from. Put its
|
|
13
|
+
// files where your static assets are (Workers' `assets` directory) under `<prefix>/runtime/`, or point
|
|
14
|
+
// the import map at a CDN with `imports`. Everything else, including streaming (`page().streamResponse`)
|
|
15
|
+
// and `injectShadowRoots`, is the Node entry's, from the same code (`core.js`).
|
|
16
|
+
import { createCompiler } from '@zubra/compiler/wasm';
|
|
17
|
+
import { createCore, parseAttributes, propsFrom, SOURCE_EXTENSION } from './core.js';
|
|
18
|
+
|
|
19
|
+
export { parseAttributes, propsFrom };
|
|
20
|
+
|
|
21
|
+
/** `a/b/../c.zb` → `a/c.zb`, for the imports between components (always "/"-separated here). */
|
|
22
|
+
function normalize(path) {
|
|
23
|
+
const parts = [];
|
|
24
|
+
for (const part of path.split('/')) {
|
|
25
|
+
if (part === '' || part === '.') continue;
|
|
26
|
+
if (part === '..') parts.pop();
|
|
27
|
+
else parts.push(part);
|
|
28
|
+
}
|
|
29
|
+
return `/${parts.join('/')}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* `wasm`: the compiler's WebAssembly module (or `compiler`: an already made `createCompiler(…)`).
|
|
34
|
+
* `components`: `{ name: source }`, where a name is the path without its extension (`forms/field`),
|
|
35
|
+
* or with it (`forms/field.zb`, `card.zb.tsx`) when the extension isn't `.zb`. Components import
|
|
36
|
+
* each other by relative path, as they do on disk. The other options are `createZubra`'s: `prefix`,
|
|
37
|
+
* `cache`, `devtools` (off by default here: the DevTools need the runtime's development build,
|
|
38
|
+
* which you would have to serve yourself) and `imports`.
|
|
39
|
+
*/
|
|
40
|
+
export function createZubra({ wasm, compiler = wasm ? createCompiler(wasm) : null, components = {}, prefix = '/_zubra', cache = true, devtools = false, imports = {} } = {}) {
|
|
41
|
+
if (!compiler) throw new Error("@zubra/server/edge: createZubra() needs the compiler's WebAssembly module (`wasm`), or a `compiler` from createCompiler()");
|
|
42
|
+
const sources = new Map(); // path → source
|
|
43
|
+
const files = new Map(); // name → path
|
|
44
|
+
// In the order a folder walk finds them (each folder's entries sorted by name, a folder where its
|
|
45
|
+
// name falls), so the CSS in head() comes out in the same order as the Node entry's.
|
|
46
|
+
const entries = Object.entries(components).map(([key, source]) => {
|
|
47
|
+
const name = key.replace(SOURCE_EXTENSION, '').replace(/^\/+/, '');
|
|
48
|
+
const extension = key.match(SOURCE_EXTENSION)?.[0] ?? '.zb';
|
|
49
|
+
return { name, path: `/components/${name}${extension}`, source: String(source) };
|
|
50
|
+
});
|
|
51
|
+
const segments = (entry) => entry.path.slice('/components/'.length).split('/');
|
|
52
|
+
entries.sort((a, b) => {
|
|
53
|
+
const x = segments(a);
|
|
54
|
+
const y = segments(b);
|
|
55
|
+
for (let i = 0; i < Math.min(x.length, y.length); i++) if (x[i] !== y[i]) return x[i] < y[i] ? -1 : 1;
|
|
56
|
+
return x.length - y.length;
|
|
57
|
+
});
|
|
58
|
+
for (const { name, path, source } of entries) {
|
|
59
|
+
sources.set(path, source);
|
|
60
|
+
files.set(name, path);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// The component and the components it imports, by path: the rule @zubra/compiler's sources() follows on disk.
|
|
64
|
+
function sourcesOf(path, found = {}) {
|
|
65
|
+
if (path in found || !sources.has(path)) return found;
|
|
66
|
+
const source = sources.get(path);
|
|
67
|
+
found[path] = source;
|
|
68
|
+
const script = /\.[jt]sx$/.test(path) ? source : source.match(/<script[^>]*>([\s\S]*?)<\/script>/i)?.[1] ?? '';
|
|
69
|
+
for (const [, specifier] of script.matchAll(/import\s+(?:[^'"]*?from\s+)?['"]([^'"]+\.zb(?:\.[jt]sx)?)['"]/g)) {
|
|
70
|
+
if (specifier.startsWith('.')) sourcesOf(normalize(`${path.slice(0, path.lastIndexOf('/'))}/${specifier}`), found);
|
|
71
|
+
}
|
|
72
|
+
return found;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return createCore({
|
|
76
|
+
request: (body) => compiler.request(body),
|
|
77
|
+
files,
|
|
78
|
+
sourcesOf: (path) => sourcesOf(path),
|
|
79
|
+
cache,
|
|
80
|
+
devtools,
|
|
81
|
+
base: prefix.replace(/\/$/, ''),
|
|
82
|
+
imports,
|
|
83
|
+
where: 'the components given to createZubra()',
|
|
84
|
+
});
|
|
85
|
+
}
|
package/src/hono-edge.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Hono middleware for Cloudflare Workers (and Deno): the components come as sources, not a folder.
|
|
2
|
+
//
|
|
3
|
+
// import wasm from '@zubra/compiler/zubra.wasm';
|
|
4
|
+
// import { zubra } from '@zubra/server/hono-edge';
|
|
5
|
+
// app.use(zubra({ wasm, components: { 'price-tag': priceTagSource } }));
|
|
6
|
+
// app.get('/', (c) => c.html(c.get('zubra').finish(layout(c.get('zubra').render('price-tag', { cents })))));
|
|
7
|
+
import { createZubra } from './edge.js';
|
|
8
|
+
import { honoMiddleware } from './hono-middleware.js';
|
|
9
|
+
|
|
10
|
+
export function zubra(options) {
|
|
11
|
+
return honoMiddleware(createZubra(options));
|
|
12
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// The Hono middleware over any Zubra instance: serves its modules (and whatever else it serves
|
|
2
|
+
// under its prefix), and sets a page as `c.get('zubra')`. `hono.js` makes the instance from a
|
|
3
|
+
// components folder; `hono-edge.js` from sources, for Workers.
|
|
4
|
+
export function honoMiddleware(instance) {
|
|
5
|
+
const middleware = async (c, next) => {
|
|
6
|
+
const response = await instance.fetch(c.req.raw);
|
|
7
|
+
if (response) return response;
|
|
8
|
+
c.set('zubra', instance.page());
|
|
9
|
+
await next();
|
|
10
|
+
};
|
|
11
|
+
middleware.zubra = instance;
|
|
12
|
+
return middleware;
|
|
13
|
+
}
|
package/src/hono.js
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
// Hono middleware (Node.js, Bun, Deno, workers with a filesystem): serves the components'
|
|
2
|
-
// modules and the runtime, and sets a page as `c.get('zubra')`.
|
|
2
|
+
// modules and the runtime, and sets a page as `c.get('zubra')`. On Cloudflare Workers, which have
|
|
3
|
+
// no components folder, use `@zubra/server/hono-edge`.
|
|
3
4
|
//
|
|
4
5
|
// import { zubra } from '@zubra/server/hono';
|
|
5
6
|
// app.use(zubra({ components: 'components' }));
|
|
6
7
|
// app.get('/', (c) => c.html(c.get('zubra').finish(layout(c.get('zubra').render('price-tag', { cents })))));
|
|
7
8
|
import { createZubra } from './index.js';
|
|
9
|
+
import { honoMiddleware } from './hono-middleware.js';
|
|
8
10
|
|
|
9
11
|
export function zubra(options) {
|
|
10
|
-
|
|
11
|
-
const middleware = async (c, next) => {
|
|
12
|
-
const response = await instance.fetch(c.req.raw);
|
|
13
|
-
if (response) return response;
|
|
14
|
-
c.set('zubra', instance.page());
|
|
15
|
-
await next();
|
|
16
|
-
};
|
|
17
|
-
middleware.zubra = instance;
|
|
18
|
-
return middleware;
|
|
12
|
+
return honoMiddleware(createZubra(options));
|
|
19
13
|
}
|