返回 slidev
contextInjection.ts
根目录 / packages / slidev / node / vite / contextInjection.ts
1 import type { Plugin } from 'vite'
2 import { templateImportContextUtils, templateInitContext, templateInjectionMarker } from './common'
3
4 const RE_EXPORT_DEFAULT_OBJECT = /export\s+default\s+\{/
5 const RE_INJECT_OPTION = /.*inject\s*:\s*([[{])/
6
7 /**
8 * Inject `$slidev` into the script block of a Vue component
9 */
10 export function createContextInjectionPlugin(): Plugin {
11 return {
12 name: 'slidev:context-injection',
13 transform: {
14 // TODO: static filter
15 async handler(code, id) {
16 if (!id.endsWith('.vue') || id.includes('/@slidev/client/') || id.includes('/packages/client/'))
17 return
18 if (code.includes(templateInjectionMarker) || code.includes('useSlideContext()'))
19 return code // Assume that the context is already imported and used
20 const imports = [
21 templateImportContextUtils,
22 templateInitContext,
23 templateInjectionMarker,
24 ]
25
26 // Find all <script> blocks
27 const matchScripts = [...code.matchAll(/<script([^>]*)>/g)]
28 // Find the <script ... setup> block
29 const setupScriptMatch = [...code.matchAll(/<script([^>]*)setup([^>]*)>/g)].at(0)
30 if (setupScriptMatch) {
31 // Only inject into the <script setup> block
32 const setupTag = setupScriptMatch[0]
33 const setupTagIndex = setupScriptMatch.index || 0
34 const setupTagEnd = setupTagIndex + setupTag.length
35 // Insert imports right after the <script setup ...> tag
36 return `${code.slice(0, setupTagEnd)}\n${imports.join('\n')}\n${code.slice(setupTagEnd)}`
37 }
38 else if (!setupScriptMatch && matchScripts.length === 1) {
39 // not a setup script
40 const matchExport = code.match(RE_EXPORT_DEFAULT_OBJECT)
41 if (matchExport) {
42 // script exports a component
43 const exportIndex = (matchExport.index || 0) + matchExport[0].length
44 let component = code.slice(exportIndex)
45 component = component.slice(0, component.indexOf('</script>'))
46
47 const scriptIndex = (matchScripts[0].index || 0) + matchScripts[0][0].length
48 const provideImport = '\nimport { injectionSlidevContext } from "@slidev/client/constants.ts"\n'
49 code = `${code.slice(0, scriptIndex)}${provideImport}${code.slice(scriptIndex)}`
50
51 let injectIndex = exportIndex + provideImport.length
52 let injectObject = '$slidev: { from: injectionSlidevContext },'
53 const matchInject = component.match(RE_INJECT_OPTION)
54 if (matchInject) {
55 // component has a inject option
56 injectIndex += (matchInject.index || 0) + matchInject[0].length
57 if (matchInject[1] === '[') {
58 // inject option in array
59 let injects = component.slice((matchInject.index || 0) + matchInject[0].length)
60 const injectEndIndex = injects.indexOf(']')
61 injects = injects.slice(0, injectEndIndex)
62 injectObject += injects.split(',').map(inject => `${inject}: {from: ${inject}}`).join(',')
63 return `${code.slice(0, injectIndex - 1)}{\n${injectObject}\n}${code.slice(injectIndex + injectEndIndex + 1)}`
64 }
65 else {
66 // inject option in object
67 return `${code.slice(0, injectIndex)}\n${injectObject}\n${code.slice(injectIndex)}`
68 }
69 }
70 // add inject option
71 return `${code.slice(0, injectIndex)}\ninject: { ${injectObject} },\n${code.slice(injectIndex)}`
72 }
73 }
74 // no setup script and not a vue component
75 return `<script setup>\n${imports.join('\n')}\n</script>\n${code}`
76 },
77 },
78 }
79 }
80
80 lines TYPESCRIPT