create-shapio 0.5.4 → 0.5.6
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/package.json +1 -1
- package/templates/astro/astro.config.mjs +3 -1
- package/templates/astro/package.json +2 -2
- package/templates/astro/src/integrations/devStaticPaths.test.ts +40 -0
- package/templates/astro/src/integrations/devStaticPaths.ts +82 -0
- package/templates/next/package.json +3 -3
- package/templates/sveltekit/package.json +2 -2
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { writeFile } from 'node:fs/promises';
|
|
2
2
|
import { defineConfig } from 'astro/config';
|
|
3
|
+
import { devStaticPaths } from './src/integrations/devStaticPaths.ts';
|
|
3
4
|
|
|
4
5
|
// `.env` (written by `npm run seed`) for the settings this file reads; the environment wins.
|
|
5
6
|
try {
|
|
@@ -46,7 +47,8 @@ export default defineConfig({
|
|
|
46
47
|
trailingSlash: 'always',
|
|
47
48
|
build: { format: 'directory' },
|
|
48
49
|
server: { headers: { 'Content-Security-Policy': FRAME_ANCESTORS } },
|
|
49
|
-
|
|
50
|
+
// devStaticPaths: in `astro dev`, an article created after the server started gets its page without a restart.
|
|
51
|
+
integrations: [frameAncestorsHeaders, devStaticPaths()],
|
|
50
52
|
/**
|
|
51
53
|
* Incremental builds: a page whose `cacheKey` (src/lib/cacheKey.ts) and code are unchanged is restored from
|
|
52
54
|
* the previous build instead of rendered. The cache lives in Astro's cacheDir, `node_modules/.astro`, which
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { DEV_WINDOW_MS } from '../lib/snapshotResolver.js';
|
|
3
|
+
import { staticPathsRefresher } from './devStaticPaths.js';
|
|
4
|
+
|
|
5
|
+
describe('staticPathsRefresher', () => {
|
|
6
|
+
it('clears for a page request at most once per dev read window', () => {
|
|
7
|
+
let time = 0;
|
|
8
|
+
const shouldClear = staticPathsRefresher(() => time);
|
|
9
|
+
expect(shouldClear('/en/articles/a-post/')).toBe(true);
|
|
10
|
+
time += DEV_WINDOW_MS - 1;
|
|
11
|
+
expect(shouldClear('/en/articles/another/')).toBe(false);
|
|
12
|
+
expect(shouldClear('/fr/')).toBe(false);
|
|
13
|
+
time += 1;
|
|
14
|
+
expect(shouldClear('/fr/articles/bonjour/?v=1')).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('treats the root and every locale path as a page', () => {
|
|
18
|
+
for (const url of ['/', '/en/', '/en/colophon/', '/fr/a-page/', undefined]) {
|
|
19
|
+
expect(staticPathsRefresher(() => 0)(url)).toBe(true);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('ignores tooling and file requests', () => {
|
|
24
|
+
const shouldClear = staticPathsRefresher(() => 0);
|
|
25
|
+
for (const url of [
|
|
26
|
+
'/_astro/x.css',
|
|
27
|
+
'/@vite/client',
|
|
28
|
+
'/@fs/Users/me/node_modules/astro/dist/runtime.js',
|
|
29
|
+
'/@id/astro:scripts/page.js',
|
|
30
|
+
'/src/styles/site.css',
|
|
31
|
+
'/node_modules/.vite/deps/chunk.js',
|
|
32
|
+
'/favicon.svg',
|
|
33
|
+
'/build.json',
|
|
34
|
+
'/en/articles/cover.webp?w=400',
|
|
35
|
+
]) {
|
|
36
|
+
expect(shouldClear(url)).toBe(false);
|
|
37
|
+
}
|
|
38
|
+
expect(shouldClear('/en/')).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { AstroIntegration } from 'astro';
|
|
2
|
+
import { DEV_WINDOW_MS } from '../lib/snapshotResolver.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `astro dev` caches each dynamic route's getStaticPaths result until the route module changes. Content comes
|
|
6
|
+
* from Shapio, not from files, so an article (or a draft, in drafts mode) created after the dev server started
|
|
7
|
+
* has no path and its page 404s.
|
|
8
|
+
*
|
|
9
|
+
* This integration clears that cache before a page request, at most once per DEV_WINDOW_MS (the window the
|
|
10
|
+
* content reads reuse), so getStaticPaths runs again with fresh content. Every page of the starter is a dynamic
|
|
11
|
+
* route (`/[locale]/…`), so every request counts as a page request except Astro's and Vite's own (`/_astro/`,
|
|
12
|
+
* `/@vite`, `/@fs`, `/@id`, `/src/`, `/node_modules/`) and anything that looks like a file. It relies on an Astro
|
|
13
|
+
* internal: Astro clears its route cache when the `astro:content-changed` event reaches the ssr and prerender
|
|
14
|
+
* Vite environments (astro/dist/vite-plugin-app/createAstroServerApp.js, sent by
|
|
15
|
+
* astro/dist/content/vite-plugin-content-virtual-mod.js). Dev only: `astro build` never runs
|
|
16
|
+
* `astro:server:setup`, and the hook does nothing unless the command is `dev`.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** Path prefixes of Astro's and Vite's own dev requests (assets, modules, the client): never a page. */
|
|
20
|
+
export const TOOLING_PATH_PREFIXES = ['/_astro/', '/@vite', '/@fs', '/@id', '/src/', '/node_modules/'];
|
|
21
|
+
|
|
22
|
+
/** The Vite environments that run Astro's renderer and hold its route cache. */
|
|
23
|
+
const RENDER_ENVIRONMENTS = ['ssr', 'prerender'];
|
|
24
|
+
|
|
25
|
+
/** A last path segment with an extension (`favicon.svg`, `build.json`): a file, not a page. */
|
|
26
|
+
const looksLikeFile = (pathname: string) =>
|
|
27
|
+
/\.[A-Za-z0-9]+$/.test(pathname.slice(pathname.lastIndexOf('/') + 1));
|
|
28
|
+
|
|
29
|
+
const isPageRequest = (pathname: string) =>
|
|
30
|
+
!TOOLING_PATH_PREFIXES.some((prefix) => pathname.startsWith(prefix)) && !looksLikeFile(pathname);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Whether a request should clear the route cache: a page request, when the last clear is at least
|
|
34
|
+
* DEV_WINDOW_MS old.
|
|
35
|
+
*/
|
|
36
|
+
export const staticPathsRefresher = (now: () => number = Date.now) => {
|
|
37
|
+
let lastClearAt: number | undefined;
|
|
38
|
+
return (url: string | undefined): boolean => {
|
|
39
|
+
if (!isPageRequest(new URL(url ?? '/', 'http://localhost').pathname)) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
const at = now();
|
|
43
|
+
if (lastClearAt !== undefined && at - lastClearAt < DEV_WINDOW_MS) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
lastClearAt = at;
|
|
47
|
+
return true;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** The dev server Astro hands `astro:server:setup` (Vite's; vite is not a direct dependency here). */
|
|
52
|
+
type DevServer = Parameters<NonNullable<AstroIntegration['hooks']['astro:server:setup']>>[0]['server'];
|
|
53
|
+
|
|
54
|
+
const clearRouteCache = (server: DevServer) => {
|
|
55
|
+
for (const name of RENDER_ENVIRONMENTS) {
|
|
56
|
+
server.environments[name]?.hot.send('astro:content-changed', {});
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const devStaticPaths = (): AstroIntegration => {
|
|
61
|
+
let isDev = false;
|
|
62
|
+
return {
|
|
63
|
+
name: 'dev-static-paths',
|
|
64
|
+
hooks: {
|
|
65
|
+
'astro:config:setup': ({ command }) => {
|
|
66
|
+
isDev = command === 'dev';
|
|
67
|
+
},
|
|
68
|
+
'astro:server:setup': ({ server }) => {
|
|
69
|
+
if (!isDev) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const shouldClear = staticPathsRefresher();
|
|
73
|
+
server.middlewares.use((request, _response, next) => {
|
|
74
|
+
if (shouldClear(request.url)) {
|
|
75
|
+
clearRouteCache(server);
|
|
76
|
+
}
|
|
77
|
+
next();
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
};
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
"test": "vitest run"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@shapio/client": "^0.5.
|
|
22
|
-
"@shapio/local": "0.5.
|
|
23
|
-
"@shapio/visual": "^0.5.
|
|
21
|
+
"@shapio/client": "^0.5.6",
|
|
22
|
+
"@shapio/local": "0.5.6",
|
|
23
|
+
"@shapio/visual": "^0.5.6",
|
|
24
24
|
"next": "16.3.8",
|
|
25
25
|
"react": "19.3.0",
|
|
26
26
|
"react-dom": "19.3.0"
|