@abs-test/absolutejs-test 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.
- package/.claude/settings.local.json +10 -0
- package/.hmr-temp/App.3e3a3834.js +65 -0
- package/.hmr-temp/App.462a6bf2.js +65 -0
- package/.hmr-temp/App.9d060aa8.js +65 -0
- package/.hmr-temp/Dropdown.4cb79868.js +43 -0
- package/.hmr-temp/Head.92c96724.js +51 -0
- package/.hmr-temp/ReactExample.031ea6c9.js +28 -0
- package/.hmr-temp/ReactExample.3ad8f8c9.js +29 -0
- package/.hmr-temp/ReactExample.b28ebc93.js +27 -0
- package/.hmr-temp/ReactExample.c4bbd06e.js +27 -0
- package/.hmr-temp/ReactExample.dcc8d0d4.js +27 -0
- package/CLAUDE.md +65 -0
- package/LICENSE +80 -0
- package/README.md +163 -0
- package/THIRD_PARTY_NOTICES.md +61 -0
- package/abs-test-absolutejs-test-0.1.0.tgz +0 -0
- package/absolutejs-absolute-0.15.12.tgz +0 -0
- package/dist/cli/index.js +98 -0
- package/dist/dev/client/cssUtils.ts +288 -0
- package/dist/dev/client/domDiff.ts +261 -0
- package/dist/dev/client/domState.ts +271 -0
- package/dist/dev/client/errorOverlay.ts +168 -0
- package/dist/dev/client/frameworkDetect.ts +63 -0
- package/dist/dev/client/handlers/html.ts +415 -0
- package/dist/dev/client/handlers/htmx.ts +248 -0
- package/dist/dev/client/handlers/react.ts +86 -0
- package/dist/dev/client/handlers/rebuild.ts +153 -0
- package/dist/dev/client/handlers/svelte.ts +129 -0
- package/dist/dev/client/handlers/vue.ts +254 -0
- package/dist/dev/client/headPatch.ts +213 -0
- package/dist/dev/client/hmrClient.ts +237 -0
- package/dist/dev/client/moduleVersions.ts +57 -0
- package/dist/dev/client/reactRefreshSetup.ts +21 -0
- package/dist/index.js +3667 -0
- package/dist/index.js.map +65 -0
- package/dist/src/build/buildReactVendor.d.ts +8 -0
- package/dist/src/build/compileSvelte.d.ts +11 -0
- package/dist/src/build/compileVue.d.ts +33 -0
- package/dist/src/build/generateManifest.d.ts +2 -0
- package/dist/src/build/generateReactIndexes.d.ts +1 -0
- package/dist/src/build/htmlScriptHMRPlugin.d.ts +13 -0
- package/dist/src/build/outputLogs.d.ts +1 -0
- package/dist/src/build/rewriteReactImports.d.ts +8 -0
- package/dist/src/build/scanEntryPoints.d.ts +1 -0
- package/dist/src/build/updateAssetPaths.d.ts +1 -0
- package/dist/src/build/wrapHTMLScript.d.ts +24 -0
- package/dist/src/cli/index.d.ts +2 -0
- package/dist/src/constants.d.ts +12 -0
- package/dist/src/core/build.d.ts +2 -0
- package/dist/src/core/devBuild.d.ts +6 -0
- package/dist/src/core/devVendorPaths.d.ts +7 -0
- package/dist/src/core/index.d.ts +4 -0
- package/dist/src/core/lookup.d.ts +3 -0
- package/dist/src/core/pageHandlers.d.ts +15 -0
- package/dist/src/dev/assetStore.d.ts +12 -0
- package/dist/src/dev/buildHMRClient.d.ts +1 -0
- package/dist/src/dev/clientManager.d.ts +26 -0
- package/dist/src/dev/configResolver.d.ts +13 -0
- package/dist/src/dev/dependencyGraph.d.ts +13 -0
- package/dist/src/dev/fileHashTracker.d.ts +2 -0
- package/dist/src/dev/fileWatcher.d.ts +3 -0
- package/dist/src/dev/moduleMapper.d.ts +21 -0
- package/dist/src/dev/moduleVersionTracker.d.ts +7 -0
- package/dist/src/dev/pathUtils.d.ts +5 -0
- package/dist/src/dev/reactComponentClassifier.d.ts +2 -0
- package/dist/src/dev/rebuildTrigger.d.ts +10 -0
- package/dist/src/dev/simpleHTMLHMR.d.ts +4 -0
- package/dist/src/dev/simpleHTMXHMR.d.ts +4 -0
- package/dist/src/dev/simpleSvelteHMR.d.ts +1 -0
- package/dist/src/dev/simpleVueHMR.d.ts +1 -0
- package/dist/src/dev/webSocket.d.ts +9 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/plugins/hmr.d.ts +62 -0
- package/dist/src/plugins/index.d.ts +3 -0
- package/dist/src/plugins/networking.d.ts +29 -0
- package/dist/src/plugins/pageRouter.d.ts +1 -0
- package/dist/src/svelte/renderToPipeableStream.d.ts +12 -0
- package/dist/src/svelte/renderToReadableStream.d.ts +13 -0
- package/dist/src/svelte/renderToString.d.ts +9 -0
- package/dist/src/utils/cleanup.d.ts +7 -0
- package/dist/src/utils/commonAncestor.d.ts +1 -0
- package/dist/src/utils/escapeScriptContent.d.ts +1 -0
- package/dist/src/utils/generateHeadElement.d.ts +17 -0
- package/dist/src/utils/getDurationString.d.ts +1 -0
- package/dist/src/utils/getEnv.d.ts +1 -0
- package/dist/src/utils/getRegisterClientScript.d.ts +10 -0
- package/dist/src/utils/index.d.ts +6 -0
- package/dist/src/utils/logger.d.ts +54 -0
- package/dist/src/utils/networking.d.ts +2 -0
- package/dist/src/utils/normalizePath.d.ts +9 -0
- package/dist/src/utils/registerClientScript.d.ts +51 -0
- package/dist/src/utils/stringModifiers.d.ts +2 -0
- package/dist/src/utils/validateSafePath.d.ts +1 -0
- package/dist/types/build.d.ts +41 -0
- package/dist/types/client.d.ts +108 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/messages.d.ts +138 -0
- package/dist/types/websocket.d.ts +6 -0
- package/eslint.config.mjs +238 -0
- package/package.json +67 -0
- package/tsconfig.build.json +20 -0
- package/types/build.ts +54 -0
- package/types/client.ts +111 -0
- package/types/index.ts +4 -0
- package/types/messages.ts +205 -0
- package/types/websocket.ts +12 -0
- package/types/window-globals.ts +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# AbsoluteJS
|
|
2
|
+
|
|
3
|
+
Full‑stack, **type‑safe** batteries‑included platform that lets you **server‑side render _any_ modern front‑end**—React, Svelte, plain HTML, HTMX (Vue & Angular coming)—with a single Bun‑powered build step.
|
|
4
|
+
|
|
5
|
+
[](https://bun.sh)
|
|
6
|
+
[](https://elysiajs.com)
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Why Absolute JS?
|
|
12
|
+
|
|
13
|
+
- **Universal SSR.** Bring your favourite UI layer; Absolute JS handles bundling, hydration, and HTML streaming.
|
|
14
|
+
- **One build, one manifest.** Call `build()` once—get a manifest mapping every page’s client and server assets, ready to wire into routes.
|
|
15
|
+
- **End‑to‑end type safety.** A unified source of truth for your types—from the database, through the server, and all the way to the client—so you can be certain of the data shape at every step.
|
|
16
|
+
- **Zero‑config philosophy.** Point the build at your folders; sane defaults light up everything else.
|
|
17
|
+
- **Plugin power.** Extend with standard Elysia plugins—ship auth, logging, i18n, and more. First‑party: `absolute-auth`, `networkingPlugin`.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Requirements
|
|
22
|
+
|
|
23
|
+
| Tool | Version | Purpose |
|
|
24
|
+
| ---------- | ------- | ------------------------------------------- |
|
|
25
|
+
| **Bun** | ≥ 1.2 | Runtime, bundler, and TypeScript transpiler |
|
|
26
|
+
| **Elysia** | latest | Web server & middleware platform |
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
bun add @absolutejs/absolute
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
// example/server.ts
|
|
42
|
+
import { staticPlugin } from '@elysiajs/static';
|
|
43
|
+
import { Elysia } from 'elysia';
|
|
44
|
+
import { file } from 'bun';
|
|
45
|
+
import { build } from 'absolutejs/core/build';
|
|
46
|
+
import {
|
|
47
|
+
handleHTMLPageRequest,
|
|
48
|
+
handleReactPageRequest,
|
|
49
|
+
handleSveltePageRequest
|
|
50
|
+
} from 'absolutejs/core/pageHandlers';
|
|
51
|
+
|
|
52
|
+
import { ReactExample } from './react/pages/ReactExample';
|
|
53
|
+
import SvelteExample from './svelte/pages/SvelteExample.svelte';
|
|
54
|
+
import { networkingPlugin } from 'absolutejs';
|
|
55
|
+
|
|
56
|
+
const manifest = await build({
|
|
57
|
+
assetsDirectory: 'example/assets',
|
|
58
|
+
buildDirectory: 'example/build',
|
|
59
|
+
htmlDirectory: 'example/html',
|
|
60
|
+
htmxDirectory: 'example/htmx',
|
|
61
|
+
reactDirectory: 'example/react',
|
|
62
|
+
svelteDirectory: 'example/svelte',
|
|
63
|
+
options: { preserveIntermediateFiles: true }
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (!manifest) throw new Error('Manifest generation failed');
|
|
67
|
+
|
|
68
|
+
let counter = 0;
|
|
69
|
+
|
|
70
|
+
export const server = new Elysia()
|
|
71
|
+
.use(staticPlugin({ assets: './example/build', prefix: '' }))
|
|
72
|
+
|
|
73
|
+
// HTML
|
|
74
|
+
.get('/', () =>
|
|
75
|
+
handleHTMLPageRequest('./example/build/html/pages/HtmlExample.html')
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
// React
|
|
79
|
+
.get('/react', () =>
|
|
80
|
+
handleReactPageRequest(ReactExample, manifest['ReactExampleIndex'], {
|
|
81
|
+
test: 123
|
|
82
|
+
})
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
// Svelte
|
|
86
|
+
.get('/svelte', () =>
|
|
87
|
+
handleSveltePageRequest(SvelteExample, manifest, { test: 456 })
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
// HTMX demo
|
|
91
|
+
.get('/htmx', () => file('./example/build/htmx/HtmxHome.html'))
|
|
92
|
+
.get('/htmx/increment', () => new Response(String(++counter)))
|
|
93
|
+
|
|
94
|
+
.use(networkingPlugin)
|
|
95
|
+
.on('error', (error) => {
|
|
96
|
+
const { request } = error;
|
|
97
|
+
console.error(
|
|
98
|
+
`Server error on ${request.method} ${request.url}: ${error.message}`
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### How it works
|
|
104
|
+
|
|
105
|
+
1. **`build()`** scans your project, bundles each framework, and returns a **manifest** that has the server, and client assets required to serve each route.
|
|
106
|
+
2. Route handlers (`handleReactPageRequest`, `handleSveltePageRequest`, …) stream HTML and inject scripts/assets based on that manifest.
|
|
107
|
+
3. The static plugin serves all compiled files from `/build`.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Plugin System
|
|
112
|
+
|
|
113
|
+
Absolute JS piggybacks on the [Elysia plugin API](https://elysiajs.com/plugins). Any Elysia plugin works out of the box; Absolute adds helpers for:
|
|
114
|
+
|
|
115
|
+
| Plugin | Description |
|
|
116
|
+
| ---------------------- | ------------------------------------------------------------------------------------------------- |
|
|
117
|
+
| **`absolute-auth`** | Full OAuth2 flow configured with 66 providers and allows full customizability with event handlers |
|
|
118
|
+
| **`networkingPlugin`** | Starts your Elysia server with HOST/PORT defaults from environment variables |
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Configuration Philosophy
|
|
123
|
+
|
|
124
|
+
Everything funnels through a single `build()` call:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
await build({
|
|
128
|
+
reactDirectory: 'src/react',
|
|
129
|
+
svelteDirectory: 'src/svelte',
|
|
130
|
+
htmlDirectory: 'src/html',
|
|
131
|
+
htmxDirectory: 'src/htmx',
|
|
132
|
+
assetsDirectory: 'public/assets',
|
|
133
|
+
options: { preserveIntermediateFiles: false }
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
No separate config files or environment variables—just explicit arguments with sensible defaults.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Roadmap
|
|
142
|
+
|
|
143
|
+
- **Angular** handlers
|
|
144
|
+
- Prisma support
|
|
145
|
+
- Biome support
|
|
146
|
+
- Hot‑reload development server
|
|
147
|
+
- First‑class Docker images & hosting recipes
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Contributing
|
|
152
|
+
|
|
153
|
+
Pull requests and issues are welcome! Whether it’s a new plugin, framework handler, or docs improvement:
|
|
154
|
+
|
|
155
|
+
1. Fork & branch.
|
|
156
|
+
2. `bun install && bun test`.
|
|
157
|
+
3. Submit a PR with a clear description.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
**Business Source License 1.1 (BSL-1.1)** – see [`LICENSE`](./LICENSE) for details.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Third-Party Notices
|
|
2
|
+
|
|
3
|
+
This product includes the following third-party software:
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Bun
|
|
8
|
+
|
|
9
|
+
- **Source:** https://github.com/oven-sh/bun
|
|
10
|
+
- **License:** MIT
|
|
11
|
+
|
|
12
|
+
MIT License
|
|
13
|
+
|
|
14
|
+
Copyright (c) 2024 Bun
|
|
15
|
+
|
|
16
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
17
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
18
|
+
in the Software without restriction, including without limitation the rights
|
|
19
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
20
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
21
|
+
furnished to do so, subject to the following conditions:
|
|
22
|
+
|
|
23
|
+
The above copyright notice and this permission notice shall be included in all
|
|
24
|
+
copies or substantial portions of the Software.
|
|
25
|
+
|
|
26
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
27
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
28
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
29
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
30
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
31
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
32
|
+
SOFTWARE.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Elysia
|
|
37
|
+
|
|
38
|
+
- **Source:** https://github.com/elysiajs/elysia
|
|
39
|
+
- **License:** MIT
|
|
40
|
+
|
|
41
|
+
MIT License
|
|
42
|
+
|
|
43
|
+
Copyright (c) 2022 SaltyAom
|
|
44
|
+
|
|
45
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
46
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
47
|
+
in the Software without restriction, including without limitation the rights
|
|
48
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
49
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
50
|
+
furnished to do so, subject to the following conditions:
|
|
51
|
+
|
|
52
|
+
The above copyright notice and this permission notice shall be included in all
|
|
53
|
+
copies or substantial portions of the Software.
|
|
54
|
+
|
|
55
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
56
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
57
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
58
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
59
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
60
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
61
|
+
SOFTWARE.
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// src/cli/index.ts
|
|
5
|
+
var {$ } = globalThis.Bun;
|
|
6
|
+
import { existsSync } from "fs";
|
|
7
|
+
import { resolve } from "path";
|
|
8
|
+
var COMPOSE_PATH = "db/docker-compose.db.yml";
|
|
9
|
+
var DEFAULT_SERVER_ENTRY = "src/backend/server.ts";
|
|
10
|
+
var readDbScripts = async () => {
|
|
11
|
+
const pkgPath = resolve("package.json");
|
|
12
|
+
if (!existsSync(pkgPath))
|
|
13
|
+
return null;
|
|
14
|
+
const pkg = await Bun.file(pkgPath).json();
|
|
15
|
+
const upCommand = pkg.scripts?.["db:up"];
|
|
16
|
+
const downCommand = pkg.scripts?.["db:down"];
|
|
17
|
+
if (!upCommand || !downCommand)
|
|
18
|
+
return null;
|
|
19
|
+
return { upCommand, downCommand };
|
|
20
|
+
};
|
|
21
|
+
var timed = async (label, fn) => {
|
|
22
|
+
process.stdout.write(label);
|
|
23
|
+
const start = performance.now();
|
|
24
|
+
await fn();
|
|
25
|
+
const duration = ((performance.now() - start) / 1000).toFixed(2);
|
|
26
|
+
process.stdout.write(` \x1B[90m${duration}s\x1B[0m
|
|
27
|
+
`);
|
|
28
|
+
};
|
|
29
|
+
var startDatabase = async (scripts) => {
|
|
30
|
+
await timed("Starting database container...", async () => {
|
|
31
|
+
const { exitCode } = await $`${{ raw: scripts.upCommand }}`.quiet().nothrow();
|
|
32
|
+
if (exitCode !== 0)
|
|
33
|
+
process.exit(exitCode);
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
var stopDatabase = async (scripts) => {
|
|
37
|
+
console.log(`
|
|
38
|
+
Stopping database container...`);
|
|
39
|
+
await $`${{ raw: scripts.downCommand }}`.quiet().nothrow();
|
|
40
|
+
};
|
|
41
|
+
var dev = async (serverEntry) => {
|
|
42
|
+
const usesDocker = existsSync(resolve(COMPOSE_PATH));
|
|
43
|
+
const scripts = usesDocker ? await readDbScripts() : null;
|
|
44
|
+
if (scripts)
|
|
45
|
+
await startDatabase(scripts);
|
|
46
|
+
const spawnServer = () => Bun.spawn(["bun", "--hot", "--no-clear-screen", serverEntry], {
|
|
47
|
+
cwd: process.cwd(),
|
|
48
|
+
env: {
|
|
49
|
+
...process.env,
|
|
50
|
+
NODE_ENV: "development"
|
|
51
|
+
},
|
|
52
|
+
stdin: "inherit",
|
|
53
|
+
stdout: "inherit",
|
|
54
|
+
stderr: "inherit"
|
|
55
|
+
});
|
|
56
|
+
let serverProcess = spawnServer();
|
|
57
|
+
let cleaning = false;
|
|
58
|
+
const cleanup = async (exitCode = 0) => {
|
|
59
|
+
if (cleaning)
|
|
60
|
+
return;
|
|
61
|
+
cleaning = true;
|
|
62
|
+
try {
|
|
63
|
+
serverProcess.kill();
|
|
64
|
+
} catch {}
|
|
65
|
+
await serverProcess.exited;
|
|
66
|
+
if (scripts)
|
|
67
|
+
await stopDatabase(scripts);
|
|
68
|
+
process.exit(exitCode);
|
|
69
|
+
};
|
|
70
|
+
process.on("SIGINT", () => cleanup(0));
|
|
71
|
+
process.on("SIGTERM", () => cleanup(0));
|
|
72
|
+
const monitorServer = async () => {
|
|
73
|
+
while (!cleaning) {
|
|
74
|
+
const exitCode = await serverProcess.exited;
|
|
75
|
+
if (cleaning)
|
|
76
|
+
continue;
|
|
77
|
+
if (exitCode === 130 || exitCode === 143) {
|
|
78
|
+
await cleanup(0);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
console.error(`\x1B[31m[cli] Server exited (code ${exitCode}), restarting...\x1B[0m`);
|
|
82
|
+
serverProcess = spawnServer();
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
await monitorServer();
|
|
86
|
+
};
|
|
87
|
+
var command = process.argv[2];
|
|
88
|
+
if (command === "dev") {
|
|
89
|
+
const serverEntry = process.argv[3] ?? DEFAULT_SERVER_ENTRY;
|
|
90
|
+
await dev(serverEntry);
|
|
91
|
+
} else {
|
|
92
|
+
const message = command ? `Unknown command: ${command}` : "No command specified";
|
|
93
|
+
console.error(message);
|
|
94
|
+
console.error("Usage: absolutejs <command>");
|
|
95
|
+
console.error("Commands:");
|
|
96
|
+
console.error(" dev [entry] Start development server");
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/* CSS reload/preload utilities for HMR */
|
|
2
|
+
|
|
3
|
+
import type { CSSUpdateResult } from '../../../types/client';
|
|
4
|
+
import { hmrState } from '../../../types/client';
|
|
5
|
+
|
|
6
|
+
export const getCSSBaseName = (href: string) => {
|
|
7
|
+
const fileName = href.split('?')[0]!.split('/').pop() || '';
|
|
8
|
+
return fileName.split('.')[0]!;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const reloadCSSStylesheets = (manifest: Record<string, string>) => {
|
|
12
|
+
const stylesheets = document.querySelectorAll('link[rel="stylesheet"]');
|
|
13
|
+
stylesheets.forEach(function (link) {
|
|
14
|
+
const href = (link as HTMLLinkElement).getAttribute('href');
|
|
15
|
+
if (!href || href.includes('htmx.min.js')) return;
|
|
16
|
+
|
|
17
|
+
let newHref: string | null = null;
|
|
18
|
+
if (manifest) {
|
|
19
|
+
const baseName = href
|
|
20
|
+
.split('/')
|
|
21
|
+
.pop()!
|
|
22
|
+
.replace(/\.[^.]*$/, '');
|
|
23
|
+
const manifestKey =
|
|
24
|
+
baseName
|
|
25
|
+
.split('-')
|
|
26
|
+
.map(function (part) {
|
|
27
|
+
return part.charAt(0).toUpperCase() + part.slice(1);
|
|
28
|
+
})
|
|
29
|
+
.join('') + 'CSS';
|
|
30
|
+
|
|
31
|
+
if (manifest[manifestKey]) {
|
|
32
|
+
newHref = manifest[manifestKey]!;
|
|
33
|
+
} else {
|
|
34
|
+
for (const [key, value] of Object.entries(manifest)) {
|
|
35
|
+
if (key.endsWith('CSS') && value.includes(baseName)) {
|
|
36
|
+
newHref = value;
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (newHref && newHref !== href) {
|
|
44
|
+
(link as HTMLLinkElement).href = newHref + '?t=' + Date.now();
|
|
45
|
+
} else {
|
|
46
|
+
const url = new URL(href, window.location.origin);
|
|
47
|
+
url.searchParams.set('t', Date.now().toString());
|
|
48
|
+
(link as HTMLLinkElement).href = url.toString();
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/* Shared CSS preload/swap logic used by HTML and HTMX handlers.
|
|
54
|
+
Returns tracking arrays for coordinating CSS load with body patching. */
|
|
55
|
+
export const processCSSLinks = (headHTML: string) => {
|
|
56
|
+
const tempDiv = document.createElement('div');
|
|
57
|
+
tempDiv.innerHTML = headHTML;
|
|
58
|
+
const newStylesheets = tempDiv.querySelectorAll('link[rel="stylesheet"]');
|
|
59
|
+
const existingStylesheets = Array.from(
|
|
60
|
+
document.head.querySelectorAll<HTMLLinkElement>(
|
|
61
|
+
'link[rel="stylesheet"]'
|
|
62
|
+
)
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const newHrefs = Array.from(newStylesheets).map(function (link) {
|
|
66
|
+
const href = link.getAttribute('href') || '';
|
|
67
|
+
return getCSSBaseName(href);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const linksToRemove: HTMLLinkElement[] = [];
|
|
71
|
+
const linksToWaitFor: Promise<void>[] = [];
|
|
72
|
+
const linksToActivate: HTMLLinkElement[] = [];
|
|
73
|
+
|
|
74
|
+
newStylesheets.forEach(function (newLink) {
|
|
75
|
+
const href = newLink.getAttribute('href');
|
|
76
|
+
if (!href) return;
|
|
77
|
+
|
|
78
|
+
const baseNew = getCSSBaseName(href);
|
|
79
|
+
|
|
80
|
+
let existingLink: HTMLLinkElement | null = null;
|
|
81
|
+
document.head
|
|
82
|
+
.querySelectorAll('link[rel="stylesheet"]')
|
|
83
|
+
.forEach(function (existing) {
|
|
84
|
+
const existingHref =
|
|
85
|
+
(existing as HTMLLinkElement).getAttribute('href') || '';
|
|
86
|
+
const baseExisting = getCSSBaseName(existingHref);
|
|
87
|
+
if (
|
|
88
|
+
baseExisting === baseNew ||
|
|
89
|
+
baseExisting.includes(baseNew) ||
|
|
90
|
+
baseNew.includes(baseExisting)
|
|
91
|
+
) {
|
|
92
|
+
existingLink = existing as HTMLLinkElement;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (existingLink) {
|
|
97
|
+
const existingHrefAttr = (
|
|
98
|
+
existingLink as HTMLLinkElement
|
|
99
|
+
).getAttribute('href');
|
|
100
|
+
const existingHref = existingHrefAttr
|
|
101
|
+
? existingHrefAttr.split('?')[0]
|
|
102
|
+
: '';
|
|
103
|
+
const newHrefBase = href.split('?')[0];
|
|
104
|
+
if (existingHref !== newHrefBase) {
|
|
105
|
+
const newLinkElement = document.createElement('link');
|
|
106
|
+
newLinkElement.rel = 'stylesheet';
|
|
107
|
+
newLinkElement.media = 'print';
|
|
108
|
+
const newHref =
|
|
109
|
+
href + (href.includes('?') ? '&' : '?') + 't=' + Date.now();
|
|
110
|
+
newLinkElement.href = newHref;
|
|
111
|
+
|
|
112
|
+
linksToRemove.push(existingLink as HTMLLinkElement);
|
|
113
|
+
linksToActivate.push(newLinkElement);
|
|
114
|
+
|
|
115
|
+
const loadPromise = createCSSLoadPromise(
|
|
116
|
+
newLinkElement,
|
|
117
|
+
newHref
|
|
118
|
+
);
|
|
119
|
+
document.head.appendChild(newLinkElement);
|
|
120
|
+
linksToWaitFor.push(loadPromise);
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
const newLinkElement = document.createElement('link');
|
|
124
|
+
newLinkElement.rel = 'stylesheet';
|
|
125
|
+
newLinkElement.media = 'print';
|
|
126
|
+
const newHref =
|
|
127
|
+
href + (href.includes('?') ? '&' : '?') + 't=' + Date.now();
|
|
128
|
+
newLinkElement.href = newHref;
|
|
129
|
+
|
|
130
|
+
linksToActivate.push(newLinkElement);
|
|
131
|
+
|
|
132
|
+
const loadPromise = createCSSLoadPromise(newLinkElement, newHref);
|
|
133
|
+
document.head.appendChild(newLinkElement);
|
|
134
|
+
linksToWaitFor.push(loadPromise);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
existingStylesheets.forEach(function (existingLink) {
|
|
139
|
+
const existingHref = existingLink.getAttribute('href') || '';
|
|
140
|
+
const baseExisting = getCSSBaseName(existingHref);
|
|
141
|
+
const stillExists = newHrefs.some(function (newBase) {
|
|
142
|
+
return (
|
|
143
|
+
baseExisting === newBase ||
|
|
144
|
+
baseExisting.includes(newBase) ||
|
|
145
|
+
newBase.includes(baseExisting)
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (!stillExists) {
|
|
150
|
+
const wasHandled = Array.from(newStylesheets).some(
|
|
151
|
+
function (newLink) {
|
|
152
|
+
const newHref = newLink.getAttribute('href') || '';
|
|
153
|
+
const baseNewLocal = getCSSBaseName(newHref);
|
|
154
|
+
return (
|
|
155
|
+
baseExisting === baseNewLocal ||
|
|
156
|
+
baseExisting.includes(baseNewLocal) ||
|
|
157
|
+
baseNewLocal.includes(baseExisting)
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
if (!wasHandled) {
|
|
163
|
+
linksToRemove.push(existingLink);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return { linksToActivate, linksToRemove, linksToWaitFor };
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const createCSSLoadPromise = (
|
|
172
|
+
linkElement: HTMLLinkElement,
|
|
173
|
+
newHref: string
|
|
174
|
+
) => {
|
|
175
|
+
return new Promise<void>(function (resolve) {
|
|
176
|
+
let resolved = false;
|
|
177
|
+
const doResolve = function () {
|
|
178
|
+
if (resolved) return;
|
|
179
|
+
resolved = true;
|
|
180
|
+
resolve();
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const verifyCSSOM = function () {
|
|
184
|
+
try {
|
|
185
|
+
const sheets = Array.from(document.styleSheets);
|
|
186
|
+
return sheets.some(function (sheet) {
|
|
187
|
+
return (
|
|
188
|
+
sheet.href &&
|
|
189
|
+
sheet.href.includes(newHref.split('?')[0]!)
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
} catch {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
linkElement.onload = function () {
|
|
198
|
+
let checkCount = 0;
|
|
199
|
+
const checkCSSOM = function () {
|
|
200
|
+
checkCount++;
|
|
201
|
+
if (verifyCSSOM() || checkCount > 10) {
|
|
202
|
+
doResolve();
|
|
203
|
+
} else {
|
|
204
|
+
requestAnimationFrame(checkCSSOM);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
requestAnimationFrame(checkCSSOM);
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
linkElement.onerror = function () {
|
|
211
|
+
setTimeout(function () {
|
|
212
|
+
doResolve();
|
|
213
|
+
}, 50);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
setTimeout(function () {
|
|
217
|
+
if (linkElement.sheet && !resolved) {
|
|
218
|
+
doResolve();
|
|
219
|
+
}
|
|
220
|
+
}, 100);
|
|
221
|
+
|
|
222
|
+
setTimeout(function () {
|
|
223
|
+
if (!resolved) {
|
|
224
|
+
doResolve();
|
|
225
|
+
}
|
|
226
|
+
}, 500);
|
|
227
|
+
});
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
/* Coordinate CSS load with body update: waits for CSS, patches body,
|
|
231
|
+
activates new CSS, removes old CSS. Handles first-update delay. */
|
|
232
|
+
export const waitForCSSAndUpdate = (
|
|
233
|
+
cssResult: CSSUpdateResult,
|
|
234
|
+
updateBody: () => void
|
|
235
|
+
) => {
|
|
236
|
+
const { linksToActivate, linksToRemove, linksToWaitFor } = cssResult;
|
|
237
|
+
|
|
238
|
+
if (linksToWaitFor.length > 0) {
|
|
239
|
+
Promise.all(linksToWaitFor).then(function () {
|
|
240
|
+
setTimeout(function () {
|
|
241
|
+
requestAnimationFrame(function () {
|
|
242
|
+
requestAnimationFrame(function () {
|
|
243
|
+
requestAnimationFrame(function () {
|
|
244
|
+
updateBody();
|
|
245
|
+
linksToActivate.forEach(function (link) {
|
|
246
|
+
link.media = 'all';
|
|
247
|
+
});
|
|
248
|
+
requestAnimationFrame(function () {
|
|
249
|
+
linksToRemove.forEach(function (link) {
|
|
250
|
+
if (link.parentNode) {
|
|
251
|
+
link.remove();
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
if (hmrState.isFirstHMRUpdate) {
|
|
255
|
+
hmrState.isFirstHMRUpdate = false;
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
}, 50);
|
|
262
|
+
});
|
|
263
|
+
} else {
|
|
264
|
+
const doUpdate = function () {
|
|
265
|
+
requestAnimationFrame(function () {
|
|
266
|
+
requestAnimationFrame(function () {
|
|
267
|
+
requestAnimationFrame(function () {
|
|
268
|
+
updateBody();
|
|
269
|
+
requestAnimationFrame(function () {
|
|
270
|
+
linksToRemove.forEach(function (link) {
|
|
271
|
+
if (link.parentNode) {
|
|
272
|
+
link.remove();
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
if (hmrState.isFirstHMRUpdate) {
|
|
282
|
+
hmrState.isFirstHMRUpdate = false;
|
|
283
|
+
setTimeout(doUpdate, 50);
|
|
284
|
+
} else {
|
|
285
|
+
doUpdate();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
};
|