vue3-core/packages/sfc-playground/src/store.ts

187 lines
4.2 KiB
TypeScript
Raw Normal View History

2021-03-28 13:35:45 +08:00
import { reactive, watchEffect } from 'vue'
import {
parse,
compileTemplate,
compileStyleAsync,
compileScript,
rewriteDefault,
CompilerError
} from '@vue/compiler-sfc'
const storeKey = 'sfc-code'
2021-03-28 14:24:25 +08:00
const saved =
localStorage.getItem(storeKey) ||
`
<template>
<h1>{{ msg }}</h1>
</template>
<script setup>
const msg = 'Hello World!'
</script>
<style scoped>
h1 {
color: #42b983;
}
</style>
`.trim()
2021-03-28 13:35:45 +08:00
// @ts-ignore
export const sandboxVueURL = import.meta.env.PROD
? '/vue.runtime.esm-browser.js' // to be copied on build
: '/src/vue-dev-proxy'
export const store = reactive({
code: saved,
compiled: {
executed: '',
js: '',
css: '',
template: ''
},
errors: [] as (string | CompilerError | SyntaxError)[]
})
const filename = 'Playground.vue'
const id = 'scope-id'
const compIdentifier = `__comp`
watchEffect(async () => {
const { code, compiled } = store
if (!code.trim()) {
return
}
localStorage.setItem(storeKey, code)
const { errors, descriptor } = parse(code, { filename, sourceMap: true })
if (errors.length) {
store.errors = errors
return
}
const hasScoped = descriptor.styles.some(s => s.scoped)
let finalCode = ''
if (
(descriptor.script && descriptor.script.lang) ||
(descriptor.scriptSetup && descriptor.scriptSetup.lang) ||
descriptor.styles.some(s => s.lang) ||
(descriptor.template && descriptor.template.lang)
) {
store.errors = [
'lang="x" pre-processors are not supported in the in-browser playground.'
]
return
}
// script
if (descriptor.script || descriptor.scriptSetup) {
try {
const compiledScript = compileScript(descriptor, {
id,
refSugar: true,
inlineTemplate: true
})
compiled.js = compiledScript.content.trim()
finalCode +=
`\n` +
rewriteDefault(
rewriteVueImports(compiledScript.content),
compIdentifier
)
} catch (e) {
store.errors = [e]
return
}
} else {
compiled.js = ''
finalCode += `\nconst ${compIdentifier} = {}`
}
// template
if (descriptor.template && !descriptor.scriptSetup) {
const templateResult = compileTemplate({
source: descriptor.template.content,
filename,
id,
scoped: hasScoped,
slotted: descriptor.slotted,
isProd: false
})
if (templateResult.errors.length) {
store.errors = templateResult.errors
return
}
compiled.template = templateResult.code.trim()
2021-03-28 14:17:55 +08:00
finalCode +=
`\n` +
rewriteVueImports(templateResult.code).replace(
/\nexport (function|const) render/,
'$1 render'
)
2021-03-28 13:35:45 +08:00
finalCode += `\n${compIdentifier}.render = render`
} else {
compiled.template = descriptor.scriptSetup
? '/* inlined in JS (script setup) */'
: '/* no template present */'
}
if (hasScoped) {
finalCode += `\n${compIdentifier}.__scopeId = ${JSON.stringify(
`data-v-${id}`
)}`
}
// styles
let css = ''
for (const style of descriptor.styles) {
if (style.module) {
// TODO error
continue
}
const styleResult = await compileStyleAsync({
source: style.content,
filename,
id,
scoped: style.scoped,
modules: !!style.module
})
if (styleResult.errors.length) {
// postcss uses pathToFileURL which isn't polyfilled in the browser
// ignore these errors for now
if (!styleResult.errors[0].message.includes('pathToFileURL')) {
store.errors = styleResult.errors
}
// proceed even if css compile errors
} else {
css += styleResult.code + '\n'
}
}
if (css) {
compiled.css = css.trim()
finalCode += `\ndocument.getElementById('__sfc-styles').innerHTML = ${JSON.stringify(
css
)}`
} else {
compiled.css = ''
}
store.errors = []
if (finalCode) {
compiled.executed =
`/* Exact code being executed in the preview iframe (different from production bundler output) */\n` +
finalCode
}
})
// TODO use proper parser
function rewriteVueImports(code: string): string {
return code.replace(
/\b(import \{.*?\}\s+from\s+)(?:"vue"|'vue')/g,
`$1"${sandboxVueURL}"`
)
}