vue远程加载sfc组件思路详解
程序员文章站
2022-07-06 18:10:07
问题
在我们的 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) // 点我 } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 小和尚念经歇后语
下一篇: 十五个吊桶打水的歇后语