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 相关文章推荐
你需要知道的JavsScript可以做什么?
Jun 29 Javascript
js 金额文本框实现代码
Feb 14 Javascript
兼容IE和Firefox火狐的上下、左右循环无间断滚动JS代码
Apr 19 Javascript
js实现发送验证码后的倒计时功能
May 28 Javascript
使用jQuery操作HTML的table表格的实例解析
Mar 13 Javascript
JS代码随机生成姓名、手机号、身份证号、银行卡号
Apr 27 Javascript
全面解析Javascript无限添加QQ好友原理
Jun 15 Javascript
JS中sort函数排序用法实例分析
Jun 16 Javascript
js获取腾讯视频ID的方法
Oct 03 Javascript
JS求解三元一次方程组值的方法
Jan 03 Javascript
解决Vue.js由于延时显示了{{message}}引用界面的问题
Aug 25 Javascript
vue实现点击隐藏与显示实例分享
Feb 13 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中的正规表达式(一)
2006/10/09 PHP
php站内搜索并高亮显示关键字的实现代码
2011/12/29 PHP
php定时计划任务的实现方法详解
2013/06/06 PHP
PHP批量生成图片缩略图的方法
2015/06/18 PHP
php 一维数组的循环遍历实现代码
2017/04/10 PHP
php ajax confirm 删除实例详解
2019/03/06 PHP
jQuery 浮动广告实现代码
2008/12/25 Javascript
ASP.NET jQuery 实例1(在TextBox里面创建一个默认提示)
2012/01/13 Javascript
jQuery 拖动层(在可视区域范围内)
2012/05/24 Javascript
js indexOf()定义和用法
2012/10/21 Javascript
jquery ajax对特殊字符进行转义防止js注入使用示例
2013/11/21 Javascript
javascript实现下拉提示选择框
2015/12/29 Javascript
JavaScript+html5 canvas绘制渐变区域完整实例
2016/01/26 Javascript
Javascript动画效果(2)
2016/10/11 Javascript
Require.js的基本用法详解
2017/07/03 Javascript
layui点击按钮添加可编辑的一行方法
2018/08/15 Javascript
解决webpack+Vue引入iView找不到字体文件的问题
2018/09/28 Javascript
Vue2.X和Vue3.0数据响应原理变化的区别
2019/11/07 Javascript
JS实现音乐导航特效
2020/01/06 Javascript
JS数组方法shift()、unshift()用法实例分析
2020/01/18 Javascript
Python中使用ConfigParser解析ini配置文件实例
2014/08/30 Python
Go/Python/Erlang编程语言对比分析及示例代码
2018/04/23 Python
Python退火算法在高次方程的应用
2018/07/26 Python
python实现根据文件关键字进行切分为多个文件的示例
2018/12/10 Python
对Python的多进程锁的使用方法详解
2019/02/18 Python
详解Django项目中模板标签及模板的继承与引用(网站中快速布置广告)
2019/03/27 Python
django foreignkey外键使用的例子 相当于left join
2019/08/06 Python
pycharm的python_stubs问题
2020/04/08 Python
python3 sleep 延时秒 毫秒实例
2020/05/04 Python
Python文件夹批处理操作代码实例
2020/07/21 Python
sklearn中的交叉验证的实现(Cross-Validation)
2021/02/22 Python
美国皮靴公司自1863年:The Frye Company
2016/11/30 全球购物
PHP如何防止SQL注入
2014/05/03 面试题
党员对照检查剖析材料
2014/10/13 职场文书
大三学生英语考试作弊检讨书
2015/01/01 职场文书
利用uni-app生成微信小程序的踩坑记录
2022/04/05 Javascript