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 extend 的简单实例
Sep 18 Javascript
js实现select跳转功能代码
Oct 22 Javascript
Redis基本知识、安装、部署、配置笔记
Mar 05 Javascript
详解javascript事件冒泡
Jan 09 Javascript
分享10个优化代码的CSS和JavaScript工具
May 11 Javascript
论Bootstrap3和Foundation5网格系统的异同
May 16 Javascript
jQuery Mobile 触摸事件实例
Jun 04 Javascript
深入浅析JS的数组遍历方法(推荐)
Jun 15 Javascript
javascript js 操作数组 增删改查的简单实现
Jun 20 Javascript
jQuery拖拽通过八个点改变div大小
Nov 29 Javascript
Bootstrap源码解读网格系统(3)
Dec 22 Javascript
localstorage实现带过期时间的缓存功能
Jun 28 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
php5.2时间相差8小时
2007/01/15 PHP
PHP无敌近乎加密方式!
2010/07/17 PHP
深入理解PHP之require/include顺序 推荐
2011/01/02 PHP
PHP eval函数使用介绍
2013/12/08 PHP
dedecms中使用php语句指南
2014/11/13 PHP
smarty中post用法实例
2014/11/28 PHP
PHP类的特性实例分析
2016/09/28 PHP
thinkPHP引入类的方法详解
2016/12/08 PHP
PHP常见加密函数用法示例【crypt与md5】
2019/01/27 PHP
jquery ui dialog里调用datepicker的问题
2009/08/06 Javascript
Extjs Ext.MessageBox.confirm 确认对话框详解
2010/04/02 Javascript
理解Javascript_03_javascript全局观
2010/10/11 Javascript
js 判断checkbox是否选中的实现代码
2010/11/23 Javascript
js创建子窗口并且回传值示例代码
2013/07/02 Javascript
jquery实现的代替传统checkbox样式插件
2015/06/19 Javascript
详解javascript事件冒泡
2016/01/09 Javascript
js表单处理中单选、多选、选择框值的获取及表单的序列化
2016/03/08 Javascript
jQuery实现的简单分页示例
2016/06/01 Javascript
js实现一个猜数字游戏
2017/03/31 Javascript
JS实现加载时锁定HTML页面元素的方法
2017/06/24 Javascript
Vue开发之封装上传文件组件与用法示例
2019/04/25 Javascript
[01:30]2016国际邀请赛中国区预选赛神秘商店火爆开启
2016/06/26 DOTA
Python解惑之True和False详解
2017/04/24 Python
深入理解Python中的super()方法
2017/11/20 Python
Python实现中值滤波去噪方式
2019/12/18 Python
TensorFlow实现打印每一层的输出
2020/01/21 Python
使用Python打造一款间谍程序的流程分析
2020/02/21 Python
python实现简单的学生管理系统
2021/02/22 Python
旅游与酒店管理的自我评价分享
2013/11/03 职场文书
写给女生的道歉信
2014/01/08 职场文书
《特殊的葬礼》教学反思
2014/04/27 职场文书
学校党员个人问题整改措施思想汇报
2014/10/08 职场文书
村党的群众路线教育实践活动总结材料
2014/10/31 职场文书
2014-2015学年工作总结
2014/11/27 职场文书
上市公司董事长岗位职责
2015/04/16 职场文书
撤诉申请书法院范本
2015/05/18 职场文书