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的history历史记录插件
Dec 11 Javascript
js中判断文本框是否为空的两种方法
Jul 31 Javascript
jQuery之折叠面板的深入解析
Jun 19 Javascript
纯javascript代码实现计算器功能(三种方法)
Sep 07 Javascript
使用JS读取XML文件的方法
Nov 25 Javascript
jQuery实现动态删除LI的方法
May 30 jQuery
jQuery进阶实践之利用最优雅的方式如何写ajax请求
Dec 20 jQuery
从零开始搭建webpack+react开发环境的详细步骤
May 18 Javascript
详解vue axios二次封装
Jul 22 Javascript
使用JS判断页面是首次被加载还是刷新
May 26 Javascript
怎么使用javascript深度拷贝一个数组
Jun 06 Javascript
更强大的vue ssr实现预取数据的方式
Jul 19 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
DC《神奇女侠2》因疫情推迟上映 温子仁新恐怖片《恶性》撤档
2020/04/09 欧美动漫
异世界新番又来了,同样是从零开始,男主的年龄降到5岁
2020/04/09 日漫
针对初学PHP者的疑难问答(1)
2006/10/09 PHP
php download.php实现代码 跳转到下载文件(response.redirect)
2009/08/26 PHP
php生成二维码的几种方式整理及使用实例
2013/06/03 PHP
php cookie名使用点号(句号)会被转换
2014/10/23 PHP
php自定义函数转换html标签示例
2016/09/29 PHP
PHP文件系统管理(实例讲解)
2017/09/19 PHP
实用javaScript技术-屏蔽类
2006/08/15 Javascript
用js实现计算代码行数的简单方法附代码
2007/08/13 Javascript
Autocomplete Textbox Example javascript实现自动完成成功
2007/08/17 Javascript
JavaScript来实现打开链接页面的简单实例
2016/06/02 Javascript
Bootstrap和Angularjs配合自制弹框的实例代码
2016/08/24 Javascript
初探nodeJS
2017/01/24 NodeJs
jQuery tip提示插件(实例分享)
2017/04/28 jQuery
实现div滚动条默认最底部以及默认最右边的示例代码
2017/11/15 Javascript
vue使用better-scroll实现下拉刷新、上拉加载
2018/11/23 Javascript
JavaScript 装逼指南(js另类写法)
2020/05/10 Javascript
在Lighttpd服务器中运行Django应用的方法
2015/07/22 Python
Python方法的延迟加载的示例代码
2017/12/18 Python
取numpy数组的某几行某几列方法
2018/04/03 Python
python基于socket实现的UDP及TCP通讯功能示例
2019/11/01 Python
解决 jupyter notebook 回车换两行问题
2020/04/15 Python
Python验证码截取识别代码实例
2020/05/16 Python
详解CSS的border边框属性及其在CSS3中的新特性
2016/05/10 HTML / CSS
印度尼西亚手表和包包商店:Urban Icon
2019/12/12 全球购物
如何实现一个自定义类的序列化
2012/05/22 面试题
外语专业毕业生个人的自荐信
2013/11/19 职场文书
小区门卫岗位职责范本
2014/08/24 职场文书
借名购房协议书范本
2014/10/06 职场文书
2014流动人口计划生育工作总结
2014/12/20 职场文书
优秀团员个人总结
2015/02/26 职场文书
企业党员岗位承诺书
2015/04/27 职场文书
机关干部作风整顿心得体会
2016/01/22 职场文书
python爬取某网站原图作为壁纸
2021/06/02 Python
Python使用Opencv打开笔记本电脑摄像头报错解问题及解决
2022/06/21 Python