返回 slidev
build.ts
根目录 / packages / slidev / node / commands / build.ts
1 import type { BuildArgs, ResolvedSlidevOptions } from '@slidev/types'
2 import type { InlineConfig, ResolvedConfig } from 'vite'
3 import { existsSync } from 'node:fs'
4 import fs from 'node:fs/promises'
5 import http from 'node:http'
6 import connect from 'connect'
7 import { join, resolve } from 'pathe'
8 import sirv from 'sirv'
9 import { build as viteBuild } from 'vite'
10 import { resolveViteConfigs } from './shared'
11
12 export async function build(
13 options: ResolvedSlidevOptions,
14 viteConfig: InlineConfig = {},
15 args: BuildArgs,
16 ) {
17 const indexHtmlId = resolve(options.userRoot, 'index.html')
18
19 let config: ResolvedConfig = undefined!
20
21 const inlineConfig = await resolveViteConfigs(
22 options,
23 {
24 plugins: [
25 {
26 name: 'slidev:build',
27 configResolved(_config) {
28 config = _config
29 },
30 resolveId: {
31 order: 'pre',
32 handler(id) {
33 if (id === indexHtmlId)
34 return id
35 return null
36 },
37 },
38 load: {
39 order: 'pre',
40 handler(id) {
41 if (id === indexHtmlId) {
42 return options.utils.indexHtml
43 }
44 },
45 },
46 },
47 ],
48 build: {
49 chunkSizeWarningLimit: 2000,
50 rollupOptions: {
51 input: {
52 index: indexHtmlId,
53 },
54 },
55 },
56 } satisfies InlineConfig,
57 viteConfig,
58 'build',
59 )
60
61 await viteBuild(inlineConfig)
62
63 const outDir = resolve(options.userRoot, config.build.outDir)
64
65 // copy or generate ogImage if it's a relative path, skip if not
66 if (options.data.config.seoMeta?.ogImage === 'auto' || options.data.config.seoMeta?.ogImage?.startsWith('.')) {
67 const filename = options.data.config.seoMeta?.ogImage === 'auto' ? 'og-image.png' : options.data.config.seoMeta.ogImage
68 const projectOgImagePath = resolve(options.userRoot, filename)
69 const outputOgImagePath = resolve(outDir, filename)
70
71 const projectOgImageExists = await fs.access(projectOgImagePath).then(() => true).catch(() => false)
72 if (projectOgImageExists) {
73 await fs.copyFile(projectOgImagePath, outputOgImagePath)
74 }
75 else if (options.data.config.seoMeta?.ogImage === 'auto') {
76 const port = 12445
77 const app = connect()
78 const server = http.createServer(app)
79 app.use(
80 config.base,
81 sirv(outDir, {
82 etag: true,
83 single: true,
84 dev: true,
85 }),
86 )
87 server.listen(port)
88
89 const { exportSlides } = await import('./export')
90 const tempDir = resolve(outDir, 'temp')
91 await fs.mkdir(tempDir, { recursive: true })
92
93 await exportSlides({
94 port,
95 base: config.base,
96 slides: options.data.slides,
97 total: options.data.slides.length,
98 format: 'png',
99 output: tempDir,
100 range: '1',
101 width: options.data.config.canvasWidth,
102 height: Math.round(options.data.config.canvasWidth / options.data.config.aspectRatio),
103 // This renders slides by URL, so memory routing falls back to history.
104 routerMode: options.data.config.routerMode === 'memory' ? 'history' : options.data.config.routerMode,
105 waitUntil: 'networkidle',
106 timeout: args.timeout || 30000,
107 perSlide: true,
108 omitBackground: false,
109 dark: args.dark,
110 })
111
112 const tempFiles = await fs.readdir(tempDir)
113 const pngFile = tempFiles.find(file => file.endsWith('.png'))
114 if (pngFile) {
115 const generatedPath = resolve(tempDir, pngFile)
116 await fs.copyFile(generatedPath, projectOgImagePath)
117 await fs.copyFile(generatedPath, outputOgImagePath)
118 }
119
120 await fs.rm(tempDir, { recursive: true, force: true })
121 server.close()
122 }
123 else {
124 throw new Error(`[Slidev] ogImage: ${filename} not found`)
125 }
126 }
127
128 // copy index.html to 404.html for GitHub Pages
129 await fs.copyFile(resolve(outDir, 'index.html'), resolve(outDir, '404.html'))
130 // _redirects for SPA
131 const redirectsPath = resolve(outDir, '_redirects')
132 if (!existsSync(redirectsPath))
133 await fs.writeFile(redirectsPath, `${config.base}* ${config.base}index.html 200\n`, 'utf-8')
134
135 if ([true, 'true', 'auto'].includes(options.data.config.download)) {
136 const { exportSlides, getExportOptions } = await import('./export')
137
138 const port = 12445
139 const app = connect()
140 const server = http.createServer(app)
141 app.use(
142 config.base,
143 sirv(outDir, {
144 etag: true,
145 single: true,
146 dev: true,
147 }),
148 )
149 server.listen(port)
150 const filename = options.data.config.exportFilename || 'slidev-exported'
151 await exportSlides({
152 port,
153 base: config.base,
154 ...getExportOptions(args, options, join(outDir, `${filename}.pdf`)),
155 })
156 server.close()
157 }
158 }
159
159 lines TYPESCRIPT