@pablozaiden/webapp 0.5.2 → 0.5.4
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/docs/deployment.md
CHANGED
|
@@ -17,6 +17,26 @@ await buildWebAppBinary({
|
|
|
17
17
|
});
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
+
The binary builder compiles the browser bundle with the framework defaults needed for `webapp` apps, including Tailwind CSS v4 processing. If an app needs extra browser build behavior, add Bun plugins or browser-only defines under `web.build`:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
await buildWebAppBinary({
|
|
24
|
+
entrypoint: "src/index.ts",
|
|
25
|
+
outfile: "./dist/my-app",
|
|
26
|
+
web: {
|
|
27
|
+
entry: "./frontend.tsx",
|
|
28
|
+
build: {
|
|
29
|
+
plugins: [myBrowserPlugin],
|
|
30
|
+
define: {
|
|
31
|
+
"process.env.MY_BROWSER_FLAG": JSON.stringify("enabled"),
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
App-provided plugins run before the framework defaults. Use `web.build.disableDefaultPlugins` only for specialized builds that intentionally replace the framework browser pipeline.
|
|
39
|
+
|
|
20
40
|
Run the binary with the same CLI contract:
|
|
21
41
|
|
|
22
42
|
```bash
|
package/docs/github-actions.md
CHANGED
|
@@ -415,6 +415,8 @@ await buildWebAppBinary({
|
|
|
415
415
|
});
|
|
416
416
|
```
|
|
417
417
|
|
|
418
|
+
`buildWebAppBinary` includes the framework browser build defaults, including Tailwind CSS v4 processing. Apps that need additional browser-only transforms can pass Bun plugins or defines through `web.build.plugins` and `web.build.define`; app plugins run before the framework defaults.
|
|
419
|
+
|
|
418
420
|
## Adaptation checklist
|
|
419
421
|
|
|
420
422
|
1. Replace `my-app` everywhere with the binary name.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pablozaiden/webapp",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "Opinionated Bun + React webapp framework for single-server apps",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@simplewebauthn/browser": "13.3.0",
|
|
45
45
|
"@simplewebauthn/server": "13.3.1",
|
|
46
|
+
"bun-plugin-tailwind": "0.1.2",
|
|
46
47
|
"jose": "6.2.3",
|
|
47
48
|
"tailwindcss": "4.3.1",
|
|
48
49
|
"zod": "4.4.3"
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { BunPlugin } from "bun";
|
|
2
|
+
import tailwindPlugin from "bun-plugin-tailwind";
|
|
1
3
|
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
4
|
import { basename, dirname, extname, resolve } from "node:path";
|
|
3
5
|
|
|
@@ -15,6 +17,11 @@ export interface BuildWebAppBinaryOptions {
|
|
|
15
17
|
define?: Record<string, string>;
|
|
16
18
|
web?: {
|
|
17
19
|
entry?: string;
|
|
20
|
+
build?: {
|
|
21
|
+
plugins?: BunPlugin[];
|
|
22
|
+
define?: Record<string, string>;
|
|
23
|
+
disableDefaultPlugins?: boolean;
|
|
24
|
+
};
|
|
18
25
|
};
|
|
19
26
|
}
|
|
20
27
|
|
|
@@ -31,21 +38,31 @@ export async function buildWebAppBinary(options: BuildWebAppBinaryOptions): Prom
|
|
|
31
38
|
try {
|
|
32
39
|
mkdirSync(cacheDir, { recursive: true });
|
|
33
40
|
const webEntry = resolve(dirname(resolve(options.entrypoint)), options.web?.entry ?? "./web/main.tsx");
|
|
34
|
-
const
|
|
41
|
+
const rendererEntry = resolve(cacheDir, "webapp-renderer-prelude.ts");
|
|
42
|
+
const clientEntry = resolve(cacheDir, "webapp-client-entry.ts");
|
|
35
43
|
const browserOutDir = resolve(cacheDir, "browser");
|
|
36
|
-
writeFileSync(
|
|
44
|
+
writeFileSync(rendererEntry, `import { createRoot } from "react-dom/client";
|
|
37
45
|
import { configureWebAppRenderer } from "@pablozaiden/webapp/web";
|
|
38
46
|
|
|
39
47
|
configureWebAppRenderer(createRoot);
|
|
40
|
-
import ${JSON.stringify(webEntry)};
|
|
41
48
|
`);
|
|
49
|
+
writeFileSync(clientEntry, `import ${JSON.stringify(webEntry)};
|
|
50
|
+
`);
|
|
51
|
+
const browserPlugins = [
|
|
52
|
+
...(options.web?.build?.plugins ?? []),
|
|
53
|
+
...(options.web?.build?.disableDefaultPlugins ? [] : [tailwindPlugin]),
|
|
54
|
+
];
|
|
42
55
|
const browserBuild = await Bun.build({
|
|
43
|
-
entrypoints: [
|
|
56
|
+
entrypoints: [rendererEntry, clientEntry],
|
|
44
57
|
outdir: browserOutDir,
|
|
45
58
|
target: "browser",
|
|
59
|
+
format: "esm",
|
|
60
|
+
splitting: true,
|
|
61
|
+
publicPath: "/webapp-compiled/",
|
|
46
62
|
minify: true,
|
|
47
63
|
sourcemap: "external",
|
|
48
|
-
define: options.define,
|
|
64
|
+
define: { ...options.define, ...options.web?.build?.define },
|
|
65
|
+
plugins: browserPlugins,
|
|
49
66
|
});
|
|
50
67
|
if (!browserBuild.success) {
|
|
51
68
|
for (const log of browserBuild.logs) {
|
|
@@ -54,13 +71,17 @@ import ${JSON.stringify(webEntry)};
|
|
|
54
71
|
throw new Error("Browser build failed");
|
|
55
72
|
}
|
|
56
73
|
const assets = browserBuild.outputs
|
|
74
|
+
.filter((output) => extname(output.path).toLowerCase() !== ".map")
|
|
57
75
|
.map((output) => {
|
|
58
76
|
const ext = extname(output.path).toLowerCase();
|
|
77
|
+
const fileName = basename(output.path);
|
|
59
78
|
const publicPath = `/webapp-compiled/${basename(output.path)}`;
|
|
79
|
+
const scriptKind = ext === ".js" ? compiledScriptKind(fileName) : undefined;
|
|
60
80
|
return {
|
|
61
81
|
path: publicPath,
|
|
62
82
|
contentType: contentTypeForOutput(ext),
|
|
63
|
-
role: ext === ".css" ? "style" :
|
|
83
|
+
role: ext === ".css" ? "style" : scriptKind ? "script" : "asset",
|
|
84
|
+
...(scriptKind ? { scriptOrder: scriptKind === "renderer" ? 0 : 1 } : {}),
|
|
64
85
|
body: readFileSync(output.path).toString("base64"),
|
|
65
86
|
};
|
|
66
87
|
});
|
|
@@ -115,3 +136,9 @@ function contentTypeForOutput(ext: string): string {
|
|
|
115
136
|
if (ext === ".map") return "application/json; charset=utf-8";
|
|
116
137
|
return "application/octet-stream";
|
|
117
138
|
}
|
|
139
|
+
|
|
140
|
+
function compiledScriptKind(fileName: string): "renderer" | "client" | undefined {
|
|
141
|
+
if (/^webapp-renderer-prelude(?:[-.][\w-]+)?\.(?:mjs|js)$/.test(fileName)) return "renderer";
|
|
142
|
+
if (/^webapp-client-entry(?:[-.][\w-]+)?\.(?:mjs|js)$/.test(fileName)) return "client";
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Server, ServerWebSocket, WebSocketHandler } from "bun";
|
|
2
|
-
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -147,6 +147,7 @@ type CompiledClientAsset = {
|
|
|
147
147
|
path: string;
|
|
148
148
|
contentType: string;
|
|
149
149
|
role: "script" | "style" | "asset";
|
|
150
|
+
scriptOrder?: number;
|
|
150
151
|
body: string;
|
|
151
152
|
};
|
|
152
153
|
|
|
@@ -180,7 +181,7 @@ function safeCachePathSegment(value: string): string {
|
|
|
180
181
|
function createDocumentCacheDir(envPrefix: string): string {
|
|
181
182
|
const root = join(tmpdir(), "webapp", safeCachePathSegment(envPrefix));
|
|
182
183
|
mkdirSync(root, { recursive: true });
|
|
183
|
-
const cacheDir = mkdtempSync(join(root, WEBAPP_DOCUMENT_CACHE_PREFIX));
|
|
184
|
+
const cacheDir = realpathSync(mkdtempSync(join(root, WEBAPP_DOCUMENT_CACHE_PREFIX)));
|
|
184
185
|
documentCacheDirs.add(cacheDir);
|
|
185
186
|
if (!documentCacheCleanupRegistered) {
|
|
186
187
|
documentCacheCleanupRegistered = true;
|
|
@@ -481,7 +482,11 @@ function generatedHtml(
|
|
|
481
482
|
const htmlAppleTouchPath = appleTouchPath.replace(/^\//, "./");
|
|
482
483
|
const styleTags = compiledAssets?.filter((asset) => asset.role === "style").map((asset) => ` <link rel="stylesheet" href="${escapeAttribute(asset.path)}" />`).join("\n") ?? "";
|
|
483
484
|
const scriptTags = compiledAssets
|
|
484
|
-
? compiledAssets
|
|
485
|
+
? compiledAssets
|
|
486
|
+
.filter((asset) => asset.role === "script")
|
|
487
|
+
.sort((left, right) => (left.scriptOrder ?? 0) - (right.scriptOrder ?? 0))
|
|
488
|
+
.map((asset) => ` <script type="module" src="${escapeAttribute(asset.path)}"></script>`)
|
|
489
|
+
.join("\n")
|
|
485
490
|
: ` <script type="module" src="${escapeAttribute(relativePrelude ?? "")}"></script>
|
|
486
491
|
<script type="module" src="${escapeAttribute(relativeEntry ?? "")}"></script>`;
|
|
487
492
|
const manifestTags = pwaEnabled(web)
|