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 相关文章推荐
实现超用户体验 table排序javascript实现代码
Jun 22 Javascript
分享一个自己写的table表格排序js插件(高效简洁)
Oct 29 Javascript
js变换显示图片的实例
Apr 16 Javascript
js调用后台servlet方法实例
Jun 09 Javascript
Bootstrap每天必学之表格
Nov 23 Javascript
使用getBoundingClientRect方法实现简洁的sticky组件的方法
Mar 22 Javascript
js字符串操作总结(必看篇)
Nov 22 Javascript
手动初始化Angular的模块与控制器
Dec 26 Javascript
JavaScript实现无穷滚动加载数据
May 06 Javascript
AngularJs实现聊天列表实时刷新功能
Jun 15 Javascript
webpack开发环境和生产环境的深入理解
Nov 08 Javascript
vue 内联样式style中的background用法说明
Aug 05 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+XML 制作简单的留言本 图文教程
2009/11/02 PHP
PHP IE中下载附件问题解决方法
2014/01/07 PHP
PHP实现根据数组某个键值大小进行排序的方法
2018/03/13 PHP
setTimeout和setInterval的浏览器兼容性分析
2007/02/27 Javascript
摘自百度的图片轮换效果代码
2007/11/19 Javascript
jQeury淡入淡出需要注意的问题
2010/09/08 Javascript
javascript继承之为什么要继承
2012/11/10 Javascript
jquery 简单应用示例总结
2013/08/09 Javascript
详解addEventListener的三个参数之useCapture
2015/03/16 Javascript
JavaScript生成SQL查询表单的方法
2015/08/13 Javascript
html+js实现简单的计算器代码(加减乘除)
2016/07/12 Javascript
基于jQuery实现页面搜索功能
2020/03/26 Javascript
解决拦截器对ajax请求的拦截实例详解
2016/12/21 Javascript
jQuery Validate 相关参数及常用的自定义验证规则
2017/03/06 Javascript
React复制到剪贴板的示例代码
2017/08/22 Javascript
react 父组件与子组件之间的值传递的方法
2017/09/14 Javascript
vue-resource拦截器设置头信息的实例
2017/10/27 Javascript
JavaScript中常见内置函数用法示例
2018/05/14 Javascript
video.js 实现视频只能后退不能快进的思路详解
2018/08/09 Javascript
小程序根据手机机型设置自定义底部导航距离
2019/06/04 Javascript
解决Vue打包后访问图片/图标不显示的问题
2019/07/25 Javascript
Python3.5 Pandas模块之Series用法实例分析
2019/04/23 Python
python选取特定列 pandas iloc,loc,icol的使用详解(列切片及行切片)
2019/08/06 Python
python实现抠图给证件照换背景源码
2019/08/20 Python
Django中的FBV和CBV用法详解
2019/09/15 Python
解决Python二维数组赋值问题
2019/11/28 Python
Scrapy爬虫文件批量运行的实现
2020/09/30 Python
Linux上比较文件的命令都有哪些
2013/09/28 面试题
大学生物业管理求职信
2013/10/24 职场文书
政治思想表现评语
2014/05/04 职场文书
好的促销活动方案
2014/08/21 职场文书
如何写早恋检讨书
2014/09/10 职场文书
个人存款证明书
2014/10/18 职场文书
优秀毕业生主要事迹材料
2015/11/04 职场文书
golang中字符串MD5生成方式总结
2021/07/04 Golang
Java使用JMeter进行高并发测试
2021/11/23 Java/Android