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 相关文章推荐
Javascript处理DOM元素事件实现代码
May 23 Javascript
JS添加删除一组文本框并对输入信息加以验证判断其正确性
Apr 11 Javascript
Jquery取得iframe下内容的方法
Nov 18 Javascript
canvas压缩图片转换成base64格式输出文件流
Mar 09 Javascript
微信小程序实战之自定义抽屉菜单(7)
Apr 18 Javascript
jQuery Validate插件ajax方式验证输入值的实例
Dec 21 jQuery
用node-webkit把web应用打包成桌面应用(windows环境)
Feb 01 Javascript
详解angular分页插件tm.pagination二次触发问题解决方案
Jul 20 Javascript
layer弹出层扩展主题的方法
Sep 11 Javascript
Vue路由 重定向和别名的区别说明
Sep 09 Javascript
JS使用setInterval计时器实现挑战10秒
Nov 08 Javascript
详解vue-cli项目在IE浏览器打开报错解决方法
Dec 10 Vue.js
微信小程序局部刷新触发整页刷新效果的实现代码
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笔记之:有规律大文件的读取与写入的分析
2013/04/26 PHP
PHP之生成GIF动画的实现方法
2013/06/07 PHP
浅谈php中include文件变量作用域
2015/06/18 PHP
深入浅析PHP无限极分类的案例教程
2016/05/09 PHP
php封装的数据库函数与用法示例【参考thinkPHP】
2016/11/08 PHP
验证javascript中Object和Function的关系的三段简单代码
2010/06/27 Javascript
基于jquery的时间段实现代码
2012/08/02 Javascript
关于JavaScript中string 的replace
2013/04/12 Javascript
js Math 对象的方法
2013/09/01 Javascript
js call方法详细介绍(js 的继承)
2013/11/18 Javascript
JavaScript的作用域和块级作用域概念理解
2014/09/21 Javascript
node.js中的fs.createWriteStream方法使用说明
2014/12/17 Javascript
jQuery实现分章节锚点“回到顶部”动画特效代码
2015/10/23 Javascript
jQuery实现的选择商品飞入文本框动画效果完整实例
2016/08/10 Javascript
vue 刷新之后 嵌套路由不变 重新渲染页面的方法
2018/09/13 Javascript
原生javascript实现连连看游戏
2019/01/03 Javascript
Vue实现导航栏点击当前标签变色功能
2020/08/19 Javascript
JS用最简单的方法实现四舍五入
2019/08/27 Javascript
vue中element 的upload组件发送请求给后端操作
2020/09/07 Javascript
vue3.0中使用element的完整步骤
2021/03/04 Vue.js
[01:07:19]2018DOTA2亚洲邀请赛 4.5 淘汰赛 Mineski vs VG 第一场
2018/04/06 DOTA
[01:06:54]DOTA2-DPC中国联赛 正赛 SAG vs DLG BO3 第二场 2月28日
2021/03/11 DOTA
Python实现周期性抓取网页内容的方法
2015/11/04 Python
pandas 使用apply同时处理两列数据的方法
2018/04/20 Python
如何通过python画loss曲线的方法
2019/06/26 Python
Python爬取12306车次信息代码详解
2020/08/12 Python
用CSS3打造HTML5的Logo(实现代码)
2016/06/16 HTML / CSS
HTML5头部标签的一些常用信息小结
2016/10/23 HTML / CSS
Kidsroom台湾:来自德国的婴儿用品
2017/12/11 全球购物
国际语言毕业生求职信
2014/07/08 职场文书
领导班子作风建设年个人整改措施
2014/09/29 职场文书
北京英语导游词
2015/02/12 职场文书
2016春节慰问信范文
2015/03/25 职场文书
小学生表扬稿范文
2015/05/05 职场文书
2016年秋季运动会广播稿
2015/12/21 职场文书
vue实现列表拖拽排序的示例代码
2022/04/08 Vue.js