JavaScript

超轻量级php框架startmvc

vue远程加载sfc组件思路详解

更新时间:2020-09-28 13:42:01 作者:startmvc
问题在我们的vue项目中(特别是后台系统),总会出现一些需要多业务线共同开发同一个项

问题

在我们的 vue 项目中(特别是后台系统),总会出现一些需要多业务线共同开发同一个项目的场景,如果各业务团队向项目中提供一些公共业务组件,但是这些组件并不能和项目一起打包,因为项目中不能因为某个私有模块的频繁变更而重复构建发布。

^_^不建议在生产环境使用,代码包含eval 

思路

在这种场景下我们需要将公共的业务组件部署到服务端,由客户端请求并渲染组件。

服务端解析.vue文件

使用vue-template-compiler 模板解析器,解析SFC(单文件组件)


const compile = require('vue-template-compiler')

// 获取sfc组件的源码
const str = fs.readFileSync(path.resolve(__dirname, `../components/sfc.vue`), 'utf-8')

// vue-loader内置,现在用来解析SFC(单文件组件)
let sfc = compile.parseComponent(str)

// 获取sfc组件配置
let sfcOptions = getComponentOption(sfc)

getComponentOption 获取sfc组件配置


import { uuid } from 'utilscore'
import stylus from 'stylus'
import sass from 'sass'
import less from 'less'
const getComponentOption = sfc => {
 // 生成data-u-id 
 const componentId = uuid(8, 16).toLocaleLowerCase() 
 // 标签添加data-u-id属性 
 const template = sfc.template ? tagToUuid(sfc.template.content, componentId) : '' 
 // 转化style(less、sass、stylus) 
 let styles = [] 
 sfc.styles.forEach(sty => { 
 switch (sty.lang) { 
 case 'stylus': 
 stylus.render(sty.content, (err, css) => styles.push(formatStyl(sty, css, componentId))) 
 break; 
 case 'sass': 
 case 'scss': 
 styles.push(formatStyl(sty, sass.renderSync({ data: sty.content }).css.toString(), componentId)) 
 break; 
 case 'less': 
 less.render(sty.content, (err, css) => styles.push(formatStyl(sty, css, componentId))) 
 break; 
 } 
 }) 
 let options = { 
 script: sfc.script ? $require(null, sfc.script.content) : {}, 
 styles, 
 template 
 } 
 return JSON.stringify(options, (k, v) => {
 if(typeof(v) === 'function') {
 let _fn = v.toString()
 return /^function()/.test(_fn) ? _fn : fn.replace(/^/,'function ')
 }
 return v
 })
}

tagToUuid  给template 中的标签追加data-u-id 


const tagToUuid = (tpl, id) => { 
 var pattern = /<[^\/]("[^"]*"|'[^']*'|[^'">])*>/g 
 return tpl.replace(pattern, $1 => { 
 return $1.replace(/<([\w\-]+)/i, ($2, $3) => `<${$3} data-u-${id}`) 
 })
}

formatStyl 处理样式的scoped


const formatStyl = (sty, css, componentId) => { 
 let cssText = css 
 if (sty.scoped) { 
 cssText = css.replace(/[\.\w\>\s]+{/g, $1 => { 
 if (/>>>/.test($1)) return $1.replace(/\s+>>>/, `[data-u-${componentId}]`) 
 return $1.replace(/\s+{/g, $2 => `[data-u-${componentId}]${$2}`) 
 }) 
 } 
 return cssText
}

$require 执行其中的的 JavaScript 代码,并返回值


const $require = (filepath, scriptContext) => {
 const filename = path.resolve(__dirname, `../${filepath}`); 
 const module = { exports: {} } 
 let code = scriptContext ? scriptContext : fs.readFileSync(filename, 'utf-8') 
 let exports = module.exports 
 code = `(function($require,module,exports,__dirname,filename){$[code]})($require,module,exports,__dirname,filename)` 
 eval(code) 
 return module.exports
} 

客户端请求组件并渲染

封装前端远程组件-remote.vue


<template> 
 <component :is="remote" v-bind="$attrs" v-on="$listeners"></component>
</template>
<script>
import Vue from "vue";
export default { 
 data() { 
 return { 
 remote: null 
 }
 }, 
 props: { 
 tagName: { 
 type: String, 
 defualt: "componentName" 
 } 
 }, 
 created() { 
 fetch("http://localhost:3000/getComponent/"+this.tagName)
 .then(res => res.json()) 
 .then(sfc => { 
 let options = this.parseObj(sfc); 
 options.styles.forEach(css => this.appendSty(css)); 
 this.remote = Vue.extend({ 
 ...options.script, 
 name: options.script.name || this.tagName, 
 template: options.template 
 }); 
 }); 
 }, 
 methods: { 
 isObject(v) { 
 return Object.prototype.toString.call(v).includes("Object"); 
 }, 
 parseObj(data) { 
 if (Array.isArray(data)) return data.map(row => this.parseObj(row)); 
 if (this.isObject(data)) { 
 let ret = {}; 
 for (let k in data) { 
 ret[k] = this.parseObj(data[k]); 
 } return ret; 
 } 
 try { 
 let pattern = /function ([\w]+)\(\) \{ \[native code\] \}/; 
 if (pattern.test(data)) { 
 return window[pattern.exec(data)[1]]; 
 } else { 
 let evalData = eval(`(${data})`); 
 return typeof evalData == "function" ? evalData : data; 
 } 
 } catch (err) { 
 return data; 
 } 
 }, 
 appendSty(css) { // 生成组件样式 
 let style = document.createElement("style"); 
 style.setAttribute("type", "text/css"); 
 var cssText = document.createTextNode(css); 
 style.appendChild(cssText); 
 var head = document.querySelector("head"); 
 head.appendChild(style); 
 } 
}};
</script>

远程组件实践

服务端sfc组件,注意javascript块要使用module.exports导出,引入脚本使用$require


<template> 
 <div class="test"> 
 <div> 
 <p @click='$emit("handleClick",'点我')'>远程组件--{{msg}}--{{text}}</p> 
 </div> 
 </div>
</template>
<script>
// 加载js脚本
let {a} = $require('utils/test.js') 
module.exports = { 
 data: function() { 
 return { 
 msg: "remote component",
 ...a,
 } 
 }, 
 props: { 
 text: { 
 type: Boolean, 
 default: true 
 } 
 },
 mounted:function(){
 console.log('prop text is',this.text)
 }
};
</script>
<style lang="stylus" scoped>
.test { 
 .test2 { 
 color: red; 
 } 
 p{ 
 color:red 
 }
}
</style>

客户端渲染


// temolate
<remote text='123456' @handleClick='handleClick'/>

// script 
methods:{
 handleClick(v){
 console.log(v) // 点我 }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

vue远程加载sfc vue 远程加载