wip(vapor): per-file vapor support in sfc playground

This commit is contained in:
Evan You 2025-02-03 12:22:45 +08:00
parent c2a91a8daf
commit aa84afc199
No known key found for this signature in database
GPG Key ID: 00E9AB7A6704CE0A
8 changed files with 26 additions and 74 deletions

View File

@ -13,7 +13,7 @@
"vite": "catalog:" "vite": "catalog:"
}, },
"dependencies": { "dependencies": {
"@vue/repl": "^4.4.3", "@vue/repl": "^4.5.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"vue": "workspace:*" "vue": "workspace:*"

View File

@ -5,11 +5,10 @@ import {
type SFCOptions, type SFCOptions,
useStore, useStore,
useVueImportMap, useVueImportMap,
File,
StoreState, StoreState,
} from '@vue/repl' } from '@vue/repl'
import Monaco from '@vue/repl/monaco-editor' import Monaco from '@vue/repl/monaco-editor'
import { ref, watchEffect, onMounted, computed, watch } from 'vue' import { ref, watchEffect, onMounted, computed } from 'vue'
const replRef = ref<InstanceType<typeof Repl>>() const replRef = ref<InstanceType<typeof Repl>>()
@ -20,7 +19,6 @@ window.addEventListener('resize', setVH)
setVH() setVH()
const useSSRMode = ref(false) const useSSRMode = ref(false)
const useVaporMode = ref(true)
const AUTO_SAVE_STORAGE_KEY = 'vue-sfc-playground-auto-save' const AUTO_SAVE_STORAGE_KEY = 'vue-sfc-playground-auto-save'
const initAutoSave: boolean = JSON.parse( const initAutoSave: boolean = JSON.parse(
@ -31,16 +29,12 @@ const autoSave = ref(initAutoSave)
const { vueVersion, productionMode, importMap } = useVueImportMap({ const { vueVersion, productionMode, importMap } = useVueImportMap({
runtimeDev: () => { runtimeDev: () => {
return import.meta.env.PROD return import.meta.env.PROD
? useVaporMode.value ? `${location.origin}/vue.runtime-with-vapor.esm-browser.js`
? `${location.origin}/vue.runtime-with-vapor.esm-browser.js`
: `${location.origin}/vue.runtime.esm-browser.js`
: `${location.origin}/src/vue-dev-proxy` : `${location.origin}/src/vue-dev-proxy`
}, },
runtimeProd: () => { runtimeProd: () => {
return import.meta.env.PROD return import.meta.env.PROD
? useVaporMode.value ? `${location.origin}/vue.runtime-with-vapor.esm-browser.prod.js`
? `${location.origin}/vue.runtime-with-vapor.esm-browser.prod.js`
: `${location.origin}/vue.runtime.esm-browser.prod.js`
: `${location.origin}/src/vue-dev-proxy-prod` : `${location.origin}/src/vue-dev-proxy-prod`
}, },
serverRenderer: import.meta.env.PROD serverRenderer: import.meta.env.PROD
@ -61,10 +55,6 @@ if (hash.startsWith('__SSR__')) {
hash = hash.slice(7) hash = hash.slice(7)
useSSRMode.value = true useSSRMode.value = true
} }
if (hash.startsWith('__VAPOR__')) {
hash = hash.slice(9)
useVaporMode.value = true
}
const files: StoreState['files'] = ref(Object.create(null)) const files: StoreState['files'] = ref(Object.create(null))
@ -75,13 +65,13 @@ const sfcOptions = computed(
inlineTemplate: productionMode.value, inlineTemplate: productionMode.value,
isProd: productionMode.value, isProd: productionMode.value,
propsDestructure: true, propsDestructure: true,
vapor: useVaporMode.value, // vapor: useVaporMode.value,
}, },
style: { style: {
isProd: productionMode.value, isProd: productionMode.value,
}, },
template: { template: {
vapor: useVaporMode.value, // vapor: useVaporMode.value,
isProd: productionMode.value, isProd: productionMode.value,
compilerOptions: { compilerOptions: {
isCustomElement: (tag: string) => isCustomElement: (tag: string) =>
@ -103,38 +93,10 @@ const store = useStore(
// @ts-expect-error // @ts-expect-error
globalThis.store = store globalThis.store = store
watch(
useVaporMode,
() => {
if (useVaporMode.value) {
files.value['src/index.html'] = new File(
'src/index.html',
`<script type="module">
import { createVaporApp } from 'vue'
import App from './App.vue'
createVaporApp(App).mount('#app')` +
'<' +
'/script>' +
`<div id="app"></div>`,
true,
)
store.mainFile = 'src/index.html'
} else if (files.value['src/index.html']?.hidden) {
delete files.value['src/index.html']
store.mainFile = 'src/App.vue'
if (store.activeFile.filename === 'src/index.html') {
store.activeFile = files.value['src/App.vue']
}
}
},
{ immediate: true },
)
// persist state // persist state
watchEffect(() => { watchEffect(() => {
const newHash = store const newHash = store
.serialize() .serialize()
.replace(/^#/, useVaporMode.value ? `#__VAPOR__` : `#`)
.replace(/^#/, useSSRMode.value ? `#__SSR__` : `#`) .replace(/^#/, useSSRMode.value ? `#__SSR__` : `#`)
.replace(/^#/, productionMode.value ? `#__PROD__` : `#`) .replace(/^#/, productionMode.value ? `#__PROD__` : `#`)
history.replaceState({}, '', newHash) history.replaceState({}, '', newHash)
@ -148,10 +110,6 @@ function toggleSSR() {
useSSRMode.value = !useSSRMode.value useSSRMode.value = !useSSRMode.value
} }
function toggleVapor() {
useVaporMode.value = !useVaporMode.value
}
function toggleAutoSave() { function toggleAutoSave() {
autoSave.value = !autoSave.value autoSave.value = !autoSave.value
localStorage.setItem(AUTO_SAVE_STORAGE_KEY, String(autoSave.value)) localStorage.setItem(AUTO_SAVE_STORAGE_KEY, String(autoSave.value))
@ -179,14 +137,12 @@ onMounted(() => {
:store="store" :store="store"
:prod="productionMode" :prod="productionMode"
:ssr="useSSRMode" :ssr="useSSRMode"
:vapor="useVaporMode"
:autoSave="autoSave" :autoSave="autoSave"
:theme="theme" :theme="theme"
@toggle-theme="toggleTheme" @toggle-theme="toggleTheme"
@toggle-prod="toggleProdMode" @toggle-prod="toggleProdMode"
@toggle-ssr="toggleSSR" @toggle-ssr="toggleSSR"
@toggle-autosave="toggleAutoSave" @toggle-autosave="toggleAutoSave"
@toggle-vapor="toggleVapor"
@reload-page="reloadPage" @reload-page="reloadPage"
/> />
<Repl <Repl
@ -204,8 +160,10 @@ onMounted(() => {
:clearConsole="false" :clearConsole="false"
:preview-options="{ :preview-options="{
customCode: { customCode: {
importCode: `import { initCustomFormatter } from 'vue'`, importCode: `import { initCustomFormatter, vaporInteropPlugin } from 'vue'`,
useCode: `if (window.devtoolsFormatters) { useCode: `
app.use(vaporInteropPlugin)
if (window.devtoolsFormatters) {
const index = window.devtoolsFormatters.findIndex((v) => v.__vue_custom_formatter) const index = window.devtoolsFormatters.findIndex((v) => v.__vue_custom_formatter)
window.devtoolsFormatters.splice(index, 1) window.devtoolsFormatters.splice(index, 1)
initCustomFormatter() initCustomFormatter()

View File

@ -14,7 +14,6 @@ const props = defineProps<{
store: ReplStore store: ReplStore
prod: boolean prod: boolean
ssr: boolean ssr: boolean
vapor: boolean
autoSave: boolean autoSave: boolean
theme: 'dark' | 'light' theme: 'dark' | 'light'
}>() }>()
@ -105,14 +104,6 @@ function toggleDark() {
> >
<span>{{ prod ? 'PROD' : 'DEV' }}</span> <span>{{ prod ? 'PROD' : 'DEV' }}</span>
</button> </button>
<button
title="Toggle vapor mode"
class="toggle-vapor"
:class="{ enabled: vapor }"
@click="$emit('toggle-vapor')"
>
<span>{{ vapor ? 'VAPOR ON' : 'VAPOR OFF' }}</span>
</button>
<button <button
title="Toggle server rendering mode" title="Toggle server rendering mode"
class="toggle-ssr" class="toggle-ssr"

View File

@ -874,6 +874,7 @@ export function compileScript(
scoped: sfc.styles.some(s => s.scoped), scoped: sfc.styles.some(s => s.scoped),
isProd: options.isProd, isProd: options.isProd,
ssrCssVars: sfc.cssVars, ssrCssVars: sfc.cssVars,
vapor,
compilerOptions: { compilerOptions: {
...(options.templateOptions && ...(options.templateOptions &&
options.templateOptions.compilerOptions), options.templateOptions.compilerOptions),
@ -942,9 +943,6 @@ export function compileScript(
: `export default` : `export default`
let runtimeOptions = `` let runtimeOptions = ``
if (vapor) {
runtimeOptions += `\n __vapor: true,`
}
if (!ctx.hasDefaultExportName && filename && filename !== DEFAULT_FILENAME) { if (!ctx.hasDefaultExportName && filename && filename !== DEFAULT_FILENAME) {
const match = filename.match(/([^/\\]+)\.\w+$/) const match = filename.match(/([^/\\]+)\.\w+$/)
if (match) { if (match) {
@ -992,6 +990,10 @@ export function compileScript(
) )
ctx.s.appendRight(endOffset, `})`) ctx.s.appendRight(endOffset, `})`)
} else { } else {
// in TS, defineVaporComponent adds the option already
if (vapor) {
runtimeOptions += `\n __vapor: true,`
}
if (defaultExport || definedOptions) { if (defaultExport || definedOptions) {
// without TS, can't rely on rest spread, so we use Object.assign // without TS, can't rely on rest spread, so we use Object.assign
// export default Object.assign(__default__, { ... }) // export default Object.assign(__default__, { ... })

View File

@ -209,11 +209,10 @@ function doCompileTemplate({
const shortId = id.replace(/^data-v-/, '') const shortId = id.replace(/^data-v-/, '')
const longId = `data-v-${shortId}` const longId = `data-v-${shortId}`
const defaultCompiler = vapor const defaultCompiler = ssr
? // TODO ssr ? (CompilerSSR as TemplateCompiler)
(CompilerVapor as TemplateCompiler) : vapor
: ssr ? (CompilerVapor as TemplateCompiler)
? (CompilerSSR as TemplateCompiler)
: CompilerDOM : CompilerDOM
compiler = compiler || defaultCompiler compiler = compiler || defaultCompiler

View File

@ -3,5 +3,6 @@ import type { VaporComponent } from './component'
/*! #__NO_SIDE_EFFECTS__ */ /*! #__NO_SIDE_EFFECTS__ */
export function defineVaporComponent(comp: VaporComponent): VaporComponent { export function defineVaporComponent(comp: VaporComponent): VaporComponent {
// TODO type inference // TODO type inference
comp.__vapor = true
return comp return comp
} }

View File

@ -39,6 +39,7 @@ const vaporInVDOMInterface: VaporInVDOMInterface = {
update(n1: VNode, n2: VNode) { update(n1: VNode, n2: VNode) {
n2.component = n1.component n2.component = n1.component
// TODO if has patchFlag, do simple diff to skip unnecessary updates
;(n2.component as any as VaporComponentInstance).rawPropsRef!.value = ;(n2.component as any as VaporComponentInstance).rawPropsRef!.value =
n2.props n2.props
}, },

View File

@ -229,8 +229,8 @@ importers:
packages-private/sfc-playground: packages-private/sfc-playground:
dependencies: dependencies:
'@vue/repl': '@vue/repl':
specifier: ^4.4.3 specifier: ^4.5.0
version: 4.4.3 version: 4.5.0
file-saver: file-saver:
specifier: ^2.0.5 specifier: ^2.0.5
version: 2.0.5 version: 2.0.5
@ -1531,8 +1531,8 @@ packages:
'@vue/reactivity@3.5.13': '@vue/reactivity@3.5.13':
resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==} resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==}
'@vue/repl@4.4.3': '@vue/repl@4.5.0':
resolution: {integrity: sha512-MKIaWgmpaDSfcQrzgsoEFW4jpFbdPYFDn9LBvXFQqEUcosheP9IoUcj/u4omp72oxsecFF5YO4/ssp4aaR8e+g==} resolution: {integrity: sha512-nWQfTzBePs5zN4qIK+vwEMEDHCuWWx2AY0utun37cSD2Qi4C84dlTtO/OL0xDzBB8Ob7250KYzIzDP3N3l3qLg==}
'@vue/runtime-core@3.5.13': '@vue/runtime-core@3.5.13':
resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==} resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==}
@ -4730,7 +4730,7 @@ snapshots:
dependencies: dependencies:
'@vue/shared': 3.5.13 '@vue/shared': 3.5.13
'@vue/repl@4.4.3': {} '@vue/repl@4.5.0': {}
'@vue/runtime-core@3.5.13': '@vue/runtime-core@3.5.13':
dependencies: dependencies: