@koehler8/cms 1.3.1 → 1.4.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/CHANGELOG.md +77 -0
- package/package.json +5 -5
- package/src/main.js +15 -11
- package/src/utils/currentRoute.js +49 -0
- package/templates/scaffolds/site/.nvmrc +1 -1
- package/templates/scaffolds/site/CLAUDE.md +2 -2
- package/templates/scaffolds/site/README.md +1 -1
- package/templates/scaffolds/site/amplify.yml +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,82 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.4.1
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **The embedded config payload is now actually used on locale-prefixed routes.**
|
|
8
|
+
vite-ssg runs the setup fn *before* `app.use(router)`, so `router.currentRoute`
|
|
9
|
+
is still `START_LOCATION` (`'/'`, `params: {}`) there — identically on every
|
|
10
|
+
page. During SSG that was already handled, because vite-ssg hands the route
|
|
11
|
+
over on `ctx.routePath`. On the **client** it calls the setup fn with no
|
|
12
|
+
argument at all, so there was nothing to resolve and the code fell through to
|
|
13
|
+
`START_LOCATION`.
|
|
14
|
+
|
|
15
|
+
`loadSiteConfig` derives its cache key from `params.locale`, so the key came
|
|
16
|
+
out `'default'` on every client page while the SSG had stamped the real locale
|
|
17
|
+
on `siteConfigLocale`. On a locale-prefixed route the two never matched, so
|
|
18
|
+
`primeConfigSync` was skipped, `hydrateFromSyncCache` missed, and the client
|
|
19
|
+
discarded the payload it had just been served and awaited a full config load
|
|
20
|
+
**inside the setup fn, on the critical path, before mount** — the exact
|
|
21
|
+
regression the priming machinery exists to prevent.
|
|
22
|
+
|
|
23
|
+
Measured on a multi-locale site, same machine and build, only this patch
|
|
24
|
+
differing. First config chunk on a `/th/` piece page: **44 ms before**
|
|
25
|
+
(DOMContentLoaded 34 ms — on the critical path), **1364 ms after** (deferred
|
|
26
|
+
to `requestIdleCallback`, as designed). The load is *deferred, not removed*:
|
|
27
|
+
`trimConfigToPage` stamps `pagesPartial` on every page, so the idle reload
|
|
28
|
+
still runs.
|
|
29
|
+
|
|
30
|
+
⚠️ A cross-environment comparison hides this completely. A production bundle
|
|
31
|
+
that needs ~1.4 s to download and execute shows its config chunks at ~1607 ms,
|
|
32
|
+
which *looks* deferred when it is in fact immediate. Only a same-environment
|
|
33
|
+
A/B separates the two.
|
|
34
|
+
|
|
35
|
+
**No action needed in a site.** Single-locale sites already stamped `default`
|
|
36
|
+
and primed correctly, so their behaviour is unchanged; multi-locale sites gain
|
|
37
|
+
the deferral.
|
|
38
|
+
|
|
39
|
+
## 1.4.0
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- **The bundled Pinia is now 4.x (was 3.x).** `createPinia()` runs Pinia 4.0.3
|
|
44
|
+
and that is the instance handed to every extension through
|
|
45
|
+
`runExtensionSetups({ app, router, pinia, ... })`.
|
|
46
|
+
|
|
47
|
+
**If you maintain an extension that declares `peerDependencies.pinia`, widen
|
|
48
|
+
it to admit `^4`** — for example `"pinia": "^3.0.0 || ^4.0.0"`. An extension
|
|
49
|
+
pinned to `^3` will fail to install alongside this release.
|
|
50
|
+
(`@koehler8/cms-ext-crypto` 1.0.0-beta.4 is in exactly that position; it is
|
|
51
|
+
not installed on any site in the fleet.)
|
|
52
|
+
|
|
53
|
+
Nothing else about extensions changes: the instance is still a single Pinia
|
|
54
|
+
per site — this package deliberately excludes `vue`, `vue-router` and `pinia`
|
|
55
|
+
from Vite's dependency pre-bundling so a linked extension cannot end up with
|
|
56
|
+
a second copy — and `vue-router` 5.3.1 already accepts `pinia@4`
|
|
57
|
+
(`^3.0.4 || ^4.0.2`).
|
|
58
|
+
|
|
59
|
+
Two consequences worth knowing, neither of which needs action in a site:
|
|
60
|
+
|
|
61
|
+
- Pinia 4 makes `@vue/devtools-api` a **required peer** at `^8.1.5`, where
|
|
62
|
+
Pinia 3 carried it as a dependency. npm installs it automatically; on a
|
|
63
|
+
consumer site the resolved version moves 7.x → 8.x, which also lets
|
|
64
|
+
`vue-router`'s own nested copies collapse into the hoisted one. In this
|
|
65
|
+
repo that made the lockfile *smaller* (318 → 307 entries).
|
|
66
|
+
- Pinia 4 is ESM-only and depends on `nostics`. That package was already
|
|
67
|
+
present in this tree, so no new package enters a site's dependency graph.
|
|
68
|
+
|
|
69
|
+
No framework source change was needed: this package defines no store of its
|
|
70
|
+
own and uses none of the APIs Pinia changed.
|
|
71
|
+
|
|
72
|
+
### Internal (not shipped)
|
|
73
|
+
|
|
74
|
+
- Dev dependency `vitest` 4.1.11 → 5.0.1 (with `@vitest/coverage-v8`). This
|
|
75
|
+
restores the repo's ability to regenerate its own `package-lock.json` under
|
|
76
|
+
npm 10: on `vitest` 4.1.x a fresh `npm install` died in arborist with
|
|
77
|
+
`Cannot read properties of null (reading 'edgesOut')`, and only the existing
|
|
78
|
+
lockfile kept the repo installable. Neither package ships.
|
|
79
|
+
|
|
3
80
|
## 1.3.1
|
|
4
81
|
|
|
5
82
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koehler8/cms",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Vertex CMS — lightweight, config-driven Vue 3 framework with theming, extensions, and SSG support",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"@unhead/vue": "^2.1.15",
|
|
81
81
|
"ajv": "^8.20.0",
|
|
82
82
|
"ajv-formats": "^3.0.1",
|
|
83
|
-
"pinia": "^
|
|
83
|
+
"pinia": "^4.0.3",
|
|
84
84
|
"sharp": "^0.35.3"
|
|
85
85
|
},
|
|
86
86
|
"optionalDependencies": {
|
|
@@ -90,11 +90,11 @@
|
|
|
90
90
|
},
|
|
91
91
|
"devDependencies": {
|
|
92
92
|
"@vitejs/plugin-vue": "^6.0.8",
|
|
93
|
-
"@vitest/coverage-v8": "^
|
|
94
|
-
"@vue/test-utils": "^2.
|
|
93
|
+
"@vitest/coverage-v8": "^5.0.1",
|
|
94
|
+
"@vue/test-utils": "^2.5.1",
|
|
95
95
|
"happy-dom": "^20.9.0",
|
|
96
96
|
"vite": "^8.3.0",
|
|
97
|
-
"vitest": "^
|
|
97
|
+
"vitest": "^5.0.1",
|
|
98
98
|
"vue": "^3.5.43",
|
|
99
99
|
"vue-router": "^5.3.1"
|
|
100
100
|
},
|
package/src/main.js
CHANGED
|
@@ -8,6 +8,7 @@ import { routes, resolveHistory, applyRouterGuards } from './router/index.js';
|
|
|
8
8
|
import { shouldEnableAnalytics, scheduleAnalyticsLoad, configureAnalyticsConsentMode } from './utils/cookieConsent.js';
|
|
9
9
|
import { loadConfigData, primeConfigSync, trimConfigToPage, resolvePageIdForRoute } from './utils/loadConfig.js';
|
|
10
10
|
import { applyInitialStateTrims } from './utils/initialStateTrim.js';
|
|
11
|
+
import { currentRouteHref } from './utils/currentRoute.js';
|
|
11
12
|
import { persistAttributionFromLocation } from './utils/trackingContext.js';
|
|
12
13
|
import { applyThemeVariables } from './themes/themeManager.js';
|
|
13
14
|
import { setActiveThemeKey } from './utils/themeColors.js';
|
|
@@ -136,19 +137,22 @@ export function createCmsApp() {
|
|
|
136
137
|
};
|
|
137
138
|
|
|
138
139
|
const extractCurrentRoute = () => {
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
// —
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
const
|
|
147
|
-
|
|
140
|
+
// vite-ssg runs this setup fn BEFORE `app.use(router)`, so
|
|
141
|
+
// `router.currentRoute` is still vue-router's START_LOCATION ('/') here,
|
|
142
|
+
// identically on every page — during SSG *and* on the client. The href
|
|
143
|
+
// that IS authoritative differs per half (ctx.routePath vs the window's
|
|
144
|
+
// own location); currentRouteHref picks it, with the full account of why
|
|
145
|
+
// the client half is not optional. Resolve it through the router — a pure
|
|
146
|
+
// match, no navigation — to recover the real path + `params.locale`.
|
|
147
|
+
const href = currentRouteHref(
|
|
148
|
+
ctx,
|
|
149
|
+
typeof window !== 'undefined' ? window.location : null,
|
|
150
|
+
);
|
|
151
|
+
if (href) {
|
|
148
152
|
try {
|
|
149
|
-
return router.resolve(
|
|
153
|
+
return router.resolve(href);
|
|
150
154
|
} catch {
|
|
151
|
-
return { path:
|
|
155
|
+
return { path: href, params: {} };
|
|
152
156
|
}
|
|
153
157
|
}
|
|
154
158
|
if (ctx?.route) {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Which href the app should resolve through the router to learn the route it is
|
|
2
|
+
// actually on.
|
|
3
|
+
//
|
|
4
|
+
// Both halves of the lifecycle are blind to this in the same way, for the same
|
|
5
|
+
// reason: vite-ssg runs the setup fn BEFORE `app.use(router)`, so
|
|
6
|
+
// `router.currentRoute` is still vue-router's START_LOCATION ('/', `params: {}`)
|
|
7
|
+
// — identically on every page.
|
|
8
|
+
//
|
|
9
|
+
// - During SSG, vite-ssg hands the route being pre-rendered over as a string on
|
|
10
|
+
// `ctx.routePath`, so that is authoritative.
|
|
11
|
+
// - On the CLIENT it calls the setup fn with no argument at all: no
|
|
12
|
+
// `ctx.routePath`, no `ctx.route`. The only authoritative source left is the
|
|
13
|
+
// window's own location.
|
|
14
|
+
//
|
|
15
|
+
// Falling through to START_LOCATION on the client is not a cosmetic miss. The
|
|
16
|
+
// config cache key is derived from `params.locale`, so it came out 'default' on
|
|
17
|
+
// every page while the SSG had stamped the real locale on `siteConfigLocale`.
|
|
18
|
+
// The two never matched on a locale-prefixed route, `primeConfigSync` was
|
|
19
|
+
// skipped, and the client discarded the embedded payload and re-fetched the
|
|
20
|
+
// whole config tree on the critical path — the exact regression the priming
|
|
21
|
+
// machinery exists to prevent. Measured on sparkbang.com/th/<piece>/ before the
|
|
22
|
+
// fix: 151 JS requests / 405 KB, 112 of them per-piece config chunks, while the
|
|
23
|
+
// 17 KB embedded payload was parsed and thrown away.
|
|
24
|
+
//
|
|
25
|
+
// Pure and dependency-free so the fall-through can be pinned by a unit test;
|
|
26
|
+
// `main.js` owns the router.resolve() call itself (a pure match, no navigation).
|
|
27
|
+
//
|
|
28
|
+
// Returns null when the caller should use `ctx.route` as-is, or when there is
|
|
29
|
+
// nothing authoritative to resolve and START_LOCATION is genuinely all there is.
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {{routePath?: unknown, route?: unknown}|null|undefined} ctx
|
|
33
|
+
* The vite-ssg setup context. `routePath` is set during SSG only.
|
|
34
|
+
* @param {{pathname?: unknown, search?: unknown}|null|undefined} location
|
|
35
|
+
* `window.location` on the client; omit/null on the server.
|
|
36
|
+
* @returns {string|null} the href to resolve, or null to fall back.
|
|
37
|
+
*/
|
|
38
|
+
export function currentRouteHref(ctx, location) {
|
|
39
|
+
const routePath = ctx?.routePath;
|
|
40
|
+
if (typeof routePath === 'string' && routePath) return routePath;
|
|
41
|
+
// A context that already carries a resolved route needs no href at all.
|
|
42
|
+
if (ctx?.route) return null;
|
|
43
|
+
const pathname = location?.pathname;
|
|
44
|
+
if (typeof pathname === 'string' && pathname) {
|
|
45
|
+
const search = typeof location?.search === 'string' ? location.search : '';
|
|
46
|
+
return `${pathname}${search}`;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
22.23.2
|
|
@@ -89,14 +89,14 @@ Edit `themes/<slug>/theme.config.js`. The CMS regenerates `virtual:cms-theme-var
|
|
|
89
89
|
|
|
90
90
|
## Lockfile and npm version — read before bumping any dep
|
|
91
91
|
|
|
92
|
-
This project pins **Node
|
|
92
|
+
This project pins **Node 22.23.2 / npm 10.9** to match AWS Amplify exactly (never 24 — it ships npm 11). Local installs with a different npm version produce subtly different lockfiles that look fine locally but break CI.
|
|
93
93
|
|
|
94
94
|
Two failure modes seen on sister sites:
|
|
95
95
|
1. `Cannot find module '@rolldown/binding-linux-x64-gnu'` — npm 11 regen stripped optional-dep entries Amplify needs.
|
|
96
96
|
2. `npm ci ... Missing: @types/react@..., @noble/hashes@... from lock file` — npm 11 omitted entries npm 10 expects.
|
|
97
97
|
|
|
98
98
|
Rules:
|
|
99
|
-
- **Always run `nvm use` before `npm install`**. `.nvmrc` pins
|
|
99
|
+
- **Always run `nvm use` before `npm install`**. `.nvmrc` pins 22.23.2 (which ships npm 10.9.x).
|
|
100
100
|
- **Prefer targeted bumps**: `npm install <pkg>@<ver> --no-audit --no-fund`. **Never** `rm package-lock.json && npm install` unless you're on the pinned npm version.
|
|
101
101
|
- **The build uses `npm install`**, not `npm ci` (see `amplify.yml`) for drift tolerance.
|
|
102
102
|
|
|
@@ -3,7 +3,7 @@ frontend:
|
|
|
3
3
|
phases:
|
|
4
4
|
preBuild:
|
|
5
5
|
commands:
|
|
6
|
-
- nvm install
|
|
6
|
+
- nvm install 22.23.2 && nvm use 22.23.2
|
|
7
7
|
# Use `npm install` (not `npm ci`) so the build tolerates
|
|
8
8
|
# lockfile/npm-version drift. See CLAUDE.md → "Lockfile and
|
|
9
9
|
# npm version" for the rationale.
|