简介
Vue Playground支持选择不同的Vue版本,实时编码并实时预览。本文主要介绍其如何实现实时编码和预览。
代码跟踪
CodeMirror.vue — vuejs/repl — GitHub1s
1 2 3 4
| editor.on('change', () => { emit('change', editor.getValue()) })
|
Editor.vue — vuejs/repl — GitHub1s
1 2 3 4
| const onChange = debounce((code: string) => { store.state.activeFile.code = code }, 250)
|
store.ts — vuejs/repl — GitHub1s
1 2
| watchEffect(() => compileFile(this, this.state.activeFile))
|
transform.ts — vuejs/repl — GitHub1s
1 2 3 4 5 6 7 8 9
|
const { errors, descriptor } = store.compiler.parse(code, { filename, sourceMap: true })
|
transform.ts — vuejs/repl — GitHub1s
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
| const clientScriptResult = await doCompileScript( store, descriptor, id, false, isTS ) clientCode += clientScriptResult ... const ssrScriptResult = await doCompileScript( store, descriptor, id, true, isTS ) ... const clientTemplateResult = await doCompileTemplate( store, descriptor, id, bindings, false, isTS ) clientCode += clientTemplateResult ... const ssrTemplateResult = await doCompileTemplate( store, descriptor, id, bindings, true, isTS ) clientCode += ssrTemplateResult ... const styleResult = await store.compiler.compileStyleAsync({ ...store.options?.style, source: style.content, filename, id, scoped: style.scoped, modules: !!style.module })
|
transform.ts — vuejs/repl — GitHub1s
1 2 3
| compiled.js = clientCode.trimStart() compiled.ssr = ssrCode.trimStart()
|
Preview.vue — vuejs/repl — GitHub1s
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 99 100 101 102 103 104 105 106 107 108 109 110 111 112
| watch( () => store.state.files['import-map.json'].code, (raw) => { try { const map = JSON.parse(raw) if (!map.imports) { store.state.errors = [`import-map.json is missing "imports" field.`] return } createSandbox() } catch (e: any) { store.state.errors = [e as Error] return } } )
...
async function updatePreview() { if (import.meta.env.PROD && clearConsole.value) { console.clear() } runtimeError.value = null runtimeWarning.value = null
let isSSR = props.ssr if (store.vueVersion) { const [_, minor, patch] = store.vueVersion.split('.') if (parseInt(minor, 10) < 2 || parseInt(patch, 10) < 27) { alert( `The selected version of Vue (${store.vueVersion}) does not support in-browser SSR.` + ` Rendering in client mode instead.` ) isSSR = false } }
try { const mainFile = store.state.mainFile
if (isSSR && mainFile.endsWith('.vue')) { const ssrModules = compileModulesForPreview(store, true) console.log( `[@vue/repl] successfully compiled ${ssrModules.length} modules for SSR.` ) await proxy.eval([ `const __modules__ = {};`, ...ssrModules, `import { renderToString as _renderToString } from 'vue/server-renderer' import { createSSRApp as _createApp } from 'vue' const AppComponent = __modules__["${mainFile}"].default AppComponent.name = 'Repl' const app = _createApp(AppComponent) app.config.unwrapInjectedRef = true app.config.warnHandler = () => {} window.__ssr_promise__ = _renderToString(app).then(html => { document.body.innerHTML = '<div id="app">' + html + '</div>' }).catch(err => { console.error("SSR Error", err) }) ` ]) }
const modules = compileModulesForPreview(store) console.log( `[@vue/repl] successfully compiled ${modules.length} module${ modules.length > 1 ? `s` : `` }.` ) const codeToEval = [ `window.__modules__ = {}\nwindow.__css__ = ''\n` + `if (window.__app__) window.__app__.unmount()\n` + (isSSR ? `` : `document.body.innerHTML = '<div id="app"></div>'`), ...modules, `document.getElementById('__sfc-styles').innerHTML = window.__css__` ]
if (mainFile.endsWith('.vue')) { codeToEval.push( `import { ${ isSSR ? `createSSRApp` : `createApp` } as _createApp } from "vue" const _mount = () => { const AppComponent = __modules__["${mainFile}"].default AppComponent.name = 'Repl' const app = window.__app__ = _createApp(AppComponent) app.config.unwrapInjectedRef = true app.config.errorHandler = e => console.error(e) app.mount('#app') } if (window.__ssr_promise__) { window.__ssr_promise__.then(_mount) } else { _mount() }` ) }
await proxy.eval(codeToEval) } catch (e: any) { runtimeError.value = (e as Error).message } }
|
结论
从moduleCompiler.ts代码中可知,编译器模块从'vue/compiler-sfc'
引入了babelParse
并基于babelParse
对用户输入的Vue
、CSS
、JS
等代码做了编译。并且Preview.vue预览器模块监听了其编译结果,当发生变化时替换预览器ifream
中的JS
脚本以体现实时预览的效果。
参考 & 引用
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)