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 代码中,ajax请求地址后加随机数防止浏览器缓存的原因
May 07 Javascript
javascript中的document.open()方法使用介绍
Oct 09 Javascript
一个获取第n个元素节点的js函数
Sep 02 Javascript
JS跨域问题详解
Nov 25 Javascript
jQuery实现复选框成对选择及对应取消的方法
Mar 03 Javascript
js实现带按钮的上下滚动效果
May 12 Javascript
ajax如何实现页面局部跳转与结果返回
Aug 24 Javascript
JavaScript中的原始值和复杂值
Jan 07 Javascript
js原生之焦点图转换加定时器实例
Dec 12 Javascript
JS经典正则表达式笔试题汇总
Dec 15 Javascript
JS控件bootstrap datepicker使用方法详解
Mar 25 Javascript
JQuery搜索框自动补全(模糊匹配)功能实现示例
Jan 08 jQuery
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中copy on write写时复制机制介绍
2014/05/13 PHP
PHP捕获Fatal error错误的方法
2014/06/11 PHP
PHP浮点数精度问题汇总
2015/05/13 PHP
双冒号 ::在PHP中的使用情况
2015/11/05 PHP
php版银联支付接口开发简明教程
2016/10/14 PHP
BOOM vs RR BO3 第二场2.13
2021/03/10 DOTA
使用jQuery Ajax功能时需要注意的一个问题(内存溢出)
2012/05/30 Javascript
javascript中encodeURI和decodeURI方法使用介绍
2013/05/06 Javascript
Javascript 多物体运动的实现
2014/12/24 Javascript
自己动手写的jquery分页控件(非常简单实用)
2015/10/28 Javascript
今天抽时间给大家整理jquery和ajax的相关知识
2015/11/17 Javascript
使用jQuery Ajax 请求webservice来实现更简练的Ajax
2016/08/04 Javascript
关于JS中二维数组的声明方法
2016/09/24 Javascript
jquery的父、子、兄弟节点查找,节点的子节点循环方法
2016/12/07 Javascript
js遍历json的key和value的实例
2017/01/22 Javascript
JavaScript数组常用的增删改查与其他属性详解
2020/10/13 Javascript
[14:21]VICI vs EG (BO3)
2018/06/07 DOTA
使用IronPython把Python脚本集成到.NET程序中的教程
2015/03/31 Python
python学习之编写查询ip程序
2016/02/27 Python
python解析html提取数据,并生成word文档实例解析
2018/01/22 Python
pycharm远程linux开发和调试代码的方法
2018/07/17 Python
详解python 注释、变量、类型
2018/08/10 Python
Python线程池模块ThreadPoolExecutor用法分析
2018/12/28 Python
使用PIL(Python-Imaging)反转图像的颜色方法
2019/01/24 Python
Django 1.10以上版本 url 配置注意事项详解
2019/08/05 Python
python异常处理和日志处理方式
2019/12/24 Python
为什么黑客都用python(123个黑客必备的Python工具)
2020/01/31 Python
基于python实现音乐播放器代码实例
2020/07/01 Python
使用sublime text3搭建Python编辑环境的实现
2021/01/12 Python
CSS3绘制不规则图形的一些方法示例
2015/11/07 HTML / CSS
世界上最大的铁人三项商店:Tri UK
2020/11/04 全球购物
深圳茁壮笔试题
2015/05/28 面试题
8和9的加减法教学反思
2014/05/01 职场文书
工程项目合作意向书
2015/05/08 职场文书
2016教师读书思廉心得体会
2016/01/23 职场文书
使用CSS实现百叶窗效果示例代码
2023/05/07 HTML / CSS