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 相关文章推荐
DHTML Slide Show script图片轮换
Mar 03 Javascript
JavaScript对象创建及继承原理实例解剖
Feb 28 Javascript
jQuery中 delegate使用的问题
Jul 03 Javascript
jquery实现的Banner广告收缩效果代码
Sep 02 Javascript
JavaScript常用基础知识强化学习
Dec 09 Javascript
JavaScript中的splice方法用法详解
Jul 20 Javascript
Bootstrap的popover(弹出框)2秒后定时消失的实现代码
Feb 27 Javascript
AngularJS service之select下拉菜单效果
Jul 28 Javascript
Node.js Stream ondata触发时机与顺序的探索
Mar 08 Javascript
使用vue-cli3 创建vue项目并配置VS Code 自动代码格式化 vue语法高亮问题
May 14 Javascript
JavaScript 链表定义与使用方法示例
Apr 28 Javascript
浅谈JS的二进制家族
May 09 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大神的十大优良习惯
2016/09/14 PHP
在laravel中使用Symfony的Crawler组件分析HTML
2017/06/19 PHP
PHP后台备份MySQL数据库的源码实例
2019/03/18 PHP
PHP创建对象的六种方式实例总结
2019/06/27 PHP
JS控制输入框内字符串长度
2014/05/21 Javascript
JavaScript利用append添加元素报错的解决方法
2014/07/01 Javascript
JS控制按钮10秒钟后可用的方法
2015/12/22 Javascript
AngularJS初始化静态模板详解
2016/01/14 Javascript
Nodejs中的this详解
2016/03/26 NodeJs
js实现上传图片及时预览
2016/05/07 Javascript
AngularJS基础 ng-keydown 指令简单示例
2016/08/02 Javascript
妙用Bootstrap的 popover插件实现校验表单提示功能
2016/08/29 Javascript
微信小程序 小程序制作及动画(animation样式)详解
2017/01/06 Javascript
bootstrap弹出层的多种触发方式
2017/05/10 Javascript
对象不支持indexOf属性或方法的解决方法(必看)
2017/05/28 Javascript
基于构造函数的五种继承方法小结
2017/07/27 Javascript
你点的 ES6一些小技巧,请查收
2018/04/25 Javascript
深入koa-bodyparser原理解析
2019/01/16 Javascript
vue中使用rem布局代码详解
2019/10/30 Javascript
[02:08]我的刀塔不可能这么可爱 胡晓桃_1
2014/06/20 DOTA
Django中STATIC_ROOT和STATIC_URL及STATICFILES_DIRS浅析
2018/05/08 Python
python自动发微信监控报警
2019/09/06 Python
python 实现在无序数组中找到中位数方法
2020/03/03 Python
Python实现加密接口测试方法步骤详解
2020/06/05 Python
Keras设置以及获取权重的实现
2020/06/19 Python
HTML5+CSS设置浮动却没有动反而在中间且错行的问题
2020/05/26 HTML / CSS
意大利奢华内衣制造商:Cosabella
2017/08/29 全球购物
介绍一下OSI七层模型
2012/07/03 面试题
工程部主管岗位职责
2013/11/17 职场文书
2014年父亲节活动方案
2014/03/06 职场文书
恶搞卫生巾广告词
2014/03/18 职场文书
校庆口号
2014/06/20 职场文书
工商局局长个人对照检查材料思想汇报
2014/09/23 职场文书
2014年学生会部门工作总结
2014/11/07 职场文书
2014年信息宣传工作总结
2014/12/18 职场文书
【2·13】一图读懂中国无线电发展
2022/02/18 无线电