一种在线预览Vue组件的思路
最近在研究一些低代码平台,设想了一种面向开发者的低代码编辑器,本文是印证这个想法的第一步,即在线预览单个组件文件。本文以Vue技术栈为例,尝试实现一个可以在线预览Vue组件的功能。
背景
拿低代码比较常用的一个场景:活动页面来举例。如果是纯手工编码,需要经历
- 启动本地开发环境
- 编写代码、调试代码
- 提交代码、CI打包、部署
不论页面的复杂程度,都需要经历这几个步骤,哪怕仅仅只是修改几个文案...
按照我的想法,开发者只需要编写一个页面组件文件,可以直接预览页面,在开发完毕后,点击一下保存就行了,最后的产出是一个完全用于生产环境的页面链接。
由于是代码编写,可以绕开低代码平台最大的缺点:不够灵活、很难扩展;也可以使用低代码平台的一些优点:开发迅速,所见及所得。
要解决这个问题,第一步就是要实现在开发时能够预览组件。
先从最简单的开始,下面是一个不依赖于外部模块的基础SFC文件
<template>
<div>
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
data() {
return {
msg: 'Hello world!'
}
}
}
</script>
<style lang='scss'>
h1 {
color: red;
}
</style>
如何在浏览器中直接预览这个组件呢?实际上vue-cli本身是提供了打包单个文件的功能的,参考:构建目标
使用下面命令,将test1.vue
文件打包成commonjs和umd模块文件,css文件会单独打包成一个文件
vue build --target lib test1.vue
最后还会生成一个demo.html文件,可以直接预览改组件内容
![image-20211207213835916](/Users/bcz/Library/Application Support/typora-user-images/image-20211207213835916.png)
对于单个sfc文件,这种方式看起来还是比较简便的,打包后直接使用对应的umd模块就可以了。
我们接下来要研究的是:直接在浏览器预览sfc组件。
一些实现思路参考
ElementUI组件库文档
ElementUI的文档网站是放在源码仓库的,因此可以看一下这个文档的源码,了解它是如何实现在线展示组件的。
以Button组件为例,找到其文档源码。从element/examples/route.config.js
文件开始,在registerRoute
找到页面路由的注册方式,最后找到button
组件的文档,可以看见其页面内容是用markdown组件编写的,红框标注的代码块最后会被渲染成可交互的页面组件
在注册路由组建时,通过loadDocs
方法引入markdown文档,并将其作为页面组件。
我们知道md文件是不能直接被js模块识别的,这里是使用了element/bin/md-loader
实现的,将markdown内容进行解析,获取code标签的代码,并转换成一个SFC组件的内容,包含script
和template
标签
上面展示了一个组件库文档实现markdowdn中代码直接渲染成组件的思路,但在ElementUI文档中的组件示例,并没有提供直接编辑修改的功能,取而代之的是提供了一个跳转到codepen在线运行的方法
我们甚至可以看看这种动态拼接代码然后跳转到codepen实现代码编辑的方法
实际上是构建了一个post form表单提交。
codepen在线JS工具
打开codepen的控制台,可以发现他的页面结构
可以看见编写的HTML、CSS、JS等代码都会被写到iframe对应的位置,由于js依赖于Vue库,还通过cdn script的形式插入到用户代码前面。
启动iframe的好处是可以获得一个隔离的沙盒,并且可以通过父子window通信实现代码的动态更新。
这种方式实际上是绕开了SFC的限制,在Vue runtime编译template并渲染render函数。跟下面这种写法实际上差不多
<style>
h1 {
color: red;
}
</style>
<script>
new Vue({
el: "#app",
template: `
<div>
<h1>{{ msg }}</h1>
</div>
`,
data() {
return {
msg: "Hello Vue",
};
},
});
</script>
那么对于SFC组件,看起来分别编写template
、style
和script
然后通过iframe运行是没有问题的,那么问题就变成了:如何在浏览器中解析SFC组件?
stroybook
storybook是一个UI组件的开发环境,官网上的文档介绍确实比较简陋,可以看看Introduction to Storybook这篇博客介绍。
使用@vue/compile-sfc编译单个文件
前面提到Vue本身是支持直接传入template
配置项的,因此只需要将SFC中的template
、script
和style
标签解析出来,然后重新构建一个Vue组件就可以
当然,从头解析sfc不是很合理,不要重复造轮子,最简单的做法是使用@vue/compile-sfc
,先看看在node环境下如何实现。
const compiler = require("@vue/compiler-sfc");
const sourceCode = `
<template>
<div>
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: "test",
el: "#app",
data() {
return {
msg: "Hello Vue",
};
},
};
<\/script>
<style scoped lang="scss">
h1 {
color: red;
span {
color:blue;
}
}
</style>
`;
const ans = compiler.parse(sourceCode, {sourceMap:false});
const { template, script, styles } = ans.descriptor;
编译script和template
其中template和script都主要稍作修改就行,即将template中的内容添加到script组件的template配置项
类似于下面的样子
// 拼接一个浏览器可以运行的组件
const output = `
const config = ${script.content.replace(/export\s+default/, "")}
new Vue({
el:"#app",
template: \`${template.content}\`,
...config
})
`;
如果需要考虑运行环境,需要再加一个babel
编译成ES5,甚至还可以加uglyJS
。
jsResult = babel.transformSync(jsResult, {
presets: ["@babel/preset-env"],
}).code;
除了template
配置项这一种方式外,还可以直接将templte编译成render方法,这里先不展开了。
编译styles
如果是单纯的css内容,直接创建一个style标签,将文件中的内容作为textContent赋值即可。但由于SFC的style标签通过lang='xxx'
属性,支持多种CSS预编译语法,下面以lang='scss'
为例
[
{
type: 'style',
content: '\nh1 {\n color: red;\n span {\n color:blue;\n }\n}\n',
loc: {
source: '\nh1 {\n color: red;\n span {\n color:blue;\n }\n}\n',
start: [Object],
end: [Object]
},
attrs: { scoped: true, lang: 'scss' },
scoped: true,
lang: 'scss'
}
]
对于这段style标签解析出来的scss代码,还需要单独编译一下,这里使用sass
for (const style of styles) {
console.log(style.content);
const result = sass.renderSync({ data: style.content });
console.log(result.css.toString());
}
可以看见scss代码已经转换成css代码了,
// 转换前
h1 {
color: red;
span {
color:blue;
}
}
// 转换后
h1 {
color: red;
}
h1 span {
color: blue;
}
如果需要SFC的scoped等特性,,可以使用@vue/compile-sfc
提供的compileStyleAsync
const id = +new Date();
for (const style of styles) {
compiler
.compileStyleAsync({
filename: descriptor.filename,
id: `data-v-${id}`,
isProd: false,
source: style.content,
scoped: style.scoped,
})
.then((res) => {
console.log(res.code);
});
}
最终输出结果
h1[data-v-1638798252945] {
color: red;
}
h1 span[data-v-1638798252945] {
color: blue;
}
使用HTTP接口代替浏览器编译
上面的@vue/compile-sfc
、babel
、sass
等模块不能完全用在浏览器环境中,因此可以提供一个HTTP接口,用来将sfc的代码转换成可以直接在浏览器运行的内容
router.post("/transform_vue_sfc", async (ctx) => {
const { code } = ctx.request.body;
// 将上面sfc的编译结果返回
const result = await parse(code);
ctx.body = {
code: 200,
msg: "success",
data: result,
};
});
这样就可以在线预览了,
在浏览器端编译
尽管最后看起来跟vue build
的结果差别不是很大,但却是我们解析了SFC内容之后手动处理的,只要我们把在浏览器中执行这些逻辑的功能实现就大功告成了。
@vue/compile-sfc的替代品
首先是如何直接在浏览器端解析模板内容,看了下@vue/compile-sfc
应该是不支持浏览器端的。由于*.vue
文件内容是通过三种标签来区分的,因此可以通过手动即系标签内容来,当然用jQuery
看起来也可以
const $data = $(`<div>${sourceCode}</div>`);
const template = $data.find("template").html();
const script = $data.find("script").html();
const styles = $data.find("style").html();
console.log({
template,
script,
styles
});
上面展示了如何使用jQuery快速解析sfc中的内容,当然还需要更完善的处理,比如标签不存在时兼容、多个style、style标签的lang属性等。
@babel/standalone
@babel/standalone是babel在浏览器和其他非nodeJS环境下工作的版本,比如直接在浏览器中运行JSX,就可以直接使用。
sass.js
sass.js提供了在浏览器端编译sass的功能,缺点是这个包的体积太大了(4M多)...
此外可能还需要考虑其他CSS预编译语言的支持。
看起来最省事的还是直接利用@vue/compile-sfc
和一系列node工具链完成相关的工作。
小结
回到开头的设想,如果不需要预览,也就不需要在实现在浏览器预览组件的功能,只要在最后保存的时候触发一下打包,生成对应的页面链接就可以了...
但这跟直接在本地开发有什么区别呢?当我敲下这行字的时候,我愣住了...
GG,重新想想我到底要解决什么问题?也许只是为了快速开发、打包和上线,那么直接本地开发环境编辑,然后上传一个**.vue**文件?试试vite?或者直接用vue serve xxx.vue
启动原型开发?
等后面再补充。
你要请我喝一杯奶茶?
版权声明:自由转载-非商用-保持署名和原文链接。
本站文章均为本人原创,参考文章我都会在文中进行声明,也请您转载时附上署名。