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


Posted in Javascript onDecember 25, 2019

问题

在我们的 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) // 点我 }
}

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

Javascript 相关文章推荐
js禁止document element对象选中文本实现代码
Mar 21 Javascript
javascript Event对象详解及使用示例
Nov 22 Javascript
JS如何判断移动端访问设备并解析对应CSS
Nov 27 Javascript
js 高效去除数组重复元素示例代码
Dec 19 Javascript
jquery自动切换tabs选项卡的具体实现
Dec 24 Javascript
xmlplus组件设计系列之路由(ViewStack)(7)
May 02 Javascript
Vue和Bootstrap的整合思路详解
Jun 30 Javascript
webpack中如何使用雪碧图的示例代码
Nov 11 Javascript
electron制作仿制qq聊天界面的示例代码
Nov 26 Javascript
通过javascript实现段落的收缩与展开
Jun 26 Javascript
el-form 多层级表单的实现示例
Sep 10 Javascript
浅谈鸿蒙 JavaScript GUI 技术栈
Sep 17 Javascript
node实现mock-plugin中间件的方法
Dec 25 #Javascript
微信小程序停止其他视频播放当前视频的实例代码
Dec 25 #Javascript
vue分页插件的使用方法
Dec 25 #Javascript
继承行为在 ES5 与 ES6 中的区别详解
Dec 24 #Javascript
在JavaScript中实现链式调用的实现
Dec 24 #Javascript
vue实现分页加载效果
Dec 24 #Javascript
微信小程序如何获取地址
Dec 24 #Javascript
You might like
php防攻击代码升级版
2010/12/29 PHP
PHP获取MAC地址的函数代码
2011/09/11 PHP
php 备份数据库代码(生成word,excel,json,xml,sql)
2013/06/23 PHP
JS获取网页属性包括宽、高等等
2014/04/03 Javascript
从零学JSON之JSON数据结构
2014/05/19 Javascript
基于jquery实现的自动补全功能
2015/03/12 Javascript
JavaScript自定义等待wait函数实例分析
2015/03/23 Javascript
jQuery垂直多级导航菜单代码分享
2015/08/18 Javascript
详解TypeScript+Vue 插件 vue-class-component的使用总结
2019/02/18 Javascript
vue-cli3+typescript初体验小结
2019/02/28 Javascript
vue v-for直接循环数字实例
2019/11/07 Javascript
Python实现的Kmeans++算法实例
2014/04/26 Python
Django 中使用流响应处理视频的方法
2018/07/20 Python
python抓取网页内容并进行语音播报的方法
2018/12/24 Python
python爬虫基础教程:requests库(二)代码实例
2019/04/09 Python
PYTHON绘制雷达图代码实例
2019/10/15 Python
基于tensorflow for循环 while循环案例
2020/06/30 Python
Python读取xlsx数据生成图标代码实例
2020/08/12 Python
Python logging模块原理解析及应用
2020/08/13 Python
scrapy中如何设置应用cookies的方法(3种)
2020/09/22 Python
KARATOV珠宝在线商店:俄罗斯珠宝品牌
2019/03/13 全球购物
中东地区最大的奢侈品市场:The Luxury Closet
2019/04/09 全球购物
在Java开发中如何选择使用哪种集合类
2016/08/09 面试题
大学信息公开实施方案
2014/03/09 职场文书
课前一分钟演讲稿
2014/08/26 职场文书
2014年国庆节演讲稿
2014/09/19 职场文书
防火标语大全
2014/10/06 职场文书
委托证明范本
2014/11/25 职场文书
邮政营业员岗位职责
2015/04/14 职场文书
《金钱的魔力》教学反思
2016/02/20 职场文书
PHP对接阿里云虚拟号的实现(号码隐私保护)
2021/04/06 PHP
解决Vue+SpringBoot+Shiro跨域问题
2021/06/09 Vue.js
JVM入门之类加载与字节码技术(类加载与类的加载器)
2021/06/15 Java/Android
react使用antd的上传组件实现文件表单一起提交功能(完整代码)
2021/06/29 Javascript
PostgreSQL并行计算算法及参数强制并行度设置方法
2022/04/07 PostgreSQL
SQL中去除重复数据的几种方法汇总(窗口函数对数据去重)
2023/05/08 MySQL