<script setup lang="ts">
import Header from './Header.vue'
-import { Repl, ReplStore, SFCOptions } from '@vue/repl'
+import {
+ Repl,
+ type SFCOptions,
+ useStore,
+ useVueImportMap,
+ mergeImportMap,
+ File,
+ StoreState,
+} from '@vue/repl'
import type Monaco from '@vue/repl/monaco-editor'
import type CodeMirror from '@vue/repl/codemirror-editor'
-import { ref, watchEffect, onMounted } from 'vue'
-import { shallowRef } from 'vue'
+import { ref, watchEffect, onMounted, computed, shallowRef, watch } from 'vue'
const EditorComponent = shallowRef<typeof Monaco | typeof CodeMirror>()
window.addEventListener('resize', setVH)
setVH()
-const useProdMode = ref(false)
const useSSRMode = ref(false)
+const useVaporMode = ref(false)
+
+const {
+ vueVersion,
+ productionMode,
+ importMap: vueImportMap,
+} = useVueImportMap({
+ runtimeDev: import.meta.env.PROD
+ ? `${location.origin}/vue.runtime.esm-browser.js`
+ : `${location.origin}/src/vue-dev-proxy`,
+ runtimeProd: import.meta.env.PROD
+ ? `${location.origin}/vue.runtime.esm-browser.prod.js`
+ : `${location.origin}/src/vue-dev-proxy-prod`,
+ serverRenderer: import.meta.env.PROD
+ ? `${location.origin}/server-renderer.esm-browser.js`
+ : `${location.origin}/src/vue-server-renderer-dev-proxy`,
+})
+
+const importMap = computed(() =>
+ mergeImportMap(vueImportMap.value, {
+ imports: {
+ 'vue/vapor': import.meta.env.PROD
+ ? `${location.origin}/vue-vapor.esm-browser.js`
+ : `${location.origin}/src/vue-vapor-dev-proxy`,
+ },
+ }),
+)
let hash = location.hash.slice(1)
if (hash.startsWith('__DEV__')) {
hash = hash.slice(7)
- useProdMode.value = false
+ productionMode.value = false
}
if (hash.startsWith('__PROD__')) {
hash = hash.slice(8)
- useProdMode.value = true
+ productionMode.value = true
}
if (hash.startsWith('__SSR__')) {
hash = hash.slice(7)
useSSRMode.value = true
}
+if (hash.startsWith('__VAPOR__')) {
+ hash = hash.slice(9)
+ useVaporMode.value = true
+}
-const store = new ReplStore({
- serializedState: hash,
- productionMode: useProdMode.value,
- defaultVueRuntimeURL: import.meta.env.PROD
- ? `${location.origin}/vue.runtime.esm-browser.js`
- : `${location.origin}/src/vue-dev-proxy`,
- defaultVueRuntimeProdURL: import.meta.env.PROD
- ? `${location.origin}/vue.runtime.esm-browser.prod.js`
- : `${location.origin}/src/vue-dev-proxy-prod`,
- defaultVueServerRendererURL: import.meta.env.PROD
- ? `${location.origin}/server-renderer.esm-browser.js`
- : `${location.origin}/src/vue-server-renderer-dev-proxy`,
-})
+const files: StoreState['files'] = ref(Object.create(null))
+const mainFile = ref('src/App.vue')
// enable experimental features
-const sfcOptions: SFCOptions = {
- script: {
- inlineTemplate: useProdMode.value,
- isProd: useProdMode.value,
- propsDestructure: true,
- },
- style: {
- isProd: useProdMode.value,
- },
- template: {
- isProd: useProdMode.value,
- compilerOptions: {
- isCustomElement: (tag: string) => tag === 'mjx-container',
+const sfcOptions = computed(
+ (): SFCOptions => ({
+ script: {
+ inlineTemplate: productionMode.value,
+ isProd: productionMode.value,
+ propsDestructure: true,
+ },
+ style: {
+ isProd: productionMode.value,
},
+ template: {
+ vapor: useVaporMode.value,
+ isProd: productionMode.value,
+ compilerOptions: {
+ isCustomElement: (tag: string) => tag === 'mjx-container',
+ },
+ },
+ }),
+)
+
+const store = useStore(
+ {
+ files,
+ vueVersion,
+ builtinImportMap: importMap,
+ sfcOptions,
+ mainFile,
},
-}
+ hash,
+)
+// @ts-expect-error
+globalThis.store = store
+
+watch(
+ useVaporMode,
+ () => {
+ if (useVaporMode.value) {
+ files.value['src/index.html'] = new File(
+ 'src/index.html',
+ `<script type="module">
+ import { render } from 'vue/vapor'
+ import App from './App.vue'
+ render(App, {}, '#app')` +
+ '<' +
+ '/script>' +
+ `<div id="app"></div>`,
+ true,
+ )
+ mainFile.value = 'src/index.html'
+ store.activeFile = files.value['src/App.vue']
+ } else if (files.value['src/index.html']?.hidden) {
+ delete files.value['src/index.html']
+ mainFile.value = 'src/App.vue'
+ }
+ },
+ { immediate: true },
+)
// persist state
watchEffect(() => {
const newHash = store
.serialize()
+ .replace(/^#/, useVaporMode.value ? `#__VAPOR__` : `#`)
.replace(/^#/, useSSRMode.value ? `#__SSR__` : `#`)
- .replace(/^#/, useProdMode.value ? `#__PROD__` : `#`)
+ .replace(/^#/, productionMode.value ? `#__PROD__` : `#`)
history.replaceState({}, '', newHash)
})
function toggleProdMode() {
- const isProd = (useProdMode.value = !useProdMode.value)
- sfcOptions.script!.inlineTemplate =
- sfcOptions.script!.isProd =
- sfcOptions.template!.isProd =
- sfcOptions.style!.isProd =
- isProd
- store.toggleProduction()
- store.setFiles(store.getFiles())
+ productionMode.value = !productionMode.value
}
function toggleSSR() {
useSSRMode.value = !useSSRMode.value
- store.setFiles(store.getFiles())
+}
+
+function toggleVapor() {
+ useVaporMode.value = !useVaporMode.value
}
function reloadPage() {
<template>
<Header
:store="store"
- :prod="useProdMode"
+ :prod="productionMode"
:ssr="useSSRMode"
+ :vapor="useVaporMode"
@toggle-theme="toggleTheme"
@toggle-prod="toggleProdMode"
@toggle-ssr="toggleSSR"
+ @toggle-vapor="toggleVapor"
@reload-page="reloadPage"
/>
<Repl
:store="store"
:showCompileOutput="true"
:autoResize="true"
- :sfcOptions="sfcOptions"
:clearConsole="false"
:preview-options="{
customCode: {
store: ReplStore
prod: boolean
ssr: boolean
+ vapor: boolean
}>()
const emit = defineEmits([
'toggle-theme',
'toggle-ssr',
'toggle-prod',
+ 'toggle-vapor',
'reload-page',
])
const currentCommit = __COMMIT__
const vueVersion = ref(`@${currentCommit}`)
-const vueURL = store.getImportMap().imports.vue
+const vueURL = store.getImportMap().imports?.vue
if (vueURL && !vueURL.startsWith(location.origin)) {
const versionMatch = vueURL.match(/runtime-dom@([^/]+)/)
if (versionMatch) vueVersion.value = versionMatch[1]
async function setVueVersion(v: string) {
vueVersion.value = `loading...`
- await store.setVueVersion(v)
+ store.vueVersion = v
vueVersion.value = v
}
function resetVueVersion() {
- store.resetVueVersion()
+ store.vueVersion = undefined
vueVersion.value = `@${currentCommit}`
}
</h1>
<div class="links">
<VersionSelect
- v-model="store.state.typescriptVersion"
+ v-model="store.typescriptVersion"
pkg="typescript"
label="TypeScript Version"
/>
>
<span>{{ prod ? 'PROD' : 'DEV' }}</span>
</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
title="Toggle server rendering mode"
class="toggle-ssr"
}
.toggle-prod span,
+.toggle-vapor span,
.toggle-ssr span {
font-size: 12px;
border-radius: 4px;
background-color: var(--green);
}
+.toggle-vapor span {
+ background-color: var(--btn-bg);
+}
+
+.toggle-vapor.enabled span {
+ color: #fff;
+ background-color: var(--green);
+}
+
.toggle-dark svg {
width: 18px;
height: 18px;