你已经是一个成熟的组件了,应该学会去渲染其他组件。
概述
这个组件的功能类似于codepen,jsfiddle等,也就是代码的即时预览,能读取vue单文件组件的代码,并根据代码创建组件实例,对组件进行渲染。
DEMO地址(Sandbox)
实现
单页面组件的结构由template
、script
与style
三种元素组成,所以首先得将这三块分离,再进行实例化、组合等操作。
获取元素内容
元素标签的格式为<name key="value" key>
的形式,在这里我们不需要获取key
和value
,只需要辨认出三种元素就行了,因为我们需要获取的是元素标签括起来的内容,标签本身是不需要的。
这里可以用正则表达式解决,<元素名[^>]*>
这一段正则将会匹配任意元素的起始标签,[^>]*
表示不为>
的任意多个(零次或以上)字符,可以完美处理<style>
或<style scoped>
情况。
在获取起始标签的位置后,从标签结尾开始进行slice
,直到</style>
结束,得到的就是元素标签的内容,代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14getContent (code, tag) {
const startRegex = new RegExp(`<${tag}[^>]*>`)
let startTag = code.match(startRegex)
if (!startTag) {
return ''
}
startTag = startTag[0]
const startPos = code.indexOf(startTag)
return code.slice(startPos + startTag.length, code.indexOf(`</${tag}>`))
}
this.getContent(this.vue, 'template')
this.getContent(this.vue, 'script')
this.getContent(this.vue, 'style')
实例化组件
由于要渲染的目标是vue组件,现在得到了三部分的代码,因此可以将template与script整合成vue options的形式,交由Vue.extends
进行组件实例化,options的结构大概是这样的:1
2
3
4
5
6
7
8{
template: String,
data () {
return {}
},
props: Object
// ...
}
实际上就是vue的非单文件写法,可以参考官方文档。这里可以看得出,只要将先前获取的template与script内容结合,就能组合出options。
template和script的内容都是字符串,template能直接放入options中,而script需要反序列化成一个对象。
这里使用new Function
对script代码进行反序列化,new Function
接受n个参数构造一个函数,前n-1个参数被视作函数的参数,第n个参数为函数的内容,为了将文本形式的对象转化成对象,可以写成return 文本对象
的形式。
由于script部分通常使用export default
进行导出,我们可以将export default
替换成return
作为函数的内容。
(不使用JSON.parse
的原因是文本形式的对象并不是JSON的形式的,格式不符,所以需要通过new Function
或eval
去动态执行代码,得到对象)
接着使用Vue.extend(options)
根据options创建出组件的构造方法,使用new Component().$mount()
挂载组件后得到的就是组件的实例,最后将实例的$el插入DOM中就完成了组件的实例化与挂载,代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14if (!this.template || !this.script) {
return
}
// 将文本形式的代码转换为options对象
const options = new Function(`${this.script.replace('export default', 'return ')}`)()
// 将template存入options中
options.template = this.template
// 生成组件构造函数
const Component = Vue.extend(options)
// 通过构造函数创建组件实例并挂载
const component = new Component().$mount()
this.component = component
// 将组件的元素对象$el插入DOM中
this.$el.appendChild(this.component.$el)
样式的绑定
style的内容为css代码,可以通过创建style元素直接使用代码,并将元素放入head中,为了在更新组件样式时避免样式冲突,应该移除先前的样式元素,所以需要给样式元素添加一个唯一的id,使用watch
监听代码的变化,发生变动时先移除已有样式,再添加新样式并重新生成id。1
2
3
4
5
6
7
8const elem = document.createElement('style')
elem.type = 'text/css'
// 得到随机id
this.id = Math.random().toString(36).substring(7)
elem.id = this.id
// 通过先前获取的样式创建style元素
elem.innerHTML = this.style
document.head.appendChild(elem)