Vue动态加载异步组件的方法


Posted in Javascript onNovember 21, 2018

背景:

目前我们项目都是按组件划分的,然后各个组件之间封装成产品。目前都是采用iframe直接嵌套页面。项目中我们还是会碰到一些通用的组件跟业务之间有通信,这种情况下iframe并不是最好的选择,iframe存在跨域的问题,当然是postMessage还是可以通信的,但也并非是最好的。目前有这么一个场景:门户需要制作通用的首页和数据概览页面,首页和数据概览页面通过小部件来自由拼接。业务组件在制作的时候只需要提供各个模块小部件的url就可以了,可是如果小部件之间还存在联系呢?那么iframe是不好的。目前采用Vue动态加载异步组件的方式来实现小组件之间的通信。当然门户也要提供一个通信的基线:Vue事件总线(空的Vue实例对象)。

内容:

使用过vue的都应该知道vue的动态加载组件components:

Vue通过is来绑定需要加载的组件。那么我们现在需要的就是如何打包组件,如果通过复制业务组件内部的代码,那么这种就需要把依赖全部找齐,并复制过去(很多情况下会漏下某个图片或css等),这种方式是比较low的,不方便维护。因此我们需要通过webpack来打包单个vue文件成js,这边一个vue打包成一个js,不需压代码分割,css分离。因为component加载时只需要加载一个文件即可。打包文件配置如下:

首先在package.json加入打包命令:

"scripts": {
  ...
  "build-outCMP": "node build/build-out-components.js"
 },

Build-out-components.js文件:

'use strict'
require('./check-versions')()

process.env.NODE_ENV = 'production'

const ora = require('ora')
const path = require('path')
const chalk = require('chalk')
const webpack = require('webpack')
const webpackConfig = require('./webpack.out-components.prod.conf')

const spinner = ora('building for sync-components...')
spinner.start()

webpack(webpackConfig, function (err, stats) {
 spinner.stop()
 if (err) throw err
 process.stdout.write(stats.toString({
  colors: true,
  modules: false,
  children: false,
  chunks: false,
  chunkModules: false
 }) + '\n\n')

 if (stats.hasErrors()) {
  console.log(chalk.red(' Build failed with errors.\n'))
  process.exit(1)
 }

 console.log(chalk.cyan(' Build complete.\n'))
 console.log(chalk.yellow(
  ' Tip: built files are meant to be served over an HTTP server.\n' +
  ' Opening index.html over file:// won\'t work.\n'
 ))
})

webpack.out-components.prod.conf.js文件配置如下

const webpack = require('webpack');
const path = require('path');
const utils = require('./utils');
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const {entry, mkdirsSync} = require('./out-components-tools')

function resolve(dir) {
 return path.join(__dirname, '..', dir)
}

mkdirsSync(resolve('/static/outComponents'))

module.exports = {
 entry: entry,
 output: {
  path: resolve('/static/outComponents'),
  filename: '[name].js',
 },
 resolve: {
  extensions: ['.js', '.vue', '.json'],
  alias: {
   'vue$': 'vue/dist/vue.esm.js',
   '@': resolve('src'),
  }
 },
 externals: {
  vue: 'vue',
  axios: 'axios'
 },
 module: {
  rules: [
   {
    test: /\.vue$/,
    loader: 'vue-loader',
    options: {
     esModule: false, // vue-loader v13 更新 默认值为 true v12及之前版本为 false, 此项配置影响 vue 自身异步组件写法以及 webpack 打包结果
     loaders: utils.cssLoaders({
      sourceMap: true,
      extract: false     // css 不做提取
     }),
     transformToRequire: {
      video: 'src',
      source: 'src',
      img: 'src',
      image: 'xlink:href'
     }
    }
   },
   {
    test: /\.js$/,
    loader: 'babel-loader',
    include: [resolve('src'), resolve('test')]
   },
   {
    test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
    loader: 'url-loader',
    options: {
     limit: 10000,
     name: utils.assetsPath('img/[name].[hash:7].[ext]')
    }
   },
   {
    test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
    loader: 'url-loader',
    options: {
     limit: 10000,
     name: utils.assetsPath('media/[name].[hash:7].[ext]')
    }
   },
   {
    test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
    loader: 'url-loader',
    options: {
     limit: 10000,
     name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
    }
   }
  ]
 },
 plugins: [
  new webpack.DefinePlugin({
   'process.env.NODE_ENV': '"production"'
  }),
  // UglifyJs do not support ES6+, you can also use babel-minify for better treeshaking: https://github.com/babel/minify
  new webpack.optimize.UglifyJsPlugin({
   compress: false,
   sourceMap: true
  }),
  // Compress extracted CSS. We are using this plugin so that possible
  // duplicated CSS from different components can be deduped.
  new OptimizeCSSPlugin({
   cssProcessorOptions: {
    safe: true
   }
  })
 ]
};

out-components-tools.js文件配置如下:

const glob = require('glob')
const fs = require('fs');
const path = require('path');
// 遍历要打包的组件
let entry = {}
var moduleSrcArray = glob.sync('./src/out-components/*')
for(var x in moduleSrcArray){
 let fileName = (moduleSrcArray[x].split('/')[3]).slice(0, -4)
 entry[fileName] = moduleSrcArray[x]
}

// 清理文件
function mkdirsSync(dirname) {
 if (fs.existsSync(dirname)) {
  deleteall(dirname)
  return true;
 } else {
  if (mkdirsSync(path.dirname(dirname))) {
   fs.mkdirSync(dirname);
   return true;
  }
 }
}
// 删除文件下的文件
function deleteall(path) {
 var files = [];
 if(fs.existsSync(path)) {
  files = fs.readdirSync(path);
  files.forEach(function(file, index) {
   var curPath = path + "/" + file;
   if(fs.statSync(curPath).isDirectory()) { // recurse
    deleteall(curPath);
   } else { // delete file
    fs.unlinkSync(curPath);
   }
  });
 }
};

exports.entry = entry
exports.mkdirsSync = mkdirsSync

build-out-components是打包的入口文件,webpack.out-components.prod.conf.js是webpack打包的配置文件,out-components-tools.js是工具库,这边是打包的entry自动获取(默认为src/out-components),还有自动删除之前打包的文件。

目前的文件目录为

Vue动态加载异步组件的方法

通过打包生产文件:

Vue动态加载异步组件的方法

在static下outComponents文件夹内的js文件。(最终打包需要打包到dist下面,这边做测试先打包在static文件下,方便后续动态组件ajax获取组件使用)

门户的小部件是通过配置url,和调整布局来生产的。因此业务组件至此已经完成了。只需要提供对门户暴露的url即可。
接下来就是门户这边加载动态组件的实现了。门户这边就相对简单了。看如下图配置:

门户通过component的动态组件来实现加载异步组件,通过ajax请求刚才打包的url,然后实例化函数new Function来赋值给mode(new Function之所以分成2部,是因此效验规则的问题,可忽略)。这样就实现了动态加载异步组件了。门户和业务组件可以各个开发,任何业务开发数据概览,门户都不需要改代码,只需要界面上配置url即可。这个异步加载组件已经结束了。这边门户需要封装一封实现异步组件。父级只需要传入url即可。这边还有个可以优化的是,可以把mode优先缓存,那么不需要每次都去加载请求。如下:

我们可以看到在门户的一个数据概览页面上加载了多个异步组件,那么异步组件之间也是可能存在通信的,这样该如何做呢?因为现在已经不是iframe嵌套了,可以通过监听一个组件,然调用另一个组件的方法,这样确实可以实现平级组件间的通信,但这样势必不可取的,因为一旦这样做了门户必须要根据业务来辅助,修改代码来实现功能。因此这边借用门户来生成vue事件总线(空的vue实例)来实现。

门户代码如下: 在this.$root上挂在一个事件总线:

created () {
    if (!this.$root.eventBus) {
     this.$root.eventBus = new Vue()
    }
   }

然后业务组件之间就可以根据自己的业务实现通信:

组件一和组件二代码如下:

<template>
   <div class="test1">
    这是一个外部组件a1
    <hello-word></hello-word>
   </div>
    </template>
  
  <script>
  import helloWord from '../components/HelloWorld'
  export default {
   data () {
    return {
     i: 0
    }
   },
   components: {
    helloWord
   },
   mounted () {
    setInterval(() => {
     this.i++
     if (this.i < 10) {
      this.test()
     }
    }, 1000)
   },
   methods: {
    test () {
     this.$root.eventBus.$emit('childEvent', this.i)
    }
   }
  }
  </script>
<template>
   <div class="test1">
    这也是外部组件哦
    <div >
     这是a1传来的{{a1}}
    </div>
   </div>
  </template>
  
  <script>
  export default {
   data () {
    return {
     a1: 0
    }
   },
   created () {
    this.$root.eventBus.$on('childEvent', this.change)
   },
   methods: {
    change (i) {
     this.a1 = i
    }
   }
  }
  </script>

业务组件就可以根据this.$root.eventBus和vue上的事件传递($emit, $on)来实现相互的通信。

总结:本篇主要借助vue的动态组件和webpack打包单文件来实现动态加载异步组件,通过vue的事件总线挂载在this.$root上来实现平级组件之间的通信。

拓展方向:这个方式不仅仅可以应用在门户单个页面上的小部件上,同样如果某个项目中的页面文件需要复用时,不想通过代码的复制,同样可以再那个文件配置打包单文件配置,打包出的文件在领一个项目中动态加载出来即可。这种模式与通用组件的install模式是有点类似的,只是这个单文件vue不是通用的,但同样可以达到打包复用页面。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
js+FSO遍历文件夹下文件并显示
Mar 07 Javascript
JavaScript中yield实用简洁实现方式
Jun 12 Javascript
JavaScript中“基本类型”之争小结
Jan 03 Javascript
JS实现可展开折叠层的鼠标拖曳效果
Oct 09 Javascript
js点击文本框后才加载验证码实例代码
Oct 20 Javascript
bootstrap Validator 模态框、jsp、表单验证 Ajax提交功能
Feb 17 Javascript
简单快速的实现js计算器功能
Aug 17 Javascript
详解layui中的树形关于取值传值问题
Jan 16 Javascript
jQuery Ajax实现Select多级关联动态绑定数据的实例代码
Oct 26 jQuery
mpvue全局引入sass文件的方法步骤
Mar 06 Javascript
Vue+Typescript中在Vue上挂载axios使用时报错问题
Aug 07 Javascript
vue 微信扫码登录(自定义样式)
Jan 06 Javascript
微信小程序局部刷新触发整页刷新效果的实现代码
Nov 21 #Javascript
跨域解决之JSONP和CORS的详细介绍
Nov 21 #Javascript
如何去除富文本中的html标签及vue、react、微信小程序中的过滤器
Nov 21 #Javascript
JSON生成Form表单的方法示例
Nov 21 #Javascript
apicloud拉起小程序并传递参数的方法示例
Nov 21 #Javascript
vue中组件的过渡动画及实现代码
Nov 21 #Javascript
详解promise.then,process.nextTick, setTimeout 以及 setImmediate的执行顺序
Nov 21 #Javascript
You might like
php设计模式 Prototype (原型模式)代码
2011/06/26 PHP
Zend Framework动作助手Url用法详解
2016/03/05 PHP
php使用PDO执行SQL语句的方法分析
2017/02/16 PHP
php中实现字符串翻转的方法
2017/02/22 PHP
PHP生成各种随机验证码的方法总结【附demo源码】
2017/06/05 PHP
php实现的中秋博饼游戏之掷骰子并输出结果功能详解
2017/11/06 PHP
解决laravel(5.5)访问public报错的问题
2019/10/12 PHP
javascript qq右下角滑出窗口 sheyMsg
2010/03/21 Javascript
Extjs显示从数据库取出时间转换JSON后的出现问题
2012/11/20 Javascript
jQuery多媒体插件jQuery Media Plugin使用详解
2014/12/19 Javascript
基于jQuery+JSON的省市二三级联动效果
2015/06/05 Javascript
jquery实现实时改变网页字体大小、字体背景色和颜色的方法
2015/08/05 Javascript
js下拉选择框与输入框联动实现添加选中值到输入框的方法
2015/08/17 Javascript
setTimeout学习小结
2017/02/08 Javascript
VUE2实现事件驱动弹窗示例
2017/10/21 Javascript
使用js获取伪元素的content实例
2017/10/24 Javascript
JS实现在文本指定位置插入内容的简单示例
2017/12/22 Javascript
vue中实现左右联动的效果
2018/06/22 Javascript
新手快速入门微信小程序组件库 iView Weapp
2019/06/24 Javascript
JS把字符串格式的时间转换成几秒前、几分钟前、几小时前、几天前等格式
2019/07/10 Javascript
Python的垃圾回收机制深入分析
2014/07/16 Python
Python中functools模块的常用函数解析
2016/06/30 Python
删除DataFrame中值全为NaN或者包含有NaN的列或行方法
2018/11/06 Python
python3 字符串/列表/元组(str/list/tuple)相互转换方法及join()函数的使用
2019/04/03 Python
Python玩转PDF的各种骚操作
2019/05/06 Python
Python Pandas中根据列的值选取多行数据
2019/07/08 Python
python制作微博图片爬取工具
2021/01/16 Python
HTML5 canvas 瀑布流文字效果的示例代码
2018/01/31 HTML / CSS
Ray-Ban雷朋美国官网:全球领先的太阳眼镜品牌
2016/07/20 全球购物
泰国折扣酒店预订:Hotels2Thailand
2018/03/20 全球购物
main 函数执行以前,还会执行什么代码
2013/04/17 面试题
数控技术应届生求职信
2013/11/13 职场文书
施工安全协议书
2013/12/11 职场文书
web前端之css水平居中代码解析
2021/05/20 HTML / CSS
python基础入门之字典和集合
2021/06/13 Python
Python类方法总结讲解
2021/07/26 Python