sveltekit-temporal 0.1.0 → 0.2.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/README.md +23 -10
- package/package.json +15 -2
- package/src/addon.js +54 -0
- package/src/index.js +7 -83
- package/src/shared.js +76 -0
package/README.md
CHANGED
|
@@ -4,7 +4,20 @@ One-shot CLI that wires the Temporal API polyfill into a SvelteKit project, with
|
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
7
|
+
## Using with the Svelte CLI (`sv add`)
|
|
8
|
+
|
|
9
|
+
If you prefer the official Svelte CLI, this package also works as an `sv` add-on:
|
|
10
|
+
|
|
7
11
|
```bash
|
|
12
|
+
# Or when creating a new project
|
|
13
|
+
npx sv create my-app
|
|
14
|
+
cd my-app
|
|
15
|
+
npx sv add sveltekit-temporal
|
|
16
|
+
|
|
17
|
+
# Into an existing project
|
|
18
|
+
npx sv add sveltekit-temporal
|
|
19
|
+
|
|
20
|
+
#Alternatively, you can run the CLI directly
|
|
8
21
|
npx sveltekit-temporal
|
|
9
22
|
```
|
|
10
23
|
|
|
@@ -59,18 +72,18 @@ export async function handle({ event, resolve }) {
|
|
|
59
72
|
|
|
60
73
|
## Runtime support
|
|
61
74
|
|
|
62
|
-
| Runtime
|
|
63
|
-
|
|
64
|
-
| Node.js ≤ 25 | No
|
|
65
|
-
| Node.js 26+
|
|
66
|
-
| Bun
|
|
67
|
-
| Deno
|
|
68
|
-
| Chrome 144+
|
|
69
|
-
| Firefox 139+ | Yes
|
|
70
|
-
| Safari
|
|
75
|
+
| Runtime | Native Temporal | Notes |
|
|
76
|
+
| ------------ | -------------------------------------- | ----------------------------------------------------------------------- |
|
|
77
|
+
| Node.js ≤ 25 | No | Polyfill required |
|
|
78
|
+
| Node.js 26+ | Yes (behind `--experimental-temporal`) | Polyfill still safe to include — the conditional check skips loading it |
|
|
79
|
+
| Bun | No | Polyfill required; Bun tracks V8 but has not shipped Temporal yet |
|
|
80
|
+
| Deno | No | Polyfill required; Deno tracks V8 but has not shipped Temporal yet |
|
|
81
|
+
| Chrome 144+ | Yes | Polyfill skipped automatically |
|
|
82
|
+
| Firefox 139+ | Yes | Polyfill skipped automatically |
|
|
83
|
+
| Safari | No | Polyfill required |
|
|
71
84
|
|
|
72
85
|
The conditional bootstrap in `src/lib/temporal.ts` checks `typeof Temporal !== 'undefined'` before loading the polyfill, so upgrading your runtime later requires no code changes.
|
|
73
86
|
|
|
74
87
|
## Switching polyfills later
|
|
75
88
|
|
|
76
|
-
|
|
89
|
+
Re-run either the CLI or `sv add` and pick the other option. Only the two files that name the package (`src/lib/temporal.*` and `src/app.d.ts`) will change.
|
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sveltekit-temporal",
|
|
3
3
|
"author": "Scott Rhamy",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"description": "Set up the Temporal API polyfill in a SvelteKit project with conditional loading on client and server.",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js",
|
|
9
|
+
"./sv": "./src/addon.js"
|
|
10
|
+
},
|
|
7
11
|
"bin": {
|
|
8
12
|
"sveltekit-temporal": "bin/cli.js"
|
|
9
13
|
},
|
|
@@ -19,12 +23,21 @@
|
|
|
19
23
|
"@clack/prompts": "^0.7.0",
|
|
20
24
|
"picocolors": "^1.0.0"
|
|
21
25
|
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"sv": "*"
|
|
28
|
+
},
|
|
29
|
+
"peerDependenciesMeta": {
|
|
30
|
+
"sv": {
|
|
31
|
+
"optional": true
|
|
32
|
+
}
|
|
33
|
+
},
|
|
22
34
|
"keywords": [
|
|
23
35
|
"svelte",
|
|
24
36
|
"sveltekit",
|
|
25
37
|
"temporal",
|
|
26
38
|
"polyfill",
|
|
27
|
-
"cli"
|
|
39
|
+
"cli",
|
|
40
|
+
"sv-add"
|
|
28
41
|
],
|
|
29
42
|
"license": "MIT"
|
|
30
43
|
}
|
package/src/addon.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { defineAddon, defineAddonOptions } from 'sv';
|
|
2
|
+
import { MARKER, buildBootstrap, buildNewAppDts, processAppDts } from './shared.js';
|
|
3
|
+
|
|
4
|
+
const options = defineAddonOptions()
|
|
5
|
+
.add('polyfill', {
|
|
6
|
+
question: 'Which Temporal polyfill would you like to use?',
|
|
7
|
+
type: 'select',
|
|
8
|
+
options: ['@js-temporal/polyfill', 'temporal-polyfill'],
|
|
9
|
+
default: '@js-temporal/polyfill'
|
|
10
|
+
})
|
|
11
|
+
.build();
|
|
12
|
+
|
|
13
|
+
export default defineAddon({
|
|
14
|
+
id: 'temporal',
|
|
15
|
+
shortDescription: 'Temporal API polyfill with conditional loading for SvelteKit',
|
|
16
|
+
options,
|
|
17
|
+
|
|
18
|
+
setup({ isKit, unsupported }) {
|
|
19
|
+
if (!isKit) unsupported('Requires SvelteKit.');
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
run({ sv, options, language, directory }) {
|
|
23
|
+
const pkgName = options.polyfill;
|
|
24
|
+
const isTs = language === 'ts';
|
|
25
|
+
const ext = language;
|
|
26
|
+
|
|
27
|
+
sv.dependency(pkgName, 'latest');
|
|
28
|
+
|
|
29
|
+
sv.file(`${directory.lib}/temporal.${ext}`, (content) => {
|
|
30
|
+
if (content && !content.includes(MARKER)) return false;
|
|
31
|
+
return buildBootstrap(pkgName, isTs);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
sv.file(`${directory.kitRoutes}/+layout.${ext}`, (content) =>
|
|
35
|
+
content.includes("import '$lib/temporal'")
|
|
36
|
+
? false
|
|
37
|
+
: `import '$lib/temporal';\n${content}`
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
sv.file(`${directory.src}/hooks.server.${ext}`, (content) =>
|
|
41
|
+
content.includes("import '$lib/temporal'")
|
|
42
|
+
? false
|
|
43
|
+
: `import '$lib/temporal';\n${content}`
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
if (isTs) {
|
|
47
|
+
sv.file(`${directory.src}/app.d.ts`, (content) =>
|
|
48
|
+
content.trim() ? processAppDts(content, pkgName) : buildNewAppDts(pkgName)
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
nextSteps: () => ['Reference Temporal directly anywhere in your app — no imports needed.']
|
|
54
|
+
});
|
package/src/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
2
|
-
import { join,
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
3
|
import { execSync } from 'node:child_process';
|
|
4
4
|
import * as p from '@clack/prompts';
|
|
5
5
|
import pc from 'picocolors';
|
|
6
|
+
import { MARKER, buildBootstrap, buildNewAppDts, processAppDts } from './shared.js';
|
|
6
7
|
|
|
7
8
|
const POLYFILLS = {
|
|
8
9
|
official: {
|
|
@@ -17,8 +18,6 @@ const POLYFILLS = {
|
|
|
17
18
|
}
|
|
18
19
|
};
|
|
19
20
|
|
|
20
|
-
const MARKER = '// sveltekit-temporal: managed block';
|
|
21
|
-
|
|
22
21
|
export async function run() {
|
|
23
22
|
console.log();
|
|
24
23
|
p.intro(pc.bgCyan(pc.black(' sveltekit-temporal ')));
|
|
@@ -190,41 +189,6 @@ function installCommand(pm, pkg) {
|
|
|
190
189
|
}
|
|
191
190
|
}
|
|
192
191
|
|
|
193
|
-
// ─── File generation ────────────────────────────────────────────────────────
|
|
194
|
-
|
|
195
|
-
function buildBootstrap(pkgName, isTypeScript) {
|
|
196
|
-
const tsLine1 = isTypeScript ? `\t// @ts-expect-error - polyfilling globalThis\n` : '';
|
|
197
|
-
const tsLine2 = isTypeScript ? `\t// @ts-expect-error - patching Date.prototype\n` : '';
|
|
198
|
-
|
|
199
|
-
return `${MARKER}
|
|
200
|
-
// Conditionally load the Temporal polyfill when the runtime lacks native support.
|
|
201
|
-
// Native (Chrome 144+, Firefox 139+) keeps zero overhead — the dynamic import is
|
|
202
|
-
// code-split by Vite and only fetched on browsers that need it.
|
|
203
|
-
|
|
204
|
-
if (typeof globalThis.Temporal === 'undefined') {
|
|
205
|
-
const { Temporal, toTemporalInstant } = await import('${pkgName}');
|
|
206
|
-
${tsLine1}\tglobalThis.Temporal = Temporal;
|
|
207
|
-
${tsLine2}\tDate.prototype.toTemporalInstant = toTemporalInstant;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export {};
|
|
211
|
-
`;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function buildAppDtsBlock(pkgName) {
|
|
215
|
-
return `${MARKER}
|
|
216
|
-
import type { Temporal as TemporalNS } from '${pkgName}';
|
|
217
|
-
|
|
218
|
-
declare global {
|
|
219
|
-
const Temporal: typeof TemporalNS;
|
|
220
|
-
|
|
221
|
-
interface Date {
|
|
222
|
-
toTemporalInstant(): TemporalNS.Instant;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
// end sveltekit-temporal managed block`;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
192
|
// ─── File writers (idempotent) ──────────────────────────────────────────────
|
|
229
193
|
|
|
230
194
|
function writeManaged(path, contents, log) {
|
|
@@ -266,58 +230,22 @@ function ensureImportInFile(path, importLine, log) {
|
|
|
266
230
|
}
|
|
267
231
|
|
|
268
232
|
function updateAppDts(path, pkgName, log) {
|
|
269
|
-
const block = buildAppDtsBlock(pkgName);
|
|
270
|
-
|
|
271
233
|
if (!existsSync(path)) {
|
|
272
|
-
const stub = `// See https://svelte.dev/docs/kit/types#app.d.ts for information about these interfaces
|
|
273
|
-
declare global {
|
|
274
|
-
namespace App {
|
|
275
|
-
// interface Error {}
|
|
276
|
-
// interface Locals {}
|
|
277
|
-
// interface PageData {}
|
|
278
|
-
// interface PageState {}
|
|
279
|
-
// interface Platform {}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
${block}
|
|
284
|
-
|
|
285
|
-
export {};
|
|
286
|
-
`;
|
|
287
234
|
mkdirSync(dirname(path), { recursive: true });
|
|
288
|
-
writeFileSync(path,
|
|
235
|
+
writeFileSync(path, buildNewAppDts(pkgName));
|
|
289
236
|
log.created.push(rel(path));
|
|
290
237
|
return;
|
|
291
238
|
}
|
|
292
239
|
|
|
293
240
|
const existing = readFileSync(path, 'utf8');
|
|
241
|
+
const result = processAppDts(existing, pkgName);
|
|
294
242
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
const pattern = new RegExp(
|
|
298
|
-
`${escapeRegex(MARKER)}[\\s\\S]*?// end sveltekit-temporal managed block\\n?`,
|
|
299
|
-
'm'
|
|
300
|
-
);
|
|
301
|
-
const replaced = existing.replace(pattern, `${block}\n`);
|
|
302
|
-
if (replaced === existing) {
|
|
303
|
-
log.skipped.push(rel(path));
|
|
304
|
-
} else {
|
|
305
|
-
writeFileSync(path, replaced);
|
|
306
|
-
log.updated.push(rel(path));
|
|
307
|
-
}
|
|
243
|
+
if (result === false) {
|
|
244
|
+
log.skipped.push(rel(path));
|
|
308
245
|
return;
|
|
309
246
|
}
|
|
310
247
|
|
|
311
|
-
|
|
312
|
-
// otherwise just append.
|
|
313
|
-
const exportMatch = existing.match(/\n\s*export\s*\{\s*\};?\s*$/);
|
|
314
|
-
let next;
|
|
315
|
-
if (exportMatch) {
|
|
316
|
-
next = existing.replace(exportMatch[0], `\n\n${block}\n${exportMatch[0]}`);
|
|
317
|
-
} else {
|
|
318
|
-
next = `${existing.trimEnd()}\n\n${block}\n\nexport {};\n`;
|
|
319
|
-
}
|
|
320
|
-
writeFileSync(path, next);
|
|
248
|
+
writeFileSync(path, result);
|
|
321
249
|
log.updated.push(rel(path));
|
|
322
250
|
}
|
|
323
251
|
|
|
@@ -326,7 +254,3 @@ export {};
|
|
|
326
254
|
function rel(absPath) {
|
|
327
255
|
return absPath.replace(process.cwd() + '/', '');
|
|
328
256
|
}
|
|
329
|
-
|
|
330
|
-
function escapeRegex(s) {
|
|
331
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
332
|
-
}
|
package/src/shared.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export const MARKER = '// sveltekit-temporal: managed block';
|
|
2
|
+
const END_MARKER = '// end sveltekit-temporal managed block';
|
|
3
|
+
|
|
4
|
+
export function buildBootstrap(pkgName, isTypeScript) {
|
|
5
|
+
const tsLine1 = isTypeScript ? `\t// @ts-expect-error - polyfilling globalThis\n` : '';
|
|
6
|
+
const tsLine2 = isTypeScript ? `\t// @ts-expect-error - patching Date.prototype\n` : '';
|
|
7
|
+
|
|
8
|
+
return `${MARKER}
|
|
9
|
+
// Conditionally load the Temporal polyfill when the runtime lacks native support.
|
|
10
|
+
// Native (Chrome 144+, Firefox 139+) keeps zero overhead — the dynamic import is
|
|
11
|
+
// code-split by Vite and only fetched on browsers that need it.
|
|
12
|
+
|
|
13
|
+
if (typeof globalThis.Temporal === 'undefined') {
|
|
14
|
+
const { Temporal, toTemporalInstant } = await import('${pkgName}');
|
|
15
|
+
${tsLine1}\tglobalThis.Temporal = Temporal;
|
|
16
|
+
${tsLine2}\tDate.prototype.toTemporalInstant = toTemporalInstant;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export {};
|
|
20
|
+
`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function buildAppDtsBlock(pkgName) {
|
|
24
|
+
return `${MARKER}
|
|
25
|
+
import type { Temporal as TemporalNS } from '${pkgName}';
|
|
26
|
+
|
|
27
|
+
declare global {
|
|
28
|
+
const Temporal: typeof TemporalNS;
|
|
29
|
+
|
|
30
|
+
interface Date {
|
|
31
|
+
toTemporalInstant(): TemporalNS.Instant;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
${END_MARKER}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function buildNewAppDts(pkgName) {
|
|
38
|
+
return `// See https://svelte.dev/docs/kit/types#app.d.ts for information about these interfaces
|
|
39
|
+
declare global {
|
|
40
|
+
namespace App {
|
|
41
|
+
// interface Error {}
|
|
42
|
+
// interface Locals {}
|
|
43
|
+
// interface PageData {}
|
|
44
|
+
// interface PageState {}
|
|
45
|
+
// interface Platform {}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
${buildAppDtsBlock(pkgName)}
|
|
50
|
+
|
|
51
|
+
export {};
|
|
52
|
+
`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function processAppDts(content, pkgName) {
|
|
56
|
+
const block = buildAppDtsBlock(pkgName);
|
|
57
|
+
|
|
58
|
+
if (content.includes(MARKER)) {
|
|
59
|
+
const pattern = new RegExp(
|
|
60
|
+
`${escapeRegex(MARKER)}[\\s\\S]*?${escapeRegex(END_MARKER)}\\n?`,
|
|
61
|
+
'm'
|
|
62
|
+
);
|
|
63
|
+
const replaced = content.replace(pattern, `${block}\n`);
|
|
64
|
+
return replaced === content ? false : replaced;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const exportMatch = content.match(/\n\s*export\s*\{\s*\};?\s*$/);
|
|
68
|
+
if (exportMatch) {
|
|
69
|
+
return content.replace(exportMatch[0], `\n\n${block}\n${exportMatch[0]}`);
|
|
70
|
+
}
|
|
71
|
+
return `${content.trimEnd()}\n\n${block}\n\nexport {};\n`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function escapeRegex(s) {
|
|
75
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
76
|
+
}
|