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 相关文章推荐
关于JQuery($.load)事件的用法和分析
Apr 09 Javascript
使用js检测浏览器是否支持html5中的video标签的方法
Mar 12 Javascript
用vue的双向绑定简单实现一个todo-list的示例代码
Aug 03 Javascript
JavaScript仿微信(电话)联系人列表滑动字母索引实例讲解(推荐)
Aug 16 Javascript
通俗解释JavaScript正则表达式快速记忆
Aug 23 Javascript
javascript闭包的使用之按钮切换功能
Aug 30 Javascript
JS html事件冒泡和事件捕获操作示例
May 01 Javascript
js模拟F11页面全屏显示
Sep 17 Javascript
Vue+Node服务器查询Mongo数据库及页面数据传递操作实例分析
Dec 20 Javascript
node.js中stream流中可读流和可写流的实现与使用方法实例分析
Feb 13 Javascript
jQuery实现html可联动的百分比进度条
Mar 26 jQuery
uniapp与webview之间的相互传值的实现
Jun 29 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
ExtJS与PHP、MySQL实现存储的方法
2010/04/02 PHP
phpcms实现验证码替换及phpcms实现全站搜索功能教程详解
2017/12/13 PHP
php高清晰度无损图片压缩功能的实现代码
2018/12/09 PHP
php经典趣味算法实例代码
2020/01/21 PHP
javascript 面向对象全新理练之数据的封装
2009/12/03 Javascript
jquery通过select列表选择框对表格数据进行过滤示例
2014/05/07 Javascript
轻松创建nodejs服务器(5):事件处理程序
2014/12/18 NodeJs
jquery.form.js实现将form提交转为ajax方式提交的方法
2015/04/07 Javascript
JavaScript编写连连看小游戏
2015/07/07 Javascript
jquery实现LED广告牌旋转系统图片切换效果代码分享
2015/08/26 Javascript
超赞的jQuery图片滑块动画特效代码汇总
2016/01/25 Javascript
JavaScript中自带的 reduce()方法使用示例详解
2016/08/10 Javascript
javascript入门之window对象【新手必看】
2016/11/22 Javascript
任意Json转成无序列表的方法示例
2016/12/09 Javascript
Extjs让combobox写起来简洁又漂亮
2017/01/05 Javascript
JS实现中国公民身份证号码有效性验证
2017/02/20 Javascript
Vue代码分割懒加载的实现方法
2017/11/23 Javascript
微信小程序如何获取用户头像和昵称
2019/09/23 Javascript
vue页面更新patch的实现示例
2020/03/25 Javascript
JavaScript实现拖动对话框效果的实现代码
2020/10/12 Javascript
[01:22:28]DOTA2-DPC中国联赛 正赛 SAG vs RNG BO3 第一场 1月18日
2021/03/11 DOTA
Python基于高斯消元法计算线性方程组示例
2018/01/17 Python
python matlibplot绘制多条曲线图
2021/02/19 Python
Python中字符串List按照长度排序
2019/07/01 Python
pytorch中的embedding词向量的使用方法
2019/08/18 Python
如何在 Django 模板中输出 &quot;{{&quot;
2020/01/24 Python
Python之Django自动实现html代码(下拉框,数据选择)
2020/03/13 Python
基于python实现把json数据转换成Excel表格
2020/05/07 Python
Python with语句用法原理详解
2020/07/03 Python
Python 生成短8位唯一id实战教程
2021/01/13 Python
HTML5-WebSocket实现聊天室示例
2016/12/15 HTML / CSS
中国电子产品批发商/跨境电商/外贸网:Sunsky-online
2020/04/20 全球购物
工商企业管理专业自荐信范文
2014/04/12 职场文书
旅游与酒店管理专业求职信
2014/07/21 职场文书
写给老婆的保证书
2015/02/27 职场文书
python 实现mysql自动增删分区的方法
2021/04/01 Python