<template>
<nav>
<h1>Vue SFC Playground</h1>
-
- <button class="share" @click="copyLink">
- <svg width="1.4em" height="1.4em" viewBox="0 0 24 24">
- <g fill="none" stroke="#626262" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
- <circle cx="18" cy="5" r="3"/>
- <circle cx="6" cy="12" r="3"/>
- <circle cx="18" cy="19" r="3"/>
- <path d="M8.59 13.51l6.83 3.98"/>
- <path d="M15.41 6.51l-6.82 3.98"/>
- </g>
- </svg>
- </button>
-
- <button class="download" @click="downloadProject">
- <svg width="1.7em" height="1.7em" viewBox="0 0 24 24">
- <g fill="#626262">
- <rect x="4" y="18" width="16" height="2" rx="1" ry="1"/>
- <rect x="3" y="17" width="4" height="2" rx="1" ry="1" transform="rotate(-90 5 18)"/>
- <rect x="17" y="17" width="4" height="2" rx="1" ry="1" transform="rotate(-90 19 18)"/>
- <path d="M12 15a1 1 0 0 1-.58-.18l-4-2.82a1 1 0 0 1-.24-1.39a1 1 0 0 1 1.4-.24L12 12.76l3.4-2.56a1 1 0 0 1 1.2 1.6l-4 3a1 1 0 0 1-.6.2z"/><path d="M12 13a1 1 0 0 1-1-1V4a1 1 0 0 1 2 0v8a1 1 0 0 1-1 1z"/>
- </g>
- </svg>
- </button>
+ <div class="links">
+ <a class="commit-link" :href="`https://github.com/vuejs/vue-next/tree/${commit}`" target="_blank">
+ vue@{{ commit }}
+ </a>
+ <a class="commit-link" href="https://app.netlify.com/sites/vue-sfc-playground/deploys" target="_blank">
+ History
+ </a>
+ <button class="share" @click="copyLink">
+ <svg width="1.4em" height="1.4em" viewBox="0 0 24 24">
+ <g fill="none" stroke="#626262" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+ <circle cx="18" cy="5" r="3"/>
+ <circle cx="6" cy="12" r="3"/>
+ <circle cx="18" cy="19" r="3"/>
+ <path d="M8.59 13.51l6.83 3.98"/>
+ <path d="M15.41 6.51l-6.82 3.98"/>
+ </g>
+ </svg>
+ </button>
+ <button class="download" @click="downloadProject">
+ <svg width="1.7em" height="1.7em" viewBox="0 0 24 24">
+ <g fill="#626262">
+ <rect x="4" y="18" width="16" height="2" rx="1" ry="1"/>
+ <rect x="3" y="17" width="4" height="2" rx="1" ry="1" transform="rotate(-90 5 18)"/>
+ <rect x="17" y="17" width="4" height="2" rx="1" ry="1" transform="rotate(-90 19 18)"/>
+ <path d="M12 15a1 1 0 0 1-.58-.18l-4-2.82a1 1 0 0 1-.24-1.39a1 1 0 0 1 1.4-.24L12 12.76l3.4-2.56a1 1 0 0 1 1.2 1.6l-4 3a1 1 0 0 1-.6.2z"/>
+ <path d="M12 13a1 1 0 0 1-1-1V4a1 1 0 0 1 2 0v8a1 1 0 0 1-1 1z"/>
+ </g>
+ </svg>
+ </button>
+ </div>
</nav>
</template>
<script setup lang="ts">
import { downloadProject } from './download/download'
+const commit = __COMMIT__
+
function copyLink() {
navigator.clipboard.writeText(location.href)
alert('Sharable URL has been copied to clipboard.')
box-shadow: 0 0 4px rgba(0, 0, 0, 0.33);
position: relative;
z-index: 999;
+ display: flex;
+ justify-content: space-between;
}
h1 {
margin: 0;
line-height: var(--nav-height);
font-weight: 500;
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.commit-link {
+ color: var(--color-branding);
+ text-decoration: none;
+ margin-left: 6px;
+ vertical-align: middle;
+ line-height: var(--nav-height);
}
.share {
- position: absolute;
- top: 14px;
- right: 56px;
+ position: relative;
+ top: 6px;
}
.download {
- position: absolute;
- top: 13px;
- right: 16px;
+ position: relative;
+ top: 8px;
+}
+
+.commit-link {
+ margin: 0 5px;
}
</style>
const modules = compileModulesForPreview()
console.log(`successfully compiled ${modules.length} modules.`)
// reset modules
- await proxy.eval(`
- window.__modules__ = {}
- window.__css__ = ''
- `)
- // evaluate modules
- for (const mod of modules) {
- await proxy.eval(mod)
- }
- // reboot
- await proxy.eval(`
- import { createApp as _createApp } from "${SANDBOX_VUE_URL}"
- if (window.__app__) {
- window.__app__.unmount()
- document.getElementById('app').innerHTML = ''
- }
- document.getElementById('__sfc-styles').innerHTML = window.__css__
- const app = window.__app__ = _createApp(__modules__["${MAIN_FILE}"].default)
- app.config.errorHandler = e => console.error(e)
- app.mount('#app')
- `)
+ await proxy.eval([
+ `window.__modules__ = {};window.__css__ = ''`,
+ ...modules,
+ `
+import { createApp as _createApp } from "${SANDBOX_VUE_URL}"
+
+if (window.__app__) {
+ window.__app__.unmount()
+ document.getElementById('app').innerHTML = ''
+}
+
+document.getElementById('__sfc-styles').innerHTML = window.__css__
+const app = window.__app__ = _createApp(__modules__["${MAIN_FILE}"].default)
+app.config.errorHandler = e => console.error(e)
+app.mount('#app')`.trim()
+ ])
} catch (e) {
runtimeError.value = e.stack
}
}
}
- eval(script: string) {
+ eval(script: string | string[]) {
return this.iframe_command('eval', { script })
}
</style>
<style id="__sfc-styles"></style>
<script type="module">
- let scriptEl
+ let scriptEls = []
window.__modules__ = {}
return Promise.resolve(window.__modules__[key])
}
- function handle_message(ev) {
+ async function handle_message(ev) {
let { action, cmd_id } = ev.data;
const send_message = (payload) => parent.postMessage( { ...payload }, ev.origin);
const send_reply = (payload) => send_message({ ...payload, cmd_id });
- const send_ok = window.send_ok = () => send_reply({ action: 'cmd_ok' });
+ const send_ok = () => send_reply({ action: 'cmd_ok' });
const send_error = (message, stack) => send_reply({ action: 'cmd_error', message, stack });
if (action === 'eval') {
try {
- if (scriptEl) {
- document.head.removeChild(scriptEl)
+ if (scriptEls.length) {
+ scriptEls.forEach(el => {
+ document.head.removeChild(el)
+ })
+ scriptEls.length = 0
}
- scriptEl = document.createElement('script')
- scriptEl.setAttribute('type', 'module')
- // send ok in the module script to ensure sequential evaluation
- // of multiple proxy.eval() calls
- scriptEl.innerHTML = ev.data.args.script + `\nwindow.send_ok()`
- document.head.appendChild(scriptEl)
+
+ let { script: scripts } = ev.data.args
+ if (typeof scripts === 'string') scripts = [scripts]
+
+ for (const script of scripts) {
+ const scriptEl = document.createElement('script')
+ scriptEl.setAttribute('type', 'module')
+ // send ok in the module script to ensure sequential evaluation
+ // of multiple proxy.eval() calls
+ const done = new Promise((resolve, reject) => {
+ window.__next__ = resolve
+ scriptEl.onerror = reject
+ })
+ scriptEl.innerHTML = script + `\nwindow.__next__()`
+ document.head.appendChild(scriptEl)
+ scriptEls.push(scriptEl)
+ await done
+ }
+ window.__next__ = undefined
+ send_ok()
} catch (e) {
send_error(e.message, e.stack);
}
const savedFiles = location.hash.slice(1)
if (savedFiles) {
- const saved = JSON.parse(decodeURIComponent(savedFiles))
+ const saved = JSON.parse(atob(savedFiles))
for (const filename in saved) {
files[filename] = new File(filename, saved[filename])
}
}
watchEffect(() => {
- location.hash = encodeURIComponent(JSON.stringify(exportFiles()))
+ history.replaceState({}, '', '#' + btoa(JSON.stringify(exportFiles())))
})
export function exportFiles() {
import path from 'path'
import { defineConfig, Plugin } from 'vite'
import vue from '@vitejs/plugin-vue'
+import execa from 'execa'
+
+const commit = execa.sync('git', ['rev-parse', 'HEAD']).stdout.slice(0, 7)
export default defineConfig({
plugins: [vue(), copyVuePlugin()],
+ define: {
+ __COMMIT__: JSON.stringify(commit)
+ },
optimizeDeps: {
exclude: ['consolidate']
}
function copyVuePlugin(): Plugin {
return {
name: 'copy-vue',
- generateBundle(_opts, bundle) {
+ generateBundle() {
const filePath = path.resolve(
__dirname,
'../vue/dist/vue.runtime.esm-browser.js'