@absolutejs/absolute 0.14.0 → 0.15.1
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 +11 -0
- package/CLAUDE.md +11 -2
- package/LICENSE +74 -18
- package/README.md +4 -4
- package/THIRD_PARTY_NOTICES.md +61 -0
- package/dist/cli/index.js +24 -8
- 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 +145 -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 +80 -0
- package/dist/dev/client/handlers/rebuild.ts +147 -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 +204 -0
- package/dist/dev/client/moduleVersions.ts +57 -0
- package/dist/dev/client/reactRefreshSetup.ts +21 -0
- package/dist/index.js +3028 -478
- package/dist/index.js.map +49 -19
- package/dist/{build → src/build}/compileSvelte.d.ts +2 -1
- package/dist/src/build/compileVue.d.ts +33 -0
- package/dist/src/build/generateReactIndexes.d.ts +1 -0
- package/dist/src/build/htmlScriptHMRPlugin.d.ts +13 -0
- package/dist/src/build/wrapHTMLScript.d.ts +24 -0
- package/dist/{core → src/core}/build.d.ts +2 -2
- package/dist/src/core/devBuild.d.ts +6 -0
- package/dist/{core → src/core}/index.d.ts +2 -1
- package/dist/src/core/lookup.d.ts +3 -0
- package/dist/{core → src/core}/pageHandlers.d.ts +4 -4
- 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/{index.d.ts → src/index.d.ts} +1 -0
- package/dist/src/plugins/hmr.d.ts +62 -0
- package/dist/{plugins → src/plugins}/index.d.ts +2 -1
- package/dist/{svelte → src/svelte}/renderToReadableStream.d.ts +3 -1
- package/dist/src/utils/getRegisterClientScript.d.ts +10 -0
- package/dist/{utils → src/utils}/index.d.ts +2 -0
- package/dist/src/utils/logger.d.ts +45 -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/{types.d.ts → types/build.d.ts} +6 -0
- package/dist/types/client.d.ts +104 -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 +5 -1
- package/package.json +19 -14
- package/tsconfig.build.json +1 -1
- package/types/build.ts +46 -0
- package/types/client.ts +109 -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/dist/build/compileVue.d.ts +0 -5
- package/dist/build/generateReactIndexes.d.ts +0 -1
- package/dist/core/lookup.d.ts +0 -1
- package/dist/utils/networking.d.ts +0 -1
- /package/dist/{build → src/build}/generateManifest.d.ts +0 -0
- /package/dist/{build → src/build}/outputLogs.d.ts +0 -0
- /package/dist/{build → src/build}/scanEntryPoints.d.ts +0 -0
- /package/dist/{build → src/build}/updateAssetPaths.d.ts +0 -0
- /package/dist/{cli → src/cli}/index.d.ts +0 -0
- /package/dist/{constants.d.ts → src/constants.d.ts} +0 -0
- /package/dist/{plugins → src/plugins}/networking.d.ts +0 -0
- /package/dist/{plugins → src/plugins}/pageRouter.d.ts +0 -0
- /package/dist/{svelte → src/svelte}/renderToPipeableStream.d.ts +0 -0
- /package/dist/{svelte → src/svelte}/renderToString.d.ts +0 -0
- /package/dist/{utils → src/utils}/cleanup.d.ts +0 -0
- /package/dist/{utils → src/utils}/commonAncestor.d.ts +0 -0
- /package/dist/{utils → src/utils}/escapeScriptContent.d.ts +0 -0
- /package/dist/{utils → src/utils}/generateHeadElement.d.ts +0 -0
- /package/dist/{utils → src/utils}/getDurationString.d.ts +0 -0
- /package/dist/{utils → src/utils}/getEnv.d.ts +0 -0
- /package/dist/{utils → src/utils}/stringModifiers.d.ts +0 -0
- /package/dist/{utils → src/utils}/validateSafePath.d.ts +0 -0
package/CLAUDE.md
CHANGED
|
@@ -11,6 +11,10 @@ AbsoluteJS is a full-stack, type-safe meta-framework for building web applicatio
|
|
|
11
11
|
```bash
|
|
12
12
|
bun run typecheck # TypeScript type checking (no emit)
|
|
13
13
|
bun run format # Prettier formatting
|
|
14
|
+
bun run lint # ESLint (do not run)
|
|
15
|
+
bun test # Run tests (do not run)
|
|
16
|
+
bun run dev # Dev server (runs example/server.ts with --hot) (ask before using always)
|
|
17
|
+
bun run build # Build the package to dist/ (do not run)
|
|
14
18
|
```
|
|
15
19
|
|
|
16
20
|
## Architecture
|
|
@@ -35,8 +39,11 @@ The central orchestrator. A single `build()` call scans, compiles, and bundles a
|
|
|
35
39
|
- **`src/core/lookup.ts`** — `asset(manifest, name)` resolves build manifest entries.
|
|
36
40
|
- **`src/build/`** — Individual compilation steps: `compileSvelte.ts`, `compileVue.ts`, `generateManifest.ts`, `generateReactIndexes.ts`, `scanEntryPoints.ts`, `updateAssetPaths.ts`.
|
|
37
41
|
- **`src/plugins/networking.ts`** — Elysia plugin for server startup with HOST/PORT config and LAN binding (`--host` flag).
|
|
42
|
+
- **`src/plugins/hmr.ts`** — HMR plugin with WebSocket-based hot reload for dev mode.
|
|
43
|
+
- **`src/dev/`** — Dev tooling: file watcher, HMR client builder, dependency graph, framework-specific HMR handlers (React, Svelte, Vue, HTML, HTMX).
|
|
44
|
+
- **`src/cli/index.ts`** — CLI entry point (`absolutejs dev [entry]`). Spawns server with `--hot`, manages database containers if docker-compose exists.
|
|
38
45
|
- **`src/utils/`** — Path validation, string transforms (`toPascal`/`toKebab`), head element generation, environment variable loading (`getEnv`).
|
|
39
|
-
- **`
|
|
46
|
+
- **`types/`** — Type definitions: `BuildConfig`, `BuildOptions`, `PropsOf<T>`, `Prettify<T>`.
|
|
40
47
|
|
|
41
48
|
### Framework SSR Pattern
|
|
42
49
|
|
|
@@ -49,8 +56,10 @@ All frameworks follow the same pattern: server renders HTML → props serialized
|
|
|
49
56
|
## Code Conventions
|
|
50
57
|
|
|
51
58
|
- **ESLint plugin `eslint-plugin-absolute`** enforces strict rules: max nesting depth of 1, min variable name length of 3 (exceptions: `_`, `id`, `db`, `OK`), explicit return types, sorted exports/keys, no default exports (except config files)
|
|
52
|
-
- **Prettier**: 4-space
|
|
59
|
+
- **Prettier**: tabs, 4-space width, 80 char print width, single quotes, no trailing commas, semicolons
|
|
53
60
|
- **TypeScript**: strict mode, ESNext target, bundler module resolution, decorator support enabled
|
|
54
61
|
- **Barrel exports**: `index.ts` files re-export alphabetically from each module directory
|
|
55
62
|
- **No default imports** from `react` or `bun` (enforced by linter)
|
|
63
|
+
- **Function expressions only**: `func-style` rule enforces arrow functions over declarations
|
|
64
|
+
- **Blank line before return** statements (enforced by `@stylistic/ts/padding-line-between-statements`)
|
|
56
65
|
- Elysia plugin pattern for server extensibility
|
package/LICENSE
CHANGED
|
@@ -1,24 +1,80 @@
|
|
|
1
|
-
|
|
2
|
-
# Creative Commons Attribution-NonCommercial 4.0 International License (CC BY-NC 4.0)
|
|
1
|
+
# Business Source License 1.1
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
**Licensor:** Alex Kahn
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
This license allows you to:
|
|
8
|
-
- **Share**: Copy and redistribute the material in any medium or format.
|
|
9
|
-
- **Adapt**: Remix, transform, and build upon the material.
|
|
5
|
+
**Licensed Work:** AbsoluteJS (https://github.com/absolutejs/absolutejs)
|
|
10
6
|
|
|
11
|
-
**
|
|
12
|
-
- **Attribution**: You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
|
|
13
|
-
- **Non-Commercial**: You may not use the material for commercial purposes.
|
|
7
|
+
**Change Date:** February 16, 2036
|
|
14
8
|
|
|
15
|
-
|
|
16
|
-
The full license text can be found at: https://creativecommons.org/licenses/by-nc/4.0/legalcode
|
|
9
|
+
**Change License:** Apache License, Version 2.0
|
|
17
10
|
|
|
18
|
-
|
|
19
|
-
For commercial use, licensing inquiries, or additional permissions, please contact:
|
|
20
|
-
Alex Kahn
|
|
21
|
-
alexkahndev@gmail.com
|
|
22
|
-
alexkahndev.github.io
|
|
23
|
-
```
|
|
11
|
+
---
|
|
24
12
|
|
|
13
|
+
## Terms
|
|
14
|
+
|
|
15
|
+
The Licensor hereby grants you the right to copy, modify, create derivative
|
|
16
|
+
works, redistribute, and make non-production use of the Licensed Work. The
|
|
17
|
+
Licensor may make an Additional Use Grant, permitting limited production use.
|
|
18
|
+
|
|
19
|
+
### Additional Use Grant
|
|
20
|
+
|
|
21
|
+
You may use the Licensed Work in production, provided your use does not include
|
|
22
|
+
any of the following:
|
|
23
|
+
|
|
24
|
+
1. **Offering a Competing Service.** You may not offer the Licensed Work, or
|
|
25
|
+
any derivative of it, to third parties as a hosted or managed service that
|
|
26
|
+
competes with the Licensed Work itself (for example, a "framework-as-a-service"
|
|
27
|
+
platform, deployment service, or cloud offering where AbsoluteJS or a
|
|
28
|
+
substantial portion of its functionality is a primary component of the value
|
|
29
|
+
provided to users).
|
|
30
|
+
|
|
31
|
+
2. **Resale or Redistribution as a Standalone Product.** You may not sell,
|
|
32
|
+
license, or distribute the Licensed Work, or any derivative or fork of it,
|
|
33
|
+
as a standalone commercial product or framework.
|
|
34
|
+
|
|
35
|
+
3. **Removal of Attribution.** Any derivative work, fork, or redistribution of
|
|
36
|
+
the Licensed Work must prominently credit AbsoluteJS and include a link to
|
|
37
|
+
the original project repository (https://github.com/absolutejs/absolutejs).
|
|
38
|
+
|
|
39
|
+
For clarity, the following uses are expressly permitted:
|
|
40
|
+
|
|
41
|
+
- Using AbsoluteJS to build and deploy your own applications, websites, or
|
|
42
|
+
SaaS products (whether commercial or non-commercial).
|
|
43
|
+
- Using AbsoluteJS as a dependency in commercial software that you build and sell,
|
|
44
|
+
as long as the software is not itself a competing framework or hosting service.
|
|
45
|
+
- Providing consulting, development, or professional services to clients using
|
|
46
|
+
AbsoluteJS.
|
|
47
|
+
- Forking and modifying the Licensed Work for your own internal use, provided
|
|
48
|
+
attribution is maintained.
|
|
49
|
+
|
|
50
|
+
### Change Date and Change License
|
|
51
|
+
|
|
52
|
+
On the Change Date specified above, or on such other date as the Licensor may
|
|
53
|
+
specify by written notice, the Licensed Work will be made available under the
|
|
54
|
+
Change License (Apache License, Version 2.0). Until the Change Date, the terms
|
|
55
|
+
of this Business Source License 1.1 apply.
|
|
56
|
+
|
|
57
|
+
### Notices
|
|
58
|
+
|
|
59
|
+
You must not remove or obscure any licensing, copyright, or other notices
|
|
60
|
+
included in the Licensed Work.
|
|
61
|
+
|
|
62
|
+
### No Warranty
|
|
63
|
+
|
|
64
|
+
THE LICENSED WORK IS PROVIDED "AS IS". THE LICENSOR HEREBY DISCLAIMS ALL
|
|
65
|
+
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
|
|
66
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO
|
|
67
|
+
EVENT SHALL THE LICENSOR BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY,
|
|
68
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR
|
|
69
|
+
IN CONNECTION WITH THE LICENSED WORK OR THE USE OR OTHER DEALINGS IN THE
|
|
70
|
+
LICENSED WORK.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Contact
|
|
75
|
+
|
|
76
|
+
For commercial licensing inquiries or additional permissions, contact:
|
|
77
|
+
|
|
78
|
+
- **Alex Kahn**
|
|
79
|
+
- alexkahndev@gmail.com
|
|
80
|
+
- alexkahndev.github.io
|
package/README.md
CHANGED
|
@@ -112,10 +112,10 @@ export const server = new Elysia()
|
|
|
112
112
|
|
|
113
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
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
|
|
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
119
|
|
|
120
120
|
---
|
|
121
121
|
|
|
@@ -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.
|
package/dist/cli/index.js
CHANGED
|
@@ -43,30 +43,46 @@ var dev = async (serverEntry) => {
|
|
|
43
43
|
const scripts = usesDocker ? await readDbScripts() : null;
|
|
44
44
|
if (scripts)
|
|
45
45
|
await startDatabase(scripts);
|
|
46
|
-
const
|
|
46
|
+
const spawnServer = () => Bun.spawn(["bun", "--hot", serverEntry], {
|
|
47
|
+
cwd: process.cwd(),
|
|
48
|
+
env: {
|
|
49
|
+
...process.env,
|
|
50
|
+
NODE_ENV: "development"
|
|
51
|
+
},
|
|
47
52
|
stdin: "inherit",
|
|
48
53
|
stdout: "inherit",
|
|
49
54
|
stderr: "inherit"
|
|
50
55
|
});
|
|
56
|
+
let serverProcess = spawnServer();
|
|
51
57
|
let cleaning = false;
|
|
52
58
|
const cleanup = async (exitCode = 0) => {
|
|
53
59
|
if (cleaning)
|
|
54
60
|
return;
|
|
55
61
|
cleaning = true;
|
|
56
62
|
try {
|
|
57
|
-
|
|
58
|
-
} catch
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
await server.exited;
|
|
63
|
+
serverProcess.kill();
|
|
64
|
+
} catch {}
|
|
65
|
+
await serverProcess.exited;
|
|
62
66
|
if (scripts)
|
|
63
67
|
await stopDatabase(scripts);
|
|
64
68
|
process.exit(exitCode);
|
|
65
69
|
};
|
|
66
70
|
process.on("SIGINT", () => cleanup(0));
|
|
67
71
|
process.on("SIGTERM", () => cleanup(0));
|
|
68
|
-
|
|
69
|
-
|
|
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();
|
|
70
86
|
};
|
|
71
87
|
var command = process.argv[2];
|
|
72
88
|
if (command === "dev") {
|
|
@@ -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
|
+
};
|