The problem

In our vue program (especially backend systems), there is always some need more lines of business to jointly develop the same scenario of the project, if the business team to the project provides some common business components, but these components are not packaged with project, project cannot because of some private module change frequently and repeat build.

^_^ not recommended in production environment, code includes eval

Train of thought

In this scenario we need to deploy common business components to the server, where the client requests and renders the components.

The server parses the. Vue file

Parsing SFC (single-file component) with vue-template-compiler template parser


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

// Get the source code of the SFC component
const str = fs.readFileSync(path.resolve(__dirname, `.. /components/sfc.vue`), 'utf-8')

// Vue-loader built-in, now used to parse SFC (single file component)
let sfc = compile.parseComponent(str)

// Obtain the SFC configuration
let sfcOptions = getComponentOption(sfc)Copy the code

GetComponentOption Obtains the SFC component configuration


import { uuid } from 'utilscore'
import stylus from 'stylus'
import sass from 'sass'
import less from 'less'
const getComponentOption = sfc= > {
    / / generated data - u - id
    const componentId = uuid(8.16).toLocaleLowerCase()    
    // Tag adds the data-u-id attribute
    const template = sfc.template ? tagToUuid(sfc.template.content, componentId) : ' '   
    Convert 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
    })
}Copy the code

TagToUuid Appends the data-u-ID to the tag in the template


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

FormatStyl handles style 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}]The ${$2}`)})}return cssText
}Copy the code

$require executes the JavaScript code and returns the value

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
} Copy the code

The client requests the component and renders it

Encapsulate front-end remote components -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 => {        
                letoptions = 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) {        
                returndata; }}, appendSty(CSS) {// Generate component styleslet 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>Copy the code

Remote Component Practices

Module. exports is used to export javascript blocks, and $require is used to import scripts

< the template > < div class = "test" > < div > < p @ click = '$emit (" handleClick, "' me ') "> remote components - {{MSG}} - {{text}} < / p > < / div > < / div > 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>Copy the code

Client-side rendering

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

// script 
methods:{
  handleClick(v){
     console.log(v) Let me} / /
}Copy the code