返回 slidev
extendConfig.ts
根目录 / packages / slidev / node / vite / extendConfig.ts
1 import type { ResolvedSlidevOptions } from '@slidev/types'
2 import type { Plugin, UserConfig } from 'vite'
3 import { fileURLToPath, pathToFileURL } from 'node:url'
4 import { slash, uniq } from '@antfu/utils'
5 import { createResolve } from 'mlly'
6 import { join } from 'pathe'
7 import { mergeConfig } from 'vite'
8 import { isInstalledGlobally, resolveImportPath, toAtFS } from '../resolver'
9
10 const RE_SLIDEV_CLIENT = /^@slidev\/client$/
11 const RE_SLIDEV_CLIENT_SUBPATH = /^@slidev\/client\/(.*)/
12 const RE_SLIDEV_VIRTUAL = /^#slidev\/(.*)/
13 const RE_MONACO_EDITOR = /\/monaco-editor(?:-core)?\//
14
15 const INCLUDE_GLOBAL = [
16 '@fix-webm-duration/fix',
17 '@typescript/ata',
18 'file-saver',
19 'lz-string',
20 'recordrtc',
21 'typescript',
22 'yaml',
23 'pptxgenjs',
24 'ansis',
25 ]
26
27 const INCLUDE_LOCAL = INCLUDE_GLOBAL.map(i => `@slidev/cli > @slidev/client > ${i}`)
28
29 // @keep-sorted
30 const EXCLUDE_GLOBAL = [
31 '@antfu/utils',
32 '@shikijs/magic-move/vue',
33 '@shikijs/monaco',
34 '@shikijs/vitepress-twoslash/client',
35 '@slidev/client',
36 '@slidev/client/constants',
37 '@slidev/client/context',
38 '@slidev/client/logic/dark',
39 '@slidev/parser',
40 '@slidev/parser/core',
41 '@slidev/rough-notation',
42 '@slidev/types',
43 '@unhead/vue',
44 '@unocss/reset',
45 '@vueuse/core',
46 '@vueuse/math',
47 '@vueuse/motion',
48 '@vueuse/shared',
49 'drauu',
50 'floating-vue',
51 'fuse.js',
52 'mermaid',
53 'monaco-editor',
54 'shiki',
55 'shiki/core',
56 'vue-demi',
57 'vue-router',
58 'vue',
59 ]
60
61 const EXCLUDE_LOCAL = EXCLUDE_GLOBAL
62
63 const ASYNC_MODULES = [
64 'file-saver',
65 'vue',
66 '@vue',
67 ]
68
69 export function createConfigPlugin(options: ResolvedSlidevOptions): Plugin {
70 const resolveClientDep = createResolve({
71 // Same as Vite's default resolve conditions
72 conditions: ['import', 'module', 'browser', 'default', options.mode === 'build' ? 'production' : 'development'],
73 url: pathToFileURL(options.clientRoot),
74 })
75 return {
76 name: 'slidev:config',
77 async config(config) {
78 const injection: UserConfig = {
79 define: options.utils.define,
80 resolve: {
81 alias: [
82 {
83 find: RE_SLIDEV_CLIENT,
84 replacement: `${toAtFS(options.clientRoot)}/index.ts`,
85 },
86 {
87 find: RE_SLIDEV_CLIENT_SUBPATH,
88 replacement: `${toAtFS(options.clientRoot)}/$1`,
89 },
90 {
91 find: RE_SLIDEV_VIRTUAL,
92 replacement: '/@slidev/$1',
93 },
94 {
95 find: 'vue',
96 replacement: await resolveImportPath('vue/dist/vue.esm-bundler.js', true),
97 },
98 ...(isInstalledGlobally.value
99 ? await Promise.all(INCLUDE_GLOBAL.map(async dep => ({
100 find: dep,
101 replacement: fileURLToPath(await resolveClientDep(dep)),
102 })))
103 : []
104 ),
105 ],
106 dedupe: ['vue'],
107 },
108 optimizeDeps: isInstalledGlobally.value
109 ? {
110 exclude: EXCLUDE_GLOBAL,
111 include: INCLUDE_GLOBAL,
112 }
113 : {
114 // We need to specify the full deps path for non-hoisted modules
115 exclude: EXCLUDE_LOCAL,
116 include: INCLUDE_LOCAL,
117 },
118 css: {
119 postcss: {
120 plugins: [
121 await import('postcss-nested').then(r => (r.default || r)()) as any,
122 ],
123 },
124 },
125 server: {
126 fs: {
127 strict: true,
128 allow: uniq([
129 options.userWorkspaceRoot,
130 options.clientRoot,
131 // Special case for PNPM global installation
132 isInstalledGlobally.value
133 ? slash(options.cliRoot).replace(/\/\.pnpm\/.*$/gi, '')
134 : options.cliRoot,
135 ...options.roots,
136 ]),
137 },
138 },
139 publicDir: join(options.userRoot, 'public'),
140 build: {
141 rollupOptions: {
142 output: {
143 chunkFileNames(chunkInfo) {
144 const DEFAULT = 'assets/[name]-[hash].js'
145
146 // Already handled in manualChunks
147 if (chunkInfo.name.includes('/'))
148 return DEFAULT
149
150 // Over 60% of the chunk is slidev client code, we put it into slidev chunk
151 if (chunkInfo.moduleIds.filter(i => isSlidevClient(i)).length > chunkInfo.moduleIds.length * 0.6)
152 return 'assets/slidev/[name]-[hash].js'
153
154 // Monaco Editor
155 if (chunkInfo.moduleIds.filter(i => i.match(RE_MONACO_EDITOR)).length > chunkInfo.moduleIds.length * 0.6)
156 return 'assets/monaco/[name]-[hash].js'
157
158 return DEFAULT
159 },
160 manualChunks(id) {
161 if (id.startsWith('/@slidev-monaco-types/') || id.includes('/@slidev/monaco-types') || id.endsWith('?monaco-types&raw'))
162 return 'monaco/bundled-types'
163 if (id.includes('/shiki/') || id.includes('/@shikijs/'))
164 return `modules/shiki`
165 if (id.startsWith('~icons/'))
166 return 'modules/unplugin-icons'
167 // It seems that moving slides out will breaks the production build
168 // Would need to find a better way to handle this
169 // const slideMatch = id.match(/\/@slidev\/slides\/(\d+)/)
170 // if (slideMatch && !id.includes('.frontmatter'))
171 // return `slides/${slideMatch[1]}`
172
173 const matchedAsyncModule = ASYNC_MODULES.find(i => id.includes(`/node_modules/${i}`))
174 if (matchedAsyncModule)
175 return `modules/${matchedAsyncModule.replace('@', '').replace('/', '-')}`
176 },
177 },
178 },
179 },
180 cacheDir: isInstalledGlobally.value ? join(options.cliRoot, 'node_modules/.vite') : undefined,
181 }
182
183 function isSlidevClient(id: string) {
184 return id.includes('/@slidev/') || id.includes('/slidev/packages/client/') || id.includes('/@vueuse/')
185 }
186
187 // function getNodeModuleName(path: string) {
188 // const nodeModuelsMatch = [...path.matchAll(/node_modules\/(@[^/]+\/[^/]+|[^/]+)\//g)]
189 // if (nodeModuelsMatch.length)
190 // return nodeModuelsMatch[nodeModuelsMatch.length - 1][1]
191 // }
192
193 return mergeConfig(injection, config)
194 },
195 configureServer(server) {
196 // serve our index.html after vite history fallback
197 return () => {
198 server.middlewares.use(async (req, res, next) => {
199 if (req.url === '/index.html') {
200 const headers = server.config.server.headers ?? {}
201
202 for (const header in headers) {
203 res.setHeader(header, headers[header]!)
204 }
205
206 res.setHeader('Content-Type', 'text/html')
207 res.statusCode = 200
208 res.end(options.utils.indexHtml)
209 return
210 }
211 next()
212 })
213 }
214 },
215 }
216 }
217
217 lines TYPESCRIPT