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 相关文章推荐
利用404错误页面实现UrlRewrite的实现代码
Aug 20 Javascript
一个页面放2段图片滚动代码出现冲突的问题如何解决
Dec 21 Javascript
jquery判断RadioButtonList和RadioButton中是否有选中项示例
Sep 29 Javascript
Javascript 普通函数和构造函数的区别
Nov 05 Javascript
详解从Node.js的child_process模块来学习父子进程之间的通信
Mar 27 Javascript
详解Angular2中Input和Output用法及示例
May 21 Javascript
AngularJS 中的数据源的循环输出
Oct 12 Javascript
vue axios整合使用全攻略
May 24 Javascript
JS使用tween.js动画库实现轮播图并且有切换功能
Jul 17 Javascript
Vue实现购物车的全选、单选、显示商品价格代码实例
May 06 Javascript
es6函数之尾递归用法实例分析
Apr 25 Javascript
vue+swiper实现左右滑动的测试题功能
Oct 30 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更快的提供文件下载的代码
2012/06/13 PHP
Yii实现显示静态页的方法
2016/04/25 PHP
javascript 延迟加载技术(lazyload)简单实现
2011/01/17 Javascript
javascript计时器事件使用详解
2014/01/07 Javascript
js调用后台、后台调用前台等方法总结
2014/04/17 Javascript
JavaScript中匿名函数用法实例
2015/03/23 Javascript
jQuery封装的tab选项卡插件分享
2015/06/16 Javascript
js贪吃蛇网页版游戏特效代码分享(挑战十关)
2015/08/24 Javascript
一步步教大家编写酷炫的导航栏js+css实现
2016/03/14 Javascript
jQuery 全选 全部选 反选 实现代码
2016/08/17 Javascript
在localStorage中存储对象数组并读取的方法
2016/09/24 Javascript
Bootstrap Search Suggest使用例子
2016/12/21 Javascript
js canvas实现放大镜查看图片功能
2017/06/08 Javascript
AngularJS点击添加样式、点击变色设置的实例代码
2017/07/27 Javascript
vue项目中使用多选框的实例代码
2020/07/22 Javascript
vue调用本地摄像头实现拍照功能
2020/08/14 Javascript
[40:03]RNG vs VG 2019国际邀请赛小组赛 BO2 第二场 8.15
2019/08/17 DOTA
Python引用(import)文件夹下的py文件的方法
2014/08/26 Python
python生成IP段的方法
2015/07/07 Python
django模板结构优化的方法
2019/02/28 Python
Python绘制股票移动均线的实例
2019/08/24 Python
简单了解Java Netty Reactor三种线程模型
2020/04/26 Python
python实现模拟器爬取抖音评论数据的示例代码
2021/01/06 Python
详解修改Anaconda中的Jupyter Notebook默认工作路径的三种方式
2021/01/24 Python
中国汽车租赁行业头部企业:一嗨租车
2019/05/16 全球购物
美国孕妇装购物网站:Motherhood Maternity
2019/09/22 全球购物
Brother加拿大官网:打印机、贴标机、缝纫机
2019/10/09 全球购物
澳大利亚家用电器在线商店:Billy Guyatts
2020/05/05 全球购物
高职助产应届生自荐信
2013/09/24 职场文书
机械设计毕业生自荐信
2014/02/02 职场文书
优秀幼教自荐信
2014/02/03 职场文书
2015大学生实训报告
2014/11/05 职场文书
中学图书馆工作总结
2015/08/11 职场文书
如何用JavaScript实现一个数组惰性求值库
2021/05/05 Javascript
解决Navicat for MySQL 连接 MySQL 报2005错误的问题
2021/05/29 MySQL
15个值得收藏的JavaScript函数
2021/09/15 Javascript