@zikojs/server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +72 -0
  3. package/package.json +63 -0
  4. package/src/code-splitter/directives.js +5 -0
  5. package/src/code-splitter/index.js +1 -0
  6. package/src/config/index.js +13 -0
  7. package/src/count.sh +9 -0
  8. package/src/entry-client/index.js +45 -0
  9. package/src/entry-server/index.js +39 -0
  10. package/src/index.js +4 -0
  11. package/src/prerender/index.js +63 -0
  12. package/src/server/api-handler.js +22 -0
  13. package/src/server/dev-server.js +21 -0
  14. package/src/server/index.js +105 -0
  15. package/src/server/response.js +6 -0
  16. package/src/server/setup-middleware.js +21 -0
  17. package/src/server-only-utils/glob-imports.js +47 -0
  18. package/src/server-only-utils/import-middlwares.js +29 -0
  19. package/src/server-only-utils/import-prerendered-routes.js +11 -0
  20. package/src/server-only-utils/index.d.ts.txt +3 -0
  21. package/src/server-only-utils/index.js +9 -0
  22. package/src/server-only-utils/manifest-parser.js +22 -0
  23. package/src/server-only-utils/read-files.js +20 -0
  24. package/src/server-only-utils/renderDomToString.js +8 -0
  25. package/src/server-only-utils/renderToString.js +8 -0
  26. package/src/server-only-utils/resolve-static-routes.js +64 -0
  27. package/src/server-only-utils/write-to-dist.js +24 -0
  28. package/src/setup/files-content.js +53 -0
  29. package/src/setup/generate-hydration-map.js +3 -0
  30. package/src/setup/generate-routes.js +22 -0
  31. package/src/setup/index.js +3 -0
  32. package/src/setup/setup-ziko-folder.js +16 -0
  33. package/src/template.js +14 -0
  34. package/src/utils/api.js +7 -0
  35. package/src/utils/html-template.js +19 -0
  36. package/src/utils/index.js +4 -0
  37. package/src/utils/isAsync.js +3 -0
  38. package/src/utils/normalize-path.js +21 -0
  39. package/src/utils/routes-matcher.js +216 -0
  40. package/src/vite/index.js +1 -0
  41. package/src/vite/vite_setup.js +37 -0
  42. package/types/global.d.ts +11 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 ZAKARIA ELALAOUI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,72 @@
1
+ [![ziko-server banner](https://raw.githubusercontent.com/zikojs/.github/main/assets/banners/ziko-server.svg)](https://github.com/zikojs)
2
+
3
+ # Ziko-server
4
+
5
+ Server-side rendering for Zikojs with file-based routing and client-side hydration.
6
+
7
+ # Features
8
+ - ✅ Server Side Rendering
9
+ - ✅ Client Side Hydration with no additional config
10
+ - ✅ File Based Routing
11
+ - ✅ Mdzjs Supports
12
+ - ✅ Supports Dynamic Routes
13
+ - ✅ Supports both Sync And Async rendering
14
+ - ✅ Supports API
15
+
16
+ <!--
17
+ compare with type
18
+ -->
19
+
20
+ # Project Structure
21
+ ```
22
+ App
23
+ ├──public/
24
+ ├── src/
25
+ ├── .ziko #generated
26
+ ├── pages/ # required
27
+ ├── index.js
28
+ ├── components/
29
+ ├── layouts/
30
+ ├── contents/
31
+ ├── db/
32
+ ├── i18n/
33
+ ├── middlewares.js
34
+ ├── tsconfig.json
35
+ ├── ziko.config.js
36
+ ├── package.json
37
+
38
+ ```
39
+
40
+
41
+ # Config
42
+
43
+ ```js
44
+ // server.js
45
+ import { createServer } from "ziko-server/server";
46
+ createServer()
47
+ ```
48
+
49
+ ```js
50
+ // entry-server.js
51
+ import {defineServerEntry} from "ziko-server/entry-server";
52
+ export default defineServerEntry({
53
+ pages : import.meta.glob("./pages/**/*{.js,.mdz}")
54
+ })
55
+ ```
56
+ ```js
57
+ // entry-client.js
58
+ import {EntryClient} from "ziko-server/entry-client";
59
+ EntryClient({
60
+ pages : import.meta.glob("./pages/**/*{.js,.mdz}")
61
+ })
62
+ ```
63
+
64
+ # ⭐️ Show your support
65
+
66
+ If you appreciate the project, kindly demonstrate your support by giving it a star!<br>
67
+
68
+ [![Star](https://img.shields.io/github/stars/zakarialaoui10/ziko-server?style=social)](https://github.com/zakarialaoui10/ziko-server)
69
+ <!--## Financial support-->
70
+ # License
71
+ This projet is licensed under the terms of MIT License
72
+ <img src="https://img.shields.io/github/license/zakarialaoui10/ziko-server?color=rgb%2820%2C21%2C169%29" width="100" align="right">
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@zikojs/server",
3
+ "version": "0.1.0",
4
+ "description": "server side rendering in zikojs with file-based-routing and client side hydration",
5
+ "type": "module",
6
+ "source": "src/index.js",
7
+ "files": [
8
+ "src",
9
+ "types",
10
+ "dist",
11
+ "LICENCE"
12
+ ],
13
+ "exports": {
14
+ ".": "./src/index.js",
15
+ "./*": "./src/*/index.js"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "keywords": [
21
+ "ziko",
22
+ "zikojs",
23
+ "ssr",
24
+ "ssg",
25
+ "rendering",
26
+ "hydration",
27
+ "server",
28
+ "middlewares"
29
+ ],
30
+ "scripts": {
31
+ "dev": "rollup -c rollup.config.js -w",
32
+ "build": "rollup -c rollup.config.js",
33
+ "test": "echo \"Error: no test specified\" && exit 1",
34
+ "start-demo": "cd demo && npm run dev "
35
+ },
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/zakarialaoui10/ziko-server.git"
39
+ },
40
+ "author": "zakaria elalaoui",
41
+ "license": "MIT",
42
+ "bugs": {
43
+ "url": "https://github.com/zakarialaoui10/ziko-server/issues"
44
+ },
45
+ "homepage": "https://github.com/zakarialaoui10/ziko-server#readme",
46
+ "devDependencies": {
47
+ "@rollup/plugin-commonjs": "^29.0.3",
48
+ "@rollup/plugin-node-resolve": "^16.0.3",
49
+ "@rollup/plugin-terser": "^1.0.0",
50
+ "rollup": "^4.62.4"
51
+ },
52
+ "dependencies": {
53
+ "compression": "^1.8.1",
54
+ "cross-env": "^10.1.0",
55
+ "express": "^5.2.1",
56
+ "extendom": "^0.0.0",
57
+ "fast-glob": "^3.3.3",
58
+ "jsdom": "^30.0.1",
59
+ "sirv": "^3.0.2",
60
+ "vite": "^8.2.1",
61
+ "ziko": "^1.8.0"
62
+ }
63
+ }
@@ -0,0 +1,5 @@
1
+ export const define_derictives=()=>{
2
+ globalThis.use_server_only = Symbol('use-server-only');
3
+ globalThis.use_server_only_end = Symbol('use-server-only-end');
4
+ globalThis.use_client_only = Symbol('use-client-only-end')
5
+ }
@@ -0,0 +1 @@
1
+ export * from './directives.js'
@@ -0,0 +1,13 @@
1
+ import { defineConfig as defineViteConfig } from "vite";
2
+ import { vite_setup } from "../vite/index.js";
3
+ export function defineConfig({ outDir = "dist" } = {}) {
4
+ return defineViteConfig(({ command, mode }) => {
5
+ const base_config = vite_setup({
6
+ outDir,
7
+ mode,
8
+ });
9
+ base_config.plugins = base_config.plugins || [];
10
+ base_config.aaa = 10
11
+ return base_config;
12
+ });
13
+ }
package/src/count.sh ADDED
@@ -0,0 +1,9 @@
1
+ #!/bin/bash
2
+
3
+ # Count total lines in all .js and .ts files, excluding dist and node_modules
4
+ total_lines=$(find . -type f \( -name "*.js" -o -name "*.ts" \) \
5
+ -not -path "*/node_modules/*" \
6
+ -not -path "*/dist/*" \
7
+ -exec wc -l {} + | awk '{total += $1} END {print total}')
8
+
9
+ echo "Total JS + TS lines: $total_lines"
@@ -0,0 +1,45 @@
1
+ import {
2
+ normalize_path,
3
+ routesMatcher,
4
+ dynamicRoutesParser,
5
+ isDynamic,
6
+ } from "../utils/index.js";
7
+
8
+ export function EntryClient({ base = '', pages } = {}) {
9
+
10
+ if (import.meta.env.DEV) pages = import.meta.glob("/src/pages/**/*{.js,.mdz}");
11
+ pages = import.meta.glob("/src/pages/**/*{.js,.mdz}");
12
+ addEventListener("load", async () => {
13
+ const data = JSON.parse(document.head.querySelector('script#ziko-data')?.textContent ?? "{}")
14
+ globalThis.Ziko = data;
15
+ const root = "./pages/";
16
+ async function hydrate(path) {
17
+ if (path.endsWith("/")) path = path.slice(0, -1);
18
+ const matchEntry = Object.entries(pages).find(([route]) =>
19
+ routesMatcher(normalize_path(route, root), `/${path}`)
20
+ );
21
+ if (!matchEntry) return;
22
+
23
+ const [mask, moduleImport] = matchEntry;
24
+
25
+ const module = await moduleImport();
26
+ let UIElement;
27
+
28
+ if (isDynamic(mask)) {
29
+ const params = dynamicRoutesParser(mask, `/${path}`);
30
+ UIElement = await module.default(params);
31
+ } else {
32
+ UIElement = await module.default();
33
+ }
34
+
35
+ UIElement.unmount();
36
+
37
+ const elementsToHydrate = [...document.querySelectorAll('[data-hydration-index]')];
38
+ elementsToHydrate.forEach(el => {
39
+ el.replaceWith(__Ziko__.__HYDRATION__.store.get(+el.dataset.hydrationIndex)().element);
40
+ });
41
+ }
42
+
43
+ hydrate(location.pathname.slice(1));
44
+ });
45
+ }
@@ -0,0 +1,39 @@
1
+ import {
2
+ renderToString,
3
+ globImports,
4
+ readFiles,
5
+ } from "../server-only-utils/index.js";
6
+
7
+ import {
8
+ routesMatcher,
9
+ dynamicRoutesParser,
10
+ isDynamic
11
+ } from "../utils/routes-matcher.js";
12
+
13
+ export function EntryServer() {
14
+ return async function render(path, req, res) {
15
+ if(path.endsWith("/")) path = path.slice(0, -1);
16
+ // const all_files = await readFiles()
17
+ const pairs = await globImports("./src/pages/**/*{.js,.mdz}")
18
+
19
+ let [mask, module] = Object.entries(pairs).find(([route]) => routesMatcher(route, `/${path}`));
20
+ let UIElement, ui, params;
21
+ if (isDynamic(mask)) params = dynamicRoutesParser(mask, `/${path}`);
22
+ const {Component, GET, POST, DELETE, UPDATE, head, prerender} = await module
23
+ if(Component){
24
+ UIElement = params ? await Component.call(this, params) : await Component();
25
+ ui = renderToString(UIElement);
26
+ }
27
+
28
+ return {
29
+ head,
30
+ ui,
31
+ prerender,
32
+ hydration_map : __Ziko__.__HYDRATION__,
33
+ GET,
34
+ POST,
35
+ DELETE,
36
+ UPDATE,
37
+ };
38
+ };
39
+ }
package/src/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export {EntryClient} from "./entry-client/index.js";
2
+ export {EntryServer} from "./entry-server/index.js";
3
+ export {createServer} from "./server/index.js";
4
+ export * from './prerender/index.js'
@@ -0,0 +1,63 @@
1
+ import {
2
+ globImports,
3
+ renderToString,
4
+ writeToDist,
5
+ ManifestParser,
6
+ resolveStaticRoutes,
7
+ } from "ziko-server/server-only-utils";
8
+
9
+ import { routesGrouper } from "../utils/routes-matcher.js";
10
+
11
+ const StaticRoutesMap = {
12
+ "/blog/[...slug]":[
13
+ { slug : 1 },
14
+ { slug : 2 },
15
+ { slug : 3 }
16
+ ],
17
+ "/bb/[lang]/dd/[id]":[
18
+ { lang : "en", id : 1},
19
+ { lang : "en", id : 2},
20
+ { lang : "en", id : 3},
21
+ { lang : "fr", id : 1},
22
+ { lang : "fr", id : 2},
23
+ { lang : "fr", id : 3}
24
+ ]
25
+ }
26
+
27
+
28
+
29
+ async function prerender({outDir = 'dist'} = "") {
30
+ const PRERENDERED_ROUTES = []
31
+ const pages = await globImports("./src/pages/**/*{.js,.ts,.mdz}")
32
+ const StaticPages = await resolveStaticRoutes(pages, StaticRoutesMap)
33
+
34
+ const grouped = routesGrouper(pages)
35
+
36
+ const Manifest = new ManifestParser(`${outDir}/.client/.vite/manifest.json`)
37
+ for(let route in grouped.static){
38
+ const Page = grouped.static[route];
39
+ const {Component, head} = Page
40
+ if(Component){
41
+ const res = await Component();
42
+ const ui = renderToString(res)
43
+ PRERENDERED_ROUTES.push(route)
44
+ writeToDist({route, ui, head, entry_client_path : Manifest.EntryClientFile, outDir})
45
+ }
46
+ }
47
+ for(let route in StaticPages){
48
+ const Page = StaticPages[route];
49
+ const {Component, head} = Page
50
+ if(Component){
51
+ const res = await Component();
52
+ const ui = renderToString(res)
53
+ PRERENDERED_ROUTES.push(route)
54
+ // console.log({hi :__Ziko__.__HYDRATION__.store, route})
55
+ // __Ziko__.__HYDRATION__.reset()
56
+ writeToDist({route, ui, head, entry_client_path : Manifest.EntryClientFile, outDir})
57
+ }
58
+ // console.log(PRERENDERED_ROUTES)
59
+ }
60
+ }
61
+
62
+
63
+ export { prerender }
@@ -0,0 +1,22 @@
1
+ export async function API_HANDLER(fn, req, res) {
2
+ const result = await fn(req, res);
3
+
4
+ if (result instanceof Response) {
5
+ const body = await result.text();
6
+ res
7
+ .status(result.status || 200)
8
+ .set(Object.fromEntries(result.headers.entries()))
9
+ .send(body);
10
+ return;
11
+ }
12
+ if (typeof result === "object" && result !== null) {
13
+ res.json(result);
14
+ return;
15
+ }
16
+ if (["string", "number", "boolean"].includes(typeof result)) {
17
+ res.send(String(result));
18
+ return;
19
+ }
20
+ if (result === undefined) return;
21
+ res.status(500).send("Unsupported response type");
22
+ }
@@ -0,0 +1,21 @@
1
+ export const dev_server = async (app, base)=>{
2
+ const { createServer } = await import("vite");
3
+ let vite = await createServer({
4
+ server: { middlewareMode: true },
5
+ appType: "custom",
6
+ base,
7
+ });
8
+ app.use(vite.middlewares);
9
+ return vite
10
+ }
11
+
12
+ // import {join} from 'path'
13
+ // export const prod_server = async (app, base, baseDir)=>{
14
+ // const compression = (await import("compression")).default;
15
+ // const sirv = (await import("sirv")).default;
16
+ // app.use(compression());
17
+ // app.use(
18
+ // base,
19
+ // sirv(join(baseDir, "./dist/.client"), { extensions: [] }),
20
+ // );
21
+ // }
@@ -0,0 +1,105 @@
1
+ import express from "express";
2
+ import { readFile } from "node:fs/promises";
3
+ import path, { join } from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ import { dev_server } from "./dev-server.js";
6
+ import { API_HANDLER } from "./api-handler.js";
7
+ import {
8
+ importMiddlewares,
9
+ importPrerenderedRoutes,
10
+ } from '../server-only-utils/index.js'
11
+
12
+ import { setup_ziko_folder } from "../setup/setup-ziko-folder.js";
13
+ import { SetupMiddleware } from './setup-middleware.js'
14
+
15
+ export async function createServer({ baseDir = process.cwd(), port = process.env.PORT || 5173 } = {}){
16
+ await setup_ziko_folder(join(baseDir, './.ziko-experimental'))
17
+ const isProduction = process.env.NODE_ENV === "production";
18
+ const base = process.env.BASE || "/";
19
+ const HTML_TEMPLATE = isProduction ? await readFile(join(baseDir, "./dist/.client/.ziko/index.html"), "utf-8") : "";
20
+ const app = express();
21
+
22
+ const Middlewares = await importMiddlewares()
23
+ app.use(Middlewares.logger)
24
+ app.use(SetupMiddleware)
25
+
26
+ let vite;
27
+ if (!isProduction) vite = await dev_server(app, base)
28
+ else {
29
+ const compression = (await import("compression")).default;
30
+ const sirv = (await import("sirv")).default;
31
+ app.use(compression());
32
+ app.use(
33
+ base,
34
+ sirv(join(baseDir, "./dist/.client"), { extensions: [] }),
35
+ );
36
+ }
37
+
38
+ // Serve HTML
39
+ app.use('/.client', express.static(path.join(process.cwd(), 'dist/.client')))
40
+ // app.use(express.static('public'))
41
+
42
+ if(isProduction){
43
+ const PRERENDERED_ROUTES = await importPrerenderedRoutes()
44
+ for(let i=0; i<PRERENDERED_ROUTES.length; i++){
45
+ app.get(PRERENDERED_ROUTES[i], (req, res)=>{
46
+ res.sendFile(path.join(process.cwd(), `dist/${PRERENDERED_ROUTES[i]}/index.html`))
47
+ })
48
+ }
49
+ }
50
+
51
+
52
+ app.get('/--ziko--', (req, res)=>{
53
+ res.json(globalThis.Ziko)
54
+ })
55
+
56
+ app.use(async (req, res) => {
57
+ try {
58
+ const url = req.originalUrl.replace(base, "");
59
+ let template;
60
+ let render;
61
+ if (!isProduction) {
62
+ template = await readFile(join(baseDir, "./.ziko/index.html"), "utf-8");
63
+ template = await vite.transformIndexHtml(url, template);
64
+ render = (await vite.ssrLoadModule("/.ziko/entry-server.js")).default;
65
+ }
66
+ else {
67
+ template = HTML_TEMPLATE;
68
+ const ENTRY_SERVER_PATH = pathToFileURL(join(baseDir, "./dist/.server/entry-server.js")).href;
69
+ render = (await import(/* @vite-ignore */ENTRY_SERVER_PATH)).default;
70
+ }
71
+ const rendered = await render(url, req);
72
+ const page = await rendered(url, req);
73
+ if(page.GET) return await API_HANDLER(page.GET, req, res)
74
+ if(page.POST) return await API_HANDLER(page.POST, req, res)
75
+ if(page.PUT) return await API_HANDLER(page.PUT, req, res)
76
+ if(page.DELETE) return await API_HANDLER(page.DELETE, req, res)
77
+ if(page.PATCH) return await API_HANDLER(page.PATCH, req, res)
78
+
79
+ const html = template
80
+ .replace(
81
+ '<!--app-session-data-->',
82
+ `<script type='application/json' id='ziko-data'>
83
+ ${JSON.stringify(Ziko, null, 2)}
84
+ </script>
85
+ `
86
+ )
87
+ // .replace(`<!--app-head-->`, page.head ?? "")
88
+ .replace(`<!--app-html-->`, page.ui ?? "")
89
+
90
+ res.status(200).set({ "Content-Type": "text/html" }).send(html);
91
+ }
92
+ catch (e) {
93
+ vite?.ssrFixStacktrace(e);
94
+ res.status(500).end(e.stack);
95
+ }
96
+ });
97
+
98
+ app.listen(port, () => {
99
+ console.log(`Server started at http://localhost:${port}`);
100
+ });
101
+
102
+ return app;
103
+ }
104
+
105
+
@@ -0,0 +1,6 @@
1
+ // type ? : ssr | ssg | json
2
+ // env ? : Dev | Prod
3
+ //
4
+ const response=(type, data)=>{
5
+
6
+ }
@@ -0,0 +1,21 @@
1
+ // import { join } from 'path';
2
+ // import { pathToFileURL } from 'url';
3
+ // const ziko_config_path = pathToFileURL(join(process.cwd(), 'ziko.config.js')).href;
4
+
5
+ export function SetupMiddleware(req, res, next) {
6
+ const protocol = req.protocol || (req.connection.encrypted ? 'https' : 'http');
7
+ const host = req.headers.host;
8
+ const origin = `${protocol}://${host}`
9
+ globalThis.Ziko = {
10
+ engine: 'zikojs',
11
+ isProd : process.env.NODE_ENV === "production",
12
+ url : req.url,
13
+ protocol,
14
+ host,
15
+ origin,
16
+ locals: {},
17
+ };
18
+ if(globalThis?.__Ziko__) __Ziko__.__HYDRATION__.reset();
19
+ Object.assign(req, {Ziko})
20
+ next();
21
+ }
@@ -0,0 +1,47 @@
1
+ import fg from 'fast-glob';
2
+ import path from 'path';
3
+ import { pathToFileURL } from 'url';
4
+ import { normalize_path } from '../utils/normalize-path.js';
5
+
6
+ export async function globImports(pattern = './src/pages/**/*.{js,ts,jsx,tsx,mdz}', { cwd = process.cwd() , root = "./pages/"} = {}) {
7
+ const files = await fg(pattern, { cwd });
8
+ const modules = {};
9
+
10
+ for (const file of files) {
11
+ const absPath = path.resolve(cwd, file);
12
+ const fileUrl = pathToFileURL(absPath).href;
13
+ const key = './' + file.replace(/\\/g, '/');
14
+ modules[key] = () => import(/* @vite-ignore */fileUrl);
15
+ }
16
+
17
+ const routes = Object.keys(modules);
18
+
19
+ const pairs = {};
20
+ for (let i = 0; i < routes.length; i++) {
21
+ const module = await modules[routes[i]]();
22
+ const {
23
+ default : Component,
24
+ head,
25
+ prerender,
26
+ GET,
27
+ POST,
28
+ PUT,
29
+ DELETE,
30
+ PATCH
31
+ } = await module
32
+
33
+ Object.assign(pairs, {
34
+ [normalize_path(routes[i], root)]: {
35
+ ...(Component && {Component}),
36
+ ...(head && {head}),
37
+ ...(prerender !== undefined && {prerender}),
38
+ ...(GET && {GET}),
39
+ ...(POST && {POST}),
40
+ ...(PUT && {PUT}),
41
+ ...(PATCH && {PATCH}),
42
+ ...(DELETE && {DELETE}),
43
+ } });
44
+ }
45
+
46
+ return pairs
47
+ }
@@ -0,0 +1,29 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { pathToFileURL } from 'url';
4
+
5
+ export async function importMiddlewares({ cwd = process.cwd() } = {}) {
6
+ const possibleFiles = [
7
+ path.join(cwd, 'src', 'middleware.js'),
8
+ path.join(cwd, 'src', 'middleware.ts')
9
+ ];
10
+
11
+ for (const file of possibleFiles) {
12
+ if (fs.existsSync(file)) {
13
+ const fileUrl = pathToFileURL(file).href;
14
+ const module = await import(/* @vite-ignore */ fileUrl);
15
+
16
+ const middlewares = {};
17
+ for (const [key, value] of Object.entries(module)) {
18
+ if (typeof value === 'function') {
19
+ middlewares[key] = value;
20
+ }
21
+ }
22
+
23
+ return middlewares;
24
+ }
25
+ }
26
+
27
+ // No middleware file found
28
+ return {};
29
+ }
@@ -0,0 +1,11 @@
1
+ import { join } from 'path';
2
+ import { pathToFileURL } from 'url';
3
+
4
+ export async function importPrerenderedRoutes({ cwd = process.cwd(), isProduction } = {}) {
5
+ const file = isProduction
6
+ ? join(cwd, 'dist/.server/prerenderd-routes.js')
7
+ : join(cwd, '.ziko/cache/.prerenderd-routes.js')
8
+ const FileUrl = pathToFileURL(file).href;
9
+ const module = await import(/* @vite-ignore */ FileUrl);
10
+ return module?.PRERENDERED_ROUTES || []
11
+ }
@@ -0,0 +1,3 @@
1
+ import {UIElement} from "ziko"
2
+ export declare function renderToString(UIElement: UIElement): string;
3
+ export declare function renderDomToString(UIElement: HTMLElement): string;
@@ -0,0 +1,9 @@
1
+ export * from "./renderToString.js";
2
+ export * from "./renderDomToString.js";
3
+ export * from "./glob-imports.js";
4
+ export * from "./write-to-dist.js";
5
+ export * from './manifest-parser.js';
6
+ export * from './import-prerendered-routes.js'
7
+ export * from './import-middlwares.js'
8
+ export * from './resolve-static-routes.js'
9
+ export * from './read-files.js'
@@ -0,0 +1,22 @@
1
+ import {readFileSync, existsSync} from 'fs';
2
+ import {join} from 'path';
3
+
4
+ export class ManifestParser {
5
+ constructor(relative_path){
6
+ this.#init(relative_path)
7
+ }
8
+ #init(relative_path){
9
+ const file = join(process.cwd(), relative_path);
10
+ if (existsSync(file)) this.manifest = JSON.parse(readFileSync(file, "utf-8"));
11
+ else {
12
+ console.error(`Manifest file not found: ${file}`)
13
+ this.manifest = null
14
+ }
15
+ }
16
+ get EntryClientFile(){
17
+ return this.manifest['.ziko/entry-client.js'].file
18
+ }
19
+ }
20
+
21
+ // const manifest = new ManifestParser('./dist/client/.vite/manifest.json')
22
+ // console.log(manifest.files)
@@ -0,0 +1,20 @@
1
+ import fg from "fast-glob";
2
+ import { readFile } from "fs/promises";
3
+ import { resolve, relative } from "path";
4
+
5
+ export async function readFiles(pattern = './src/**/*.{js,ts}', { cwd = process.cwd() , root = "./pages/"} = {}) {
6
+ const files = await fg(pattern, { cwd });
7
+ const absoluteRoot = resolve(root);
8
+ console.log({files})
9
+ const modules = {};
10
+
11
+ const result = new Map()
12
+ for (const file of files) {
13
+ const code = await readFile(file, "utf8");
14
+ if(!code.includes('use client')) continue;
15
+ // Normalize route to something like "src/pages/index.js"
16
+ const route = relative(absoluteRoot, file).replace(/\\/g, "/");
17
+ result.set(route, code);
18
+ }
19
+ console.log(result)
20
+ }
@@ -0,0 +1,8 @@
1
+ import {JSDOM} from "jsdom"
2
+ const {document} = new JSDOM().window;
3
+ globalThis.document = document
4
+
5
+ const renderDomToString=UIElement=>UIElement.outerHTML;
6
+ export {
7
+ renderDomToString
8
+ }
@@ -0,0 +1,8 @@
1
+ import {JSDOM} from "jsdom"
2
+ const {document} = new JSDOM().window;
3
+ globalThis.document = document
4
+
5
+ const renderToString=UIElement=>UIElement.element?.outerHTML;
6
+ export {
7
+ renderToString
8
+ }
@@ -0,0 +1,64 @@
1
+ export async function resolveStaticRoutes(routes, StaticRoutesMap) {
2
+ const result = {};
3
+
4
+ for (const [routePattern, handler] of Object.entries(routes)) {
5
+ const { Component, prerender, head } = handler;
6
+ if(!Component || prerender === false) continue;
7
+ const staticParamsList = StaticRoutesMap[routePattern];
8
+ if (staticParamsList && /\[.*\]/.test(routePattern)) {
9
+ for (const params of staticParamsList) {
10
+ let resolvedRoute = routePattern;
11
+
12
+ // Handle [...param] → can include slashes
13
+ resolvedRoute = resolvedRoute.replace(/\[\.\.\.(\w+)\]/g, (_, key) => {
14
+ return encodeURIComponent(params[key])?.replace(/%2F/g, '/');
15
+ });
16
+
17
+ // Handle [param]+ or [param]
18
+ resolvedRoute = resolvedRoute.replace(/\[(\w+)\]\+?/g, (_, key) => {
19
+ return encodeURIComponent(params[key]);
20
+ });
21
+
22
+ result[resolvedRoute] = {
23
+ Component : () => Component(params),
24
+ head
25
+ }
26
+ }
27
+ } else {
28
+ result[routePattern] = {
29
+ Component,
30
+ head
31
+ }
32
+ }
33
+ }
34
+
35
+ return result;
36
+ }
37
+
38
+
39
+ // DEMO
40
+
41
+ // const StaticRoutesMap = {
42
+ // "/blog/[...slug]": [
43
+ // { slug: "2025/oct/post1" },
44
+ // { slug: "2025/oct/post2" },
45
+ // { slug: "2025/nov/post3" }
46
+ // ],
47
+ // "/user/[id]+": [
48
+ // { id: 1 },
49
+ // { id: 2 },
50
+ // { id: 3 }
51
+ // ],
52
+ // "/product/[category]/[id]": [
53
+ // { category: "books", id: 12 },
54
+ // { category: "tech", id: 5 }
55
+ // ]
56
+ // };
57
+
58
+ // const routes = {
59
+ // "/blog/[...slug]": (params) => `Blog page for ${params.slug}`,
60
+ // "/user/[id]+": (params) => `User ${params.id}`,
61
+ // "/product/[category]/[id]": (params) => `Product ${params.category} #${params.id}`
62
+ // };
63
+
64
+ // resolveStaticRoutes(routes, StaticRoutesMap).then(console.log);
@@ -0,0 +1,24 @@
1
+ import {mkdir, writeFile} from 'fs/promises';
2
+ import {join, dirname} from 'path';
3
+
4
+ export async function writeToDist({route, ui, head, outDir = 'dist', entry_client_path = ''}={}) {
5
+ entry_client_path = `/.client/${entry_client_path}`
6
+ const out = `
7
+ <!doctype html>
8
+ <html>
9
+ <head>
10
+ <script type="module" src="${entry_client_path}"></script>
11
+ <script type='application/json' id='ziko-data'>
12
+ ${JSON.stringify(globalThis?.Ziko ?? {}, null, 2)}
13
+ </script>
14
+ </head>
15
+ <body>
16
+ ${ui}
17
+ </body>
18
+ </html>
19
+ `
20
+ const filePath = join(outDir, route === '/' ? '' : route, 'index.html');
21
+ await mkdir(dirname(filePath), { recursive: true });
22
+ await writeFile(filePath, out, 'utf8');
23
+ console.log(`✔️ Saved ${route} → ${filePath}`);
24
+ }
@@ -0,0 +1,53 @@
1
+ const HTMLINDEX = `
2
+ <!doctype html>
3
+ <html>
4
+ <head>
5
+ <title>Ziko - App </title>
6
+ <meta charset="UTF-8" />
7
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
8
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9
+ <script type="module" src="/.ziko/entry-client.js"></script>
10
+ <!--app-head-->
11
+ </head>
12
+ <body>
13
+ <!--app-html-->
14
+ </body>
15
+ </html>
16
+ `.trim()
17
+
18
+ const SERVER = `
19
+ import { createServer } from "ziko-server/server";
20
+ createServer()
21
+ `.trim()
22
+
23
+ const ENTRY_SERVER = `
24
+ import {EntryServer} from "ziko-server/entry-server";
25
+ export default EntryServer
26
+ `.trim()
27
+
28
+ const ENTRY_CLIENT = `
29
+ import {EntryClient} from "ziko-server/entry-client";
30
+ EntryClient({
31
+ base : new URL(".", import.meta.url).pathname
32
+ })
33
+ `.trim()
34
+
35
+ const PRERENDER_SCRIPT = `
36
+ import {prerender} from 'ziko-server'
37
+ prerender()
38
+ `
39
+
40
+ const GENERATE_ROUTES_SCRIPT = `
41
+ import { generate_routes } from "ziko-server/setup";
42
+ generate_routes()
43
+ `
44
+
45
+ export const Files_Content = {
46
+ 'index.html': HTMLINDEX,
47
+ 'server.js' : SERVER,
48
+ 'entry-server.js' : ENTRY_SERVER,
49
+ 'entry-client.js' : ENTRY_CLIENT,
50
+ 'scripts/prerender.js' : PRERENDER_SCRIPT,
51
+ 'scripts/generate-routes.js' : GENERATE_ROUTES_SCRIPT,
52
+ 'cache/.keep' : null
53
+ }
@@ -0,0 +1,3 @@
1
+ export async function generate_hydration_map() {
2
+
3
+ }
@@ -0,0 +1,22 @@
1
+ import {writeFileSync} from "fs"
2
+ import { join } from "path";
3
+
4
+ const __pages__ = {
5
+ "/src/pages/index.js": () => import("/src/pages/index.js"),
6
+ "/src/pages/me.js": () => import("/src/pages/me.js"),
7
+ "/src/pages/about/index.js": () => import("/src/pages/about/index.js"),
8
+ };
9
+
10
+ export function generate_routes(){
11
+ const Pages = stringify(__pages__);
12
+ const Output = `export const pages = ${Pages}`
13
+ const path = join(process.cwd(), './.ziko/cache/.generated-routes.js')
14
+ writeFileSync(path, Output)
15
+ }
16
+
17
+ function stringify(obj) {
18
+ const entries = Object.entries(obj).map(
19
+ ([key, val]) => ` "${key}": ${val.toString()}`
20
+ );
21
+ return `{\n${entries.join(",\n")}\n}`;
22
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./generate-routes.js"
2
+ export * from './setup-ziko-folder.js'
3
+ export * from './generate-hydration-map.js'
@@ -0,0 +1,16 @@
1
+ import { mkdir, writeFile } from 'fs/promises';
2
+ import { dirname, join } from 'path';
3
+ import { Files_Content } from './files-content.js';
4
+
5
+ export async function setup_ziko_folder(basePath) {
6
+ for (const [file, content] of Object.entries(Files_Content)) {
7
+ const fullPath = join(basePath, file);
8
+ const dir = dirname(fullPath);
9
+ await mkdir(dir, { recursive: true });
10
+ try {
11
+ const existing = await readFile(fullPath, 'utf8');
12
+ if (existing === content) continue;
13
+ } catch {}
14
+ await writeFile(fullPath, content ?? '', 'utf8');
15
+ }
16
+ }
@@ -0,0 +1,14 @@
1
+ const HTML_TEMPLATE =`
2
+ <!doctype html>
3
+ <html>
4
+ <head>
5
+ <title> Ziko - Ssr - Template </title>
6
+ <!--app-head-->
7
+ </head>
8
+ <body>
9
+ <!--app-html-->
10
+ </body>
11
+ </html>
12
+ `.trim()
13
+
14
+ export default HTML_TEMPLATE;
@@ -0,0 +1,7 @@
1
+ export function isAPI(fn) {
2
+ return (
3
+ fn
4
+ && (typeof fn === 'function')
5
+ && ['GET', 'POST', 'DELETE', 'PUT'].includes(fn.name)
6
+ )
7
+ }
@@ -0,0 +1,19 @@
1
+ export const HTMLTemplate = `
2
+ <!doctype html>
3
+ <html lang="en">
4
+ <head>
5
+ <title>Z</title>
6
+ <meta charset="UTF-8" />
7
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
8
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9
+ <meta name="description" content="test ziko-ssr"/>
10
+ <title>Vite App</title>
11
+ <!--app-head-->
12
+ </head>
13
+ <body>
14
+ <!--app-html-->
15
+ <script type="module" src="/src/entry-client.js" async></script>
16
+ <!-- <script type="module" src="node_modules/ziko-server/entry/client.js" async></script> -->
17
+ </body>
18
+ </html>
19
+ `
@@ -0,0 +1,4 @@
1
+ export * from "./normalize-path.js";
2
+ export * from "./routes-matcher.js";
3
+ export * from "./html-template.js";
4
+ export * from "./isAsync.js";
@@ -0,0 +1,3 @@
1
+ export function isAsync(fn) {
2
+ return fn && fn.constructor && fn.constructor.name === "AsyncFunction";
3
+ }
@@ -0,0 +1,21 @@
1
+ export function normalize_path(inputPath, root = './src/pages', extensions = ['js', 'ts']) {
2
+ if(root.at(-1)==="/") root = root.slice(0, -1)
3
+ const normalizedPath = inputPath.replace(/\\/g, '/')
4
+ // .replace(/\[(\w+)\]/g, '$1/:$1');
5
+ const parts = normalizedPath.split('/');
6
+ const rootParts = root.split('/');
7
+ const rootIndex = parts.indexOf(rootParts[rootParts.length - 1]);
8
+ if (rootIndex !== -1) {
9
+ const subsequentParts = parts.slice(rootIndex + 1);
10
+ const lastPart = subsequentParts[subsequentParts.length - 1];
11
+ const isIndexFile = lastPart === 'index.js' || lastPart === 'index.ts';
12
+ const hasValidExtension = extensions.some(ext => lastPart === `.${ext}` || lastPart.endsWith(`.${ext}`));
13
+ if (isIndexFile) {
14
+ return '/' + (subsequentParts.length > 1 ? subsequentParts.slice(0, -1).join('/') : '');
15
+ }
16
+ if (hasValidExtension) {
17
+ return '/' + subsequentParts.join('/').replace(/\.(js|ts)$/, '');
18
+ }
19
+ }
20
+ return '';
21
+ }
@@ -0,0 +1,216 @@
1
+ function routesMatcher(mask, route) {
2
+ const maskSegments = mask.split("/").filter(Boolean);
3
+ const routeSegments = route.split("/").filter(Boolean);
4
+
5
+ let i = 0,
6
+ j = 0;
7
+
8
+ while (i < maskSegments.length && j < routeSegments.length) {
9
+ const maskSegment = maskSegments[i];
10
+ const routeSegment = routeSegments[j];
11
+
12
+ // Catch-all segment `[...param]`
13
+ if (maskSegment.startsWith("[...") && maskSegment.endsWith("]")) {
14
+ const remainingMaskSegments = maskSegments.length - i - 1;
15
+
16
+ if (remainingMaskSegments === 0) {
17
+ // Last segment, matches all remaining
18
+ return true;
19
+ }
20
+
21
+ // Calculate minimum required route segments for remaining mask
22
+ let requiredSegments = 0;
23
+ for (let k = i + 1; k < maskSegments.length; k++) {
24
+ if (!maskSegments[k].endsWith("]+")) {
25
+ requiredSegments++;
26
+ }
27
+ }
28
+
29
+ const remainingRouteSegments = routeSegments.length - j;
30
+ if (remainingRouteSegments < requiredSegments) return false;
31
+
32
+ const segmentsToConsume = remainingRouteSegments - requiredSegments;
33
+ if (segmentsToConsume < 1) return false;
34
+
35
+ j += segmentsToConsume;
36
+ i++;
37
+ continue;
38
+ }
39
+
40
+ // Optional `[param]+`
41
+ if (maskSegment.startsWith("[") && maskSegment.endsWith("]+")) {
42
+ if (routeSegment) j++;
43
+ i++;
44
+ continue;
45
+ }
46
+
47
+ // Dynamic `[param]`
48
+ if (maskSegment.startsWith("[") && maskSegment.endsWith("]")) {
49
+ i++;
50
+ j++;
51
+ continue;
52
+ }
53
+
54
+ // Must match static segment
55
+ if (maskSegment !== routeSegment) return false;
56
+
57
+ i++;
58
+ j++;
59
+ }
60
+
61
+ // Handle optional parameters at the end
62
+ while (i < maskSegments.length) {
63
+ const seg = maskSegments[i];
64
+ if (seg.endsWith("]+")) {
65
+ i++;
66
+ continue;
67
+ }
68
+ return false;
69
+ }
70
+
71
+ return i === maskSegments.length && j === routeSegments.length;
72
+ }
73
+
74
+ function dynamicRoutesParser(mask, route) {
75
+ const maskSegments = mask.split("/").filter(Boolean);
76
+ const routeSegments = route.split("/").filter(Boolean);
77
+ const params = {};
78
+
79
+ let i = 0,
80
+ j = 0;
81
+
82
+ while (i < maskSegments.length && j < routeSegments.length) {
83
+ const maskSegment = maskSegments[i];
84
+ const routeSegment = routeSegments[j];
85
+
86
+ // Catch-all `[...param]`
87
+ if (maskSegment.startsWith("[...") && maskSegment.endsWith("]")) {
88
+ const paramName = maskSegment.slice(4, -1);
89
+
90
+ const remainingMaskSegments = maskSegments.length - i - 1;
91
+ if (remainingMaskSegments === 0) {
92
+ params[paramName] = routeSegments.slice(j).join("/");
93
+ break;
94
+ }
95
+
96
+ let requiredSegments = 0;
97
+ for (let k = i + 1; k < maskSegments.length; k++) {
98
+ if (!maskSegments[k].endsWith("]+")) {
99
+ requiredSegments++;
100
+ }
101
+ }
102
+
103
+ const remainingRouteSegments = routeSegments.length - j;
104
+ const segmentsToConsume = remainingRouteSegments - requiredSegments;
105
+
106
+ if (segmentsToConsume >= 1) {
107
+ params[paramName] = routeSegments
108
+ .slice(j, j + segmentsToConsume)
109
+ .join("/");
110
+ j += segmentsToConsume;
111
+ } else {
112
+ return {};
113
+ }
114
+
115
+ i++;
116
+ continue;
117
+ }
118
+
119
+ // Optional `[param]+`
120
+ if (maskSegment.startsWith("[") && maskSegment.endsWith("]+")) {
121
+ const paramName = maskSegment.slice(1, -2);
122
+ if (routeSegment) {
123
+ params[paramName] = routeSegment;
124
+ j++;
125
+ }
126
+ i++;
127
+ continue;
128
+ }
129
+
130
+ // Dynamic `[param]`
131
+ if (maskSegment.startsWith("[") && maskSegment.endsWith("]")) {
132
+ const paramName = maskSegment.slice(1, -1);
133
+ params[paramName] = routeSegment;
134
+ } else if (maskSegment !== routeSegment) {
135
+ return {};
136
+ }
137
+
138
+ i++;
139
+ j++;
140
+ }
141
+
142
+ return params;
143
+ }
144
+
145
+ function isDynamic(path) {
146
+ const DynamicPattern = /(:\w+|\[\.\.\.\w+\]|\[\w+\]\+?)/;
147
+ return DynamicPattern.test(path);
148
+ }
149
+
150
+
151
+ function routesGrouper(routeMap) {
152
+ const grouped = {
153
+ static: {},
154
+ dynamic: {},
155
+ };
156
+
157
+ for (const [path, value] of Object.entries(routeMap)) {
158
+ if (isDynamic(path)) {
159
+ const segments = path.split("/").filter(Boolean);
160
+ const optionalIndex = segments.findIndex(seg => seg.endsWith("]+"));
161
+ const hasInvalidOptional =
162
+ optionalIndex !== -1 && optionalIndex !== segments.length - 1;
163
+
164
+ if (hasInvalidOptional) {
165
+ throw new Error(
166
+ `Invalid optional param position in route: "${path}" — optional parameters can only appear at the end.`,
167
+ );
168
+ }
169
+
170
+ grouped.dynamic[path] = value;
171
+ } else {
172
+ grouped.static[path] = value;
173
+ }
174
+ }
175
+
176
+ return grouped;
177
+ }
178
+
179
+
180
+ export { routesMatcher, dynamicRoutesParser, isDynamic, routesGrouper };
181
+
182
+ // // DEMO
183
+ // console.log("=== EXISTING TESTS ===");
184
+ // console.log(routesMatcher("/user/[id]+", "/user")); // true
185
+ // console.log(routesMatcher("/user/[id]+", "/user/42")); // true
186
+ // console.log(routesMatcher("/blog/[...slug]", "/blog/a/b")); // true
187
+ // console.log(routesMatcher("/blog/[id]", "/blog")); // false
188
+ // console.log(routesMatcher("/product/:id", "/product/99")); // true
189
+
190
+ // console.log("\n=== PARSER TESTS ===");
191
+ // console.log(dynamicRoutesParser("/user/[id]+", "/user"));
192
+ // // 👉 {}
193
+
194
+ // console.log(dynamicRoutesParser("/user/[id]+", "/user/42"));
195
+ // // 👉 { id: "42" }
196
+
197
+ // console.log(dynamicRoutesParser("/blog/[...slug]", "/blog/2025/oct/post"));
198
+ // // 👉 { slug: "2025/oct/post" }
199
+
200
+ // console.log(
201
+ // dynamicRoutesParser("/product/[category]/[id]+", "/product/electronics"),
202
+ // );
203
+ // // 👉 { category: "electronics" }
204
+
205
+ // console.log("\n=== FIX TEST ===");
206
+ // console.log(dynamicRoutesParser("/[...slug]/[id]", "/sl1/sl2/9"));
207
+ // // 👉 { slug: "sl1/sl2", id: "9" }
208
+
209
+ // console.log(dynamicRoutesParser("/[slug]/[...id]", "/sl1/id1/id2"));
210
+ // // 👉 { slug: "sl1", id: "id1/id2" }
211
+
212
+ // console.log(dynamicRoutesParser("/blog/lang/[lang]/id/[id]", "/blog/lang/en/id/10"));
213
+ // // 👉 { lang: "en", id: "10" }
214
+
215
+
216
+ // // Only the last one that can be optional
@@ -0,0 +1 @@
1
+ export * from './vite_setup.js'
@@ -0,0 +1,37 @@
1
+ import { resolve } from 'path'
2
+ export function vite_setup({outDir = 'dist'} = {}){
3
+ const Target = process.env.TARGET;
4
+ const isClient = (Target === 'client')
5
+ const isServer = (Target === 'server')
6
+ if(isClient){
7
+ return {
8
+ build : {
9
+ modulePreload: false,
10
+ outDir : `${outDir}/.client`,
11
+ emptyOutDir: true,
12
+ manifest: true,
13
+ rollupOptions: {
14
+ input: {
15
+ main : resolve(process.cwd(), ".ziko/entry-client.js"),
16
+ index : resolve(process.cwd(), '.ziko/index.html')
17
+ },
18
+ output: {
19
+ entryFileNames: `assets/[name].[hash].js`,
20
+ chunkFileNames: `assets/[name].[hash].js`,
21
+ assetFileNames: `assets/[name].[hash].[ext]`,
22
+ },
23
+ },
24
+ }
25
+ }
26
+ }
27
+ if(isServer){
28
+ return {
29
+ build : {
30
+ outDir : `${outDir}/.server`,
31
+ ssr : resolve(process.cwd(), ".ziko/entry-server.js"),
32
+ copyPublicDir: false,
33
+ }
34
+ }
35
+ }
36
+ return {}
37
+ }
@@ -0,0 +1,11 @@
1
+ export {};
2
+
3
+ declare global {
4
+ const Ziko: {
5
+ locals? : {
6
+ auth? : {}
7
+ }
8
+ };
9
+ const use_server_only : unique symbol;
10
+ const use_server_only_end : unique symbol;
11
+ }