vue遠程加載sfc組件思路詳解
問題
在我們的 vue 項目中(特別是后臺系統(tǒng)),總會出現(xiàn)一些需要多業(yè)務線共同開發(fā)同一個項目的場景,如果各業(yè)務團隊向項目中提供一些公共業(yè)務組件,但是這些組件并不能和項目一起打包,因為項目中不能因為某個私有模塊的頻繁變更而重復構建發(fā)布。
^_^不建議在生產環(huán)境使用,代碼包含eval
思路
在這種場景下我們需要將公共的業(yè)務組件部署到服務端,由客戶端請求并渲染組件。
服務端解析.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內置,現(xiàn)在用來解析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 執(zhí)行其中的的 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中formdata傳值給后臺時參數(shù)為空的問題
這篇文章主要介紹了vue中formdata傳值給后臺時參數(shù)為空的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06vue 使用Jade模板寫html,stylus寫css的方法
這篇文章主要介紹了vue 使用Jade模板寫html,stylus寫css的方法,文中還給大家提到了使用jade注意事項,需要的朋友可以參考下2018-02-02vue項目中vue-i18n和element-ui國際化開發(fā)實現(xiàn)過程
這篇文章主要介紹了vue項目中vue-i18n和element-ui國際化開發(fā)實現(xiàn)過程,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2018-04-04如何解決Vue3組合式API模式下動態(tài)組件不渲染問題
這篇文章主要介紹了如何解決Vue3組合式API模式下動態(tài)組件不渲染問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教<BR>2024-03-03