背景:

目前我们项目都是按组件划分的,然后各个组件之间封装成产品。目前都是采用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),还有自动删除之前打包的文件。
目前的文件目录为

通过打包生产文件:

在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不是通用的,但同样可以达到打包复用页面。

原文地址:https://segmentfault.com/a/1190000017059266

Vue动态加载异步组件的更多相关文章

  1. vue实践---vue动态加载组件

    开发中遇到要加载10个或者更多的,类型相同的组件时,如果用普通的 import 引入组件,components注册组件,代码显得太啰嗦了,这时候就需要用到 require.context 动态加载这些 ...

  2. vue动态加载组件

    vue动态加载组件,可以使用以下方式 <component :is="propertyname" v-for="tab in tabs"></ ...

  3. Vue 动态加载组件

    为什么要动态加载呢?而不是一次性加载呢? 一次性?你能保证你拿的内容不多,那从性能方面说还是OK的.否则,就该什么时候用,就什么时候取. 得出这想法,源于前几天上班赶产品的故事: A组件是父亲,B组件 ...

  4. VUE 动态加载组件的四种方式

    动态加载组件的四种方式: 1.使用import导入组件,可以获取到组件 var name = 'system'; var myComponent =() => import('../compon ...

  5. netcore实践:跨平台动态加载native组件

    缘起netcore框架下实现基于zmq的应用. 在.net framework时代,我们进行zmq开发由很多的选择,比较常用的有clrzmq4和NetMQ. 其中clrzmq是基于libzmq的Int ...

  6. vue动态加载图片,取消格式验证

    vue 一. 动态加载图片 (以vue模板为例) app.vue 代码如下: <template> <div id="app"> <img :src= ...

  7. Vue动态加载图片图片不显示

    图片是放在assets文件夹下的 使用require进行解决 图片不显示的原因 在webpack,将图片放在assets中,会将图片图片来当做模块来用,因为是动态加载的,所以url-loader将无法 ...

  8. vue 动态加载图片路径报错解决方法

    最近遇到图片路径加载报错的问题 之前一直都是把图片放到assets的文件下的.总是报错,看到一些文章并且尝试成功了,特意记录下 首先先说明下vue-cli的assets和static的两个文件的区别, ...

  9. Vue(二十八)el-cascader 动态加载 - 省市区组件

    1.后台接口为点击加载下一级 ,传省市区id <template> <el-cascader v-model="selectedOptions" placehol ...

随机推荐

  1. OSG实现利用菲波那契网格(Fibonacci lattice 或 Fibonacci grid)均分球面

    #include<Windows.h> #include<osg/Node> #include<osg/Geode> #include<osg/Group&g ...

  2. 在vue中使用pug

    安装pug npm i pug pug-loader pug-cli pug-filters -D pug :安装pug pug-loader:pug的loader pug-cli:pug 编译工具 ...

  3. Makefile知识点

    1.标准Makefile文件示例如下: #把.o .C .cxx .cpp .hxx .h添加到后缀列表中. .SUFFIXES: .o .C .cxx .cpp .hxx .h #设置变量并赋值,方 ...

  4. leetcode算法题笔记|two sum

    题目: 我的答案: /** * @param {number[]} nums * @param {number} target * @return {number[]} */ var twoSum = ...

  5. PAT甲级——A1024 Palindromic Number

    A number that will be the same when it is written forwards or backwards is known as a Palindromic Nu ...

  6. Codec入门

    Codec 提供了一些公共的编解码实现,比如Base64, Hex, MD5等等. 工具类 package com.cxl.beanutil.util; import org.apache.commo ...

  7. 记一次msf入侵win10,并拍照

    好久没有玩kali了,刚才看到一位大佬msf渗透win10的思路,我感觉不错,我就来复现一下 kali :192.168.45.136 win10 : 192.168.45.137 1  首先,我们查 ...

  8. 评价目标检测(object detection)模型的参数:IOU,AP,mAP

    首先我们为什么要使用这些呢? 举个简单的例子,假设我们图像里面只有1个目标,但是定位出来10个框,1个正确的,9个错误的,那么你要按(识别出来的正确的目标/总的正确目标)来算,正确率100%,但是其实 ...

  9. Math concepts / 数学概念

    链接网址:Math concepts / 数学概念 – https://www.codelast.com/math-concepts-%e6%95%b0%e5%ad%a6%e6%a6%82%e5%bf ...

  10. Jupyter notebook使用matplotlib不出图解决办法

    1.在jupyter notebook使用plot的时候没有显示图像2.在命令行知道需要使用ipython --pylab进入ipython环境才能做出图像,jupyter notebook该怎么设置 ...