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 相关文章推荐
JavaScript实现动态增加文件域表单
Feb 12 Javascript
js null,undefined,字符串小结
Aug 21 Javascript
js正则表达式的使用详解
Jul 09 Javascript
JS复制到剪贴板示例代码
Oct 30 Javascript
jqplot通过ajax动态画折线图的方法及思路
Dec 08 Javascript
js图片自动轮播代码分享(js图片轮播)
May 06 Javascript
Vue.js2.0中的变化小结
Oct 24 Javascript
js生成word中图片处理方法
Jan 06 Javascript
js保留两位小数方法总结
Jan 31 Javascript
JS数组去重常用方法实例小结【4种方法】
May 28 Javascript
分享5个小技巧让你写出更好的 JavaScript 条件语句
Oct 20 Javascript
JavaScript中arguments的使用方法详解
Dec 20 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合并数组+号和array_merge的区别
2015/06/25 PHP
PHP之图片上传类实例代码(加了缩略图)
2016/06/30 PHP
Ubuntu 16.04中Laravel5.4升级到5.6的步骤
2018/12/07 PHP
JavaScript入门教程(8) Location地址对象
2009/01/31 Javascript
JavaScript的arguments对象应用示例
2014/09/15 Javascript
使用JavaScript进行进制转换将字符串转换为十进制
2014/09/21 Javascript
jQuery 判断图片是否加载完成方法汇总
2015/08/10 Javascript
js常用的继承--组合式继承
2017/03/06 Javascript
详解vue-router基本使用
2017/04/18 Javascript
微信小程序之圆形进度条实现思路
2018/02/22 Javascript
vue 实现强制类型转换 数字类型转为字符串
2019/11/07 Javascript
vue 实现 rem 布局或vw 布局的方法
2019/11/13 Javascript
Vue切换div显示隐藏,多选,单选代码解析
2020/07/14 Javascript
vue+elementUI(el-upload)图片压缩,默认同比例压缩操作
2020/08/10 Javascript
vue设置默认首页的操作
2020/08/12 Javascript
JavaScript中的执行环境和作用域链
2020/09/04 Javascript
vue swipeCell滑动单元格(仿微信)的实现示例
2020/09/14 Javascript
python client使用http post 到server端的代码
2013/02/10 Python
Python中正则表达式的详细教程
2015/04/30 Python
Python中字典(dict)合并的四种方法总结
2017/08/10 Python
python实现连续图文识别
2018/12/18 Python
python调用虹软2.0第三版的具体使用
2019/02/22 Python
Python判断字符串是否xx开始或结尾的示例
2019/08/08 Python
Python Web框架之Django框架Form组件用法详解
2019/08/16 Python
Django实现简单的分页功能
2021/02/22 Python
CSS3动画之利用requestAnimationFrame触发重新播放功能
2019/09/11 HTML / CSS
纯CSS3制作的简洁蓝白风格的登录模板(非IE效果更好)
2013/08/11 HTML / CSS
html5实现多图片预览上传及点击可拖拽控件
2018/03/15 HTML / CSS
HTML5 body设置自适应全屏
2020/05/07 HTML / CSS
Michael Kors澳大利亚官网:世界知名的奢侈饰品和成衣设计师
2020/02/13 全球购物
社区包粽子活动方案
2014/01/21 职场文书
应聘英语教师求职信
2014/04/24 职场文书
2014年计生协会工作总结
2014/11/21 职场文书
幼儿园三八妇女节活动总结
2015/02/06 职场文书
2015年度党员自我评价范文
2015/03/03 职场文书
设置IIS Express并发数
2022/07/07 Servers