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控制回车事件的代码
Feb 20 Javascript
基于jquery的3d效果实现代码
Mar 23 Javascript
javascript学习笔记(三) String 字符串类型介绍
Jun 19 Javascript
E3 tree 1.6在Firefox下显示问题的修复方法
Jan 30 Javascript
javascript顺序加载图片的方法
Jul 18 Javascript
AngularJS中下拉框的基本用法示例
Oct 11 Javascript
详解swiper在vue中的应用(以3.0为例)
Sep 20 Javascript
JS浅拷贝和深拷贝原理与实现方法分析
Feb 28 Javascript
微信小程序利用swiper+css实现购物车商品删除功能
Mar 06 Javascript
layer.msg()去掉默认时间,实现手动关闭的方法
Sep 12 Javascript
jQuery实现鼠标拖拽登录框移动效果
Sep 13 jQuery
原生JavaScript实现五子棋游戏
Nov 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使用数组实现队列
2012/02/05 PHP
关于mysql字符集设置了character_set_client=binary 在gbk情况下会出现表描述是乱码的情况
2013/01/06 PHP
PHP计数器的实现代码
2013/06/08 PHP
thinkphp框架实现删除和批量删除
2016/06/29 PHP
PHP将整数数字转换为罗马数字实例分享
2019/03/17 PHP
php 多进程编程父进程的阻塞与非阻塞实例分析
2020/02/22 PHP
潜说js对象和数组
2011/05/25 Javascript
通过下拉框的值来确定输入框是否可以为空的代码
2011/10/18 Javascript
cnblogs 代码高亮显示后的代码复制问题解决实现代码
2011/12/14 Javascript
通过JS自动隐藏手机浏览器的地址栏实现原理与代码
2013/01/02 Javascript
jquery.blockUI.js上传滚动等待效果实现思路及代码
2013/03/18 Javascript
javascript:json数据的页面绑定示例代码
2014/01/26 Javascript
将HTML的左右尖括号等转义成实体形式的两种实现方式
2014/05/04 Javascript
jQuery中prevUntil()方法用法实例
2015/01/08 Javascript
基于jQuery+PHP+Mysql实现在线拍照和在线浏览照片
2015/09/06 Javascript
jQuery实现简单的DIV拖动效果
2016/02/19 Javascript
jQuery  ready方法实现原理详解
2016/10/19 Javascript
利用jquery实现实时更新歌词的方法
2017/01/06 Javascript
javascript的hashCode函数实现代码小结
2020/08/11 Javascript
[02:38]DOTA2英雄基础教程 噬魂鬼
2014/01/03 DOTA
Python解释执行原理分析
2014/08/22 Python
python中闭包Closure函数作为返回值的方法示例
2017/12/17 Python
Python使用win32 COM实现Excel的写入与保存功能示例
2018/05/03 Python
Python实现按逗号分隔列表的方法
2018/10/23 Python
解决python3中的requests解析中文页面出现乱码问题
2019/04/19 Python
Python使用指定字符长度切分数据示例
2019/12/05 Python
军训的自我鉴定
2013/12/10 职场文书
中式面点餐厅创业计划书
2014/01/29 职场文书
信息管理专业自荐书
2014/06/05 职场文书
工作证明范本(2篇)
2014/09/14 职场文书
2015年元旦标语大全
2014/12/09 职场文书
新生入学欢迎词
2015/01/26 职场文书
民事调解书范文
2015/05/20 职场文书
《半截蜡烛》教学反思
2016/02/19 职场文书
《七月的天山》教学反思
2016/02/19 职场文书
分享五个Node.js开发的优秀实践 
2022/04/07 NodeJs