vue 遠程加載sfc組件思路

問題

在咱們的 vue 項目中(特別是後臺系統),總會出現一些須要多業務線共同開發同一個項目的場景,若是各業務團隊向項目中提供一些公共業務組件,可是這些組件並不能和項目一塊兒打包,由於項目中不能由於某個私有模塊的頻繁變動而重複構建發佈。javascript

^_^不建議在生產環境使用,代碼包含eval 

思路

在這種場景下咱們須要將公共的業務組件部署到服務端,由客戶端請求並渲染組件。
css

服務端解析.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) // 點我 }
}複製代碼
相關文章
相關標籤/搜索