Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions apps/web/content/docs/2.components/01.props.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,15 +140,17 @@ const { name, price, stock, available = true } = defineProps<ProductTileProps>()
```svelte [ProductTile.svelte]
<svelte:options runes={true} preserveWhitespace={false} />

<script lang="ts">
import type { ClassValue } from "svelte/elements";

<script lang="ts" module>
export interface ProductTileProps {
name: string;
price: number;
stock: number;
available?: boolean;
}
</script>

<script lang="ts">
import type { ClassValue } from "svelte/elements";

type Props = ProductTileProps & { class?: ClassValue; style?: string };

Expand Down Expand Up @@ -526,14 +528,16 @@ defineProps<ArticleSkeletonProps>();
```svelte [ArticleSkeleton.svelte]
<svelte:options runes={true} preserveWhitespace={false} />

<script lang="ts">
import type { ClassValue } from "svelte/elements";

<script lang="ts" module>
export interface ArticleSkeletonProps {
title: string;
summary?: string;
minutes?: number;
}
</script>

<script lang="ts">
import type { ClassValue } from "svelte/elements";

type Props = ArticleSkeletonProps & { class?: ClassValue; style?: string };

Expand Down Expand Up @@ -982,9 +986,7 @@ const {
```svelte [Notice.svelte]
<svelte:options runes={true} preserveWhitespace={false} />

<script lang="ts">
import type { ClassValue } from "svelte/elements";

<script lang="ts" module>
export interface NoticeProps {
message: string;
title?: string;
Expand All @@ -994,6 +996,10 @@ const {
tags?: string[];
author?: { name: string };
}
</script>

<script lang="ts">
import type { ClassValue } from "svelte/elements";

type Props = NoticeProps & { class?: ClassValue; style?: string };

Expand Down
16 changes: 10 additions & 6 deletions apps/web/content/docs/2.components/02.templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -625,14 +625,16 @@ const { status, carrier = undefined, eta = undefined } = defineProps<OrderStatus
```svelte [OrderStatus.svelte]
<svelte:options runes={true} preserveWhitespace={false} />

<script lang="ts">
import type { ClassValue } from "svelte/elements";

<script lang="ts" module>
export interface OrderStatusProps {
status: "pending" | "shipped" | "delivered" | "cancelled";
carrier?: string;
eta?: string;
}
</script>

<script lang="ts">
import type { ClassValue } from "svelte/elements";

type Props = OrderStatusProps & { class?: ClassValue; style?: string };

Expand Down Expand Up @@ -989,13 +991,15 @@ const { title, steps } = defineProps<StepListProps>();
```svelte [StepList.svelte]
<svelte:options runes={true} preserveWhitespace={false} />

<script lang="ts">
import type { ClassValue } from "svelte/elements";

<script lang="ts" module>
export interface StepListProps {
title: string;
steps: string[];
}
</script>

<script lang="ts">
import type { ClassValue } from "svelte/elements";

type Props = StepListProps & { class?: ClassValue; style?: string };

Expand Down
8 changes: 5 additions & 3 deletions apps/web/content/docs/2.components/03.attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -506,16 +506,18 @@ const { label, size, primary, busy = false, badge } = defineProps<ActionButtonPr
```svelte [ActionButton.svelte]
<svelte:options runes={true} preserveWhitespace={false} />

<script lang="ts">
import type { ClassValue } from "svelte/elements";

<script lang="ts" module>
export interface ActionButtonProps {
label: string;
size: "small" | "medium" | "large";
primary: boolean;
busy?: boolean;
badge: number;
}
</script>

<script lang="ts">
import type { ClassValue } from "svelte/elements";

type Props = ActionButtonProps & { class?: ClassValue; style?: string };

Expand Down
8 changes: 5 additions & 3 deletions apps/web/content/docs/2.components/04.svg.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,14 +160,16 @@ const { label, size = 24, colour = "#1f6feb" } = defineProps<StatusIconProps>();
```svelte [StatusIcon.svelte]
<svelte:options runes={true} preserveWhitespace={false} />

<script lang="ts">
import type { ClassValue } from "svelte/elements";

<script lang="ts" module>
export interface StatusIconProps {
label: string;
size?: number;
colour?: string;
}
</script>

<script lang="ts">
import type { ClassValue } from "svelte/elements";

type Props = StatusIconProps & { class?: ClassValue; style?: string };

Expand Down
10 changes: 6 additions & 4 deletions apps/web/content/docs/2.components/05.state.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,16 @@ function increment() {
```svelte [Counter.svelte]
<svelte:options runes={true} preserveWhitespace={false} />

<script lang="ts">
import { untrack } from "svelte";
import type { ClassValue } from "svelte/elements";

<script lang="ts" module>
export interface CounterProps {
initial?: number;
step?: number;
}
</script>

<script lang="ts">
import { untrack } from "svelte";
import type { ClassValue } from "svelte/elements";

type Props = CounterProps & {
onchange?: (value: number) => void;
Expand Down
8 changes: 5 additions & 3 deletions apps/web/content/docs/2.components/06.events.md
Original file line number Diff line number Diff line change
Expand Up @@ -1997,9 +1997,7 @@ function archive() {
```svelte [FileRow.svelte]
<svelte:options runes={true} preserveWhitespace={false} />

<script lang="ts">
import type { ClassValue } from "svelte/elements";

<script lang="ts" module>
export interface FileInfo {
path: string;
size: number;
Expand All @@ -2009,6 +2007,10 @@ function archive() {
path: string;
size: number;
}
</script>

<script lang="ts">
import type { ClassValue } from "svelte/elements";

type Props = FileRowProps & {
onrefresh?: () => void;
Expand Down
20 changes: 12 additions & 8 deletions apps/web/content/docs/2.components/08.lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,16 @@ onMounted(() => {
```svelte [ArticlePreview.svelte]
<svelte:options runes={true} preserveWhitespace={false} />

<script lang="ts">
import { onMount } from "svelte";
import type { ClassValue } from "svelte/elements";

<script lang="ts" module>
export interface ArticlePreviewProps {
title: string;
text: string;
}
</script>

<script lang="ts">
import { onMount } from "svelte";
import type { ClassValue } from "svelte/elements";

type Props = ArticlePreviewProps & {
onready?: (characters: number) => void;
Expand Down Expand Up @@ -704,14 +706,16 @@ onUnmounted(() => {
```svelte [AutoRefresh.svelte]
<svelte:options runes={true} preserveWhitespace={false} />

<script lang="ts">
import { onMount } from "svelte";
import type { ClassValue } from "svelte/elements";

<script lang="ts" module>
export interface AutoRefreshProps {
/** Milliseconds between two refreshes. */
interval: number;
}
</script>

<script lang="ts">
import { onMount } from "svelte";
import type { ClassValue } from "svelte/elements";

type Props = AutoRefreshProps & {
onrefresh?: (count: number) => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -544,13 +544,15 @@ const hintId = `uf-id-${useId()}`;
```svelte [EmailField.svelte]
<svelte:options runes={true} preserveWhitespace={false} />

<script lang="ts">
import type { ClassValue } from "svelte/elements";

<script lang="ts" module>
export interface EmailFieldProps {
label: string;
hint: string;
}
</script>

<script lang="ts">
import type { ClassValue } from "svelte/elements";

type Props = EmailFieldProps & { class?: ClassValue; style?: string };

Expand Down
8 changes: 5 additions & 3 deletions apps/web/content/docs/2.components/10.child-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,13 +221,15 @@ function clear(label: string) {
```svelte [Field.svelte]
<svelte:options runes={true} preserveWhitespace={false} />

<script lang="ts">
import type { ClassValue } from "svelte/elements";

<script lang="ts" module>
export interface FieldProps {
label: string;
tone?: "info" | "warn";
}
</script>

<script lang="ts">
import type { ClassValue } from "svelte/elements";

type Props = FieldProps & {
onclear?: (label: string) => void;
Expand Down
7 changes: 5 additions & 2 deletions packages/codegen/src/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,12 @@ function formatErrors(errors: readonly { message: string }[]): string {
* A `<script>` block whose code is TypeScript, starting a line (the top level of a `.vue` or
* `.svelte` file: printed markup never starts a line with `<script`, as the analyser rejects
* the element and text escapes `<`). Its code runs to the first `</script`, where Vue's and
* Svelte's parsers end it too; the targets write `<\/script` inside copied literals.
* Svelte's parsers end it too; the targets write `<\/script` inside copied literals. A quoted
* attribute value may hold a `>`, as a generic component's does (`generics="T extends
* Array<string>"`, ADR-0068), which ends no tag.
*/
const SCRIPT_BLOCK = /^<script\b[^>]*\blang="ts"[^>]*>/gm;
const SCRIPT_BLOCK =
/^<script\b(?:[^>"']|"[^"]*"|'[^']*')*?\blang="ts"(?:[^>"']|"[^"]*"|'[^']*')*>/gm;
const SCRIPT_END = /<\/script/i;

/** Astro's frontmatter fence: `---` alone on a line. */
Expand Down
11 changes: 11 additions & 0 deletions packages/codegen/test/format.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ async function formatted(path: string, contents: string): Promise<string> {
const line = `<p>${"<a>link</a> ".repeat(12)}</p>`;

describe("formatOutput on script blocks", () => {
// A generic component's attribute (ADR-0068) holds a `>` inside its quotes, which ends no tag.
it.each([
["Pill.svelte", '<script lang="ts" generics="T extends Array<string>">', " "],
["Pill.vue", `<script setup lang="ts" generic='T extends Record<string, "a" | "b">'>`, ""],
])("finds a script block whose attribute holds a `>`, in %s", async (path, open, indent) => {
const contents = [open, "let value:T|null=null", "</script>", "", "<p></p>", ""].join("\n");
expect(await formatted(path, contents)).toBe(
[open, `${indent}let value: T | null = null;`, "</script>", "", "<p></p>", ""].join("\n"),
);
});

it('formats a Vue `<script setup lang="ts">` as TypeScript, unindented, and leaves the template', async () => {
const contents = [
'<script setup lang="ts">',
Expand Down
23 changes: 20 additions & 3 deletions packages/target-svelte/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@ The Svelte 5 target of the Unframework compiler: runes-mode components. Every co
declares `<svelte:options runes={true} preserveWhitespace={false} />`, so one without runes never
compiles in legacy mode and the markup's layout holds whatever the consumer's whitespace option.

A component is that line, an instance script when it takes props or has a setup, and its markup
(plan §6):
A component is that line, a module script when it exports a type or declares injection keys, an
instance script when it takes props or has a setup, and its markup (plan §6):

```svelte
<svelte:options runes={true} preserveWhitespace={false} />

<script lang="ts">
<script lang="ts" module>
export interface BadgeProps {
label: string;
tone?: "info" | "warn";
}
</script>

<script lang="ts">
let { label, tone = "info" }: BadgeProps = $props();
</script>

Expand All @@ -32,6 +34,21 @@ A component is that line, an instance script when it takes props or has a setup,
(`$`-prefixed) is named `props`. Script lines are indented, but for lines inside a multi-line
literal, which the target finds by parsing the code. `</script` in copied code is written
`<\/script`, as a guard: the analyser rejects it.
- **Types** (ADR-0067, ADR-0068). The types a component reads from another file are imported by
name, under the source's names: a `.ts` module's or a package's from the specifier as written,
`import type { Tone } from "./types"`, and a child's from its output, beside the child,
`import Field, { type FieldProps } from "./Field.svelte"`. The output of the module's main
component also writes the module's type exports: an exported type no component reaches, and a
re-export, `export type { Tone } from "./types"`. An output that exports a type declares the
types it copies and imports in `<script lang="ts" module>`, which the instance script reads, so
a consumer imports them from the component's file:
`import type { BadgeProps } from "./Badge.svelte"`. A generic component writes its type
parameters in the instance script's `generics`, `<script lang="ts" generics="T extends string">`.
An attribute cannot hold its own quote, so every string literal in them is double-quoted, a
template literal stays in backticks, a `'` inside either is written `\u0027`, the same type, and
the attribute is single-quoted where the text holds a `"`:
`generics='T extends "don\u0027t" | "b"'`. A scoped slot's props that name a type
parameter are `Snippet<[{ item: T }]>`; both cells are native.
- **Setup** (ADR-0045 to ADR-0049), in source order after `$props()`, with what it imports from
`svelte` and `svelte/events` (each name claimed beside the source's):

Expand Down
Loading
Loading