将字符串编译为Vue组件
我在做某个项目时遇见了一个刁钻的需求,需要将一个字符串编译成 Vue 组件。
我拿到这个需求的第一想法就是Vue的Compiler里应该有现成的API,但是很遗憾并没有。
在经过四个小时的Google, StackOverflow, NPM 搜索后,我终于找到了一个解决方案。
vue-inbrowser-compiler
当我找到这个包的时候我差点哭出来,结果又是五个小时的奋战终于把他适配了Vue3.
因为这个是Webpack, 所以我们只能曲线救国。
首先安装:
pnpm add vue-inbrowser-compiler
然后我试了一下文档的Demo, 并不能用,输出一看脑子嗡嗡的。
{
"script": "\nconst Vue = require(\"vue\");const {pushScopeId: _pushScopeId, popScopeId: _popScopeId} = Vue\nconst __sfc__ = (function() {\nreturn {setup: function setup(){\n\n\nvar x = ref(0)\nonMounted(function () {\n x.value = 1\n})\n\n\nreturn {x: x}\nfunction defineProps(props){ return props;}\nfunction defineEmits(){ return function emit() {}}\nfunction defineExpose(){}\n}}\n})()\n __sfc__.render = function() {const { toDisplayString: _toDisplayString, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue\n\nreturn function render(_ctx, _cache, $props, $setup, $data, $options) {\n return (_openBlock(), _createElementBlock(\"div\", null, _toDisplayString(_ctx.x), 1 /* TEXT */))\n}}\n\n\n__sfc__.render = __sfc__.render()\n\nreturn __sfc__",
"setup": true,
"raw": {
"template": "\n <div>{{ x }}</div>\n",
"script": "\nreturn {setup(){\n\n\nconst x = ref(0)\nonMounted(() => {\n x.value = 1\n})\n\n\nreturn {x}\nfunction defineProps(props){ return props;}\nfunction defineEmits(){ return function emit() {}}\nfunction defineExpose(){}\n}}\n",
"setup": true
}
}
注意这里的const Vue = require("vue")
, 这个Vue是CommonJS的模块,而我们通常使用的是ESM的模块。所以我想了一个大胆的方式,直接用正则给替换掉!
script = script.replace(/const Vue = require\("vue"\)/g, '')
那么Vue从哪里来呢,当然是全局!也只有这种方法了呜呜呜QAQ!!!!!
<script src="https://unpkg.com/[email protected]/dist/vue.global.js"></script>
Vue有个打包版本叫vue.global.js
, 这个版本是全局暴露Vue
对象的,所以我们可以直接使用。
然后我们发现vite默认的vue不是esm-bundler, 会报错,所以我们要设置alias:
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'vue': 'vue/dist/vue.esm-bundler.js'
}
}
})
完成这些就可以啦!
接下来贴上测试代码:
<script setup lang="ts">
import { defineComponent } from 'vue';
import { compile } from 'vue-inbrowser-compiler';
const result = compile(
`<script setup>
const x = ref(0)
onMounted(() => {
x.value = 1
})
<\/script>
<template>
<div>{{ x }}</div>
</template>
`)
console.log(result)
const component = defineComponent(Function(result.script.replace('const Vue = require("vue");', ''))())
console.log(component)
</script>
<template>
<component :is="component" />
</template>