@lincyaw/render-slides 1.0.0-rc.0 → 1.0.0-rc.2
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/README.md +19 -6
- package/package.json +14 -25
- package/src/node/presmium.ts +82 -264
- package/src/deck/style.css +0 -215
- package/src/node/html.ts +0 -15
- package/src/node/markdown.ts +0 -20
- package/src/node/slidev.ts +0 -550
- package/theme/layouts/cover.vue +0 -12
- package/theme/layouts/default.vue +0 -20
- package/theme/package.json +0 -24
- package/theme/parts/SlideFooter.vue +0 -28
- package/theme/setup/mermaid.ts +0 -9
- package/theme/setup/shiki.ts +0 -36
- package/theme/setup/vite-plugins.ts +0 -25
- package/theme/styles/book.css +0 -282
- package/theme/styles/index.ts +0 -2
package/README.md
CHANGED
|
@@ -1,24 +1,37 @@
|
|
|
1
1
|
# @lincyaw/render-slides
|
|
2
2
|
|
|
3
|
-
Presmium's slides renderer:
|
|
3
|
+
Presmium's slides renderer: the slides are the viewer's slides page, built and served as the web form is (`@lincyaw/web`). Each section is a slide on a canvas of a fixed size, drawn in the Book (书卷) layout and scaled as a whole to the screen; plugin components render in it as on the web page. It also gives the document's exports of the slides: PDF, pictures and PowerPoint.
|
|
4
4
|
|
|
5
|
-
Part of [Presmium](https://github.com/Lincyaw/presmium), which
|
|
5
|
+
Part of [Presmium](https://github.com/Lincyaw/presmium), which compiles canonical lecture.json teaching documents into web pages, slides, Jupyter notebooks and narrated playback.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
pnpm add -D @lincyaw/cli @lincyaw/
|
|
10
|
+
pnpm add -D @lincyaw/cli @lincyaw/web @lincyaw/render-slides
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
`presmium build` builds the slides into `dist/slides/` when it is installed (or `--to slides`); `presmium dev --to slides` serves the slides page (`?form=slides`).
|
|
14
|
+
|
|
15
|
+
The exports print the slides page in Chromium, from `playwright-chromium`, an optional peer dependency. Install it in the document's project to export:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
pnpm add -D playwright-chromium
|
|
19
|
+
pnpm exec playwright install chromium
|
|
20
|
+
```
|
|
14
21
|
|
|
15
22
|
## Configuration
|
|
16
23
|
|
|
17
|
-
In
|
|
24
|
+
In `lecture.json`, `metadata.renderers.slides`: `base` (the site path), `port` (the dev server), `aspectRatio` (`16/9` by default; `4:3` or a number) and `canvasWidth` (980 by default), which give the canvas the slides are drawn on.
|
|
25
|
+
|
|
26
|
+
A section's `{hints}` gives its slide, under `slides`: `layout` (`default`, `cover`, `center` or `full`; the first section is `cover` unless it says otherwise), `class` (classes added to the slide) and `zoom` (a scale of its content).
|
|
27
|
+
|
|
28
|
+
## Exports
|
|
29
|
+
|
|
30
|
+
The exporters `pdf`, `png` (a picture of each slide, or of each step) and `pptx` (a picture of each slide, its narration as the speaker notes) run as the workspace service's jobs and from `presmium-export` (`@lincyaw/addon-publish`).
|
|
18
31
|
|
|
19
32
|
## Documentation
|
|
20
33
|
|
|
21
|
-
- [Author guide](https://github.com/Lincyaw/presmium/blob/main/site/guide/lecture.
|
|
34
|
+
- [Author guide](https://github.com/Lincyaw/presmium/blob/main/site/guide/locales/en/lecture.json)
|
|
22
35
|
|
|
23
36
|
## License
|
|
24
37
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lincyaw/render-slides",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
4
|
-
"description": "Presmium's slides renderer:
|
|
3
|
+
"version": "1.0.0-rc.2",
|
|
4
|
+
"description": "Presmium's slides renderer: the viewer's slides page, built as the web form is, and its exports as PDF, pictures and PowerPoint",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
"bugs": "https://github.com/Lincyaw/presmium/issues",
|
|
13
13
|
"keywords": [
|
|
14
14
|
"presmium",
|
|
15
|
-
"slidev",
|
|
16
15
|
"slides",
|
|
17
16
|
"renderer"
|
|
18
17
|
],
|
|
@@ -28,34 +27,24 @@
|
|
|
28
27
|
"./package.json": "./package.json"
|
|
29
28
|
},
|
|
30
29
|
"files": [
|
|
31
|
-
"src"
|
|
32
|
-
"theme"
|
|
30
|
+
"src"
|
|
33
31
|
],
|
|
34
32
|
"dependencies": {
|
|
35
|
-
"
|
|
36
|
-
"@slidev/theme-default": "^0.25.0",
|
|
37
|
-
"dompurify": "3.4.17",
|
|
38
|
-
"jsdom": "29.1.1",
|
|
39
|
-
"mdast-util-from-markdown": "2.1.0",
|
|
40
|
-
"mdast-util-gfm": "^3.1.0",
|
|
41
|
-
"mdast-util-to-markdown": "^2.2.0",
|
|
42
|
-
"micromark-extension-gfm": "3.0.0",
|
|
43
|
-
"shiki": "^4.5.0",
|
|
44
|
-
"vue": "^3.5.42",
|
|
45
|
-
"yaml": "^2.9.1"
|
|
33
|
+
"pptxgenjs": "4.0.1"
|
|
46
34
|
},
|
|
47
35
|
"peerDependencies": {
|
|
48
|
-
"
|
|
49
|
-
"@lincyaw/
|
|
50
|
-
"@lincyaw/
|
|
36
|
+
"playwright-chromium": "^1.40.0",
|
|
37
|
+
"@lincyaw/kernel": "^1.0.0-rc.2",
|
|
38
|
+
"@lincyaw/web": "^1.0.0-rc.2"
|
|
39
|
+
},
|
|
40
|
+
"peerDependenciesMeta": {
|
|
41
|
+
"playwright-chromium": {
|
|
42
|
+
"optional": true
|
|
43
|
+
}
|
|
51
44
|
},
|
|
52
45
|
"devDependencies": {
|
|
53
|
-
"pdf-lib": "^1.17.1",
|
|
54
|
-
"pdfjs-dist": "5.7.284",
|
|
55
46
|
"playwright-chromium": "1.64.1",
|
|
56
|
-
"@lincyaw/
|
|
57
|
-
"@lincyaw/
|
|
58
|
-
"@lincyaw/host": "1.0.0-rc.0",
|
|
59
|
-
"@lincyaw/viewer": "1.0.0-rc.0"
|
|
47
|
+
"@lincyaw/kernel": "1.0.0-rc.2",
|
|
48
|
+
"@lincyaw/web": "1.0.0-rc.2"
|
|
60
49
|
}
|
|
61
50
|
}
|
package/src/node/presmium.ts
CHANGED
|
@@ -1,276 +1,94 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { ChildProcess } from 'node:child_process'
|
|
3
|
-
import { existsSync, realpathSync } from 'node:fs'
|
|
4
|
-
import { copyFile, mkdir, readdir, readFile, rm, symlink, writeFile } from 'node:fs/promises'
|
|
1
|
+
import { mkdir, readFile, rm } from 'node:fs/promises'
|
|
5
2
|
import { createRequire } from 'node:module'
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
import { blockText, sectionNarration } from '@lincyaw/kernel'
|
|
5
|
+
import type { ExportContext, Exporter, IRDocument, PresmiumNodePlugin } from '@lincyaw/kernel'
|
|
6
|
+
import { playwrightChromium } from '@lincyaw/kernel/node'
|
|
7
|
+
import { printSlides, slidesRenderer } from '@lincyaw/web/slides'
|
|
8
|
+
import type { PrintingChromium, PrintOptions } from '@lincyaw/web/slides'
|
|
9
|
+
import PptxGenJS from 'pptxgenjs'
|
|
10
|
+
|
|
11
|
+
const { version } = createRequire(import.meta.url)('../../package.json') as { version: string }
|
|
12
|
+
|
|
13
|
+
// PowerPoint measures a slide in inches, at 96 pixels each.
|
|
14
|
+
const PIXELS_PER_INCH = 96
|
|
15
|
+
const PICTURES = '.pictures'
|
|
16
|
+
|
|
17
|
+
/** Whether the document has the slides form: its configuration names it, or names no renderer. */
|
|
18
|
+
function hasSlides(doc: IRDocument): boolean {
|
|
19
|
+
const named = Object.keys(doc.config.renderers)
|
|
20
|
+
return named.length === 0 || named.includes('slides')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* What the one who asked chose (`presmium-export`'s flags): `steps`, a page for every step; `slides`, the slides by number
|
|
25
|
+
* from 1; `dark`; `timeout`, how long the page may take, in milliseconds.
|
|
26
|
+
*/
|
|
27
|
+
function printChoices(options: Record<string, unknown>): Pick<PrintOptions, 'steps' | 'slides' | 'dark' | 'timeout'> {
|
|
28
|
+
const slides = Array.isArray(options.slides) ? options.slides.filter((n): n is number => Number.isInteger(n) && n > 0) : undefined
|
|
29
|
+
return {
|
|
30
|
+
steps: options.steps === true,
|
|
31
|
+
...slides ? { slides } : {},
|
|
32
|
+
...options.dark === true ? { dark: true } : {},
|
|
33
|
+
...typeof options.timeout === 'number' && options.timeout > 0 ? { timeout: options.timeout } : {},
|
|
34
|
+
}
|
|
35
|
+
}
|
|
15
36
|
|
|
16
|
-
|
|
17
|
-
|
|
37
|
+
/** Builds the slides and prints them. */
|
|
38
|
+
async function printed(ctx: ExportContext, format: 'pdf' | 'png', output: string) {
|
|
39
|
+
ctx.progress('build')
|
|
40
|
+
const dir = await ctx.build('slides')
|
|
41
|
+
ctx.progress('print')
|
|
42
|
+
const chromium = await playwrightChromium<PrintingChromium>(ctx.deckDir, import.meta.url)
|
|
43
|
+
const result = await printSlides({ dir, chromium, format, output, signal: ctx.signal, ...printChoices(ctx.options) })
|
|
44
|
+
return { ...result, doc: JSON.parse(await readFile(join(dir, 'ir.json'), 'utf8')) as IRDocument }
|
|
45
|
+
}
|
|
18
46
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const { version } = require('../../package.json') as { version: string }
|
|
47
|
+
function slidesExporter(id: string, label: Record<string, string>, run: Exporter['run']): Exporter {
|
|
48
|
+
return { id, label, available: hasSlides, run }
|
|
49
|
+
}
|
|
23
50
|
|
|
24
51
|
const plugin: PresmiumNodePlugin = {
|
|
25
52
|
id: 'presmium.slides',
|
|
26
53
|
version,
|
|
27
54
|
kernel: '^1.0.0',
|
|
28
|
-
renderer:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
},
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const file = join(dir, 'slides.md')
|
|
63
|
-
if (await readFile(file, 'utf8').catch(() => '') !== deck.markdown) await writeFile(file, deck.markdown)
|
|
64
|
-
await writeRevision(dir, ctx, next)
|
|
65
|
-
},
|
|
66
|
-
close: server.close,
|
|
55
|
+
renderer: slidesRenderer,
|
|
56
|
+
exporters: [
|
|
57
|
+
slidesExporter('pdf', { 'zh-CN': '幻灯片 PDF', 'en': 'Slides as PDF' }, async (ctx) => {
|
|
58
|
+
await printed(ctx, 'pdf', join(ctx.outDir, 'slides.pdf'))
|
|
59
|
+
return { files: ['slides.pdf'] }
|
|
60
|
+
}),
|
|
61
|
+
slidesExporter('png', { 'zh-CN': '幻灯片图片', 'en': 'Slides as pictures' }, async (ctx) => {
|
|
62
|
+
const { pages } = await printed(ctx, 'png', ctx.outDir)
|
|
63
|
+
return { files: pages.map(page => page.file!) }
|
|
64
|
+
}),
|
|
65
|
+
// A picture of each slide, its narration as the speaker notes.
|
|
66
|
+
slidesExporter('pptx', { 'zh-CN': '幻灯片 PowerPoint', 'en': 'Slides as PowerPoint' }, async (ctx) => {
|
|
67
|
+
const pictures = join(ctx.outDir, PICTURES)
|
|
68
|
+
await mkdir(pictures, { recursive: true })
|
|
69
|
+
try {
|
|
70
|
+
const { shape, pages, doc } = await printed(ctx, 'png', pictures)
|
|
71
|
+
ctx.progress('pptx')
|
|
72
|
+
const pptx = new PptxGenJS()
|
|
73
|
+
const [w, h] = [shape.width / PIXELS_PER_INCH, shape.height / PIXELS_PER_INCH]
|
|
74
|
+
pptx.defineLayout({ name: 'PRESMIUM', width: w, height: h })
|
|
75
|
+
pptx.layout = 'PRESMIUM'
|
|
76
|
+
pptx.title = doc.meta.title
|
|
77
|
+
const sections = new Map(doc.sections.map(section => [section.id, section]))
|
|
78
|
+
for (const page of pages) {
|
|
79
|
+
const slide = pptx.addSlide()
|
|
80
|
+
slide.addImage({ path: join(pictures, page.file!), x: 0, y: 0, w, h })
|
|
81
|
+
const section = sections.get(page.section)
|
|
82
|
+
const notes = section ? sectionNarration(section).map(blockText).join('\n\n') : ''
|
|
83
|
+
if (notes) slide.addNotes(notes)
|
|
84
|
+
}
|
|
85
|
+
await pptx.writeFile({ fileName: join(ctx.outDir, 'slides.pptx') })
|
|
86
|
+
return { files: ['slides.pptx'] }
|
|
87
|
+
} finally {
|
|
88
|
+
await rm(pictures, { recursive: true, force: true })
|
|
67
89
|
}
|
|
68
|
-
},
|
|
69
|
-
|
|
90
|
+
}),
|
|
91
|
+
],
|
|
70
92
|
}
|
|
71
93
|
|
|
72
94
|
export default plugin
|
|
73
|
-
|
|
74
|
-
const hasHtml = (node: MdastNode): boolean => node.type === 'html' || (node.children ?? []).some(hasHtml)
|
|
75
|
-
|
|
76
|
-
function located(error: unknown, doc: IRDocument, ctx: RenderContext): unknown {
|
|
77
|
-
const output = error instanceof SlidevError ? error.output : errorMessage(error)
|
|
78
|
-
const at = /slides\.md__slidev_(\d+)\.md(?::\d+(?::\d+)?)?\s*\n(?:\w*Error: )?([^\n]+)/.exec(output)
|
|
79
|
-
const section = at && doc.sections[Number(at[1]) - 1]
|
|
80
|
-
if (!at || !section) return error
|
|
81
|
-
const reason = at[2]!.replace(/^\w*Error: /, '').trim()
|
|
82
|
-
const raw = [...irBlocks(section.blocks)].filter(block => block.role === 'visual' && nodeMdast(block.node).some(hasHtml))
|
|
83
|
-
const source = raw.length === 1 ? raw[0]!.source : section.source
|
|
84
|
-
ctx.report({
|
|
85
|
-
checkId: 'presmium.slides',
|
|
86
|
-
severity: 'error',
|
|
87
|
-
message: `slide ${at[1]} (${section.title || section.id}) does not build: ${reason}`,
|
|
88
|
-
source: { file: source.file, line: source.line },
|
|
89
|
-
})
|
|
90
|
-
return new Error(`slidev build failed at slide ${at[1]}: ${reason}`)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function base(ctx: RenderContext): string[] {
|
|
94
|
-
return ctx.base !== '/' ? ['--base', ctx.base] : []
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
async function writeDeck(dir: string, ctx: RenderContext, deck: SlidevDeck, doc: IRDocument): Promise<void> {
|
|
98
|
-
await mkdir(dir, { recursive: true })
|
|
99
|
-
await writeFile(join(dir, '.gitignore'), '*\n')
|
|
100
|
-
const packages = [PACKAGE_DIR, ...addonDirs(doc, ctx)]
|
|
101
|
-
const optimize = optimizeDeps(dir, packages)
|
|
102
|
-
await writeFile(join(dir, 'vite.config.ts'), viteConfig(ctx, await vitePluginEntries(ctx, doc.config.presmium), optimize, allowed(ctx, packages)))
|
|
103
|
-
await copyFile(STYLE, join(dir, 'style.css'))
|
|
104
|
-
await rm(join(dir, 'public'), { recursive: true, force: true })
|
|
105
|
-
await mkdir(join(dir, 'public'))
|
|
106
|
-
const shared = join(ctx.deckDir, 'public')
|
|
107
|
-
for (const entry of await readdir(shared).catch(() => [])) await symlink(join(shared, entry), join(dir, 'public', entry), 'junction')
|
|
108
|
-
await publish(dir, deck.assets)
|
|
109
|
-
const license = join(dir, 'public', FONT_LICENSE)
|
|
110
|
-
if (!existsSync(join(dir, 'public', FONT_LICENSE.split('/')[0]!))) {
|
|
111
|
-
await mkdir(dirname(license), { recursive: true })
|
|
112
|
-
await writeFile(license, fontLicense(import.meta.url))
|
|
113
|
-
}
|
|
114
|
-
await rm(join(dir, 'components'), { recursive: true, force: true })
|
|
115
|
-
if (existsSync(join(ctx.deckDir, 'components'))) await symlink(join(ctx.deckDir, 'components'), join(dir, 'components'), 'junction')
|
|
116
|
-
await writePreviewSetup(dir, ctx)
|
|
117
|
-
await writeFile(join(dir, 'slides.md'), deck.markdown)
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
async function writePreviewSetup(dir: string, ctx: RenderContext): Promise<void> {
|
|
121
|
-
await rm(join(dir, 'setup'), { recursive: true, force: true })
|
|
122
|
-
await rm(join(dir, PREVIEW_FILE), { force: true })
|
|
123
|
-
const core = ctx.pluginDirs[ADDON_CORE]
|
|
124
|
-
if (!ctx.preview || !core) return
|
|
125
|
-
const agent = join(core, 'src/client/preview.ts')
|
|
126
|
-
const options = { origins: ctx.preview.origins, document: previewDocument(ctx) }
|
|
127
|
-
await mkdir(join(dir, 'setup'))
|
|
128
|
-
await writeFile(join(dir, 'setup/root.ts'), `// Generated by @lincyaw/render-slides for a preview of the workspace service; do not edit.
|
|
129
|
-
import { startSlidesPreview } from ${JSON.stringify(agent)}
|
|
130
|
-
|
|
131
|
-
export default function () {
|
|
132
|
-
startSlidesPreview(${JSON.stringify(options)})
|
|
133
|
-
}
|
|
134
|
-
`)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async function writeRevision(dir: string, ctx: RenderContext, doc: IRDocument): Promise<void> {
|
|
138
|
-
if (!ctx.preview) return
|
|
139
|
-
const revision = doc.meta[PREVIEW_REVISION]
|
|
140
|
-
const json = `${JSON.stringify(typeof revision === 'string' ? { revision } : {})}\n`
|
|
141
|
-
const file = join(dir, PREVIEW_FILE)
|
|
142
|
-
if (await readFile(file, 'utf8').catch(() => '') !== json) await writeFile(file, json)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
interface VitePluginsEntry {
|
|
146
|
-
file: string
|
|
147
|
-
config: Record<string, unknown>
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
async function nodeEntry(dir: string): Promise<string | undefined> {
|
|
151
|
-
const manifest: unknown = JSON.parse(await readFile(join(dir, 'package.json'), 'utf8').catch(() => '{}'))
|
|
152
|
-
const exports = isRecord(manifest) ? manifest.exports : undefined
|
|
153
|
-
const entry = isRecord(exports) ? exports['./presmium'] : undefined
|
|
154
|
-
return typeof entry === 'string' ? resolve(dir, entry) : undefined
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
async function vitePluginEntries(ctx: RenderContext, presmium: Record<string, unknown>): Promise<VitePluginsEntry[]> {
|
|
158
|
-
if (ctx.vitePlugins.length === 0) return []
|
|
159
|
-
const entries: VitePluginsEntry[] = []
|
|
160
|
-
for (const name of ctx.plugins) {
|
|
161
|
-
const dir = ctx.pluginDirs[name]
|
|
162
|
-
const file = dir && await nodeEntry(dir)
|
|
163
|
-
if (!file) continue
|
|
164
|
-
const plugin = (await import(pathToFileURL(file).href).catch(() => ({}))).default as PresmiumNodePlugin | undefined
|
|
165
|
-
if (typeof plugin?.vitePlugins !== 'function') continue
|
|
166
|
-
const config = presmium[plugin.id]
|
|
167
|
-
entries.push({ file, config: isRecord(config) ? config : {} })
|
|
168
|
-
}
|
|
169
|
-
return entries
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function allowed(ctx: RenderContext, packages: string[]): string[] {
|
|
173
|
-
const installed = packages.flatMap((dir) => {
|
|
174
|
-
const parts = realpathSync(dir).split(sep)
|
|
175
|
-
const at = parts.indexOf('node_modules')
|
|
176
|
-
return at < 0 ? [] : [parts.slice(0, at + 1).join(sep)]
|
|
177
|
-
})
|
|
178
|
-
return [...new Set([ctx.deckDir, ...installed])]
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function viteConfig(ctx: RenderContext, entries: VitePluginsEntry[], optimize: { include: string[], exclude: string[] }, allow: string[]): string {
|
|
182
|
-
const imports = entries.map(({ file }, i) => `import plugin${i} from ${JSON.stringify(file)}\n`).join('')
|
|
183
|
-
const deck = JSON.stringify({ deckDir: ctx.deckDir, workspaceRoot: ctx.workspaceRoot })
|
|
184
|
-
const plugins = entries.map(({ config }, i) => ` ...(plugin${i}.vitePlugins?.({ ...deck, config: ${JSON.stringify(config)} }) ?? []),\n`).join('')
|
|
185
|
-
const server = {
|
|
186
|
-
...ctx.server ? { proxy: { '^/__presmium/': { target: ctx.server.url, ws: true } }, fs: { allow } } : {},
|
|
187
|
-
...ctx.preview ? { headers: { 'Content-Security-Policy': previewFrameAncestors(ctx.preview.origins) } } : {},
|
|
188
|
-
}
|
|
189
|
-
const served = Object.keys(server).length > 0 ? ` server: ${JSON.stringify(server)},\n` : ''
|
|
190
|
-
const optimized = optimize.exclude.length > 0 ? ` optimizeDeps: ${JSON.stringify(optimize)},\n` : ''
|
|
191
|
-
const preview = ctx.preview ? ` presmiumPreview(),\n` : ''
|
|
192
|
-
return `${imports}${ctx.preview ? previewPlugin() : ''}\nconst deck = ${deck}\n\nexport default {\n cacheDir: 'node_modules/.vite',\n plugins: [\n${preview}${plugins} ],\n${served}${optimized}}\n`
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function previewPlugin(): string {
|
|
196
|
-
return `import { readFileSync } from 'node:fs'
|
|
197
|
-
import { fileURLToPath } from 'node:url'
|
|
198
|
-
|
|
199
|
-
function presmiumPreview() {
|
|
200
|
-
const file = fileURLToPath(new URL(${JSON.stringify(`./${PREVIEW_FILE}`)}, import.meta.url))
|
|
201
|
-
const read = () => {
|
|
202
|
-
try {
|
|
203
|
-
return JSON.parse(readFileSync(file, 'utf8'))
|
|
204
|
-
} catch {
|
|
205
|
-
return {}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
return {
|
|
209
|
-
name: 'presmium:preview',
|
|
210
|
-
configureServer(server) {
|
|
211
|
-
server.ws.on('presmium:preview', (_data, client) => client.send('presmium:revision', read()))
|
|
212
|
-
server.watcher.add(file)
|
|
213
|
-
let timer
|
|
214
|
-
server.watcher.on('change', (changed) => {
|
|
215
|
-
if (changed !== file) return
|
|
216
|
-
clearTimeout(timer)
|
|
217
|
-
timer = setTimeout(() => server.ws.send('presmium:revision', read()), 150)
|
|
218
|
-
})
|
|
219
|
-
},
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
`
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
async function publish(dir: string, assets: Map<string, LocalAsset>): Promise<void> {
|
|
226
|
-
for (const found of assets.values()) copyPublished(found, join(dir, 'public'))
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function slidevBin(): string {
|
|
230
|
-
const pkg = require.resolve('@slidev/cli/package.json')
|
|
231
|
-
return join(dirname(pkg), (require(pkg) as { bin: { slidev: string } }).bin.slidev)
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
class SlidevError extends Error {
|
|
235
|
-
readonly output: string
|
|
236
|
-
constructor(message: string, output: string) {
|
|
237
|
-
super(`${message}\n${output.slice(-4000)}`)
|
|
238
|
-
this.output = output
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function slidev(args: string[], cwd: string): Promise<void> {
|
|
243
|
-
return new Promise((resolve, reject) => {
|
|
244
|
-
const child = spawn(process.execPath, [...TYPESCRIPT_IMPORT, slidevBin(), ...args], { cwd, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
245
|
-
let output = ''
|
|
246
|
-
child.stdout.on('data', (chunk: Buffer) => output += chunk)
|
|
247
|
-
child.stderr.on('data', (chunk: Buffer) => output += chunk)
|
|
248
|
-
child.on('error', reject)
|
|
249
|
-
child.on('close', code => code === 0 ? resolve() : reject(new SlidevError(`slidev ${args[0]} exited with code ${code}`, output)))
|
|
250
|
-
})
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function slidevDev(dir: string, args: string[]): Promise<{ url: string, close(): Promise<void> }> {
|
|
254
|
-
const child: ChildProcess = spawn(process.execPath, [...TYPESCRIPT_IMPORT, slidevBin(), 'slides.md', ...args], { cwd: dir, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
255
|
-
const exited = new Promise<void>(resolve => child.once('exit', () => resolve()))
|
|
256
|
-
const close = async () => {
|
|
257
|
-
if (child.exitCode === null) child.kill('SIGTERM')
|
|
258
|
-
await exited
|
|
259
|
-
}
|
|
260
|
-
return new Promise((resolve, reject) => {
|
|
261
|
-
let output: string | undefined = ''
|
|
262
|
-
child.stderr!.on('data', (chunk: Buffer) => {
|
|
263
|
-
if (output !== undefined) output += chunk
|
|
264
|
-
})
|
|
265
|
-
child.stdout!.on('data', (chunk: Buffer) => {
|
|
266
|
-
if (output === undefined) return
|
|
267
|
-
output += chunk
|
|
268
|
-
const url = /(http:\/\/localhost:\d+\/\S*)/.exec(output.replace(/\x1B\[[\d;]*m/g, ''))?.[1]
|
|
269
|
-
if (!url) return
|
|
270
|
-
output = undefined
|
|
271
|
-
resolve({ url, close })
|
|
272
|
-
})
|
|
273
|
-
child.once('error', reject)
|
|
274
|
-
child.once('exit', code => reject(new Error(`slidev exited with code ${code}\n${output?.slice(-4000) ?? ''}`)))
|
|
275
|
-
})
|
|
276
|
-
}
|