@blinkk/root 1.0.0-rc.4 → 1.0.0-rc.40
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/LICENSE +22 -0
- package/bin/root.js +6 -32
- package/dist/{chunk-MJCIAH6K.js → chunk-7IDJ3PBT.js} +1 -1
- package/dist/{chunk-MJCIAH6K.js.map → chunk-7IDJ3PBT.js.map} +1 -1
- package/dist/{chunk-CY3DVKXO.js → chunk-HRGMPZCK.js} +443 -116
- package/dist/chunk-HRGMPZCK.js.map +1 -0
- package/dist/chunk-IUQLRDFW.js +237 -0
- package/dist/chunk-IUQLRDFW.js.map +1 -0
- package/dist/{chunk-J2ANSYAE.js → chunk-ODXHLJOY.js} +82 -9
- package/dist/chunk-ODXHLJOY.js.map +1 -0
- package/dist/{chunk-WNXIRMFF.js → chunk-TZAHHHA4.js} +27 -7
- package/dist/chunk-TZAHHHA4.js.map +1 -0
- package/dist/cli.d.ts +18 -3
- package/dist/cli.js +8 -4
- package/dist/core.d.ts +5 -3
- package/dist/core.js +8 -3
- package/dist/core.js.map +1 -1
- package/dist/functions.d.ts +1 -1
- package/dist/functions.js +7 -7
- package/dist/functions.js.map +1 -1
- package/dist/middleware.d.ts +12 -5
- package/dist/middleware.js +3 -1
- package/dist/node.d.ts +10 -2
- package/dist/node.js +5 -1
- package/dist/render.d.ts +1 -1
- package/dist/render.js +351 -289
- package/dist/render.js.map +1 -1
- package/dist/{types-9209ea89.d.ts → types-403nR8i5.d.ts} +96 -8
- package/package.json +38 -35
- package/dist/chunk-CY3DVKXO.js.map +0 -1
- package/dist/chunk-DFBTOMQF.js +0 -61
- package/dist/chunk-DFBTOMQF.js.map +0 -1
- package/dist/chunk-J2ANSYAE.js.map +0 -1
- package/dist/chunk-WNXIRMFF.js.map +0 -1
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
// src/node/load-config.ts
|
|
6
6
|
import path2 from "node:path";
|
|
7
7
|
import { bundleRequire } from "bundle-require";
|
|
8
|
+
import { build } from "esbuild";
|
|
8
9
|
|
|
9
10
|
// src/utils/fsutils.ts
|
|
10
11
|
import { promises as fs } from "node:fs";
|
|
@@ -26,6 +27,12 @@ async function makeDir(dirpath) {
|
|
|
26
27
|
await fs.mkdir(dirpath, { recursive: true });
|
|
27
28
|
}
|
|
28
29
|
}
|
|
30
|
+
async function copyDir(srcdir, dstdir) {
|
|
31
|
+
if (!fsExtra.existsSync(srcdir)) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
fsExtra.copySync(srcdir, dstdir, { overwrite: true });
|
|
35
|
+
}
|
|
29
36
|
async function copyGlob(pattern, srcdir, dstdir) {
|
|
30
37
|
const files = await glob(pattern, { cwd: srcdir });
|
|
31
38
|
if (files.length > 0) {
|
|
@@ -42,6 +49,10 @@ async function loadJson(filepath) {
|
|
|
42
49
|
const content = await fs.readFile(filepath, "utf-8");
|
|
43
50
|
return JSON.parse(content);
|
|
44
51
|
}
|
|
52
|
+
async function writeJson(filepath, data) {
|
|
53
|
+
const content = JSON.stringify(data, null, 2);
|
|
54
|
+
await writeFile(filepath, content);
|
|
55
|
+
}
|
|
45
56
|
async function isDirectory(dirpath) {
|
|
46
57
|
return fs.stat(dirpath).then((fsStat) => {
|
|
47
58
|
return fsStat.isDirectory();
|
|
@@ -89,17 +100,62 @@ async function loadRootConfig(rootDir, options) {
|
|
|
89
100
|
}
|
|
90
101
|
return Object.assign({}, config, { rootDir });
|
|
91
102
|
}
|
|
103
|
+
async function bundleRootConfig(rootDir, outPath) {
|
|
104
|
+
const configPath = path2.resolve(rootDir, "root.config.ts");
|
|
105
|
+
const exists = await fileExists(configPath);
|
|
106
|
+
if (!exists) {
|
|
107
|
+
throw new Error(`${configPath} does not exist`);
|
|
108
|
+
}
|
|
109
|
+
await build({
|
|
110
|
+
entryPoints: [configPath],
|
|
111
|
+
bundle: true,
|
|
112
|
+
minify: true,
|
|
113
|
+
platform: "node",
|
|
114
|
+
outfile: outPath,
|
|
115
|
+
sourcemap: "inline",
|
|
116
|
+
metafile: true,
|
|
117
|
+
format: "esm",
|
|
118
|
+
plugins: [
|
|
119
|
+
{
|
|
120
|
+
name: "externalize-deps",
|
|
121
|
+
setup(build2) {
|
|
122
|
+
build2.onResolve({ filter: /.*/ }, (args) => {
|
|
123
|
+
const id = args.path;
|
|
124
|
+
if (id[0] !== "." && !path2.isAbsolute(id)) {
|
|
125
|
+
return {
|
|
126
|
+
external: true
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
async function loadBundledConfig(rootDir, options) {
|
|
137
|
+
const configPath = path2.resolve(rootDir, "dist/root.config.js");
|
|
138
|
+
const exists = await fileExists(configPath);
|
|
139
|
+
if (!exists) {
|
|
140
|
+
throw new Error(`${configPath} does not exist`);
|
|
141
|
+
}
|
|
142
|
+
const module = await import(configPath);
|
|
143
|
+
let config = module.default || {};
|
|
144
|
+
if (typeof config === "function") {
|
|
145
|
+
config = await config(options) || {};
|
|
146
|
+
}
|
|
147
|
+
return Object.assign({}, config, { rootDir });
|
|
148
|
+
}
|
|
92
149
|
|
|
93
150
|
// src/node/vite.ts
|
|
94
151
|
import { createServer } from "vite";
|
|
95
152
|
async function createViteServer(rootConfig, options) {
|
|
96
|
-
var _a, _b, _c;
|
|
97
153
|
const rootDir = rootConfig.rootDir;
|
|
98
154
|
const viteConfig = rootConfig.vite || {};
|
|
99
|
-
let hmrOptions =
|
|
100
|
-
if (
|
|
155
|
+
let hmrOptions = viteConfig.server?.hmr;
|
|
156
|
+
if (options?.hmr === false) {
|
|
101
157
|
hmrOptions = false;
|
|
102
|
-
} else if (typeof hmrOptions === "undefined" &&
|
|
158
|
+
} else if (typeof hmrOptions === "undefined" && options?.port) {
|
|
103
159
|
hmrOptions = { port: options.port + 10 };
|
|
104
160
|
}
|
|
105
161
|
const viteServer = await createServer({
|
|
@@ -117,16 +173,29 @@ async function createViteServer(rootConfig, options) {
|
|
|
117
173
|
},
|
|
118
174
|
appType: "custom",
|
|
119
175
|
optimizeDeps: {
|
|
176
|
+
// As of vite v5 / esbuild v19, experimentalDecorators need to be
|
|
177
|
+
// explicitly set, and for some reason this option isn't read from the
|
|
178
|
+
// project's tsconfig.json file by default.
|
|
179
|
+
// See: https://vitejs.dev/blog/announcing-vite5
|
|
180
|
+
esbuildOptions: {
|
|
181
|
+
tsconfigRaw: {
|
|
182
|
+
compilerOptions: {
|
|
183
|
+
target: "esnext",
|
|
184
|
+
experimentalDecorators: true,
|
|
185
|
+
useDefineForClassFields: false
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
},
|
|
120
189
|
...viteConfig.optimizeDeps || {},
|
|
121
190
|
include: [
|
|
122
|
-
...
|
|
123
|
-
...
|
|
191
|
+
...options?.optimizeDeps || [],
|
|
192
|
+
...viteConfig.optimizeDeps?.include || []
|
|
124
193
|
],
|
|
125
|
-
extensions: [...
|
|
194
|
+
extensions: [...viteConfig.optimizeDeps?.extensions || [], ".tsx"]
|
|
126
195
|
},
|
|
127
196
|
ssr: {
|
|
128
197
|
...viteConfig.ssr || {},
|
|
129
|
-
noExternal: ["@blinkk/root"]
|
|
198
|
+
noExternal: ["@blinkk/root", "@blinkk/root-cms/richtext"]
|
|
130
199
|
},
|
|
131
200
|
esbuild: {
|
|
132
201
|
...viteConfig.esbuild || {},
|
|
@@ -151,15 +220,19 @@ export {
|
|
|
151
220
|
isJsFile,
|
|
152
221
|
writeFile,
|
|
153
222
|
makeDir,
|
|
223
|
+
copyDir,
|
|
154
224
|
copyGlob,
|
|
155
225
|
rmDir,
|
|
156
226
|
loadJson,
|
|
227
|
+
writeJson,
|
|
157
228
|
isDirectory,
|
|
158
229
|
fileExists,
|
|
159
230
|
dirExists,
|
|
160
231
|
directoryContains,
|
|
161
232
|
loadRootConfig,
|
|
233
|
+
bundleRootConfig,
|
|
234
|
+
loadBundledConfig,
|
|
162
235
|
createViteServer,
|
|
163
236
|
viteSsrLoadModule
|
|
164
237
|
};
|
|
165
|
-
//# sourceMappingURL=chunk-
|
|
238
|
+
//# sourceMappingURL=chunk-ODXHLJOY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/node/load-config.ts","../src/utils/fsutils.ts","../src/node/vite.ts"],"sourcesContent":["import path from 'node:path';\nimport {bundleRequire} from 'bundle-require';\nimport {build} from 'esbuild';\nimport {RootConfig} from '../core/config';\nimport {fileExists} from '../utils/fsutils';\n\nexport interface ConfigOptions {\n command: string;\n}\n\nexport async function loadRootConfig(\n rootDir: string,\n options: ConfigOptions\n): Promise<RootConfig> {\n const configPath = path.resolve(rootDir, 'root.config.ts');\n const exists = await fileExists(configPath);\n if (!exists) {\n throw new Error(`${configPath} does not exist`);\n }\n const configBundle = await bundleRequire({\n filepath: configPath,\n });\n let config = configBundle.mod.default || {};\n if (typeof config === 'function') {\n config = (await config(options)) || {};\n }\n return Object.assign({}, config, {rootDir});\n}\n\n/**\n * Compiles a root.config.ts file to root.config.js.\n */\nexport async function bundleRootConfig(rootDir: string, outPath: string) {\n const configPath = path.resolve(rootDir, 'root.config.ts');\n const exists = await fileExists(configPath);\n if (!exists) {\n throw new Error(`${configPath} does not exist`);\n }\n await build({\n entryPoints: [configPath],\n bundle: true,\n minify: true,\n platform: 'node',\n outfile: outPath,\n sourcemap: 'inline',\n metafile: true,\n format: 'esm',\n plugins: [\n {\n name: 'externalize-deps',\n setup(build) {\n build.onResolve({filter: /.*/}, (args) => {\n const id = args.path;\n if (id[0] !== '.' && !path.isAbsolute(id)) {\n return {\n external: true,\n };\n }\n return null;\n });\n },\n },\n ],\n });\n}\n\n/**\n * Loads a pre-bundled config file from dist/root.config.js.\n */\nexport async function loadBundledConfig(\n rootDir: string,\n options: ConfigOptions\n): Promise<RootConfig> {\n const configPath = path.resolve(rootDir, 'dist/root.config.js');\n const exists = await fileExists(configPath);\n if (!exists) {\n throw new Error(`${configPath} does not exist`);\n }\n const module = await import(configPath);\n let config = module.default || {};\n if (typeof config === 'function') {\n config = (await config(options)) || {};\n }\n return Object.assign({}, config, {rootDir});\n}\n","import {promises as fs} from 'node:fs';\nimport path from 'node:path';\n\nimport fsExtra from 'fs-extra';\nimport glob from 'tiny-glob';\n\nexport function isJsFile(filename: string) {\n return !!filename.match(/\\.(j|t)sx?$/);\n}\n\nexport async function writeFile(filepath: string, content: string) {\n const dirPath = path.dirname(filepath);\n await makeDir(dirPath);\n await fs.writeFile(filepath, content);\n}\n\nexport async function makeDir(dirpath: string) {\n try {\n await fs.access(dirpath);\n } catch (e) {\n await fs.mkdir(dirpath, {recursive: true});\n }\n}\n\nexport async function copyDir(srcdir: string, dstdir: string) {\n if (!fsExtra.existsSync(srcdir)) {\n return;\n }\n fsExtra.copySync(srcdir, dstdir, {overwrite: true});\n}\n\n/**\n * Copies a glob of files from one directory to another.\n *\n * Example:\n *\n * ```\n * await copyGlob('*.css', 'src/styles', 'dist/html/styles');\n * ```\n */\nexport async function copyGlob(\n pattern: string,\n srcdir: string,\n dstdir: string\n) {\n const files = await glob(pattern, {cwd: srcdir});\n if (files.length > 0) {\n await makeDir(dstdir);\n }\n files.forEach((file) => {\n fsExtra.copySync(path.resolve(srcdir, file), path.resolve(dstdir, file));\n });\n}\n\nexport async function rmDir(dirpath: string) {\n await fs.rm(dirpath, {recursive: true, force: true});\n}\n\nexport async function loadJson<T = unknown>(filepath: string): Promise<T> {\n const content = await fs.readFile(filepath, 'utf-8');\n return JSON.parse(content);\n}\n\nexport async function writeJson(filepath: string, data: any) {\n const content = JSON.stringify(data, null, 2);\n await writeFile(filepath, content);\n}\n\nexport async function isDirectory(dirpath: string) {\n return fs\n .stat(dirpath)\n .then((fsStat) => {\n return fsStat.isDirectory();\n })\n .catch((err) => {\n if (err.code === 'ENOENT') {\n return false;\n }\n throw err;\n });\n}\n\nexport function fileExists(filepath: string): Promise<boolean> {\n return fs\n .access(filepath)\n .then(() => true)\n .catch(() => false);\n}\n\nexport async function dirExists(dirpath: string): Promise<boolean> {\n try {\n const stat = await fs.stat(dirpath);\n return stat.isDirectory();\n } catch (error) {\n if (error.code === 'ENOENT') {\n return false;\n }\n throw error;\n }\n}\n\nexport async function directoryContains(\n dirpath: string,\n subpath: string\n): Promise<boolean> {\n const outer = await fs.realpath(dirpath);\n const inner = await fs.realpath(subpath);\n const rel = path.relative(outer, inner);\n return !rel.startsWith('..');\n}\n\nexport async function listFilesRecursive(dirPath: string): Promise<string[]> {\n const files: string[] = [];\n const entries = await fs.readdir(dirPath, {withFileTypes: true});\n for (const entry of entries) {\n const fullPath = path.join(dirPath, entry.name);\n if (entry.isDirectory()) {\n const subFiles = await listFilesRecursive(fullPath);\n files.push(...subFiles);\n } else {\n files.push(fullPath);\n }\n }\n return files;\n}\n","import path from 'node:path';\n\nimport {createServer, ViteDevServer} from 'vite';\n\nimport {RootConfig} from '../core/config.js';\nimport {getVitePlugins} from '../core/plugin.js';\n\nexport interface CreateViteServerOptions {\n /** Override HMR settings. */\n hmr?: boolean;\n /** The port the server will run on. */\n port?: number;\n /** List of files to include in the optimizeDeps.include config. */\n optimizeDeps?: string[];\n}\n\n/**\n * Returns a vite dev server.\n */\nexport async function createViteServer(\n rootConfig: RootConfig,\n options?: CreateViteServerOptions\n): Promise<ViteDevServer> {\n const rootDir = rootConfig.rootDir;\n const viteConfig = rootConfig.vite || {};\n\n let hmrOptions = viteConfig.server?.hmr;\n if (options?.hmr === false) {\n hmrOptions = false;\n } else if (typeof hmrOptions === 'undefined' && options?.port) {\n // Automatically set the HMR port to `port + 10`. This allows multiple\n // root.js dev servers to run without conflicts.\n hmrOptions = {port: options.port + 10};\n }\n\n const viteServer = await createServer({\n ...viteConfig,\n mode: 'development',\n root: rootDir,\n // publicDir is disabled from the vite dev server since it's handled by the\n // root dev server directly, which allows user middlewares to override\n // files in the public dir.\n publicDir: false,\n server: {\n ...(viteConfig.server || {}),\n middlewareMode: true,\n hmr: hmrOptions,\n },\n appType: 'custom',\n optimizeDeps: {\n // As of vite v5 / esbuild v19, experimentalDecorators need to be\n // explicitly set, and for some reason this option isn't read from the\n // project's tsconfig.json file by default.\n // See: https://vitejs.dev/blog/announcing-vite5\n esbuildOptions: {\n tsconfigRaw: {\n compilerOptions: {\n target: 'esnext',\n experimentalDecorators: true,\n useDefineForClassFields: false,\n },\n },\n },\n ...(viteConfig.optimizeDeps || {}),\n include: [\n ...(options?.optimizeDeps || []),\n ...(viteConfig.optimizeDeps?.include || []),\n ],\n extensions: [...(viteConfig.optimizeDeps?.extensions || []), '.tsx'],\n },\n ssr: {\n ...(viteConfig.ssr || {}),\n noExternal: ['@blinkk/root', '@blinkk/root-cms/richtext'],\n },\n esbuild: {\n ...(viteConfig.esbuild || {}),\n jsx: 'automatic',\n jsxImportSource: 'preact',\n },\n plugins: [\n ...(viteConfig.plugins || []),\n ...getVitePlugins(rootConfig.plugins || []),\n ],\n });\n return viteServer;\n}\n\n/**\n * Shortcut `viteServer.ssrLoadModule()` without starting an actual dev server.\n */\nexport async function viteSsrLoadModule(\n rootConfig: RootConfig,\n file: string\n): Promise<Record<string, any>> {\n const viteServer = await createViteServer(rootConfig, {hmr: false});\n const module = await viteServer.ssrLoadModule(file);\n await viteServer.close();\n return module;\n}\n"],"mappings":"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","names":["path","path","build"]}
|
|
@@ -1,14 +1,32 @@
|
|
|
1
1
|
// src/middleware/common.ts
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import micromatch from "micromatch";
|
|
3
4
|
function rootProjectMiddleware(options) {
|
|
4
5
|
return (req, _, next) => {
|
|
5
6
|
req.rootConfig = options.rootConfig;
|
|
6
7
|
next();
|
|
7
8
|
};
|
|
8
9
|
}
|
|
10
|
+
function headersMiddleware(options) {
|
|
11
|
+
const headersUserConfig = options.rootConfig.server?.headers || [];
|
|
12
|
+
const headersConfig = headersUserConfig.filter((headerConfig) => {
|
|
13
|
+
return headerConfig.source && headerConfig.headers && headerConfig.headers.length > 0;
|
|
14
|
+
});
|
|
15
|
+
return (req, res, next) => {
|
|
16
|
+
headersConfig.forEach((headerConfig) => {
|
|
17
|
+
if (micromatch.isMatch(req.path, headerConfig.source)) {
|
|
18
|
+
headerConfig.headers.forEach((header) => {
|
|
19
|
+
if (header.key) {
|
|
20
|
+
res.setHeader(String(header.key), String(header.value));
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
next();
|
|
26
|
+
};
|
|
27
|
+
}
|
|
9
28
|
function trailingSlashMiddleware(options) {
|
|
10
|
-
|
|
11
|
-
const trailingSlash = (_a = options.rootConfig.server) == null ? void 0 : _a.trailingSlash;
|
|
29
|
+
const trailingSlash = options.rootConfig.server?.trailingSlash;
|
|
12
30
|
return (req, res, next) => {
|
|
13
31
|
if (trailingSlash === true && !path.extname(req.path) && !req.path.endsWith("/")) {
|
|
14
32
|
const redirectPath = `${req.path}/`;
|
|
@@ -46,8 +64,8 @@ function removeTrailingSlashes(urlPath) {
|
|
|
46
64
|
import busboy from "busboy";
|
|
47
65
|
var DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
48
66
|
function multipartMiddleware(options) {
|
|
49
|
-
const maxFileSize =
|
|
50
|
-
|
|
67
|
+
const maxFileSize = options?.maxFileSize || DEFAULT_MAX_FILE_SIZE;
|
|
68
|
+
const handler = (req, res, next) => {
|
|
51
69
|
const contentType = String(req.headers["content-type"] || "");
|
|
52
70
|
if (req.method === "POST" && contentType.startsWith("multipart/form-data")) {
|
|
53
71
|
const parser = busboy({ headers: req.headers });
|
|
@@ -100,13 +118,14 @@ function multipartMiddleware(options) {
|
|
|
100
118
|
next();
|
|
101
119
|
}
|
|
102
120
|
};
|
|
121
|
+
return handler;
|
|
103
122
|
}
|
|
104
123
|
|
|
105
124
|
// src/middleware/session.ts
|
|
106
125
|
var SESSION_COOKIE = "__session";
|
|
107
126
|
var DEFAULT_MAX_AGE = 60 * 60 * 24 * 5 * 1e3;
|
|
108
127
|
function sessionMiddleware(options) {
|
|
109
|
-
const maxAge =
|
|
128
|
+
const maxAge = options?.maxAge || DEFAULT_MAX_AGE;
|
|
110
129
|
return (req, res, next) => {
|
|
111
130
|
const cookieValue = String(req.signedCookies[SESSION_COOKIE] || "");
|
|
112
131
|
const session = Session.fromCookieValue(cookieValue);
|
|
@@ -115,7 +134,7 @@ function sessionMiddleware(options) {
|
|
|
115
134
|
res.saveSession = (saveSessionOptions) => {
|
|
116
135
|
const secureCookie = Boolean(process.env.NODE_ENV !== "development");
|
|
117
136
|
const cookieValue2 = session.toString();
|
|
118
|
-
const sameSite =
|
|
137
|
+
const sameSite = saveSessionOptions?.sameSite || "strict";
|
|
119
138
|
res.cookie(SESSION_COOKIE, cookieValue2, {
|
|
120
139
|
maxAge,
|
|
121
140
|
httpOnly: true,
|
|
@@ -174,10 +193,11 @@ function base64Decode(str) {
|
|
|
174
193
|
|
|
175
194
|
export {
|
|
176
195
|
rootProjectMiddleware,
|
|
196
|
+
headersMiddleware,
|
|
177
197
|
trailingSlashMiddleware,
|
|
178
198
|
multipartMiddleware,
|
|
179
199
|
SESSION_COOKIE,
|
|
180
200
|
sessionMiddleware,
|
|
181
201
|
Session
|
|
182
202
|
};
|
|
183
|
-
//# sourceMappingURL=chunk-
|
|
203
|
+
//# sourceMappingURL=chunk-TZAHHHA4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/middleware/common.ts","../src/middleware/multipart.ts","../src/middleware/session.ts"],"sourcesContent":["import path from 'node:path';\nimport micromatch from 'micromatch';\nimport {RootConfig} from '../core/config';\nimport {Request, Response, NextFunction} from '../core/types';\n\n/**\n * Middleware that injects the root.js project config into the request context.\n */\nexport function rootProjectMiddleware(options: {rootConfig: RootConfig}) {\n return (req: Request, _: Response, next: NextFunction) => {\n req.rootConfig = options.rootConfig;\n next();\n };\n}\n\n/**\n * Middleware that injects HTTP headers from the `server.headers` config in\n * root.config.ts.\n */\nexport function headersMiddleware(options: {rootConfig: RootConfig}) {\n const headersUserConfig = options.rootConfig.server?.headers || [];\n // Filter header config values that are invalid.\n const headersConfig = headersUserConfig.filter((headerConfig) => {\n return (\n headerConfig.source &&\n headerConfig.headers &&\n headerConfig.headers.length > 0\n );\n });\n return (req: Request, res: Response, next: NextFunction) => {\n headersConfig.forEach((headerConfig) => {\n if (micromatch.isMatch(req.path, headerConfig.source)) {\n headerConfig.headers.forEach((header) => {\n if (header.key) {\n res.setHeader(String(header.key), String(header.value));\n }\n });\n }\n });\n next();\n };\n}\n\n/**\n * Trailing slash middleware. Handles trailing slash redirects (preserving any\n * query params) using the `server.trailingSlash` config in root.config.ts.\n */\nexport function trailingSlashMiddleware(options: {rootConfig: RootConfig}) {\n const trailingSlash = options.rootConfig.server?.trailingSlash;\n\n return (req: Request, res: Response, next: NextFunction) => {\n // If `trailingSlash: false`, force a trailing slash in the URL.\n if (\n trailingSlash === true &&\n !path.extname(req.path) &&\n !req.path.endsWith('/')\n ) {\n const redirectPath = `${req.path}/`;\n redirectWithQuery(req, res, 301, redirectPath);\n return;\n }\n\n // If `trailingSlash: false`, remove any trailing slash from the URL.\n if (\n trailingSlash === false &&\n !path.extname(req.path) &&\n req.path !== '/' &&\n req.path.endsWith('/')\n ) {\n const redirectPath = removeTrailingSlashes(req.path);\n redirectWithQuery(req, res, 301, redirectPath);\n return;\n }\n\n next();\n };\n}\n\n/**\n * Issues an HTTP redirect, preserving any query params from the original req.\n */\nfunction redirectWithQuery(\n req: Request,\n res: Response,\n redirectCode: number,\n redirectPath: string\n) {\n const queryStr = getQueryStr(req);\n const redirectUrl = queryStr ? `${redirectPath}?${queryStr}` : redirectPath;\n res.redirect(redirectCode, redirectUrl);\n}\n\n/**\n * Returns the query string for a request, or empty string if no query.\n */\nfunction getQueryStr(req: Request): string {\n const qIndex = req.originalUrl.indexOf('?');\n if (qIndex === -1) {\n return '';\n }\n return req.originalUrl.slice(qIndex + 1);\n}\n\n/**\n * Removes trailing slashes from a URL path.\n * Note: A path with only slashes (e.g. `///`) returns `/`.\n */\nfunction removeTrailingSlashes(urlPath: string) {\n while (urlPath.endsWith('/') && urlPath !== '/') {\n urlPath = urlPath.slice(0, -1);\n }\n return urlPath;\n}\n","import busboy from 'busboy';\nimport {RequestHandler} from 'express';\nimport {Request, Response, NextFunction, MultipartFile} from '../core/types';\n\nconst DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB\n\n/**\n * Middleware for parsing multipart file uploads that's compatible with the dev\n * server and Firebase Functions.\n *\n * Context:\n * https://stackoverflow.com/questions/47242340/how-to-perform-an-http-file-upload-using-express-on-cloud-functions-for-firebase\n */\nexport function multipartMiddleware(options?: {\n maxFileSize?: number;\n}): RequestHandler {\n const maxFileSize = options?.maxFileSize || DEFAULT_MAX_FILE_SIZE;\n const handler: any = (req: Request, res: Response, next: NextFunction) => {\n const contentType = String(req.headers['content-type'] || '');\n if (\n req.method === 'POST' &&\n contentType.startsWith('multipart/form-data')\n ) {\n const parser = busboy({headers: req.headers});\n\n // Data storage for fields and files\n const fields: {[fieldname: string]: string} = {};\n const files: {[name: string]: MultipartFile} = {};\n\n // Handle field data.\n parser.on('field', (fieldname: string, val: any) => {\n fields[fieldname] = val;\n });\n\n // Handle file data. Files are saved to an in-memory buffer.\n parser.on(\n 'file',\n (\n fieldname: string,\n file: any,\n meta: {\n filename: string;\n encoding: string;\n mimeType: string;\n }\n ) => {\n const {filename, encoding, mimeType: mimetype} = meta;\n const fileChunks: Uint8Array[] = [];\n let totalBytesRead = 0;\n\n file.on('data', (chunk: Uint8Array) => {\n totalBytesRead += chunk.length;\n\n if (totalBytesRead > maxFileSize) {\n // File size exceeds the limit, stop reading.\n console.error(`File size exceeds the limit: ${fieldname}.`);\n file.removeAllListeners('data');\n // Consume and discard remaining data.\n file.resume();\n } else {\n fileChunks.push(chunk);\n }\n });\n\n file.on('end', () => {\n if (totalBytesRead <= maxFileSize) {\n const buffer = Buffer.concat(fileChunks);\n\n files[fieldname] = {\n fieldname,\n buffer,\n originalName: filename,\n encoding,\n mimetype,\n };\n }\n });\n }\n );\n\n // Update `req.body` and `req.files`.\n parser.on('finish', () => {\n req.body = fields;\n req.files = files;\n next();\n });\n\n // Pipe the request to Busboy. On Firebase Functions, `rawBody` contains\n // the multipart request. Otherwise the express request can be piped to\n // the Busboy parser.\n if (req.rawBody) {\n parser.end(req.rawBody);\n } else {\n req.pipe(parser);\n }\n } else {\n next();\n }\n };\n return handler;\n}\n","import {NextFunction, Request, Response} from '../core/types';\n\nexport const SESSION_COOKIE = '__session';\n\n// 5 days.\nconst DEFAULT_MAX_AGE = 60 * 60 * 24 * 5 * 1000;\n\nexport interface SessionMiddlewareOptions {\n maxAge?: number;\n}\n\nexport interface SaveSessionOptions {\n sameSite?: 'strict' | 'lax' | 'none';\n}\n\n/**\n * Middleware for storing session data stored in an http cookie called\n * `__session`. This cookie is compatible with Firebase Hosting:\n * https://firebase.google.com/docs/hosting/manage-cache#using_cookies\n */\nexport function sessionMiddleware(options?: SessionMiddlewareOptions) {\n const maxAge = options?.maxAge || DEFAULT_MAX_AGE;\n return (req: Request, res: Response, next: NextFunction) => {\n const cookieValue = String(req.signedCookies[SESSION_COOKIE] || '');\n const session = Session.fromCookieValue(cookieValue);\n req.session = session;\n res.session = session;\n res.saveSession = (saveSessionOptions?: SaveSessionOptions) => {\n // \"secure\" cookies require https, so disable \"secure\" when in development.\n const secureCookie = Boolean(process.env.NODE_ENV !== 'development');\n const cookieValue = session.toString();\n const sameSite = saveSessionOptions?.sameSite || 'strict';\n res.cookie(SESSION_COOKIE, cookieValue, {\n maxAge: maxAge,\n httpOnly: true,\n secure: secureCookie,\n signed: true,\n sameSite: sameSite,\n });\n };\n req.hooks.add('beforeRender', () => {\n res.saveSession();\n });\n next();\n };\n}\n\nexport class Session {\n private data: Record<string, string> = {};\n\n constructor(data?: Record<string, string>) {\n this.data = data || {};\n }\n\n static fromCookieValue(cookieValue: string) {\n const data: Record<string, string> = {};\n if (cookieValue.startsWith('b64:')) {\n try {\n const encodedStr = cookieValue.slice(4);\n const params = new URLSearchParams(base64Decode(encodedStr));\n for (const [key, value] of params.entries()) {\n data[key] = value;\n }\n } catch (err) {\n console.warn('failed to parse session cookie:', err);\n return new Session();\n }\n }\n return new Session(data);\n }\n\n getItem(key: string): string | null {\n return this.data[key] ?? null;\n }\n\n setItem(key: string, value: string) {\n this.data[key] = value;\n }\n\n removeItem(key: string) {\n delete this.data[key];\n }\n\n toString(): string {\n const params = new URLSearchParams(this.data);\n return `b64:${base64Encode(params.toString())}`;\n }\n}\n\nfunction base64Encode(str: string): string {\n return Buffer.from(str, 'utf-8').toString('base64');\n}\n\nfunction base64Decode(str: string): string {\n return Buffer.from(str, 'base64').toString('utf-8');\n}\n"],"mappings":"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","names":["cookieValue"]}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as Server } from './types-
|
|
1
|
+
import { S as Server } from './types-403nR8i5.js';
|
|
2
2
|
import 'express';
|
|
3
3
|
import 'preact';
|
|
4
4
|
import 'vite';
|
|
@@ -8,10 +8,18 @@ import 'js-beautify';
|
|
|
8
8
|
interface BuildOptions {
|
|
9
9
|
ssrOnly?: boolean;
|
|
10
10
|
mode?: string;
|
|
11
|
-
concurrency?: number;
|
|
11
|
+
concurrency?: string | number;
|
|
12
12
|
}
|
|
13
13
|
declare function build(rootProjectDir?: string, options?: BuildOptions): Promise<void>;
|
|
14
14
|
|
|
15
|
+
type DeployTarget = 'appengine' | 'firebase';
|
|
16
|
+
interface CreatePackageOptions {
|
|
17
|
+
mode?: string;
|
|
18
|
+
out?: string;
|
|
19
|
+
target?: DeployTarget;
|
|
20
|
+
}
|
|
21
|
+
declare function createPackage(rootProjectDir?: string, options?: CreatePackageOptions): Promise<void>;
|
|
22
|
+
|
|
15
23
|
interface DevOptions {
|
|
16
24
|
host?: string;
|
|
17
25
|
}
|
|
@@ -37,4 +45,11 @@ declare function createProdServer(options: {
|
|
|
37
45
|
rootDir: string;
|
|
38
46
|
}): Promise<Server>;
|
|
39
47
|
|
|
40
|
-
|
|
48
|
+
declare class CliRunner {
|
|
49
|
+
private name;
|
|
50
|
+
private version;
|
|
51
|
+
constructor(name: string, version: string);
|
|
52
|
+
run(argv: string[]): Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export { CliRunner, build, createDevServer, createPackage, createPreviewServer, createProdServer, dev, preview, start };
|
package/dist/cli.js
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
|
+
CliRunner,
|
|
2
3
|
build,
|
|
3
4
|
createDevServer,
|
|
5
|
+
createPackage,
|
|
4
6
|
createPreviewServer,
|
|
5
7
|
createProdServer,
|
|
6
8
|
dev,
|
|
7
9
|
preview,
|
|
8
10
|
start
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-HRGMPZCK.js";
|
|
12
|
+
import "./chunk-TZAHHHA4.js";
|
|
13
|
+
import "./chunk-ODXHLJOY.js";
|
|
12
14
|
import "./chunk-QKBMWK5B.js";
|
|
13
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-IUQLRDFW.js";
|
|
14
16
|
export {
|
|
17
|
+
CliRunner,
|
|
15
18
|
build,
|
|
16
19
|
createDevServer,
|
|
20
|
+
createPackage,
|
|
17
21
|
createPreviewServer,
|
|
18
22
|
createProdServer,
|
|
19
23
|
dev,
|
package/dist/core.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as Route } from './types-
|
|
2
|
-
export {
|
|
1
|
+
import { R as Route } from './types-403nR8i5.js';
|
|
2
|
+
export { i as ConfigureServerHook, j as ConfigureServerOptions, C as ContentSecurityPolicyConfig, n as GetStaticPaths, G as GetStaticProps, r as Handler, H as HandlerContext, u as HandlerRenderFn, t as HandlerRenderOptions, L as LocaleGroup, M as MultipartFile, N as NextFunction, P as Plugin, p as Request, o as RequestMiddleware, q as Response, b as RootConfig, e as RootHeaderConfig, c as RootI18nConfig, d as RootRedirectConfig, f as RootSecurityConfig, g as RootServerConfig, a as RootUserConfig, s as RouteModule, m as RouteParams, S as Server, X as XFrameOptionsConfig, k as configureServerPlugins, h as defineConfig, l as getVitePlugins } from './types-403nR8i5.js';
|
|
3
3
|
import * as preact$1 from 'preact';
|
|
4
4
|
import { FunctionalComponent, ComponentChildren } from 'preact';
|
|
5
5
|
import 'express';
|
|
@@ -116,6 +116,8 @@ interface RequestContext {
|
|
|
116
116
|
locale: string;
|
|
117
117
|
/** Translations map for the current locale. */
|
|
118
118
|
translations: Record<string, string>;
|
|
119
|
+
/** CSP nonce value. */
|
|
120
|
+
nonce?: string;
|
|
119
121
|
}
|
|
120
122
|
/**
|
|
121
123
|
* A hook that returns information about the current route.
|
|
@@ -144,4 +146,4 @@ declare function useRequestContext(): RequestContext;
|
|
|
144
146
|
*/
|
|
145
147
|
declare function useTranslations(): (str: string, params?: Record<string, string | number>) => string;
|
|
146
148
|
|
|
147
|
-
export { Body, HTML_CONTEXT, Head, Html, I18nContext, RequestContext, Route, Script, getTranslations, useI18nContext, useRequestContext, useTranslations };
|
|
149
|
+
export { Body, HTML_CONTEXT, Head, Html, type I18nContext, type RequestContext, Route, Script, getTranslations, useI18nContext, useRequestContext, useTranslations };
|
package/dist/core.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
getTranslations,
|
|
9
9
|
useI18nContext,
|
|
10
10
|
useRequestContext
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-7IDJ3PBT.js";
|
|
12
12
|
|
|
13
13
|
// src/core/config.ts
|
|
14
14
|
function defineConfig(config) {
|
|
@@ -58,8 +58,13 @@ var Script = (props) => {
|
|
|
58
58
|
|
|
59
59
|
// src/core/hooks/useTranslations.ts
|
|
60
60
|
function useTranslations() {
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
let i18nContext = null;
|
|
62
|
+
try {
|
|
63
|
+
i18nContext = useI18nContext();
|
|
64
|
+
} catch (err) {
|
|
65
|
+
console.warn("I18nContext not found");
|
|
66
|
+
}
|
|
67
|
+
const translations = i18nContext?.translations || {};
|
|
63
68
|
const t = (str, params) => {
|
|
64
69
|
const key = normalizeString(str);
|
|
65
70
|
let translation = translations[key] ?? key ?? "";
|
package/dist/core.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/config.ts","../src/core/components/Body.tsx","../src/core/components/Head.ts","../src/core/components/Script.ts","../src/core/hooks/useTranslations.ts"],"sourcesContent":["import {UserConfig as ViteUserConfig} from 'vite';\n\nimport {HtmlMinifyOptions} from '../render/html-minify';\nimport {HtmlPrettyOptions} from '../render/html-pretty';\n\nimport {Plugin} from './plugin';\nimport {RequestMiddleware} from './types';\n\nexport interface RootUserConfig {\n /**\n * Canonical domain the website will serve on. Useful for things like the\n * sitemap, SEO tags, etc.\n */\n domain?: string;\n\n /**\n * Config for auto-injecting custom element dependencies.\n */\n elements?: {\n /**\n * A list of directories to use to look for custom elements. The dir path\n * should be relative to the project dir, e.g. \"path/to/elements\".\n */\n include?: string[];\n\n /**\n * A list of RegEx patterns to exclude. The string passed to the RegEx is\n * the file URL relative to the project root, e.g. \"/elements/foo/foo.ts\".\n */\n exclude?: RegExp[];\n };\n\n /**\n * Config options for localization and internationalization.\n */\n i18n?: RootI18nConfig;\n\n /**\n * Config options for the Root.js express server.\n */\n server?: RootServerConfig;\n\n /**\n * Vite config.\n * @see {@link https://vitejs.dev/config/} for more information.\n */\n vite?: ViteUserConfig;\n\n /**\n * Whether to automatically minify HTML output. This is enabled by default,\n * in order to disable, pass `minifyHtml: false` to root.config.ts.\n */\n minifyHtml?: boolean;\n\n /**\n * Options to pass to html-minifier-terser.\n */\n minifyHtmlOptions?: HtmlMinifyOptions;\n\n /**\n * Whether to pretty print HTML output.\n */\n prettyHtml?: boolean;\n\n /**\n * Options to pass to js-beautify.\n */\n prettyHtmlOptions?: HtmlPrettyOptions;\n\n /**\n * Whether to include a sitemap.xml file to the build output.\n */\n sitemap?: boolean;\n\n /**\n * Plugins.\n */\n plugins?: Plugin[];\n}\n\nexport type RootConfig = RootUserConfig & {\n rootDir: string;\n};\n\nexport interface LocaleGroup {\n label?: string;\n locales: string[];\n}\n\nexport interface RootI18nConfig {\n /**\n * Locales enabled for the site.\n */\n locales?: string[];\n\n /**\n * The default locale to use. Defaults is `en`.\n */\n defaultLocale?: string;\n\n /**\n * URL format for localized content. Default is `/{locale}/{path}`.\n */\n urlFormat?: string;\n\n /**\n * Localization groups, to help UIs (like Root.js CMS) logically group\n * locales.\n */\n groups?: Record<string, LocaleGroup>;\n}\n\nexport interface RootServerConfig {\n /**\n * An array of middleware to add to the express server. These middleware are\n * added to the beginning of the express app.\n */\n middlewares?: RequestMiddleware[];\n\n /**\n * The `trailingSlash` config allows you to control how the server handles\n * trailing slashes. This config only affects URLs that do not have a file\n * extension (i.e. HTML paths).\n *\n * - When `true`, the server redirects URLs to add a trailing slash\n * - When `false`, the server redirects URLs to remove a trailing slash\n * - When unspecified, the server allows URLs with and without trailing slash\n */\n trailingSlash?: boolean;\n\n /**\n * Cookie secret for the session middleware.\n */\n sessionCookieSecret?: string | string[];\n}\n\nexport function defineConfig(config: RootUserConfig): RootUserConfig {\n return config;\n}\n","import {ComponentChildren, FunctionalComponent} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nimport {HTML_CONTEXT} from './Html';\n\nexport type BodyProps = preact.JSX.HTMLAttributes<HTMLBodyElement> & {\n children?: ComponentChildren;\n};\n\n/**\n * The `<Body>` component can be used to update attrs in the `<body>` tag.\n *\n * Usage:\n *\n * ```tsx\n * <Body className=\"body\">\n * <h1>Hello world</h1>\n * </Body>\n * ```\n *\n * Output:\n *\n * ```html\n * <body class=\"body\">\n * <h1>Hello world</h1>\n * </body>\n */\nexport const Body: FunctionalComponent<BodyProps> = ({children, ...attrs}) => {\n const context = useContext(HTML_CONTEXT);\n if (!context) {\n throw new Error(\n 'HTML_CONTEXT not found, double-check usage of the <Body> component'\n );\n }\n context.bodyAttrs = attrs;\n return <>{children}</>;\n};\n","import {ComponentChildren, FunctionalComponent} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nimport {HTML_CONTEXT} from './Html';\n\nexport type HeadProps = preact.JSX.HTMLAttributes<HTMLHeadElement> & {\n children?: ComponentChildren;\n};\n\n/**\n * The <Head> component can be used for injecting elements into the HTML head\n * tag from any part of a page. The <Head> can be added via any component or\n * sub-component and will automatically be hoisted to the `<head>` element.\n *\n * Usage:\n *\n * ```tsx\n * <Head>\n * <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n * </Head>\n * ```\n */\nexport const Head: FunctionalComponent<HeadProps> = ({children, ...attrs}) => {\n const context = useContext(HTML_CONTEXT);\n if (!context) {\n throw new Error(\n 'HTML_CONTEXT not found, double-check usage of the <Head> component'\n );\n }\n context.headComponents.push(children);\n context.headAttrs = attrs;\n return null;\n};\n","import {FunctionalComponent} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nimport {HTML_CONTEXT} from './Html';\n\nexport type ScriptProps = preact.JSX.HTMLAttributes<HTMLScriptElement>;\n\n/**\n * The <Script> component is used for rendering any custom script modules. At\n * the moment, the system only pre-renders and bundles files that are in the\n * `/bundles` folder at the root of the project.\n *\n * Usage:\n *\n * ```tsx\n * <Script src=\"/bundles/main.ts\" />\n * ```\n */\nexport const Script: FunctionalComponent<ScriptProps> = (props) => {\n const context = useContext(HTML_CONTEXT);\n if (!context) {\n throw new Error(\n 'HTML_CONTEXT not found, double-check usage of the <Script> component'\n );\n }\n context.scriptDeps.push(props);\n return null;\n};\n","import {useI18nContext} from './useI18nContext';\n\n/**\n * A hook that returns a function that can be used to translate a string, and\n * optionally replace any parameterized values that are surrounded in curly\n * braces.\n *\n * Usage:\n *\n * ```ts\n * const t = useTranslations();\n * t('Hello {name}', {name: 'Bob'});\n * // => 'Bounjour Bob'\n */\nexport function useTranslations() {\n const context = useI18nContext();\n const translations = context.translations || {};\n const t = (str: string, params?: Record<string, string | number>) => {\n const key = normalizeString(str);\n let translation = translations[key] ?? key ?? '';\n if (params) {\n for (const param of Object.keys(params)) {\n const val = String(params[param] ?? '');\n translation = translation.replaceAll(`{${param}}`, val);\n }\n }\n return translation;\n };\n return t;\n}\n\n/**\n * Cleans a string that's used for translations. Performs the following:\n * - Removes any leading/trailing whitespace\n * - Removes spaces at the end of any line\n */\nexport function normalizeString(str: string) {\n const lines = String(str)\n .trim()\n .split('\\n')\n .map((line) => line.trimEnd());\n return lines.join('\\n');\n}\n"],"mappings":";;;;;;;;;;;;;AAwIO,SAAS,aAAa,QAAwC;AACnE,SAAO;AACT;;;ACzIA,SAAQ,kBAAiB;AAkChB;AARF,IAAM,OAAuC,CAAC,EAAC,UAAU,GAAG,MAAK,MAAM;AAC5E,QAAM,UAAU,WAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,UAAQ,YAAY;AACpB,SAAO,gCAAG,UAAS;AACrB;;;ACnCA,SAAQ,cAAAA,mBAAiB;AAqBlB,IAAM,OAAuC,CAAC,EAAC,UAAU,GAAG,MAAK,MAAM;AAC5E,QAAM,UAAUC,YAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,UAAQ,eAAe,KAAK,QAAQ;AACpC,UAAQ,YAAY;AACpB,SAAO;AACT;;;AC/BA,SAAQ,cAAAC,mBAAiB;AAiBlB,IAAM,SAA2C,CAAC,UAAU;AACjE,QAAM,UAAUC,YAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,UAAQ,WAAW,KAAK,KAAK;AAC7B,SAAO;AACT;;;ACbO,SAAS,kBAAkB;AAChC,QAAM,UAAU,eAAe;AAC/B,QAAM,eAAe,QAAQ,gBAAgB,CAAC;AAC9C,QAAM,IAAI,CAAC,KAAa,WAA6C;AACnE,UAAM,MAAM,gBAAgB,GAAG;AAC/B,QAAI,cAAc,aAAa,GAAG,KAAK,OAAO;AAC9C,QAAI,QAAQ;AACV,iBAAW,SAAS,OAAO,KAAK,MAAM,GAAG;AACvC,cAAM,MAAM,OAAO,OAAO,KAAK,KAAK,EAAE;AACtC,sBAAc,YAAY,WAAW,IAAI,KAAK,KAAK,GAAG;AAAA,MACxD;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,gBAAgB,KAAa;AAC3C,QAAM,QAAQ,OAAO,GAAG,EACrB,KAAK,EACL,MAAM,IAAI,EACV,IAAI,CAAC,SAAS,KAAK,QAAQ,CAAC;AAC/B,SAAO,MAAM,KAAK,IAAI;AACxB;","names":["useContext","useContext","useContext","useContext"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/config.ts","../src/core/components/Body.tsx","../src/core/components/Head.ts","../src/core/components/Script.ts","../src/core/hooks/useTranslations.ts"],"sourcesContent":["import {UserConfig as ViteUserConfig} from 'vite';\n\nimport {HtmlMinifyOptions} from '../render/html-minify';\nimport {HtmlPrettyOptions} from '../render/html-pretty';\n\nimport {Plugin} from './plugin';\nimport {RequestMiddleware} from './types';\n\nexport interface RootUserConfig {\n /**\n * Canonical domain the website will serve on. Useful for things like the\n * sitemap, SEO tags, etc.\n */\n domain?: string;\n\n /**\n * The base URL path that the site will serve on. Defaults to `/`;\n */\n base?: string;\n\n /**\n * Config for auto-injecting custom element dependencies.\n */\n elements?: {\n /**\n * A list of directories to use to look for custom elements. The dir path\n * should be relative to the project dir, e.g. \"path/to/elements\".\n */\n include?: string[];\n\n /**\n * A list of RegEx patterns to exclude. The string passed to the RegEx is\n * the file URL relative to the project root, e.g. \"/elements/foo/foo.ts\".\n */\n exclude?: RegExp[];\n };\n\n /**\n * Config options for localization and internationalization.\n */\n i18n?: RootI18nConfig;\n\n /**\n * Config options for the Root.js express server.\n */\n server?: RootServerConfig;\n\n /**\n * Vite config.\n * @see {@link https://vitejs.dev/config/} for more information.\n */\n vite?: ViteUserConfig;\n\n /**\n * Whether to automatically minify HTML output. This is enabled by default,\n * in order to disable, pass `minifyHtml: false` to root.config.ts.\n */\n minifyHtml?: boolean;\n\n /**\n * Options to pass to html-minifier-terser.\n */\n minifyHtmlOptions?: HtmlMinifyOptions;\n\n /**\n * Whether to pretty print HTML output.\n */\n prettyHtml?: boolean;\n\n /**\n * Options to pass to js-beautify.\n */\n prettyHtmlOptions?: HtmlPrettyOptions;\n\n /**\n * Whether to include a sitemap.xml file to the build output.\n */\n sitemap?: boolean;\n\n /**\n * Plugins.\n */\n plugins?: Plugin[];\n}\n\nexport type RootConfig = RootUserConfig & {\n rootDir: string;\n};\n\nexport interface LocaleGroup {\n label?: string;\n locales: string[];\n}\n\nexport interface RootI18nConfig {\n /**\n * Locales enabled for the site.\n */\n locales?: string[];\n\n /**\n * The default locale to use. Defaults is `en`.\n */\n defaultLocale?: string;\n\n /**\n * URL format for localized content. Default is `/[locale]/[base]/[path]`.\n */\n urlFormat?: string;\n\n /**\n * Localization groups, to help UIs (like Root.js CMS) logically group\n * locales.\n */\n groups?: Record<string, LocaleGroup>;\n}\n\nexport interface RootRedirectConfig {\n source: string;\n destination: string;\n type?: number;\n}\n\nexport interface RootHeaderConfig {\n /** A glob pattern match (regex not supported yet). */\n source: string;\n headers: Array<{\n key: string;\n value: string;\n }>;\n}\n\nexport interface ContentSecurityPolicyConfig {\n directives?: Record<string, string[]>;\n reportOnly?: boolean;\n}\n\nexport interface XFrameOptionsConfig {\n action: 'DENY' | 'SAMEORIGIN';\n}\n\nexport interface RootSecurityConfig {\n /**\n * Content-Security-Policy config. If enabled, a nonce is auto-generated\n * for every request and appended to script and stylesheet tags. You can\n * validate your CSP headers using a tool like {@link https://csp-evaluator.withgoogle.com/}.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP}\n */\n contentSecurityPolicy?: ContentSecurityPolicyConfig | boolean;\n\n /**\n * Strict-Transport-Security config. When enabled, the header value is set\n * to `Strict-Transport-Security: max-age=63072000; includeSubDomains; preload`.\n */\n strictTransportSecurity?: boolean;\n\n /**\n * X-Content-Type-Options config. When enabled, the header value is set to\n * `X-Content-Type-Options: nosniff`.\n */\n xContentTypeOptions?: boolean;\n\n /**\n * X-Frame-Options config. Setting this value to `true` will default the\n * header value to `X-Frame-Options: SAMEORIGIN`.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options}\n */\n xFrameOptions?: 'DENY' | 'SAMEORIGIN' | boolean;\n\n /**\n * X-XSS-Protection config. When enabled, the header value is set to\n * `X-XSS-Protection: 1; mode=block`.\n */\n xXssProtection?: boolean;\n}\n\nexport interface RootServerConfig {\n /**\n * An array of middleware to add to the express server. These middleware are\n * added to the beginning of the express app.\n */\n middlewares?: RequestMiddleware[];\n\n /**\n * The `trailingSlash` config allows you to control how the server handles\n * trailing slashes. This config only affects URLs that do not have a file\n * extension (i.e. HTML paths).\n *\n * - When `true`, the server redirects URLs to add a trailing slash\n * - When `false`, the server redirects URLs to remove a trailing slash\n * - When unspecified, the server allows URLs with and without trailing slash\n */\n trailingSlash?: boolean;\n\n /**\n * Cookie secret for the session middleware.\n */\n sessionCookieSecret?: string | string[];\n\n /**\n * List of redirects.\n */\n redirects?: RootRedirectConfig[];\n\n /**\n * HTTP headers to add to a response.\n */\n headers?: RootHeaderConfig[];\n\n /**\n * HTTP security settings. By default, all security settings are enabled with\n * commonly used default values.\n */\n security?: RootSecurityConfig;\n}\n\nexport function defineConfig(config: RootUserConfig): RootUserConfig {\n return config;\n}\n","import {ComponentChildren, FunctionalComponent} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nimport {HTML_CONTEXT} from './Html';\n\nexport type BodyProps = preact.JSX.HTMLAttributes<HTMLBodyElement> & {\n children?: ComponentChildren;\n};\n\n/**\n * The `<Body>` component can be used to update attrs in the `<body>` tag.\n *\n * Usage:\n *\n * ```tsx\n * <Body className=\"body\">\n * <h1>Hello world</h1>\n * </Body>\n * ```\n *\n * Output:\n *\n * ```html\n * <body class=\"body\">\n * <h1>Hello world</h1>\n * </body>\n */\nexport const Body: FunctionalComponent<BodyProps> = ({children, ...attrs}) => {\n const context = useContext(HTML_CONTEXT);\n if (!context) {\n throw new Error(\n 'HTML_CONTEXT not found, double-check usage of the <Body> component'\n );\n }\n context.bodyAttrs = attrs;\n return <>{children}</>;\n};\n","import {ComponentChildren, FunctionalComponent} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nimport {HTML_CONTEXT} from './Html';\n\nexport type HeadProps = preact.JSX.HTMLAttributes<HTMLHeadElement> & {\n children?: ComponentChildren;\n};\n\n/**\n * The <Head> component can be used for injecting elements into the HTML head\n * tag from any part of a page. The <Head> can be added via any component or\n * sub-component and will automatically be hoisted to the `<head>` element.\n *\n * Usage:\n *\n * ```tsx\n * <Head>\n * <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n * </Head>\n * ```\n */\nexport const Head: FunctionalComponent<HeadProps> = ({children, ...attrs}) => {\n const context = useContext(HTML_CONTEXT);\n if (!context) {\n throw new Error(\n 'HTML_CONTEXT not found, double-check usage of the <Head> component'\n );\n }\n context.headComponents.push(children);\n context.headAttrs = attrs;\n return null;\n};\n","import {FunctionalComponent} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nimport {HTML_CONTEXT} from './Html';\n\nexport type ScriptProps = preact.JSX.HTMLAttributes<HTMLScriptElement>;\n\n/**\n * The <Script> component is used for rendering any custom script modules. At\n * the moment, the system only pre-renders and bundles files that are in the\n * `/bundles` folder at the root of the project.\n *\n * Usage:\n *\n * ```tsx\n * <Script src=\"/bundles/main.ts\" />\n * ```\n */\nexport const Script: FunctionalComponent<ScriptProps> = (props) => {\n const context = useContext(HTML_CONTEXT);\n if (!context) {\n throw new Error(\n 'HTML_CONTEXT not found, double-check usage of the <Script> component'\n );\n }\n context.scriptDeps.push(props);\n return null;\n};\n","import {I18nContext, useI18nContext} from './useI18nContext';\n\n/**\n * A hook that returns a function that can be used to translate a string, and\n * optionally replace any parameterized values that are surrounded in curly\n * braces.\n *\n * Usage:\n *\n * ```ts\n * const t = useTranslations();\n * t('Hello {name}', {name: 'Bob'});\n * // => 'Bounjour Bob'\n */\nexport function useTranslations() {\n // Ignore I18nContext not found error when used with client-side rehydration.\n let i18nContext: I18nContext | null = null;\n try {\n i18nContext = useI18nContext();\n } catch (err) {\n console.warn('I18nContext not found');\n }\n const translations = i18nContext?.translations || {};\n const t = (str: string, params?: Record<string, string | number>) => {\n const key = normalizeString(str);\n let translation = translations[key] ?? key ?? '';\n if (params) {\n for (const param of Object.keys(params)) {\n const val = String(params[param] ?? '');\n translation = translation.replaceAll(`{${param}}`, val);\n }\n }\n return translation;\n };\n return t;\n}\n\n/**\n * Cleans a string that's used for translations. Performs the following:\n * - Removes any leading/trailing whitespace\n * - Removes spaces at the end of any line\n */\nexport function normalizeString(str: string) {\n const lines = String(str)\n .trim()\n .split('\\n')\n .map((line) => line.trimEnd());\n return lines.join('\\n');\n}\n"],"mappings":";;;;;;;;;;;;;AA0NO,SAAS,aAAa,QAAwC;AACnE,SAAO;AACT;;;AC3NA,SAAQ,kBAAiB;AAkChB;AARF,IAAM,OAAuC,CAAC,EAAC,UAAU,GAAG,MAAK,MAAM;AAC5E,QAAM,UAAU,WAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,UAAQ,YAAY;AACpB,SAAO,gCAAG,UAAS;AACrB;;;ACnCA,SAAQ,cAAAA,mBAAiB;AAqBlB,IAAM,OAAuC,CAAC,EAAC,UAAU,GAAG,MAAK,MAAM;AAC5E,QAAM,UAAUC,YAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,UAAQ,eAAe,KAAK,QAAQ;AACpC,UAAQ,YAAY;AACpB,SAAO;AACT;;;AC/BA,SAAQ,cAAAC,mBAAiB;AAiBlB,IAAM,SAA2C,CAAC,UAAU;AACjE,QAAM,UAAUC,YAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,UAAQ,WAAW,KAAK,KAAK;AAC7B,SAAO;AACT;;;ACbO,SAAS,kBAAkB;AAEhC,MAAI,cAAkC;AACtC,MAAI;AACF,kBAAc,eAAe;AAAA,EAC/B,SAAS,KAAK;AACZ,YAAQ,KAAK,uBAAuB;AAAA,EACtC;AACA,QAAM,eAAe,aAAa,gBAAgB,CAAC;AACnD,QAAM,IAAI,CAAC,KAAa,WAA6C;AACnE,UAAM,MAAM,gBAAgB,GAAG;AAC/B,QAAI,cAAc,aAAa,GAAG,KAAK,OAAO;AAC9C,QAAI,QAAQ;AACV,iBAAW,SAAS,OAAO,KAAK,MAAM,GAAG;AACvC,cAAM,MAAM,OAAO,OAAO,KAAK,KAAK,EAAE;AACtC,sBAAc,YAAY,WAAW,IAAI,KAAK,KAAK,GAAG;AAAA,MACxD;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,gBAAgB,KAAa;AAC3C,QAAM,QAAQ,OAAO,GAAG,EACrB,KAAK,EACL,MAAM,IAAI,EACV,IAAI,CAAC,SAAS,KAAK,QAAQ,CAAC;AAC/B,SAAO,MAAM,KAAK,IAAI;AACxB;","names":["useContext","useContext","useContext","useContext"]}
|
package/dist/functions.d.ts
CHANGED
package/dist/functions.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createPreviewServer,
|
|
3
3
|
createProdServer
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-HRGMPZCK.js";
|
|
5
|
+
import "./chunk-TZAHHHA4.js";
|
|
6
|
+
import "./chunk-ODXHLJOY.js";
|
|
7
7
|
import "./chunk-QKBMWK5B.js";
|
|
8
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-IUQLRDFW.js";
|
|
9
9
|
|
|
10
10
|
// src/functions/server.ts
|
|
11
11
|
import path from "node:path";
|
|
12
12
|
import { onRequest } from "firebase-functions/v2/https";
|
|
13
13
|
function server(options) {
|
|
14
14
|
let rootServer;
|
|
15
|
-
const rootDir = path.resolve(
|
|
16
|
-
return onRequest(
|
|
15
|
+
const rootDir = path.resolve(options?.rootDir || process.cwd());
|
|
16
|
+
return onRequest(options?.httpsOptions || {}, async (req, res) => {
|
|
17
17
|
if (!rootServer) {
|
|
18
|
-
if (
|
|
18
|
+
if (options?.mode === "preview") {
|
|
19
19
|
rootServer = await createPreviewServer({ rootDir });
|
|
20
20
|
} else {
|
|
21
21
|
rootServer = await createProdServer({ rootDir });
|
package/dist/functions.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/functions/server.ts"],"sourcesContent":["import path from 'node:path';\nimport {HttpsOptions, onRequest} from 'firebase-functions/v2/https';\nimport {createPreviewServer, createProdServer} from '../cli/cli';\nimport {Server} from '../core/types';\n\nexport interface ProdServerOptions {\n rootDir?: string;\n mode?: 'preview' | 'production';\n httpsOptions?: HttpsOptions;\n}\n\n/**\n * Firebase Function that runs a Root.js server running in SSR mode.\n */\nexport function server(options?: ProdServerOptions) {\n let rootServer: Server;\n const rootDir = path.resolve(options?.rootDir || process.cwd());\n return onRequest(options?.httpsOptions || {}, async (req, res) => {\n if (!rootServer) {\n if (options?.mode === 'preview') {\n rootServer = await createPreviewServer({rootDir});\n } else {\n rootServer = await createProdServer({rootDir});\n }\n }\n await rootServer(req, res);\n });\n}\n"],"mappings":";;;;;;;;;;AAAA,OAAO,UAAU;AACjB,SAAsB,iBAAgB;AAa/B,SAAS,OAAO,SAA6B;AAClD,MAAI;AACJ,QAAM,UAAU,KAAK,
|
|
1
|
+
{"version":3,"sources":["../src/functions/server.ts"],"sourcesContent":["import path from 'node:path';\nimport {HttpsOptions, onRequest} from 'firebase-functions/v2/https';\nimport {createPreviewServer, createProdServer} from '../cli/cli';\nimport {Server} from '../core/types';\n\nexport interface ProdServerOptions {\n rootDir?: string;\n mode?: 'preview' | 'production';\n httpsOptions?: HttpsOptions;\n}\n\n/**\n * Firebase Function that runs a Root.js server running in SSR mode.\n */\nexport function server(options?: ProdServerOptions) {\n let rootServer: Server;\n const rootDir = path.resolve(options?.rootDir || process.cwd());\n return onRequest(options?.httpsOptions || {}, async (req, res) => {\n if (!rootServer) {\n if (options?.mode === 'preview') {\n rootServer = await createPreviewServer({rootDir});\n } else {\n rootServer = await createProdServer({rootDir});\n }\n }\n await rootServer(req, res);\n });\n}\n"],"mappings":";;;;;;;;;;AAAA,OAAO,UAAU;AACjB,SAAsB,iBAAgB;AAa/B,SAAS,OAAO,SAA6B;AAClD,MAAI;AACJ,QAAM,UAAU,KAAK,QAAQ,SAAS,WAAW,QAAQ,IAAI,CAAC;AAC9D,SAAO,UAAU,SAAS,gBAAgB,CAAC,GAAG,OAAO,KAAK,QAAQ;AAChE,QAAI,CAAC,YAAY;AACf,UAAI,SAAS,SAAS,WAAW;AAC/B,qBAAa,MAAM,oBAAoB,EAAC,QAAO,CAAC;AAAA,MAClD,OAAO;AACL,qBAAa,MAAM,iBAAiB,EAAC,QAAO,CAAC;AAAA,MAC/C;AAAA,IACF;AACA,UAAM,WAAW,KAAK,GAAG;AAAA,EAC3B,CAAC;AACH;","names":[]}
|
package/dist/middleware.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { b as RootConfig,
|
|
2
|
-
export {
|
|
3
|
-
import 'express';
|
|
1
|
+
import { b as RootConfig, p as Request, q as Response, N as NextFunction } from './types-403nR8i5.js';
|
|
2
|
+
export { v as SESSION_COOKIE, x as SaveSessionOptions, z as Session, w as SessionMiddlewareOptions, y as sessionMiddleware } from './types-403nR8i5.js';
|
|
3
|
+
import { RequestHandler } from 'express';
|
|
4
4
|
import 'preact';
|
|
5
5
|
import 'vite';
|
|
6
6
|
import 'html-minifier-terser';
|
|
@@ -12,6 +12,13 @@ import 'js-beautify';
|
|
|
12
12
|
declare function rootProjectMiddleware(options: {
|
|
13
13
|
rootConfig: RootConfig;
|
|
14
14
|
}): (req: Request, _: Response, next: NextFunction) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Middleware that injects HTTP headers from the `server.headers` config in
|
|
17
|
+
* root.config.ts.
|
|
18
|
+
*/
|
|
19
|
+
declare function headersMiddleware(options: {
|
|
20
|
+
rootConfig: RootConfig;
|
|
21
|
+
}): (req: Request, res: Response, next: NextFunction) => void;
|
|
15
22
|
/**
|
|
16
23
|
* Trailing slash middleware. Handles trailing slash redirects (preserving any
|
|
17
24
|
* query params) using the `server.trailingSlash` config in root.config.ts.
|
|
@@ -29,6 +36,6 @@ declare function trailingSlashMiddleware(options: {
|
|
|
29
36
|
*/
|
|
30
37
|
declare function multipartMiddleware(options?: {
|
|
31
38
|
maxFileSize?: number;
|
|
32
|
-
}):
|
|
39
|
+
}): RequestHandler;
|
|
33
40
|
|
|
34
|
-
export { multipartMiddleware, rootProjectMiddleware, trailingSlashMiddleware };
|
|
41
|
+
export { headersMiddleware, multipartMiddleware, rootProjectMiddleware, trailingSlashMiddleware };
|
package/dist/middleware.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SESSION_COOKIE,
|
|
3
3
|
Session,
|
|
4
|
+
headersMiddleware,
|
|
4
5
|
multipartMiddleware,
|
|
5
6
|
rootProjectMiddleware,
|
|
6
7
|
sessionMiddleware,
|
|
7
8
|
trailingSlashMiddleware
|
|
8
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-TZAHHHA4.js";
|
|
9
10
|
export {
|
|
10
11
|
SESSION_COOKIE,
|
|
11
12
|
Session,
|
|
13
|
+
headersMiddleware,
|
|
12
14
|
multipartMiddleware,
|
|
13
15
|
rootProjectMiddleware,
|
|
14
16
|
sessionMiddleware,
|
package/dist/node.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { b as RootConfig } from './types-
|
|
1
|
+
import { b as RootConfig } from './types-403nR8i5.js';
|
|
2
2
|
import { ViteDevServer } from 'vite';
|
|
3
3
|
import 'express';
|
|
4
4
|
import 'preact';
|
|
@@ -9,6 +9,14 @@ interface ConfigOptions {
|
|
|
9
9
|
command: string;
|
|
10
10
|
}
|
|
11
11
|
declare function loadRootConfig(rootDir: string, options: ConfigOptions): Promise<RootConfig>;
|
|
12
|
+
/**
|
|
13
|
+
* Compiles a root.config.ts file to root.config.js.
|
|
14
|
+
*/
|
|
15
|
+
declare function bundleRootConfig(rootDir: string, outPath: string): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Loads a pre-bundled config file from dist/root.config.js.
|
|
18
|
+
*/
|
|
19
|
+
declare function loadBundledConfig(rootDir: string, options: ConfigOptions): Promise<RootConfig>;
|
|
12
20
|
|
|
13
21
|
interface CreateViteServerOptions {
|
|
14
22
|
/** Override HMR settings. */
|
|
@@ -27,4 +35,4 @@ declare function createViteServer(rootConfig: RootConfig, options?: CreateViteSe
|
|
|
27
35
|
*/
|
|
28
36
|
declare function viteSsrLoadModule(rootConfig: RootConfig, file: string): Promise<Record<string, any>>;
|
|
29
37
|
|
|
30
|
-
export { ConfigOptions, CreateViteServerOptions, createViteServer, loadRootConfig, viteSsrLoadModule };
|
|
38
|
+
export { type ConfigOptions, type CreateViteServerOptions, bundleRootConfig, createViteServer, loadBundledConfig, loadRootConfig, viteSsrLoadModule };
|
package/dist/node.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
+
bundleRootConfig,
|
|
2
3
|
createViteServer,
|
|
4
|
+
loadBundledConfig,
|
|
3
5
|
loadRootConfig,
|
|
4
6
|
viteSsrLoadModule
|
|
5
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-ODXHLJOY.js";
|
|
6
8
|
import "./chunk-QKBMWK5B.js";
|
|
7
9
|
export {
|
|
10
|
+
bundleRootConfig,
|
|
8
11
|
createViteServer,
|
|
12
|
+
loadBundledConfig,
|
|
9
13
|
loadRootConfig,
|
|
10
14
|
viteSsrLoadModule
|
|
11
15
|
};
|
package/dist/render.d.ts
CHANGED