@samizdata/create-mozaic 0.0.0-stage → 0.2.0
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/LICENSE +674 -0
- package/README.md +19 -2
- package/bin/create-mozaic.js +10 -0
- package/package.json +32 -3
- package/src/index.js +157 -0
- package/template/.claude/skills/mozaic-chart/SKILL.md +27 -0
- package/template/.github/workflows/deploy.yml +38 -0
- package/template/AGENTS.md +32 -0
- package/template/CLAUDE.md +1 -0
- package/template/README.md +57 -0
- package/template/_gitignore +13 -0
- package/template/analysis/R/01_prepare.R +21 -0
- package/template/analysis/py/prepare.py +37 -0
- package/template/data/processed/example.csv +31 -0
- package/template/data/raw/.gitkeep +0 -0
- package/template/justfile +25 -0
- package/template/mozaic.config.ts +15 -0
- package/template/package.json +41 -0
- package/template/pyproject.toml +6 -0
- package/template/src/app.d.ts +13 -0
- package/template/src/app.html +12 -0
- package/template/src/charts/example/Chart.svelte +100 -0
- package/template/src/charts/example/data.ts +12 -0
- package/template/src/charts/example/messages.ts +51 -0
- package/template/src/charts/index.ts +27 -0
- package/template/src/lib/theme.ts +11 -0
- package/template/src/routes/+layout.svelte +5 -0
- package/template/src/routes/+layout.ts +3 -0
- package/template/src/routes/+page.svelte +120 -0
- package/template/src/routes/embed/[chart]/[locale]/+page.svelte +20 -0
- package/template/src/routes/embed/[chart]/[locale]/+page.ts +14 -0
- package/template/src/routes/embed/manifest.json/+server.ts +26 -0
- package/template/src/routes/embed.js/+server.ts +9 -0
- package/template/tsconfig.json +7 -0
- package/template/vite.config.ts +18 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { scaleLinear } from 'd3-scale';
|
|
3
|
+
import { extent, max } from 'd3-array';
|
|
4
|
+
import { Chart, Header, Title, Subtitle, Footer, Notes, Source, DataTable } from '@samizdata/mozaic/frame';
|
|
5
|
+
import { Plot, Svg, Html } from '@samizdata/mozaic/core';
|
|
6
|
+
import { Line, LineEndLabels, Points } from '@samizdata/mozaic/marks';
|
|
7
|
+
import { AxisX, AxisY, GridY, Legend } from '@samizdata/mozaic/guides';
|
|
8
|
+
import { PointerArea, Tooltip } from '@samizdata/mozaic/interactive';
|
|
9
|
+
import { createLocale } from '@samizdata/mozaic/locale';
|
|
10
|
+
import type { ChartProps } from '../index';
|
|
11
|
+
import { messages } from './messages';
|
|
12
|
+
import { regions, rows, type Row } from './data';
|
|
13
|
+
|
|
14
|
+
let { style, locale, medium = 'web', width }: ChartProps = $props();
|
|
15
|
+
|
|
16
|
+
const t = $derived(messages[locale]);
|
|
17
|
+
const fmt = $derived(createLocale(locale));
|
|
18
|
+
const theme = $derived(style);
|
|
19
|
+
// Colour follows the region, in palette order.
|
|
20
|
+
const color = $derived(Object.fromEntries(regions.map((r, i) => [r, theme.palettes.categorical[i]])));
|
|
21
|
+
|
|
22
|
+
const years = [...new Set(rows.map((d) => d.year))];
|
|
23
|
+
const x = scaleLinear().domain(extent(years) as [number, number]);
|
|
24
|
+
const y = scaleLinear()
|
|
25
|
+
.domain([0, max(rows, (d) => d.value) ?? 1])
|
|
26
|
+
.nice();
|
|
27
|
+
const series = regions.map((region) => ({ region, values: rows.filter((d) => d.region === region) }));
|
|
28
|
+
const ends = series.map((s) => s.values[s.values.length - 1]);
|
|
29
|
+
|
|
30
|
+
let hoverYear = $state<number | null>(null);
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<Chart {style} {locale} {medium} {width} alt={t.alt}>
|
|
34
|
+
<Header>
|
|
35
|
+
<Title>{t.title}</Title>
|
|
36
|
+
<Subtitle>{t.subtitle}</Subtitle>
|
|
37
|
+
<Legend shape="line" items={regions.map((r) => ({ label: t.regions[r] ?? r, color: color[r] }))} />
|
|
38
|
+
</Header>
|
|
39
|
+
|
|
40
|
+
<Plot
|
|
41
|
+
{x}
|
|
42
|
+
{y}
|
|
43
|
+
height={({ breakpoint }) => (breakpoint === 's' ? 240 : 320)}
|
|
44
|
+
padding={{ left: 36, right: 80, top: 12, bottom: 28 }}
|
|
45
|
+
>
|
|
46
|
+
{#snippet children(plot)}
|
|
47
|
+
<Svg>
|
|
48
|
+
<GridY ticks={5} />
|
|
49
|
+
<AxisY ticks={5} />
|
|
50
|
+
<AxisX tickValues={years.filter((_, i) => plot.breakpoint !== 's' || i % 3 === 0)} format={String} />
|
|
51
|
+
{#each series as s (s.region)}
|
|
52
|
+
<Line data={s.values} x={(d: Row) => d.year} y={(d: Row) => d.value} stroke={color[s.region]} />
|
|
53
|
+
{/each}
|
|
54
|
+
<LineEndLabels
|
|
55
|
+
data={ends}
|
|
56
|
+
x={(d) => d.year}
|
|
57
|
+
y={(d) => d.value}
|
|
58
|
+
text={(d) => t.regions[d.region] ?? d.region}
|
|
59
|
+
color={(d) => color[d.region]}
|
|
60
|
+
/>
|
|
61
|
+
{#if hoverYear !== null}
|
|
62
|
+
<Points
|
|
63
|
+
data={rows.filter((d) => d.year === hoverYear)}
|
|
64
|
+
x={(d) => d.year}
|
|
65
|
+
y={(d) => d.value}
|
|
66
|
+
fill={(d) => color[d.region]}
|
|
67
|
+
/>
|
|
68
|
+
{/if}
|
|
69
|
+
<PointerArea
|
|
70
|
+
onmove={({ x: px }) => (hoverYear = Math.round(plot.x.invert?.(px) ?? years[0]))}
|
|
71
|
+
onleave={() => (hoverYear = null)}
|
|
72
|
+
/>
|
|
73
|
+
</Svg>
|
|
74
|
+
<Html>
|
|
75
|
+
{#if hoverYear !== null}
|
|
76
|
+
<Tooltip x={plot.x(hoverYear) ?? 0} y={plot.height / 2}>
|
|
77
|
+
<strong>{hoverYear}</strong>
|
|
78
|
+
{#each rows.filter((d) => d.year === hoverYear).toSorted((a, b) => b.value - a.value) as d (d.region)}
|
|
79
|
+
<div>{t.regions[d.region] ?? d.region}: <strong>{fmt.number(d.value)}</strong></div>
|
|
80
|
+
{/each}
|
|
81
|
+
</Tooltip>
|
|
82
|
+
{/if}
|
|
83
|
+
</Html>
|
|
84
|
+
{/snippet}
|
|
85
|
+
</Plot>
|
|
86
|
+
|
|
87
|
+
<Footer>
|
|
88
|
+
<Notes>{t.notes}</Notes>
|
|
89
|
+
<Source>{t.source}</Source>
|
|
90
|
+
</Footer>
|
|
91
|
+
<DataTable
|
|
92
|
+
data={rows}
|
|
93
|
+
columns={[
|
|
94
|
+
{ key: 'region', label: t.region, value: (d: Row) => t.regions[d.region] ?? d.region },
|
|
95
|
+
{ key: 'year', label: t.year, value: (d: Row) => d.year, format: String },
|
|
96
|
+
{ key: 'value', label: t.value, value: (d: Row) => d.value }
|
|
97
|
+
]}
|
|
98
|
+
maxRows={10}
|
|
99
|
+
/>
|
|
100
|
+
</Chart>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { autoType, csvParse } from 'd3-dsv';
|
|
2
|
+
// The analysis scripts write this file (see analysis/). Vite inlines it at build time.
|
|
3
|
+
import raw from '../../../data/processed/example.csv?raw';
|
|
4
|
+
|
|
5
|
+
export interface Row {
|
|
6
|
+
year: number;
|
|
7
|
+
region: string;
|
|
8
|
+
value: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const rows = csvParse(raw, autoType) as unknown as Row[];
|
|
12
|
+
export const regions = [...new Set(rows.map((d) => d.region))];
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { LocaleCode } from '@samizdata/mozaic/locale';
|
|
2
|
+
|
|
3
|
+
export const messages: Record<
|
|
4
|
+
LocaleCode,
|
|
5
|
+
{
|
|
6
|
+
title: string;
|
|
7
|
+
subtitle: string;
|
|
8
|
+
alt: string;
|
|
9
|
+
notes: string;
|
|
10
|
+
source: string;
|
|
11
|
+
year: string;
|
|
12
|
+
value: string;
|
|
13
|
+
region: string;
|
|
14
|
+
/** Display names for the region codes in the data. */
|
|
15
|
+
regions: Record<string, string>;
|
|
16
|
+
}
|
|
17
|
+
> = {
|
|
18
|
+
en: {
|
|
19
|
+
title: 'Example: values rose in every region',
|
|
20
|
+
subtitle: 'Value by region, 2015–2024 (example data)',
|
|
21
|
+
alt: 'Line chart of an example value for three regions from 2015 to 2024. All three rise; the capital rises fastest.',
|
|
22
|
+
notes: 'Example data generated by analysis/. Replace it with your own.',
|
|
23
|
+
source: 'Example',
|
|
24
|
+
year: 'Year',
|
|
25
|
+
value: 'Value',
|
|
26
|
+
region: 'Region',
|
|
27
|
+
regions: { North: 'North', South: 'South', Capital: 'Capital' }
|
|
28
|
+
},
|
|
29
|
+
ro: {
|
|
30
|
+
title: 'Exemplu: valorile au crescut în toate regiunile',
|
|
31
|
+
subtitle: 'Valoarea pe regiuni, 2015–2024 (date de exemplu)',
|
|
32
|
+
alt: 'Grafic liniar al unei valori de exemplu pentru trei regiuni, 2015–2024. Toate cresc; capitala crește cel mai repede.',
|
|
33
|
+
notes: 'Date de exemplu generate de analysis/. Înlocuiți-le cu datele proprii.',
|
|
34
|
+
source: 'Exemplu',
|
|
35
|
+
year: 'Anul',
|
|
36
|
+
value: 'Valoarea',
|
|
37
|
+
region: 'Regiunea',
|
|
38
|
+
regions: { North: 'Nord', South: 'Sud', Capital: 'Capitala' }
|
|
39
|
+
},
|
|
40
|
+
ru: {
|
|
41
|
+
title: 'Пример: значения выросли во всех регионах',
|
|
42
|
+
subtitle: 'Значение по регионам, 2015–2024 (пример данных)',
|
|
43
|
+
alt: 'Линейный график примерного значения для трёх регионов, 2015–2024. Все растут; быстрее всех столица.',
|
|
44
|
+
notes: 'Пример данных, созданный скриптами analysis/. Замените своими данными.',
|
|
45
|
+
source: 'Пример',
|
|
46
|
+
year: 'Год',
|
|
47
|
+
value: 'Значение',
|
|
48
|
+
region: 'Регион',
|
|
49
|
+
regions: { North: 'Север', South: 'Юг', Capital: 'Столица' }
|
|
50
|
+
}
|
|
51
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Component } from 'svelte';
|
|
2
|
+
import type { LocaleCode } from '@samizdata/mozaic/locale';
|
|
3
|
+
import type { Medium, Theme } from '@samizdata/mozaic/styles';
|
|
4
|
+
import Example from './example/Chart.svelte';
|
|
5
|
+
import { messages as exampleMessages } from './example/messages';
|
|
6
|
+
|
|
7
|
+
/** Props every chart component accepts. */
|
|
8
|
+
export interface ChartProps {
|
|
9
|
+
style: Theme;
|
|
10
|
+
locale: LocaleCode;
|
|
11
|
+
medium?: Medium;
|
|
12
|
+
width?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ChartEntry {
|
|
16
|
+
/** URL segment: embed/<slug>/<locale>/ */
|
|
17
|
+
slug: string;
|
|
18
|
+
title: (locale: LocaleCode) => string;
|
|
19
|
+
component: Component<ChartProps>;
|
|
20
|
+
/** Print widths to export (names from the style's print.widths). */
|
|
21
|
+
print: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Every chart in this project. Add new charts here. */
|
|
25
|
+
export const charts: ChartEntry[] = [
|
|
26
|
+
{ slug: 'example', title: (locale) => exampleMessages[locale].title, component: Example, print: ['column', 'double'] }
|
|
27
|
+
];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { extendTheme } from '@samizdata/mozaic/styles';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The house style for every chart in this project. It starts from mozaic's
|
|
5
|
+
* neutral `base`; change tokens, palettes and the logo here. To use an outlet's
|
|
6
|
+
* style instead, copy its brand's `datavis/mozaic/` folder to `src/lib/brand/`
|
|
7
|
+
* (do not edit the copy) and re-export it:
|
|
8
|
+
*
|
|
9
|
+
* export { samizdata as theme } from './brand/theme.js';
|
|
10
|
+
*/
|
|
11
|
+
export const theme = extendTheme({ name: '__PROJECT_NAME__', label: '__PROJECT_TITLE__' });
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<!-- Preview page: every chart in every language, with embed codes and downloads. Not meant for readers. -->
|
|
2
|
+
<script lang="ts">
|
|
3
|
+
import { embedCode } from '@samizdata/mozaic/embed';
|
|
4
|
+
import { downloadPng, downloadSvg } from '@samizdata/mozaic/export';
|
|
5
|
+
import { mm } from '@samizdata/mozaic/core';
|
|
6
|
+
import type { LocaleCode } from '@samizdata/mozaic/locale';
|
|
7
|
+
import { charts } from '../charts';
|
|
8
|
+
import { config } from '../../mozaic.config';
|
|
9
|
+
import { theme } from '../lib/theme';
|
|
10
|
+
|
|
11
|
+
let locale = $state<LocaleCode>(config.locales[0]);
|
|
12
|
+
let width = $state(640);
|
|
13
|
+
let medium = $state<'web' | 'print'>('web');
|
|
14
|
+
const frames: Record<string, HTMLElement> = $state({});
|
|
15
|
+
|
|
16
|
+
const figure = (slug: string) => frames[slug]?.querySelector('.mz-chart') as HTMLElement;
|
|
17
|
+
const url = (slug: string) => `${config.baseUrl || '/'}embed/${slug}/${locale}/`;
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<svelte:head><title>{config.title} · preview</title></svelte:head>
|
|
21
|
+
|
|
22
|
+
<main>
|
|
23
|
+
<h1>{config.title}</h1>
|
|
24
|
+
<p class="meta">Style: <strong>{theme.label}</strong> · Project: <code>{config.project}</code></p>
|
|
25
|
+
<div class="controls">
|
|
26
|
+
<label
|
|
27
|
+
>Language <select bind:value={locale}
|
|
28
|
+
>{#each config.locales as l (l)}<option value={l}>{l}</option>{/each}</select
|
|
29
|
+
></label
|
|
30
|
+
>
|
|
31
|
+
<label
|
|
32
|
+
>Medium <select bind:value={medium}><option value="web">web</option><option value="print">print</option></select
|
|
33
|
+
></label
|
|
34
|
+
>
|
|
35
|
+
{#if medium === 'web'}
|
|
36
|
+
<label>Width {width}px <input type="range" min="300" max="1000" step="10" bind:value={width} /></label>
|
|
37
|
+
{:else}
|
|
38
|
+
<label
|
|
39
|
+
>Print width <select bind:value={width}
|
|
40
|
+
>{#each Object.entries(theme.print.widths) as [name, w] (name)}<option value={Math.round(mm(w))}
|
|
41
|
+
>{name} ({w} mm)</option
|
|
42
|
+
>{/each}</select
|
|
43
|
+
></label
|
|
44
|
+
>
|
|
45
|
+
{/if}
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
{#each charts as chart (chart.slug)}
|
|
49
|
+
<section>
|
|
50
|
+
<h2>{chart.title(locale)} <a href="embed/{chart.slug}/{locale}/">embed page</a></h2>
|
|
51
|
+
<div class="frame" style:width="{width}px" bind:this={frames[chart.slug]}>
|
|
52
|
+
<chart.component style={theme} {locale} {medium} width={medium === 'print' ? width : undefined} />
|
|
53
|
+
</div>
|
|
54
|
+
<div class="actions">
|
|
55
|
+
<button onclick={() => downloadSvg(figure(chart.slug), `${chart.slug}-${locale}.svg`)}>Download SVG</button>
|
|
56
|
+
<button onclick={() => downloadPng(figure(chart.slug), `${chart.slug}-${locale}.png`)}>Download PNG</button>
|
|
57
|
+
</div>
|
|
58
|
+
<textarea readonly rows="3"
|
|
59
|
+
>{embedCode({
|
|
60
|
+
src: url(chart.slug),
|
|
61
|
+
title: chart.title(locale),
|
|
62
|
+
scriptSrc: `${config.baseUrl || '/'}embed.js`
|
|
63
|
+
})}</textarea
|
|
64
|
+
>
|
|
65
|
+
</section>
|
|
66
|
+
{/each}
|
|
67
|
+
</main>
|
|
68
|
+
|
|
69
|
+
<style>
|
|
70
|
+
main {
|
|
71
|
+
max-width: 1040px;
|
|
72
|
+
margin: 0 auto;
|
|
73
|
+
padding: 1.5rem 16px 4rem;
|
|
74
|
+
font-family: system-ui, sans-serif;
|
|
75
|
+
}
|
|
76
|
+
.meta {
|
|
77
|
+
color: #555;
|
|
78
|
+
}
|
|
79
|
+
.controls {
|
|
80
|
+
display: flex;
|
|
81
|
+
flex-wrap: wrap;
|
|
82
|
+
gap: 1rem 1.5rem;
|
|
83
|
+
padding: 0.75rem 1rem;
|
|
84
|
+
border: 1px solid #ddd;
|
|
85
|
+
border-radius: 6px;
|
|
86
|
+
}
|
|
87
|
+
label {
|
|
88
|
+
display: flex;
|
|
89
|
+
flex-direction: column;
|
|
90
|
+
font-size: 0.8125rem;
|
|
91
|
+
font-weight: 600;
|
|
92
|
+
}
|
|
93
|
+
section {
|
|
94
|
+
margin-top: 2.5rem;
|
|
95
|
+
}
|
|
96
|
+
h2 {
|
|
97
|
+
font-size: 1rem;
|
|
98
|
+
}
|
|
99
|
+
h2 a {
|
|
100
|
+
font-size: 0.8125rem;
|
|
101
|
+
font-weight: 400;
|
|
102
|
+
}
|
|
103
|
+
.frame {
|
|
104
|
+
max-width: 100%;
|
|
105
|
+
padding: 16px;
|
|
106
|
+
border: 1px solid #ddd;
|
|
107
|
+
border-radius: 6px;
|
|
108
|
+
box-sizing: border-box;
|
|
109
|
+
}
|
|
110
|
+
.actions {
|
|
111
|
+
display: flex;
|
|
112
|
+
gap: 0.5rem;
|
|
113
|
+
margin: 0.5rem 0;
|
|
114
|
+
}
|
|
115
|
+
textarea {
|
|
116
|
+
width: 100%;
|
|
117
|
+
font-family: ui-monospace, monospace;
|
|
118
|
+
font-size: 0.75rem;
|
|
119
|
+
}
|
|
120
|
+
</style>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!-- One embeddable chart. Query parameters (?medium=print&width=208) are read after hydration. -->
|
|
2
|
+
<script lang="ts">
|
|
3
|
+
import { Embed, useEmbedParams } from '@samizdata/mozaic/embed';
|
|
4
|
+
import { charts } from '../../../../charts';
|
|
5
|
+
import { theme } from '../../../../lib/theme';
|
|
6
|
+
import type { PageProps } from './$types';
|
|
7
|
+
|
|
8
|
+
let { data }: PageProps = $props();
|
|
9
|
+
const params = useEmbedParams();
|
|
10
|
+
const chart = $derived(charts.find((c) => c.slug === data.slug)!);
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<svelte:head>
|
|
14
|
+
<title>{chart.title(data.locale)}</title>
|
|
15
|
+
<meta name="robots" content="noindex" />
|
|
16
|
+
</svelte:head>
|
|
17
|
+
|
|
18
|
+
<Embed>
|
|
19
|
+
<chart.component style={theme} locale={data.locale} medium={params.medium} width={params.width} />
|
|
20
|
+
</Embed>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { error } from '@sveltejs/kit';
|
|
2
|
+
import type { LocaleCode } from '@samizdata/mozaic/locale';
|
|
3
|
+
import { charts } from '../../../../charts';
|
|
4
|
+
import { config } from '../../../../../mozaic.config';
|
|
5
|
+
import type { EntryGenerator, PageLoad } from './$types';
|
|
6
|
+
|
|
7
|
+
export const entries: EntryGenerator = () =>
|
|
8
|
+
charts.flatMap((chart) => config.locales.map((locale) => ({ chart: chart.slug, locale })));
|
|
9
|
+
|
|
10
|
+
export const load: PageLoad = ({ params }) => {
|
|
11
|
+
const chart = charts.find((c) => c.slug === params.chart);
|
|
12
|
+
if (!chart || !config.locales.includes(params.locale as LocaleCode)) error(404, 'No such chart');
|
|
13
|
+
return { slug: chart.slug, locale: params.locale as LocaleCode };
|
|
14
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { json } from '@sveltejs/kit';
|
|
2
|
+
import type { EmbedManifest } from '@samizdata/mozaic/embed';
|
|
3
|
+
import { charts } from '../../../charts';
|
|
4
|
+
import { config } from '../../../../mozaic.config';
|
|
5
|
+
import { theme } from '../../../lib/theme';
|
|
6
|
+
|
|
7
|
+
export const prerender = true;
|
|
8
|
+
export const trailingSlash = 'never';
|
|
9
|
+
|
|
10
|
+
/** Lists every embed page for `mozaic export`. */
|
|
11
|
+
export function GET() {
|
|
12
|
+
const manifest: EmbedManifest = {
|
|
13
|
+
project: config.project,
|
|
14
|
+
embeds: charts.flatMap((chart) =>
|
|
15
|
+
config.locales.map((locale) => ({
|
|
16
|
+
slug: chart.slug,
|
|
17
|
+
style: theme.name,
|
|
18
|
+
locale,
|
|
19
|
+
title: chart.title(locale),
|
|
20
|
+
path: `embed/${chart.slug}/${locale}/`,
|
|
21
|
+
print: Object.fromEntries(chart.print.map((name) => [name, theme.print.widths[name]]))
|
|
22
|
+
}))
|
|
23
|
+
)
|
|
24
|
+
};
|
|
25
|
+
return json(manifest);
|
|
26
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { hostScript } from '@samizdata/mozaic/embed';
|
|
2
|
+
|
|
3
|
+
export const prerender = true;
|
|
4
|
+
export const trailingSlash = 'never';
|
|
5
|
+
|
|
6
|
+
/** The script host pages include to auto-size embed iframes. */
|
|
7
|
+
export function GET() {
|
|
8
|
+
return new Response(hostScript, { headers: { 'content-type': 'text/javascript; charset=utf-8' } });
|
|
9
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import adapter from '@sveltejs/adapter-static';
|
|
2
|
+
import { sveltekit } from '@sveltejs/kit/vite';
|
|
3
|
+
import { defineConfig } from 'vite';
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [
|
|
7
|
+
sveltekit({
|
|
8
|
+
compilerOptions: {
|
|
9
|
+
runes: ({ filename }) => (filename.split(/[/\\]/).includes('node_modules') ? undefined : true)
|
|
10
|
+
},
|
|
11
|
+
adapter: adapter(),
|
|
12
|
+
prerender: {
|
|
13
|
+
// The manifest and host script are not linked from any page.
|
|
14
|
+
entries: ['*', '/embed/manifest.json', '/embed.js']
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
]
|
|
18
|
+
});
|