Vize

Experimentals

experimentals is the shared opt-in surface for proposed Vue syntax, tooling-only type checks, and backend routes that must stay off unless a project asks for them. It is separate from stable compiler options and from Vue runtime features: every key is disabled by default, every RFC flag is explicit, and stable options win when both surfaces can describe the same backend choice.

Use this page as the source of truth when enabling:

  • Vue RFC #823 patterned templates
  • Vue RFC #831 in-tag comments
  • Vue RFC #833 <Self> component references
  • Vue RFC #734 strict slot child checks
  • Vize backend experiments such as server-script, SFC Vapor, and JSX Vapor routing

The tables below cover every public switch, including off behavior and precedence. RFC-specific diagnostics, direct API entry points, implementation boundaries, and deferred syntax are expanded in Vue RFC Experimental Details.

Prefer the recommended camelCase names in new TypeScript config:

import { defineConfig } from "vize";

export default defineConfig({
  experimentals: {
    patternedTemplate: true, inTagComment: true,
    selfComponent: true, strictSlotChildren: true,
    serverScript: false, vapor: null, jsxVapor: {},
  },
});

For JSON and Pkl config, the same values are accepted. Use {} only when you want to enable the feature while leaving room for future nested options:

{
  "experimentals": {
    "patternedTemplate": true,
    "inTagComment": true,
    "strictSlotChildren": {},
    "vapor": false
  }
}
amends "node_modules/vize/pkl/vize.pkl"

experimentals {
  patternedTemplate = true
  inTagComment = true
  strictSlotChildren = new Mapping {}
  vapor = false
}

Switch Values

Each key is a switch:

Value Meaning
omitted Disabled
false Disabled, including when overriding a shared opt-in
null Disabled; useful for JSON config generated from nullable settings
true Enabled
{} Enabled, with an object shape reserved for future per-feature options

Missing keys, false, and null are off. true and {} are on.

Typed TypeScript and Pkl config should use only true, false, null, or {}. The config loader treats other present non-false and non-null JSON values as enabled for compatibility, but that is not the documented public shape.

Precedence

Shared vize.config.* values are the default for tools that load project config. Direct Vite plugin options win over shared config, including explicit opt-outs:

import { defineConfig } from "vite";
import vize from "@vizejs/vite-plugin";

export default defineConfig({
  plugins: [
    vize({
      experimentals: {
        patternedTemplate: false,
        inTagComment: true,
      },
    }),
  ],
});

In this example, patternedTemplate: false disables a shared config opt-in for this plugin instance, while inTagComment: true enables the parser feature for the same instance.

Stable compiler options take precedence where both surfaces exist:

  • vize({ vapor }) wins over compiler.vapor, then experimentals.vapor.
  • vize({ jsxMode }) wins over compiler.jsxMode, then experimentals.jsxVapor.
  • Resolved fields such as experimentalPatternedTemplate are low-level compiler switches. Use them only when an integration already owns config resolution.

Surface Matrix

Flag Enabled behavior Off behavior Enforced by Boundary
patternedTemplate Parses and lowers v-match / direct v-when branches v-match, v-when, and v-case report the required opt-in DOM, SSR, Vapor, SFC, WASM compile APIs Canon and native Maestro check coverage and narrow branches; Content Mapper and complete navigation coverage remain deferred
inTagComment Parses // comments in opening tags and preserves them for tooling // is invalid tag syntax Parser, DOM, SSR, Vapor, SFC, WASM compile APIs No runtime output and no browser in-DOM support
selfComponent Treats exact <Self> as the current component <Self> is an ordinary component tag DOM, SSR, Vapor, SFC, WASM compile APIs Render functions and JSX are outside this flag
strictSlotChildren Emits virtual TypeScript child assertions No child-type assertions are generated vize check, LSP/type-check project APIs Open slots and any remain TypeScript-permissive
serverScript Passes experimentalServerScript to native compiler integrations The native flag stays false Native compiler integrations that expose it Host-defined until a runtime documents semantics
vapor Falls back to SFC Vapor when stable Vapor is unset SFCs keep the stable/default backend Vite plugin, package build config vize({ vapor }) and compiler.vapor win
jsxVapor Falls back to JSX/TSX Vapor when stable JSX mode is unset JSX/TSX keep the stable/default backend Vite plugin, package build config vize({ jsxMode }) and compiler.jsxMode win

For entry-point coverage, smallest useful proofs, and low-level native API examples, see Experimentals Reference.

Flag Reference

Flag Upstream / source Resolved field Compatibility names
patternedTemplate Vue RFC #823 experimentalPatternedTemplate pattenedTemplate
inTagComment Vue RFC #831 experimentalInTagComments intagComment
selfComponent Vue RFC #833 experimentalSelfComponent self_component in JSON
strictSlotChildren Vue RFC #734 experimentalStrictSlotChildren strict_slot_children in JSON
serverScript Server script compiler experiment experimentalServerScript "server script", server_script in JSON
vapor SFC Vapor backend routing compiler.vapor fallback none
jsxVapor JSX/TSX Vapor default compiler.jsxMode fallback none

Do not set a recommended flag and its alias at the same time. Compatibility aliases exist so older configs keep loading, but mixed spelling makes behavior depend on compatibility resolution order. New config should use patternedTemplate, inTagComment, selfComponent, strictSlotChildren, serverScript, vapor, and jsxVapor.

Patterned Templates

Enable patternedTemplate to parse v-match containers and direct v-when branch children from Vue RFC #823:

export default defineConfig({
  experimentals: {
    patternedTemplate: true,
  },
});
<script setup lang="ts">
type Entry =
  | { kind: "article"; data: { title: string; published: boolean } }
  | { kind: "draft"; reason: string }
  | { kind: "archived"; id: string };

const entry = ref<Entry>({ kind: "draft", reason: "editing" });
</script>

<template v-match="entry">
  <article v-when="{ kind: 'article', data: const article } if (article.published)">
    {{ article.title }}
  </article>
  <p v-when="{ kind: 'draft' } | { kind: 'archived' }">Hidden</p>
  <p v-when="_">No published article</p>
</template>

The matched subject is evaluated once. Branches are checked top to bottom, the first matching branch renders, and no later branch falls through. v-when="_" is the fallback pattern and must be unique and last when it is an unguarded top-level fallback.

Supported patterns include:

Pattern Example Notes
Literal v-when="'ready'", v-when="404" Uses strict equality
Value v-when="Status.Active" Identifiers and member expressions use SameValueZero comparison, including NaN
Wildcard v-when="_" Matches without introducing a binding
Const binding v-when="const value" let and var are rejected
Object v-when="{ kind: 'ok', value: const result }" Extra object properties are allowed
Object shorthand v-when="{ kind: 'ok', const data }" { const data } means { data: const data }
Object rest v-when="{ kind: 'error', ...const payload }" Rest bindings must use const; lone ... is accepted
Array / tuple v-when="[const first, ...const rest]" Requires Array.isArray; rest allows additional items
Or `v-when="'idle' 'loading'"`
As binding v-when="{ kind: 'ok', const data } as entry" Binds the matched value in addition to nested bindings
Guard v-when="{ kind: 'error', error: const e } if (e.retriable)" The guard runs after the pattern matches

v-case remains a compatibility alias for older Vize experiments, but new templates should use v-when. The shorthand candidates discussed in RFC #823 are not enabled: ?=, |=, and ~= are not public Vize syntax.

Invalid placements are reported instead of silently compiling. A v-when branch must be a direct child of a v-match container, v-match must have at least one direct branch, and v-when does not accept directive arguments or modifiers. Without the flag, Vize reports that experimentals.patternedTemplate is required. Canon supports opt-in narrowing, missing-coverage errors and unreachable-arm warnings; see the RFC detail page for the remaining compiler/editor boundaries.

In-Tag Comments

Enable inTagComment to parse compile-time-only // comments inside opening tag attribute lists from Vue RFC #831:

export default defineConfig({
  experimentals: {
    inTagComment: true,
  },
});
<template>
  <LegacySelect
    :options="options"
    // @vue-expect-error legacy API accepts string IDs
    :selected-id="selectedId"
  />
</template>

The comment may appear after the tag name, between complete attributes or directives, or after the last attribute before > or />. Put the closing delimiter on the next line after a trailing //, because same-line > or /> is consumed as comment text. Vize stores the source text on the template root comments list for tooling; it is not emitted as a child node and it does not generate runtime output. // inside an attribute value remains ordinary text:

<template>
  <a href="https://example.test/path//segment">Link</a>
</template>

The normal template comments compiler option does not enable or disable this syntax. If the flag is off, the parser reports the unexpected solidus instead of accepting the opening tag. This syntax is intended for SFC/tooling pipelines that own template parsing; do not rely on it in raw in-DOM templates that must be parsed first by a browser HTML parser.

Self Component

Enable selfComponent to reserve the exact <Self> tag for recursive component references from Vue RFC #833:

export default defineConfig({
  experimentals: {
    selfComponent: true,
  },
});
<script setup lang="ts">
defineProps<{
  node: { id: string; children: Array<{ id: string; children: unknown[] }> };
}>();
</script>

<template>
  <li>
    {{ node.id }}
    <ul v-if="node.children.length">
      <Self v-for="child in node.children" :key="child.id" :node="child" />
    </ul>
  </li>
</template>

In SFC builds, Vize resolves <Self> to the current component name from compiler metadata or from the filename. Direct template APIs can pass componentName when no SFC filename is available. Without selfComponent, <Self> stays a normal component tag and any local component binding named Self keeps its ordinary meaning. With the flag on, exact <Self> shadows local, imported, and global components named Self.

The flag is threaded through DOM, SSR, and Vapor compilation. When enabled and a component name is available, emitted component resolution uses the current component name and Vue's maybe-self-reference hint. The reserved tag is exact and case-sensitive: <Self> is special, while <self> is not.

Strict Slot Children

The Playground exposes this flag under Experimental in Canon. Its sample provides an input to a button-only slot. With TypeScript (Monaco) enabled, the generated child contract produces a type diagnostic; the Virtual TS tab also shows the assertion. This is a single-file browser check, not the CLI's project-wide Corsa checker.

Enable strictSlotChildren to add virtual-TypeScript checks for slot children from Vue RFC #734. This flag is for vize check, the LSP/type-check project APIs, and declaration-aware virtual code; ordinary template compilation does not add runtime behavior.

export default defineConfig({
  experimentals: {
    strictSlotChildren: true,
  },
});

A component can expose a structural slot contract through __vizeSlots:

import TabItem from "./TabItem.vue";

declare const Tabs: {
  readonly __vizeSlots?: {
    default: () => (typeof TabItem)[];
    footer: () => [HTMLButtonElement];
  };
};

Vize compares provided children with that contract:

<template>
  <Tabs>
    <TabItem />
    <div />

    <template #footer>
      <button>Save</button>
    </template>
  </Tabs>
</template>

With the flag enabled, Vize synthesizes slot child assertions so TypeScript can compare the provided nodes with the slot return contract. Native DOM children map to their corresponding HTML*Element, component children map to typeof ComponentRef, and text or interpolation children map to string. Named slots and default slots are both checked. defineSlots contracts export the same marker shape. Comments and structural wrapper nodes are skipped when they do not contribute a child value.

Because this is a tooling constraint, failures appear as TypeScript diagnostics from vize check or the language server. Builds that only compile Vue templates will not enforce it. Slot contracts using open index signatures or any degrade to TypeScript's permissive behavior.

Server Script

Enable serverScript only for hosts that explicitly test the server-script compiler experiment:

export default defineConfig({
  experimentals: {
    serverScript: true,
  },
});

The flag resolves to the native experimentalServerScript compiler field; when it is omitted, false, or null, the native field stays disabled. Some runtimes keep this switch reserved until their compiler stage supports the experiment, so application code should not rely on it as a stable syntax contract. Prefer feature-specific host documentation when a framework starts consuming this switch.

Vapor

Enable vapor to route SFC compilation through experimental Vapor backend support when stable compiler.vapor and direct vize({ vapor }) are not set:

export default defineConfig({
  experimentals: {
    vapor: true,
  },
});

Prefer compiler.vapor or direct vize({ vapor }) when a project has made Vapor a stable build choice. Those stable/direct values win even when experimentals.vapor is true, so an explicit vize({ vapor: false }) is a per-plugin opt-out. Keep experimentals.vapor for trial runs, compatibility probes, and tests that need to prove the experimental fallback path still works.

JSX Vapor

Enable jsxVapor to default JSX and TSX files to Vapor output when stable compiler.jsxMode and direct vize({ jsxMode }) are not set:

export default defineConfig({
  experimentals: {
    jsxVapor: true,
  },
});

Prefer compiler.jsxMode: "vapor" or direct vize({ jsxMode: "vapor" }) for a stable project-wide backend choice. Those stable/direct values win over experimentals.jsxVapor, and jsxMode: "vdom" is the explicit opt-out. Component-level "use vue:vapor" and "use vue:vdom" directives still describe the source file's own intent and can override the default mode for that file.

Direct API Fields

Most applications should configure experimentals. Lower-level integrations can pass resolved boolean fields to compile, compileVapor, parseTemplate, compileSfc, compileSfcBatch, or compileSfcBatchWithResults. Those fields do not understand aliases, {} switch objects, or shared-config precedence; see Experimentals Reference.