smbls 3.14.858 → 3.14.860
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/README.md +88 -4
- package/core.js +2 -1
- package/dist/smbls.cjs +46 -46
- package/dist/smbls.cjs.map +3 -3
- package/dist/smbls.core.analyze-VTXSSOLD.esm.js +2 -0
- package/dist/{smbls.core.analyze-PPLS2PAN.esm.js.map → smbls.core.analyze-VTXSSOLD.esm.js.map} +1 -1
- package/dist/smbls.core.chunk-EYPFXZ4X.esm.js +7 -0
- package/dist/smbls.core.chunk-EYPFXZ4X.esm.js.map +6 -0
- package/dist/smbls.core.chunk-GBKTEN3N.esm.js +2 -0
- package/dist/smbls.core.chunk-GBKTEN3N.esm.js.map +6 -0
- package/dist/smbls.core.chunk-JCEI7VBV.esm.js +11 -0
- package/dist/smbls.core.chunk-JCEI7VBV.esm.js.map +1 -0
- package/dist/smbls.core.chunk-NKYGHX2D.esm.js +2 -0
- package/dist/smbls.core.chunk-NKYGHX2D.esm.js.map +6 -0
- package/dist/smbls.core.chunk-R4N5YYND.esm.js +36 -0
- package/dist/smbls.core.chunk-R4N5YYND.esm.js.map +1 -0
- package/dist/smbls.core.chunk-RYOPPMCY.esm.js +27 -0
- package/dist/smbls.core.chunk-RYOPPMCY.esm.js.map +1 -0
- package/dist/smbls.core.chunk-XCEZBJ7G.esm.js +22 -0
- package/dist/smbls.core.chunk-XCEZBJ7G.esm.js.map +6 -0
- package/dist/smbls.core.chunk-Y2TIBI2V.esm.js +2 -0
- package/dist/smbls.core.chunk-Y2TIBI2V.esm.js.map +1 -0
- package/dist/{smbls.core.dropdown-U5YELSQ4.esm.js → smbls.core.dropdown-PCM5SZVC.esm.js} +2 -2
- package/dist/{smbls.core.dropdown-U5YELSQ4.esm.js.map → smbls.core.dropdown-PCM5SZVC.esm.js.map} +1 -1
- package/dist/smbls.core.esm.js +1 -1
- package/dist/smbls.core.fetch-B6OIXR7B.esm.js +2 -0
- package/dist/smbls.core.fetch-B6OIXR7B.esm.js.map +1 -0
- package/dist/{smbls.core.fetchOnCreate-IYA76D26.esm.js → smbls.core.fetchOnCreate-C37HV2DR.esm.js} +2 -2
- package/dist/smbls.core.inspect-WIXUJM56.esm.js +8 -0
- package/dist/{smbls.core.inspect-H4D6PCC2.esm.js.map → smbls.core.inspect-WIXUJM56.esm.js.map} +1 -1
- package/dist/smbls.core.lazyParts-SWCTR443.esm.js +2 -0
- package/dist/smbls.core.lazyParts-SWCTR443.esm.js.map +6 -0
- package/dist/smbls.core.sync-WT3G6BPW.esm.js +2 -0
- package/dist/smbls.core.sync-WT3G6BPW.esm.js.map +6 -0
- package/dist/smbls.core.windows-O3PZONYV.esm.js +2 -0
- package/dist/{smbls.core.windows-C5F2CSRH.esm.js.map → smbls.core.windows-O3PZONYV.esm.js.map} +1 -1
- package/dist/smbls.embed.iife.js +1 -1
- package/dist/smbls.embed.iife.js.map +1 -1
- package/dist/smbls.esm.js +46 -46
- package/dist/smbls.esm.js.map +1 -1
- package/dist/smbls.iife-string.js +2 -2
- package/dist/smbls.iife.js +1 -1
- package/dist/smbls.iife.js.map +1 -1
- package/dist/smbls.server-utils.esm.js +11 -11
- package/dist/smbls.server-utils.esm.js.map +3 -3
- package/dist/smbls.tree-shake.json +46 -0
- package/index.js +17 -3
- package/package.json +8 -8
- package/dist/smbls.core.analyze-PPLS2PAN.esm.js +0 -2
- package/dist/smbls.core.chunk-D76NO2P7.esm.js +0 -2
- package/dist/smbls.core.chunk-D76NO2P7.esm.js.map +0 -1
- package/dist/smbls.core.chunk-FE4HFNDE.esm.js +0 -7
- package/dist/smbls.core.chunk-FE4HFNDE.esm.js.map +0 -6
- package/dist/smbls.core.chunk-HN4JF7TR.esm.js +0 -36
- package/dist/smbls.core.chunk-HN4JF7TR.esm.js.map +0 -1
- package/dist/smbls.core.chunk-I5EGBLTJ.esm.js +0 -2
- package/dist/smbls.core.chunk-I5EGBLTJ.esm.js.map +0 -6
- package/dist/smbls.core.chunk-LIQLNILS.esm.js +0 -36
- package/dist/smbls.core.chunk-LIQLNILS.esm.js.map +0 -1
- package/dist/smbls.core.chunk-PDZOHW5Y.esm.js +0 -2
- package/dist/smbls.core.chunk-PDZOHW5Y.esm.js.map +0 -6
- package/dist/smbls.core.chunk-VG333WGF.esm.js +0 -22
- package/dist/smbls.core.chunk-VG333WGF.esm.js.map +0 -6
- package/dist/smbls.core.fetch-RTXZ25AR.esm.js +0 -2
- package/dist/smbls.core.fetch-RTXZ25AR.esm.js.map +0 -1
- package/dist/smbls.core.inspect-H4D6PCC2.esm.js +0 -8
- package/dist/smbls.core.sync-GXBAJHAF.esm.js +0 -2
- package/dist/smbls.core.sync-GXBAJHAF.esm.js.map +0 -6
- package/dist/smbls.core.windows-C5F2CSRH.esm.js +0 -2
- /package/dist/{smbls.core.fetchOnCreate-IYA76D26.esm.js.map → smbls.core.fetchOnCreate-C37HV2DR.esm.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,82 @@
|
|
|
1
1
|
# smbls
|
|
2
2
|
|
|
3
|
+
## 3.14.860
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 2522f1c: `fetch.early`: the early script carries only the parts its declaration uses (routes, rpc, slice, after, wait, …), with the address, language and argument rules compiled at build time; its tag loads with `fetchpriority="high"`; the CLI renames the file's locals with terser (5.31.6, exact; names only, the esbuild output stays when terser is missing). XMA's early file: 4,627 B → 3,114 B Brotli.
|
|
8
|
+
|
|
9
|
+
Behaviour: `firstView` requests go after the first view and load at idle; `wait: 'head'` waits through a MutationObserver only; a select with a key that its config text never names is not sent early (the app sends it as usual).
|
|
10
|
+
|
|
11
|
+
`partialAs` marks the first part of a list that the early script fetched, so the list knows it holds a part.
|
|
12
|
+
|
|
13
|
+
- 2522f1c: A window focus (or the tab turning visible) that finds a query's entry missing or stale refetches it as a refresh: the cache is skipped, but a request already in flight on the key is joined. It ran as a forced run, which skips that join, so a focus during a cold load sent the same request again once per mounted declaration on the key (the symbols.app home sent GET /core/presets three times). A request that started before the key's last invalidation is never joined. Reconnect, `refetchInterval`, `el.__ref.refetch()` and `fetchNextPage` stay forced.
|
|
14
|
+
- f718247: `el.call('prefetchQuery', config)` asks for a query ahead of its mount from project code (XMA #378). The entry is keyed by the calling element — params resolved with it, its language and tenant scope — so the page's query reads it. A prefetch registers its request in flight and joins one already out, so a query that starts meanwhile sends no second request; a fresh entry answers with no request. `queryClient.prefetchQuery(config, context)` shares requests the same way.
|
|
15
|
+
|
|
16
|
+
`infinite: { rows, merge, param }` pages answers that are objects (an rpc's `{ items, total, has_more }`): `rows` names where an answer keeps its rows, `merge(pages, el, state)` builds the value the state takes, `param` names the params key (or a function) that carries the next step; the first page is the caller's params. An infinite rpc used to drop its step and ask for page 1 again. `fetchNextPage` / `fetchPreviousPage` page the element's infinite config wherever it sits in its `fetch` list, so an `on: 'stateChange'` config can come first.
|
|
17
|
+
|
|
18
|
+
- c21bff7: A route entry can carry a head of its own: `'/blueprint': { extends: 'SiteAiPage', metadata: { title: '…', robots: 'noindex' } }`. The entry's keys win over the static `metadata` of the component it extends (found in the project's components, then the linked libraries); a string entry gets the component's. The served head (`getPageMetadata`), the SSR head (`extractMetadata`) and the browser read it the same way (`routeEntryMetadata`, `@symbo.ls/utils`). Two entries that name one component and carry only a head are the same page for the router: the page element stays mounted and helmet shows the new entry's head (the `routeHead` plugin hook).
|
|
19
|
+
|
|
20
|
+
An app that routes writes its head once, when its first page is in: the app's metadata and the first page's metadata merged in one pass, so the served title changes at most once at boot. A host that wrote each page's own head into the page it serves can keep it with `<meta name="smbls-head" content="served">` (opt-in): a value equal to the served one writes nothing.
|
|
21
|
+
|
|
22
|
+
A non-production channel serves `<meta name="robots" content="noindex" data-smbls-host="forced">`. Into a marked tag helmet writes only a value that keeps the page out of the index; it never writes `index, follow` over it and never removes it. A production head carries no mark, so the project's `robots` applies there.
|
|
23
|
+
|
|
24
|
+
The served page's `<html lang>` is the address's language (`/ka/…` → `lang="ka"`).
|
|
25
|
+
|
|
26
|
+
- c62254b: `set()` runs a design-system family after the families its values name, whatever the key order of the config. `color` runs before `gradient`, `theme` and `shadow`, and `gradient` runs before `theme`. Every other family keeps its authored position. Before, `set()` ran the families in key order: a config with `theme` before `color` resolved every theme param against an empty palette, so `--theme-*` held raw words (`gray12`, `gray1+6`) and a scheme that named a colour pair emitted no variable. A `gradient` after `theme` left `--theme-secondary-background: gradient-dark`, a `gradient` before `color` left raw colour stops (an invalid gradient), and a `shadow` before `color` wrote `var(--spacing-GRAY1.2)`.
|
|
27
|
+
- 77a4634: A typography or spacing override for a media name that the design-system media table lacks is skipped, with one dev-only warning per family and name. Before, the media pass wrote it anyway: `@media undefined` in the variables of both families and `@media screen and undefined` in the typography templates and the reset. No browser matches those at-rules. system/default's typography and spacing carry `'@default': {...}` and no media `default` exists, so every render that used system/default shipped this dead CSS. `@default` is not taken as the base sequence: no browser ever applied it, and as the base it would change what every consumer renders (system/default's spacing ratio would go from 1.618 to 1.25).
|
|
28
|
+
- 0d684b1: The classic-script and node builds (`smbls.iife.js`, `smbls.iife-string.js`, `smbls.embed.iife.js`, `smbls.cjs`) no longer read the `globalThis.__SMBLS_BUILD_<NAME>__` build flags at run time. Only an app's `smbls build` sets these flags, and it bundles the esm / esm-core dist. Nothing bundles the classic builds again, and nothing sets the flags at run time, so every read there was `undefined` (the side that keeps everything). `build.mjs` now folds them to `false` with esbuild `define`, and the minifier drops the dead side. The names come from the sources, so a new flag folds with no list to keep in step. Measured on 3.14.859: 73 reads in the iife and the cjs, 8 in the embed. Gzip goes from 274,102 to 271,474 B (iife), from 76,045 to 75,690 B (embed, cap 77,824 B) and from 281,989 to 279,437 B (cjs). The iife no longer carries the dead loader for the lazy server-utils file. The esm and esm-core builds keep every flag for the app build.
|
|
29
|
+
|
|
30
|
+
## 3.14.859
|
|
31
|
+
|
|
32
|
+
### Patch Changes
|
|
33
|
+
|
|
34
|
+
- 3aa9e6a: `@symbo.ls/sdk` on demand. A page whose code reads the SDK only through `await import('@symbo.ls/sdk')` gets no SDK bytes in its first document; the SDK loads on the first call, one instance per page, the same build the page would have inlined.
|
|
35
|
+
|
|
36
|
+
frank: a literal `import('<host-registered package>')` in code (never a comment, a string or a member call `x.import(…)`) is emitted as the lazy marker `@symbo.ls/utils` `lazyHostImportExpr(spec)` — registry first, else the page loader `globalThis[Symbol.for('smbls.importHostPackage')]`, else a named rejection `SMBLS_HOST_PACKAGE_NO_LOADER`. Before, frank emitted the `import()` verbatim: a bare private specifier that rejects in the browser. The static tier (`import { X } from '@symbo.ls/sdk'`) is unchanged.
|
|
37
|
+
|
|
38
|
+
utils: `hostPackages.js` holds the shared contract — the marker forms (a stability contract: forms are added, never edited), `classifyHostPackageReads(text, spec, range)` (`none` / `lazy` / `static`; every exact marker is set aside, any other mention is static, so a page inlines in every case it did before), `hostPackageSourcesTag` (the `window.__SMBLS_HOST_SOURCES__` declaration a lazy page carries) and `runtimeProvidesHostPackageLoader` (a page goes lazy only under an smbls runtime that installs the loader).
|
|
39
|
+
|
|
40
|
+
smbls: the host tier moves to `src/hostPackages.js` (re-exported by `frameworkRegistry.js`). New `importHostPackage(spec)` (also `Smbls.importHostPackage`), installed by the full entry as `globalThis[Symbol.for('smbls.importHostPackage')]`: one classic script per package from the page's declaration, with SRI; concurrent calls share one load; registers the global first-wins; named, retryable rejections (`LOAD_FAILED`, `TIMEOUT`, `NO_GLOBAL`) remove the failed script so a retry loads again. `smbls/core` installs nothing.
|
|
41
|
+
|
|
42
|
+
runner (`smbls start` / `smbls build`): a lazy payload gets the declaration instead of the inlined SDK, and the runner serves the SDK file at `/__smbls/host/sdk.<sha256-16>.js` (immutable; any other file under `/__smbls/host/` is a 404); `smbls build` writes it beside `index.html`. Static and SDK-free payloads are byte-identical to before.
|
|
43
|
+
|
|
44
|
+
- b0240a2: The host-package tier leaves a build whose source reads no host-registered package. `smbls build` with `build.treeShakeFeatures` now has the optional feature `hostPackages`: the full entry's on-demand loader (`importHostPackage`) and the shared contract of `@symbo.ls/utils` `hostPackages.js` (the lazy marker forms, the error codes, `classifyHostPackageReads`, `lazyHostImportExpr`, the declaration writer, the asset path). Every utils export is `context.utils`, so a `smbls/core` app that never reads `@symbo.ls/sdk` carried all of it.
|
|
45
|
+
|
|
46
|
+
The CLI keeps the feature when any file its scan reaches reads `@symbo.ls/sdk`: the scan runs `classifyHostPackageReads` (frank's rule) over each file, and a static import, an `import()`, a lazy marker or any other mention counts. It also keeps the feature when the source names an export of the tier, or `keep` lists `hostPackages`. A dropped build keeps the names: each function reports the feature once and returns nothing, the two predicates answer `false`, and the marker forms and error codes are `null`. The full entry still installs the loader, and each call rejects with `SMBLS_HOST_PACKAGE_NO_LOADER` and a message that names the option. Node, the page runtimes, frank and the IIFE never set the flag, so nothing changes there.
|
|
47
|
+
|
|
48
|
+
- e8eaf28: Framework parts that only `context.lazy` groups use leave the main bundle. `smbls build` with `build.treeShakeFeatures` and `"smblsEntry": "core"` scans the main code and each group's own code apart (comments aside). When only group code uses `inertOutside`, the bridge hooks (`onBridgeMount`) or `TooltipLayer`, the build moves them into one small file (`smbls.core.lazyParts-*.js`). smbls loads this file together with the first group load and installs the parts before the group merges, whether that load comes before or after the first render. A part the main code uses, or one `keep` names, stays in the main bundle with no extra request. A group whose entry file the build cannot name keeps every part in the main bundle. XMA: main bundle −2,495 B Brotli; the admin load gets one more request (4.2 KB Brotli).
|
|
49
|
+
- f8d78c4: A lazy route can be built ahead as its group's real page (XMA question 6).
|
|
50
|
+
symbols.json `brender.lazyGroups: 'load' | ['group', …]` makes the build's
|
|
51
|
+
first navigation load the group and render the route as the group's page
|
|
52
|
+
(the group's `when` is asked at build time; false builds the placeholder).
|
|
53
|
+
The page names the groups it was built with (`<meta name="brender-lazy">`)
|
|
54
|
+
and `smbls build` preloads each group's chunk (`<link rel="modulepreload">`
|
|
55
|
+
after the page's own preloads, found through the chunk's source map;
|
|
56
|
+
`build.modulePreload: "strict"` fails the build without one). In the browser
|
|
57
|
+
the boot reads the tag into the new `context.lazyFirstView` (router): the
|
|
58
|
+
first navigation loads those groups and renders their pages, so the takeover
|
|
59
|
+
adopts the built page instead of mounting the placeholder over it. Public
|
|
60
|
+
pages can move out of the main bundle and still arrive built.
|
|
61
|
+
|
|
62
|
+
The default build (no `brender.lazyGroups`) is fixed the same way: a route
|
|
63
|
+
whose group loaded during the render (a context that declares `lazy`, e.g.
|
|
64
|
+
through `brender.context`) already showed the group's page, but the page
|
|
65
|
+
named no group, so the browser mounted the placeholder over it (1 adoption
|
|
66
|
+
miss; on a slow run the built page was removed and rendered again). The
|
|
67
|
+
router now records a first navigation that showed the placeholder and then
|
|
68
|
+
the group's page; the page names that group and preloads its chunk. The
|
|
69
|
+
built content does not change.
|
|
70
|
+
|
|
71
|
+
- b11e6c6: `polyglot.defaultLang` may be a function `(polyglot) => code`, called each time the default is asked (utils `polyglotDefaultLang`; every reader of the default goes through `polyglotFallbackLang`). The `fetch.early` pre-boot script carries its source and calls it before it decides. The language the pre-boot `startLang(ctx)` hook chose for a page load is recorded by the script (utils `polyglotEarlyStartLang`) and is the start language in every `addressLang` mode: under `'strict'` an unprefixed page started in the static default and asked for its labels a second time. A visitor's own stored choice still never wins under `'strict'`; an admin whose hook returns `null` keeps its own rule.
|
|
72
|
+
- 6ecdb94: `afterFirstView` create option (config.js / context.js / `create(App, { afterFirstView })`): `(app, state, context)` runs once per app, a task after the first view is in — after `create()` rendered it or adopted the pre-rendered page, after the first navigation settled — the moment fetch.early's `afterFirstView` hook has. An app no longer polls `window.__BRENDER_HYDRATION__` to learn that a built page was taken over. A hook that throws or rejects is reported, never rethrown. brender switches it off for its server renders.
|
|
73
|
+
- a529b80: Server render and page replace (W7): `el.context.ssr` is true while a component renders in a server render, so app code can tell it runs there. smbls replaces a pre-rendered page in short tasks and swaps it in at once; an adoption that throws falls back to the one-task replace. brender exports its page-weight trims (text between sprite symbols no longer stops the sprite trim) and `stylesheetRules`, which turns a stored stylesheet text back into its rules. `smbls build`: each pre-rendered route carries its own icons after its content (Fix B), and the cli reads brender's page trims as optional exports, so an older brender still builds.
|
|
74
|
+
- d42696c: frank: `workers/<name>.js` is a discovered folder of module Web Workers. `toJSON` ships each worker bundled (esbuild, one ES module, imports inlined) in a `workers` section (`{ <name>: { type: 'module', file, code } }`); a `new URL('<file>', import.meta.url)` inside the worker graph (a `.wasm` beside its glue) resolves through `resolveAssetUrl` (push uploads, `'dataurl'` inlines). `toFS` writes a worker back only where the tree lacks it. The app starts a worker with the bundler-native `new Worker(new URL('../workers/<name>.js', import.meta.url), { type: 'module' })`. frank-audit: `workers/` is a frank slot (no FA006) and FA409 allows `self.X` / `globalThis.X` writes in a worker file (`window` stays banned); FA701 documents head-metadata defaults.
|
|
75
|
+
|
|
76
|
+
helmet: the runtime head never writes a label's stand-in (a polyglot key whose language is still loading, `@symbo.ls/utils` `evaluateText`) — the tag keeps its value until the label lands — and on a head the build wrote (`<meta name="brender">` / `brender-stamp`), on its served path, the app root's metadata does not replace a tag the document carries until the app's values change (a language switch) or the reader navigates. Measured on dev.xma.info (3.14.856): the served title flipped to the app.js default, then to raw `ui.meta.*` keys, before the page's labelled title.
|
|
77
|
+
|
|
78
|
+
Platform half: `@symbo.ls/utils` `SMBLS_PATH_PREFIX` / `workerScriptPath` / `workerNameFromPath` are the one path contract. frank points every published caller's `new URL('…/workers/<name>.js', import.meta.url)` at `new URL('/__smbls/workers/<name>.js', globalThis.location.href)`. mermaid `resolveSmblsRequest` serves `data.workers.<name>.code` there (`text/javascript`, `no-cache` + ETag, `nosniff`), and its boot script no longer inlines `data.workers`.
|
|
79
|
+
|
|
3
80
|
## 3.14.858
|
|
4
81
|
|
|
5
82
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -175,9 +175,23 @@ When things happen:
|
|
|
175
175
|
node whose element is not built yet is held and dispatched again, once, on
|
|
176
176
|
the same node when the adoption is done (an element already adopted handles
|
|
177
177
|
its own events at once); input during the adoption does not close the
|
|
178
|
-
window. `firstRender: false` adopts in one task, as before
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
window. `firstRender: false` adopts in one task, as before.
|
|
179
|
+
- A boot that REPLACES the server markup (no registry, `hydrate: false`, a
|
|
180
|
+
platform pre-render) builds in the same short tasks: the app is built
|
|
181
|
+
outside the document while the server's page stays on screen with its
|
|
182
|
+
styles, and the server's markup and styles go in the task that inserts the
|
|
183
|
+
app — one page or the other on screen, never both, never neither.
|
|
184
|
+
`window.__BRENDER_HYDRATION__` is `{ replaced: true }` before the app's
|
|
185
|
+
first hook runs, in tasks as in one task: code that tells an adoption from
|
|
186
|
+
a fresh build (`el.context.prerendered` and no flag yet) reads a fresh
|
|
187
|
+
build in every hook. The first page's hooks and `onRouteChanged` run in the
|
|
188
|
+
task that inserts the app, so a hook there that reads layout (`focus()`,
|
|
189
|
+
`getBoundingClientRect()`) makes the new page's first style and layout part
|
|
190
|
+
of that task; read layout after the first frame or in `afterFirstView`.
|
|
191
|
+
`progressive: true` does not apply to a replace; `firstRender: false`
|
|
192
|
+
replaces in one task, and so does the fallback of an adoption that throws
|
|
193
|
+
(it has taken back the server nodes it had claimed). A server render keeps
|
|
194
|
+
the whole boot in one task.
|
|
181
195
|
- A pre-rendered page shows the data of its build (`smbls build` pages: until
|
|
182
196
|
the next build). `el.context.prerender` is the build it comes from,
|
|
183
197
|
`{ stamp, time }` (`<meta name="brender-stamp">`). When the page is flagged
|
|
@@ -193,6 +207,28 @@ When things happen:
|
|
|
193
207
|
nothing extra. See `@symbo.ls/fetch`, "A pre-built page shows
|
|
194
208
|
the data of its build".
|
|
195
209
|
|
|
210
|
+
### When the first view is in (`afterFirstView`)
|
|
211
|
+
|
|
212
|
+
Work that must not compete with the first view — an analytics beacon, a page
|
|
213
|
+
view, warming the next route — goes in the `afterFirstView` create option
|
|
214
|
+
(config.js, context.js, or `create(App, { afterFirstView })`):
|
|
215
|
+
|
|
216
|
+
```js
|
|
217
|
+
// config.js
|
|
218
|
+
export default {
|
|
219
|
+
afterFirstView: (app, state, context) => { app.call('pageView') }
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
It runs once per app, a task after the first view is in: after `create()`
|
|
224
|
+
rendered it — on a pre-rendered page, after the adoption — and after the
|
|
225
|
+
first navigation settled (a route behind an awaited guard renders later). It
|
|
226
|
+
is the moment fetch.early's own `afterFirstView` hook runs, so an app needs
|
|
227
|
+
no poll on `window.__BRENDER_HYDRATION__` to learn that a built page was
|
|
228
|
+
taken over. Called as the root's `onCreate` is, `(app, state, context)`; a
|
|
229
|
+
hook that throws (or rejects) is reported, never rethrown. A server render
|
|
230
|
+
(brender) never runs it.
|
|
231
|
+
|
|
196
232
|
## Plugins
|
|
197
233
|
|
|
198
234
|
domql supports a plugin system via `context.plugins`. Plugins hook into the element lifecycle and can extend how event handlers, props, and metadata are processed.
|
|
@@ -273,7 +309,7 @@ Also includes `@symbo.ls/cli`, `@symbo.ls/fetch`, `@symbo.ls/sync`, and `@symbo.
|
|
|
273
309
|
| Import | File | What it is |
|
|
274
310
|
|--------|------|------------|
|
|
275
311
|
| `smbls` | `dist/smbls.esm.js` (+ `smbls.cjs`) | The full runtime: the public API **plus the platform registry** — every bundled package registered on `globalThis.__SMBLS_PKGS__` so published or fetched project code (frank-serialized functions, `smbls fetch --scope libs` libraries, raw JSON handed to `create()`) resolves its imports. One file. |
|
|
276
|
-
| `smbls/core` | `dist/smbls.core.esm.js` + chunks | The same public API **without the registry** (no `registerHostPackages` / `HOST_REGISTERED_PACKAGES`), no Simone client, no socket.io. Code-split: each kit and platform plugin is its own file, downloaded only when the app's configuration switches it on. |
|
|
312
|
+
| `smbls/core` | `dist/smbls.core.esm.js` + chunks | The same public API **without the registry** (no `registerHostPackages` / `HOST_REGISTERED_PACKAGES` / `importHostPackage`, and no on-demand host-package loader), no Simone client, no socket.io. Code-split: each kit and platform plugin is its own file, downloaded only when the app's configuration switches it on. |
|
|
277
313
|
| `smbls/iife` | `dist/smbls.iife.js` | The full runtime as one script (`window.Smbls`) — what the platform serves. |
|
|
278
314
|
|
|
279
315
|
Use `smbls/core` for an app that runs **no** published or fetched project code — that code reads the registry and gets `{}` without it. Select it for **every** importer of `smbls` at once (a bundler alias), never by importing both entries: they are two bundles, so two copies of the runtime. With the Symbols CLI and parcel, set `"smblsEntry": "core"` in `symbols.json`. With vite: `resolve: { alias: [{ find: /^smbls$/, replacement: 'smbls/core' }] }`.
|
|
@@ -294,6 +330,54 @@ In `smbls/core` an app that keeps a kit on pays one module request at boot: the
|
|
|
294
330
|
|
|
295
331
|
Every dist bundle has a source map next to it (`<file>.map`, sources included). The embed's map is not linked from the file, to keep the embed's byte budget.
|
|
296
332
|
|
|
333
|
+
## Host packages: the platform SDK
|
|
334
|
+
|
|
335
|
+
`@symbo.ls/sdk` is a **host-registered package**: `smbls` (public on npm) never bundles it and no public CDN serves it — the page that runs the code supplies it. Project code reads it in one of two ways.
|
|
336
|
+
|
|
337
|
+
**Static** — `import { SDK } from '@symbo.ls/sdk'` at the top of a file. frank emits a synchronous read of `globalThis.__SMBLS_PKGS__['@symbo.ls/sdk']` with a named error on a miss, and the page runtime inlines the SDK and registers it before boot:
|
|
338
|
+
|
|
339
|
+
```js
|
|
340
|
+
Smbls.registerHostPackages({ '@symbo.ls/sdk': sdkNamespace }) // before Smbls.create(...)
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
**On demand** — a literal `import('@symbo.ls/sdk')` inside the function that needs it:
|
|
344
|
+
|
|
345
|
+
```js
|
|
346
|
+
export const openAccount = async function () {
|
|
347
|
+
const { SDK } = await import('@symbo.ls/sdk')
|
|
348
|
+
const sdk = new SDK({ apiUrl })
|
|
349
|
+
// …
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
frank rewrites that call into a fixed marker expression (`@symbo.ls/utils` `lazyHostImportExpr`). The page runtimes recognise the marker (`classifyHostPackageReads`): when **every** mention of the SDK in the boot payload is a marker, the first document carries no SDK bytes, only a small declaration of a same-origin, content-hashed, immutable file:
|
|
354
|
+
|
|
355
|
+
```js
|
|
356
|
+
window.__SMBLS_HOST_SOURCES__ = {
|
|
357
|
+
'@symbo.ls/sdk': { src: '/__smbls/host/sdk.<sha256-16hex>.js', integrity: 'sha384-…', global: 'SymbolsSDK' }
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
On the first call the marker reads the registry, then asks `importHostPackage` (the full entry installs it as `globalThis[Symbol.for('smbls.importHostPackage')]`; hand-written code can call `Smbls.importHostPackage('@symbo.ls/sdk')`). The loader inserts one `<script>` for the declared file, registers the namespace, and resolves it. The semantics are the inline's: one SDK instance per page (registry first, one shared load, additive registration), the same SDK build the page would have inlined.
|
|
362
|
+
|
|
363
|
+
A failure rejects with an `Error` that has `code`, `specifier` and `retryable`. Catch it and offer a retry:
|
|
364
|
+
|
|
365
|
+
| `code` | When | `retryable` |
|
|
366
|
+
|---|---|---|
|
|
367
|
+
| `SMBLS_HOST_PACKAGE_LOAD_FAILED` | the file did not load (the script is removed; a retry inserts a new one) | yes |
|
|
368
|
+
| `SMBLS_HOST_PACKAGE_TIMEOUT` | the file did not load in 30 s | yes |
|
|
369
|
+
| `SMBLS_HOST_PACKAGE_NO_GLOBAL` | the file loaded but defined no global | yes |
|
|
370
|
+
| `SMBLS_HOST_PACKAGE_NO_SOURCE` | the page declares no file and registered nothing — every server render | no |
|
|
371
|
+
| `SMBLS_HOST_PACKAGE_NO_DOM` | no document to load into | no |
|
|
372
|
+
| `SMBLS_HOST_PACKAGE_UNKNOWN` | the specifier is not host-registered | no |
|
|
373
|
+
| `SMBLS_HOST_PACKAGE_NO_LOADER` | no smbls runtime on the page provides the loader | no |
|
|
374
|
+
|
|
375
|
+
Rules for a page that should stay lazy:
|
|
376
|
+
|
|
377
|
+
- Write only the on-demand form; no static `import … from '@symbo.ls/sdk'` anywhere in the page's parts (app, libraries, kits).
|
|
378
|
+
- Name the specifier nowhere else inside a function body — frank publishes body comments and strings as written, and any other mention makes the page inline the SDK (it still works: the marker then answers from the registry).
|
|
379
|
+
- Push with a CLI whose frank has the marker. An older frank publishes the `import()` as written, and a browser cannot resolve that bare private specifier.
|
|
380
|
+
|
|
297
381
|
## SEO metadata
|
|
298
382
|
|
|
299
383
|
Define metadata on your app or pages. Values can be static or functions:
|
package/core.js
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
// kits and platform plugins are separate files loaded only when switched on.
|
|
15
15
|
//
|
|
16
16
|
// The export list below is index.js's, minus the registry's own surface
|
|
17
|
-
// (`registerHostPackages`, `HOST_REGISTERED_PACKAGES`
|
|
17
|
+
// (`registerHostPackages`, `HOST_REGISTERED_PACKAGES`, `importHostPackage` —
|
|
18
|
+
// and index.js's install of that on-demand loader); it is a copy, not an
|
|
18
19
|
// import of index.js, which would bring the registry along.
|
|
19
20
|
// __tests__/coreEntry.test.js keeps the two lists in step, and from source
|
|
20
21
|
// both entries share every module instance. Never import both PREBUILT
|