0%

VueRepl源码解析之如何实现动态引入不同版本的Vue

简介

Vue Playground支持选择不同的Vue版本,实时编码并实时预览。本文主要介绍其如何实现选择不同的Vue版本。

image-20230110185644361

核心代码

store.ts — vuejs/repl — GitHub1s

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

async setVueVersion(version: string) {
this.vueVersion = version
const compilerUrl = `https://unpkg.com/@vue/compiler-sfc@${version}/dist/compiler-sfc.esm-browser.js`
const runtimeUrl = `https://unpkg.com/@vue/runtime-dom@${version}/dist/runtime-dom.esm-browser.js`
const ssrUrl = `https://unpkg.com/@vue/server-renderer@${version}/dist/server-renderer.esm-browser.js`
this.pendingCompiler = import(/* @vite-ignore */ compilerUrl)
this.compiler = await this.pendingCompiler
this.pendingCompiler = null
this.state.vueRuntimeURL = runtimeUrl
this.state.vueServerRendererURL = ssrUrl
const importMap = this.getImportMap()
const imports = importMap.imports || (importMap.imports = {})
imports.vue = runtimeUrl
imports['vue/server-renderer'] = ssrUrl
this.setImportMap(importMap)
this.forceSandboxReset()
console.info(`[@vue/repl] Now using Vue version: ${version}`)
}

https://github1s.com/vuejs/repl/blob/HEAD/src/output/Preview.vue#L52

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
watch(() => store.state.resetFlip, createSandbox)

function createSandbox() {
if (sandbox) {
// clear prev sandbox
proxy.destroy()
stopUpdateWatcher && stopUpdateWatcher()
container.value.removeChild(sandbox)
}

sandbox = document.createElement('iframe')
sandbox.setAttribute(
'sandbox',
[
'allow-forms',
'allow-modals',
'allow-pointer-lock',
'allow-popups',
'allow-same-origin',
'allow-scripts',
'allow-top-navigation-by-user-activation'
].join(' ')
)

const importMap = store.getImportMap()
if (!importMap.imports) {
importMap.imports = {}
}
if (!importMap.imports.vue) {
importMap.imports.vue = store.state.vueRuntimeURL
}
const sandboxSrc = srcdoc.replace(
/<!--IMPORT_MAP-->/,
JSON.stringify(importMap)
)
sandbox.srcdoc = sandboxSrc
container.value.appendChild(sandbox)

proxy = new PreviewProxy(sandbox, {
on_fetch_progress: (progress: any) => {
// pending_imports = progress;
},
on_error: (event: any) => {
const msg =
event.value instanceof Error ? event.value.message : event.value
if (
msg.includes('Failed to resolve module specifier') ||
msg.includes('Error resolving module specifier')
) {
runtimeError.value =
msg.replace(/\. Relative references must.*$/, '') +
`.\nTip: edit the "Import Map" tab to specify import paths for dependencies.`
} else {
runtimeError.value = event.value
}
},
on_unhandled_rejection: (event: any) => {
let error = event.value
if (typeof error === 'string') {
error = { message: error }
}
runtimeError.value = 'Uncaught (in promise): ' + error.message
},
on_console: (log: any) => {
if (log.duplicate) {
return
}
if (log.level === 'error') {
if (log.args[0] instanceof Error) {
runtimeError.value = log.args[0].message
} else {
runtimeError.value = log.args[0]
}
} else if (log.level === 'warn') {
if (log.args[0].toString().includes('[Vue warn]')) {
runtimeWarning.value = log.args
.join('')
.replace(/\[Vue warn\]:/, '')
.trim()
}
}
},
on_console_group: (action: any) => {
// group_logs(action.label, false);
},
on_console_group_end: () => {
// ungroup_logs();
},
on_console_group_collapsed: (action: any) => {
// group_logs(action.label, true);
}
})

sandbox.addEventListener('load', () => {
proxy.handle_links()
stopUpdateWatcher = watchEffect(updatePreview)
})
}

结论

更换Vue版本时,从unpkg获取了构建完毕的Vue runtime, 并重建了用于渲染结果的iframe

参考 & 引用

https://sfc.vuejs.org/

core/packages/sfc-playground/src at main · vuejs/core (github.com)

vuejs/repl: Vue SFC REPL as a Vue 3 component (github.com)