Upgrade from 1.2 to 1.3
1.3 is a minor release: new features (the static adapter, site, sitemap, robots, document.seo, hash-based CSP, trustHtml, isClientError, imports.appDirs and imports.sharedDirs, CSS class completion in the editor, class sorting with Prettier…) are optional. One type-only change is released as an approved exception to the stability policy: csstype-typed @meekojs/meeko/postcss declarations. Other changes are noticeable when updating: new diagnostics, Prettier formatting output, handling of global failures in the browser and server, and server logs in the browser console during meeko dev. This guide collects them and what you should review for each. The full list, including additions and fixes, is in the CHANGELOG; compatibility commitments are in the stability policy.
Recommended order#
- Update
@meekojs/meekoto the exact version1.3.0and recompile or rebuild, including prerendered pages: generated code and runtime must have the same version. If you use the VS Code extension, update it to1.3.0; Meeko DevTools is unchanged (still1.0.0). - Run
meeko check. FixMEEK108errors, which identify code that already failed, and reviewMEEK107andMEEK109warnings, which do not fail the check (see New diagnostics). - If you use
@meekojs/meeko/postcss, check your@utilitiesmodules with TypeScript (see csstype-typed@meekojs/meeko/postcssdeclarations). - If you format with the Prettier plugin, run
prettier --writeonce and save the result in a separate commit: formatting output changes (see Formatting with Prettier). - Review
onErrorinmountClientAppandserver/runtime.ts, andswitchstatements overErrorSource: they receive failures they did not receive before, and an unhandled rejection no longer terminates the server process (see Errors). - If
meeko.config.tshas animports.serverDirsentry outsideserver/, fix it: configuration no longer loads (see Auto-import directories).
Approved exception#
@meekojs/meeko/postcss declarations typed with csstype#
The declarations object of rule() and atRule() (UtilityDeclarations) uses csstype types instead of accepting any key with a string or number. The editor completes standard and vendor-prefixed properties, in camelCase and kebab-case, and each value's keywords. Plugin-generated CSS is unchanged: this is a type-only fix, and almost everything now rejected already failed in the browser, which silently discards an unknown property or padding: 4. csstype becomes a type-only package dependency.
What to review when checking @utilities modules with TypeScript:
- A property csstype does not know (
backgroundColr) is an error: correct the name. For a recent property or at-rule not yet in csstype, write CSS withcss`…`. - Lengths and times are strings with units, except
0:padding: 4, which the plugin wrote without a unit, is an error. Writepadding: "4px". Numbers remain valid where CSS accepts them without units (opacity,zIndex,flexGrow,lineHeight,fontWeight…). - An object constructed outside the call loses its keywords:
const base = { position: "absolute" }is{ position: string }, which is no longer assignable. Annotate it withUtilityDeclarationsor useas const. - Reading declarations with a
stringkey (decls[key]in aforoverObject.keys(decls)) producesTS7053withnoImplicitAny(included instrict), becauseUtilityDeclarationsno longer has the[property: string]index signature. UseObject.entries(decls)or akeyof UtilityDeclarationskey.
// app/styles/utilities/surface.ts
import { defineUtilities } from "@meekojs/meeko/postcss";
import type { UtilityDeclarations } from "@meekojs/meeko/postcss";
// Before: compiled, but the browser discarded “padding: 4”
const base = { position: "absolute", padding: 4 };
// After
const base: UtilityDeclarations = { position: "absolute", padding: "4px" };
// or: const base = { position: "absolute", padding: "4px" } as const;
export default defineUtilities(({ rule }) => [rule(".surface", base)]);--* properties, kebab-case keys, fallback arrays (also with as const), undefined, !important and dynamic keys from Object.fromEntries or a Record<string, string> still compile. atRule() also accepts descriptors from @font-face, @property, @page, @counter-style, @font-palette-values and @view-transition, with the new UtilityAtRuleDeclarations type. See TypeScript-generated utilities.
New diagnostics#
MEEK107: html="..." without trustHtml#
meeko check, meeko build and the editor warn with MEEK107 when the value of html="..." is not assignable to TrustedHtml | null | undefined: a string, string | undefined, concatenation with a string or any (for example, the result of JSON.parse or response.json()). It is a warning: it does not fail the check or change SSR, hydration or generated code. Inserting an unmarked string is deprecated and will become a meeko check error in 2.0.
Mark HTML where it is produced, not immediately before the template. trustHtml does not sanitize, only declares trust: sanitize HTML from users or third parties first. To display text, {expr} escapes and needs none of this.
// server/content/articles.ts
import DOMPurify from "isomorphic-dompurify";
import { trustHtml } from "@meekojs/meeko";
import type { TrustedHtml } from "@meekojs/meeko";
export function commentHtml(input: string): TrustedHtml {
return trustHtml(DOMPurify.sanitize(input));
}- Import
TrustedHtmlandtrustHtmlfrom@meekojs/meeko; they are not auto-imports. - The marker reaches the template from
<script server>,loadandServerData<T>: declareTrustedHtmlin the shared types for that data. - String literal types and their unions do not warn (
html="'<br>'"). An unannotated<script>constant, such asconst icon = "<svg>…</svg>", reaches the template asstring: writeas constor mark it withtrustHtml.
See Trusted HTML.
MEEK108 and MEEK109: each API in its place#
A component's <script> and template run in SSR and in the browser; <script server> and *.route.ts sidecars run only on the server. meeko check, meeko build (also the Vite plugin in meeko dev) and the editor now identify every API used where it does not exist, with a message stating where it is used:
MEEK108error. In<script>or the template: a server auto-import (defineApiRoute,defineRpc,defineServerMiddleware,defineSocketChannel,defineServerRuntime,ApiErroror one fromserver/utils,imports.serverDirsor a*.server.ts), a value import from@meekojs/meeko/serveror/cloudflare, or an import fromserver/or a*.server.*module. In<script server>: a component macro (defineProps,defineEmits,defineExpose,defineModel,defineSnippets,useElementRef) andonMounted. All of this already failed before1.3(the auto-import did not resolve in the browser bundle,/serverand/cloudflarethrow when loaded there, importingserver/stoppedmeeko build); the error now arrives earlier, on its line. Request data withuseApioruseRpcQuery, or move the code to<script server>.MEEK109warning. In<script>or the template:definePage, which does not configure the route there, and value imports from@meekojs/meeko/node,/bun,/deno,/passwordor/build, whose functions only work on the server. That code compiles and loads, so it is a warning; it is deprecated and will be an error in2.0.usePage,redirectandnotFoundwithout imports in<script>produceMEEK073, as they already did when imported.
import type remains allowed in all cases, and a component inside server/ (an email template, for example) can use everything. The build and meeko dev only apply MEEK108 to the browser bundle, so a component outside server/ importing only server code still compiles; meeko check and the editor, which do not know the import graph, check it as browser code: place it in server/ for them to accept it.
<!-- Before: app/pages/about.meeko. definePage had no effect here (MEEK109) -->
<script lang="ts">
const route = definePage({ render: "prerender" });
</script>
<!-- After -->
<script server lang="ts">
export const route = definePage({ render: "prerender" });
</script>Also, completion in <script> and the template no longer offers exports from @meekojs/meeko/server, /node, /bun, /deno, /password, /build and /cloudflare, server/ and *.server.* modules, or definePage, usePage, redirect and notFound; <script server> still offers them. See Where each API is used.
Formatting with Prettier#
The first two changes affect formatting output, which the stability policy allows to change in a minor release. prettier --check may flag files that passed with 1.2, and editor format-on-save may change untouched lines. Format the project once after updating and save the result in a separate commit to avoid mixing it with other changes.
Normalized class attribute#
In .meeko templates, the value of each static element class="…" is always normalized, with no option, as Prettier does with standalone HTML: leading and trailing whitespace is removed, any sequence of spaces, tabs or line breaks becomes one space, and duplicate classes disappear (keeping the first). The element's classes are unchanged because Meeko's static attributes do not interpolate {expr}. bind:class, class on a component tag (<Card class="…">, a static prop) and values with character references (&) are untouched.
Arrays: meekoPreserveArrayBreaks#
The new meekoPreserveArrayBreaks option, enabled by default, applies objects' objectWrap: "preserve" rule to arrays: an array, destructuring pattern or tuple with a line break between [ and its first item prints one item per line even if it fits in printWidth. Without that break, Prettier decides; to collapse an array, join [ with the first item and format again.
To do this, the plugin replaces Prettier's estree printer, so with the plugin in project configuration, the rule reaches all JavaScript and TypeScript formatted by Prettier (.ts, .js, .tsx, .mjs…), not just .meeko files. The printer delegates to the running Prettier's estree, even if it is a different 3.x version from Meeko's dependency. If another configured plugin also replaces estree, Prettier uses only the last in the plugins list. To restore Prettier's exact output, disable it in project Prettier configuration, also read by the editor (the language server has no separate setting):
{ "plugins": ["@meekojs/meeko/prettier"], "meekoPreserveArrayBreaks": false }Class ordering: meekoSortClasses#
Optional and disabled ("off"). "alphabetical" sorts classes in each static class="…" by character code, and "stylesheet" by first appearance in app/styles/main.css (processed with the project's Vite and PostCSS, as in meeko dev) and then the component's <style>, like prettier-plugin-tailwindcss. In the CLI, the option is only recognized when passing the plugin through --plugin (--plugin @meekojs/meeko/prettier --meeko-sort-classes stylesheet). If the stylesheet is absent or fails, classes are only normalized and stderr warns once. In an untrusted VS Code workspace, sorting is disabled because stylesheet processing executes project code. The prettier --cache cache does not know about the stylesheet: after changing it, run the CLI without --cache. See Sorting by the global stylesheet.
Errors#
Global browser failures#
While the application is mounted, mountClientApp listens for error and unhandledrejection on window. Uncaught exceptions and unhandled promise rejections in code outside Meeko (a throwing setTimeout, fetch().then() without catch, a library) reach onError and plugins' app.onError, with new sources "uncaught" and "rejection"; without either, they reach the first active provideErrorHandler. What to review:
- More failures in observers. An
onErrorsending every error to a logging service now also receives those from libraries and detached asynchronous code. The same applies toprovideErrorHandlerinapp/app.meekoin an application withoutonError. - Fewer console messages. When an observer receives a failure, Meeko calls
preventDefault()and the browser does not repeat it in the console. If you relied on seeing it there, log it yourself from the observer. Without any observer, the browser displays it as usual. - An error already received by a Meeko observer is not reported again, and the opaque
"Script error."from a cross-origin script loaded without CORS is left to the console.
ErrorSource gains "uncaught" and "rejection"#
ErrorSource, the browser observers' second argument, is a union Meeko delivers to the application, so according to the stability policy, it can gain members in a minor release. An exhaustive switch checking never stops compiling: add a default branch.
provideErrorHandler((error, source) => {
switch (source) {
case "navigation":
showNavigationError(error);
break;
case "uncaught":
case "rejection":
report(error, { global: true });
break;
default:
report(error);
}
});To narrow errors in these observers, which receive unknown, isClientError(error, "UNAUTHENTICATED") from @meekojs/meeko/framework checks a ClientError and its code, including one from another copy of Meeko on the page, where instanceof fails.
Failures outside a request: processErrors#
On the server, an unawaited promise inside a handler, a throwing setTimeout or a library callback falls outside the request's try. Until 1.2, Node, Bun and Deno terminated the process on the first unhandled rejection or uncaught exception, including in meeko dev. Since 1.3, meeko dev, meeko build outputs (Node, Bun, Deno and Workers), and serveNode and serveDeno with handleSignals apply this policy by default:
- An unhandled rejection reaches
onErrorinserver/runtime.tsand the server keeps serving. - An uncaught exception reaches
onErrorand the server shuts down gracefully: it stops accepting connections, waits for open responses andwaitUntiltasks, closes the application and exits with code 1.
What to review:
- If deployment relied on a rejection restarting the process (a supervisor,
restart: always), declareprocessErrors: { unhandledRejection: "shutdown" }. onErrorreceives these failures with a third argument,{ kind, inRequest }(typeServerErrorInfo); request failures do not carry it. The ID is the request's (x-request-id) when the runtime retains its asynchronous context until the event; otherwise it is a new ID. WithoutonError, Meeko writes the full error to the console.- If the application registered its own
process.on("uncaughtException" | "unhandledRejection"), Meeko only reports toonErrorand lets the application decide. - Cloudflare Workers has no process to close: only rejections are reported.
// server/runtime.ts
export default defineServerRuntime(() => ({
processErrors: { unhandledRejection: "shutdown" }, // or false for the runtime's behavior
onError(error, requestId, info) {
if (info) logger.fatal({ kind: info.kind, inRequest: info.inRequest, requestId }, error);
else logger.error({ requestId }, error);
},
}));processErrors: false disables both cases and lets the runtime decide, as in 1.2; "off" for one disables only that case. A custom host using neither serveNode nor serveDeno can apply the same policy with handleProcessErrors(app, { close }) from @meekojs/meeko/server. See Failures outside a request.
Development#
Server logs in the browser console#
In meeko dev, anything server code writes with console.log, info, warn, error or debug while handling a request also appears in the browser console, grouped by request with the server badge, like Nuxt's features.devLogs. Terminal output remains the same. This exists only in development, never in meeko build, meeko start or meeko preview, and only reaches the browser on the same machine: requests from loopback, to a loopback host and without proxy headers, so not visitors through a tunnel such as ngrok or cloudflared either.
If an end-to-end test against meeko dev fails on any browser console error, it now also sees server console.error messages. To disable it:
// meeko.config.ts
export default defineMeekoConfig({ dev: { serverLogs: false } });The HMR WebSocket port (MEEKO_HMR_PORT) must not be published through a tunnel: anyone connecting to it receives logs from all requests. See Server logs in the browser console.
.meeko/styles/main.css#
meeko dev writes processed app/styles/main.css (with resolved @import rules and expanded @utilities) to .meeko/styles/main.css so the editor completes generated classes. This is a new file inside .meeko/, which already contains other generated files: do not edit or version it. Vite does not watch it, so writing it does not reload the page; meeko build does not write it and meeko clean deletes it. Its content is not contractual, but its path stays during 1.x, so you can name it in "css.styleSheets" of extensions such as HTML CSS Support. See Processed global stylesheet for the editor.
Configuration and build#
Auto-import folders#
imports.appDirs and imports.sharedDirs add, alongside imports.serverDirs, auto-import directories inside app/ (with app/ scope: browser and SSR) and shared/ (with shared/ scope). They are optional:
// meeko.config.ts
export default defineMeekoConfig({
imports: {
appDirs: ["app/services"],
sharedDirs: ["shared/validators"],
serverDirs: ["server/services"],
},
});When adopted, an export from those directories with the same name as another auto-import in its scope produces a collision error, as in default directories, and a *.server.ts in an appDirs directory is server code (a component using it reports MEEK108).
What changes without adopting them is validation: each imports entry must be a relative path inside its directory, without . or .., and is checked when loading meeko.config.ts. An invalid serverDirs entry ("lib/services", "../shared", an absolute path), rejected until 1.2 when reading auto-imports, now stops configuration loading, and therefore every command reading it, with an error naming the entry: imports.serverDirs[0] must be a relative path inside server/: lib/services. An imports value that is not an object is also an error. In a trusted workspace, the editor flags the entry with MEEK_CONFIG. If a script searched for the old text (serverDir de autoimports debe ser…), make it use the exit code. See Auto-import directories.
Static site, site, sitemap, robots and document.seo#
Optional and change nothing until enabled:
staticadapter.meeko build --adapter static(orserver: { adapter: "static" }) writes a site for any static hosting to.meeko/output/static/, with each page as<ruta>/index.html, its payload<ruta>/index.meeko.jsonand404.htmlfromapp/error.meeko. All pages must userender: "prerender", and the build fails before compilation with a list of what requires a server ("ssr","stream"or"client"pages, API, RPC, Socket, server middleware and module server handlers). See Static site.site. The public origin for absolute URLs written by the build; without it,MEEKO_PUBLIC_ORIGINfrom the build environment is used. Does not changeOriginchecks, which still readMEEKO_PUBLIC_ORIGINorpublicOriginat runtime.sitemapandrobots. Generatesitemap.xmlandrobots.txtduring the build, for every adapter. Withsitemapand no origin, the build fails. If the project already haspublic/sitemap.xmlorpublic/robots.txt, remove it before enabling the corresponding option: the build fails on collisions. Seesitemap.xmlandrobots.txt.document.seo. Adds<link rel="canonical">,og:url,og:titleandog:descriptionto each document. A page or layout already declaring the same tag overrides it, preserving your tags. WithcanonicalorogUrlenabled and no origin,meeko buildfails. See Canonical and Open Graph per page.
CSP with hashes#
prerender/manifest.json gains the optional scriptHashes field on each page, with the 'sha256-…' sources of its inline scripts; a custom index reader must tolerate that field. inlineScriptHashes(html) from @meekojs/meeko/build and contentSecurityPolicy(policy, hashes) from @meekojs/meeko/server let you construct the header on your own host, and the static adapter does so with server.contentSecurityPolicy, server.headers and server.headerFiles. With server.contentSecurityPolicy on the static adapter, an on* attribute or javascript: URL on a page fails the build because a hash cannot allow them: use a script or the component's on:. meeko start does not add that header: for SSR pages the approach remains DocumentOptions.nonce. See Hash-based CSP.
Types#
definePage with a single signature#
A new first signature, whose option is the union of PageDefinition and ClientPageDefinition, resolves calls without type arguments. With an incomplete render, the editor now offers "ssr", "stream", "prerender" and "client" instead of only "client". Inference is unchanged (the load context by mode, head data and required load in the result), but two visible things change:
- The error message for
render: "client"withprerenderEntriesnow starts with “No overload matches this call” and identifiesrender,prerenderEntriesandloadas incompatible. If a type test checked the old text, update it. - Hover shows the new signature, with one more type parameter,
T.
The four 1.2 overloads remain afterward for calls specifying type arguments through D (definePage<P, S, D, C>(…), the way to type context C): the result remains the render mode's type, with required load if declared. A call with partial type arguments (definePage<P>(…) or definePage<P, S>(…)) still checks options by mode, but its result is now PageDefinition<P, S, unknown, unknown>, even if render is "client", and load is optional in the type. If your code reads the result type, remove the type arguments, which are inferred from options, or specify them through D.
Editor#
VS Code extension 1.3.0 includes Meeko 1.3's language server and Prettier plugin. Visible changes:
- CSS class completion in
class="…"andbind:classstrings: classes from the component's<style>, other components'<style>withoutscoped, andapp/styles/main.csswith its local@importrules, plus those from.meeko/styles/main.cssifmeeko devwrote it. Hover shows the rule and go to definition leads to the selector. Works in untrusted workspaces because it only reads CSS. If you used HTML CSS Support only for this, you no longer need it. See CSS class completion. - Format on save:
classnormalization,meekoPreserveArrayBreaksand, if requested by project Prettier configuration,meekoSortClasses(see Formatting with Prettier). The extension's bundled server does not bundle Vite: with"stylesheet", it loads the project's Vite and, if absent, only normalizes. MEEK107,MEEK108andMEEK109diagnostics on their lines, and<script>and template completion without server APIs (see New diagnostics).imports.appDirsandimports.sharedDirsare also read in an untrusted workspace, as literal string arrays, likeimports.serverDirs.