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 相关文章推荐
yepnope.js 异步加载资源文件
Sep 08 Javascript
jQuery EasyUI API 中文文档 - PropertyGrid属性表格
Nov 18 Javascript
jQuery中find()方法用法实例
Jan 07 Javascript
javaScript基础语法介绍
Feb 28 Javascript
基于Angularjs实现分页功能
May 30 Javascript
JS实现星星评分功能实例代码(两种方法)
Jun 09 Javascript
JS中微信小程序自定义底部弹出框
Dec 22 Javascript
深入理解Node module模块
Mar 26 Javascript
vue translate peoject实现在线翻译功能【新手必看】
Jun 07 Javascript
Bootstrap-table自定义可编辑每页显示记录数
Sep 07 Javascript
微信小程序利用button控制条件标签的变量问题
Mar 15 Javascript
深入理解Vue的数据响应式
May 15 Vue.js
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
DOTA2游戏同人动画《龙之血》导演接受采访
2021/03/05 欧美动漫
PHP中实现图片的锐化
2006/10/09 PHP
推荐一本PHP程序猿都应该拜读的书
2014/12/31 PHP
php 流程控制switch的简单实例
2016/06/07 PHP
php车辆违章查询数据示例
2016/10/14 PHP
JS禁用浏览器退格键实现思路及代码
2013/10/29 Javascript
AngularJs基本特性解析(一)
2016/07/21 Javascript
JS实现双击内容变为可编辑状态
2017/03/03 Javascript
Bootstrap模态框插入视频的实现代码
2017/06/25 Javascript
JS实现数组简单去重及数组根据对象中的元素去重操作示例
2018/01/05 Javascript
React 组件中的 bind(this)示例代码
2018/09/16 Javascript
基于mpvue搭建微信小程序项目框架的教程详解
2019/04/10 Javascript
Preload基础使用方法详解
2020/02/03 Javascript
javascript canvas封装动态时钟
2020/09/30 Javascript
vue的webcamjs集成方式
2020/11/16 Javascript
Python批量修改文件后缀的方法
2014/01/26 Python
基于Python实现的百度贴吧网络爬虫实例
2015/04/17 Python
python实现人脸识别经典算法(一) 特征脸法
2018/03/13 Python
python合并同类型excel表格的方法
2018/04/01 Python
numpy.delete删除一列或多列的方法
2018/04/03 Python
spark: RDD与DataFrame之间的相互转换方法
2018/06/07 Python
python mqtt 客户端的实现代码实例
2019/09/25 Python
基于python及pytorch中乘法的使用详解
2019/12/27 Python
python 如何把docker-compose.yaml导入到数据库相关条目里
2021/01/15 Python
numpy实现RNN原理实现
2021/03/02 Python
5分钟让你掌握css3阴影、倒影、渐变小技巧(小编推荐)
2016/08/15 HTML / CSS
迪拜航空官方网站:flydubai
2017/04/20 全球购物
意大利在线眼镜精品店:Ottica Lipari
2019/11/11 全球购物
Bonprix法国:时尚、鞋子、家居
2020/12/29 全球购物
DIY手工制作经营店创业计划书
2014/02/01 职场文书
数控个人求职信范文
2014/02/03 职场文书
殡葬服务心得体会
2014/09/11 职场文书
交通安全横幅标语
2014/10/07 职场文书
2015年感恩母亲节的演讲稿
2015/03/18 职场文书
活动简报范文
2015/07/22 职场文书
导游词之山东八仙过海景区
2019/11/11 职场文书