@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.
Files changed (107) hide show
  1. package/.claude/settings.local.json +10 -0
  2. package/.hmr-temp/App.3e3a3834.js +65 -0
  3. package/.hmr-temp/App.462a6bf2.js +65 -0
  4. package/.hmr-temp/App.9d060aa8.js +65 -0
  5. package/.hmr-temp/Dropdown.4cb79868.js +43 -0
  6. package/.hmr-temp/Head.92c96724.js +51 -0
  7. package/.hmr-temp/ReactExample.031ea6c9.js +28 -0
  8. package/.hmr-temp/ReactExample.3ad8f8c9.js +29 -0
  9. package/.hmr-temp/ReactExample.b28ebc93.js +27 -0
  10. package/.hmr-temp/ReactExample.c4bbd06e.js +27 -0
  11. package/.hmr-temp/ReactExample.dcc8d0d4.js +27 -0
  12. package/CLAUDE.md +65 -0
  13. package/LICENSE +80 -0
  14. package/README.md +163 -0
  15. package/THIRD_PARTY_NOTICES.md +61 -0
  16. package/abs-test-absolutejs-test-0.1.0.tgz +0 -0
  17. package/absolutejs-absolute-0.15.12.tgz +0 -0
  18. package/dist/cli/index.js +98 -0
  19. package/dist/dev/client/cssUtils.ts +288 -0
  20. package/dist/dev/client/domDiff.ts +261 -0
  21. package/dist/dev/client/domState.ts +271 -0
  22. package/dist/dev/client/errorOverlay.ts +168 -0
  23. package/dist/dev/client/frameworkDetect.ts +63 -0
  24. package/dist/dev/client/handlers/html.ts +415 -0
  25. package/dist/dev/client/handlers/htmx.ts +248 -0
  26. package/dist/dev/client/handlers/react.ts +86 -0
  27. package/dist/dev/client/handlers/rebuild.ts +153 -0
  28. package/dist/dev/client/handlers/svelte.ts +129 -0
  29. package/dist/dev/client/handlers/vue.ts +254 -0
  30. package/dist/dev/client/headPatch.ts +213 -0
  31. package/dist/dev/client/hmrClient.ts +237 -0
  32. package/dist/dev/client/moduleVersions.ts +57 -0
  33. package/dist/dev/client/reactRefreshSetup.ts +21 -0
  34. package/dist/index.js +3667 -0
  35. package/dist/index.js.map +65 -0
  36. package/dist/src/build/buildReactVendor.d.ts +8 -0
  37. package/dist/src/build/compileSvelte.d.ts +11 -0
  38. package/dist/src/build/compileVue.d.ts +33 -0
  39. package/dist/src/build/generateManifest.d.ts +2 -0
  40. package/dist/src/build/generateReactIndexes.d.ts +1 -0
  41. package/dist/src/build/htmlScriptHMRPlugin.d.ts +13 -0
  42. package/dist/src/build/outputLogs.d.ts +1 -0
  43. package/dist/src/build/rewriteReactImports.d.ts +8 -0
  44. package/dist/src/build/scanEntryPoints.d.ts +1 -0
  45. package/dist/src/build/updateAssetPaths.d.ts +1 -0
  46. package/dist/src/build/wrapHTMLScript.d.ts +24 -0
  47. package/dist/src/cli/index.d.ts +2 -0
  48. package/dist/src/constants.d.ts +12 -0
  49. package/dist/src/core/build.d.ts +2 -0
  50. package/dist/src/core/devBuild.d.ts +6 -0
  51. package/dist/src/core/devVendorPaths.d.ts +7 -0
  52. package/dist/src/core/index.d.ts +4 -0
  53. package/dist/src/core/lookup.d.ts +3 -0
  54. package/dist/src/core/pageHandlers.d.ts +15 -0
  55. package/dist/src/dev/assetStore.d.ts +12 -0
  56. package/dist/src/dev/buildHMRClient.d.ts +1 -0
  57. package/dist/src/dev/clientManager.d.ts +26 -0
  58. package/dist/src/dev/configResolver.d.ts +13 -0
  59. package/dist/src/dev/dependencyGraph.d.ts +13 -0
  60. package/dist/src/dev/fileHashTracker.d.ts +2 -0
  61. package/dist/src/dev/fileWatcher.d.ts +3 -0
  62. package/dist/src/dev/moduleMapper.d.ts +21 -0
  63. package/dist/src/dev/moduleVersionTracker.d.ts +7 -0
  64. package/dist/src/dev/pathUtils.d.ts +5 -0
  65. package/dist/src/dev/reactComponentClassifier.d.ts +2 -0
  66. package/dist/src/dev/rebuildTrigger.d.ts +10 -0
  67. package/dist/src/dev/simpleHTMLHMR.d.ts +4 -0
  68. package/dist/src/dev/simpleHTMXHMR.d.ts +4 -0
  69. package/dist/src/dev/simpleSvelteHMR.d.ts +1 -0
  70. package/dist/src/dev/simpleVueHMR.d.ts +1 -0
  71. package/dist/src/dev/webSocket.d.ts +9 -0
  72. package/dist/src/index.d.ts +5 -0
  73. package/dist/src/plugins/hmr.d.ts +62 -0
  74. package/dist/src/plugins/index.d.ts +3 -0
  75. package/dist/src/plugins/networking.d.ts +29 -0
  76. package/dist/src/plugins/pageRouter.d.ts +1 -0
  77. package/dist/src/svelte/renderToPipeableStream.d.ts +12 -0
  78. package/dist/src/svelte/renderToReadableStream.d.ts +13 -0
  79. package/dist/src/svelte/renderToString.d.ts +9 -0
  80. package/dist/src/utils/cleanup.d.ts +7 -0
  81. package/dist/src/utils/commonAncestor.d.ts +1 -0
  82. package/dist/src/utils/escapeScriptContent.d.ts +1 -0
  83. package/dist/src/utils/generateHeadElement.d.ts +17 -0
  84. package/dist/src/utils/getDurationString.d.ts +1 -0
  85. package/dist/src/utils/getEnv.d.ts +1 -0
  86. package/dist/src/utils/getRegisterClientScript.d.ts +10 -0
  87. package/dist/src/utils/index.d.ts +6 -0
  88. package/dist/src/utils/logger.d.ts +54 -0
  89. package/dist/src/utils/networking.d.ts +2 -0
  90. package/dist/src/utils/normalizePath.d.ts +9 -0
  91. package/dist/src/utils/registerClientScript.d.ts +51 -0
  92. package/dist/src/utils/stringModifiers.d.ts +2 -0
  93. package/dist/src/utils/validateSafePath.d.ts +1 -0
  94. package/dist/types/build.d.ts +41 -0
  95. package/dist/types/client.d.ts +108 -0
  96. package/dist/types/index.d.ts +4 -0
  97. package/dist/types/messages.d.ts +138 -0
  98. package/dist/types/websocket.d.ts +6 -0
  99. package/eslint.config.mjs +238 -0
  100. package/package.json +67 -0
  101. package/tsconfig.build.json +20 -0
  102. package/types/build.ts +54 -0
  103. package/types/client.ts +111 -0
  104. package/types/index.ts +4 -0
  105. package/types/messages.ts +205 -0
  106. package/types/websocket.ts +12 -0
  107. 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
+ [![bun-required](https://img.shields.io/badge/runtime-bun%401.x-yellowgreen?logo=bun)](https://bun.sh)
6
+ [![elysia-required](https://img.shields.io/badge/server-elysia%40latest-blue?logo=elysia)](https://elysiajs.com)
7
+ ![license](https://img.shields.io/badge/license-BSL--1.1-lightgrey)
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
@@ -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
+ };