Vue動態(tài)加載異步組件的方法
背景:
目前我們項(xiàng)目都是按組件劃分的,然后各個(gè)組件之間封裝成產(chǎn)品。目前都是采用iframe直接嵌套頁面。項(xiàng)目中我們還是會碰到一些通用的組件跟業(yè)務(wù)之間有通信,這種情況下iframe并不是最好的選擇,iframe存在跨域的問題,當(dāng)然是postMessage還是可以通信的,但也并非是最好的。目前有這么一個(gè)場景:門戶需要制作通用的首頁和數(shù)據(jù)概覽頁面,首頁和數(shù)據(jù)概覽頁面通過小部件來自由拼接。業(yè)務(wù)組件在制作的時(shí)候只需要提供各個(gè)模塊小部件的url就可以了,可是如果小部件之間還存在聯(lián)系呢?那么iframe是不好的。目前采用Vue動態(tài)加載異步組件的方式來實(shí)現(xiàn)小組件之間的通信。當(dāng)然門戶也要提供一個(gè)通信的基線:Vue事件總線(空的Vue實(shí)例對象)。
內(nèi)容:
使用過vue的都應(yīng)該知道vue的動態(tài)加載組件components:
Vue通過is來綁定需要加載的組件。那么我們現(xiàn)在需要的就是如何打包組件,如果通過復(fù)制業(yè)務(wù)組件內(nèi)部的代碼,那么這種就需要把依賴全部找齊,并復(fù)制過去(很多情況下會漏下某個(gè)圖片或css等),這種方式是比較low的,不方便維護(hù)。因此我們需要通過webpack來打包單個(gè)vue文件成js,這邊一個(gè)vue打包成一個(gè)js,不需壓代碼分割,css分離。因?yàn)閏omponent加載時(shí)只需要加載一個(gè)文件即可。打包文件配置如下:
首先在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 更新 默認(rèn)值為 true v12及之前版本為 false, 此項(xiàng)配置影響 vue 自身異步組件寫法以及 webpack 打包結(jié)果
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自動獲?。J(rèn)為src/out-components),還有自動刪除之前打包的文件。
目前的文件目錄為

通過打包生產(chǎn)文件:

在static下outComponents文件夾內(nèi)的js文件。(最終打包需要打包到dist下面,這邊做測試先打包在static文件下,方便后續(xù)動態(tài)組件ajax獲取組件使用)
門戶的小部件是通過配置url,和調(diào)整布局來生產(chǎn)的。因此業(yè)務(wù)組件至此已經(jīng)完成了。只需要提供對門戶暴露的url即可。
接下來就是門戶這邊加載動態(tài)組件的實(shí)現(xiàn)了。門戶這邊就相對簡單了。看如下圖配置:
門戶通過component的動態(tài)組件來實(shí)現(xiàn)加載異步組件,通過ajax請求剛才打包的url,然后實(shí)例化函數(shù)new Function來賦值給mode(new Function之所以分成2部,是因此效驗(yàn)規(guī)則的問題,可忽略)。這樣就實(shí)現(xiàn)了動態(tài)加載異步組件了。門戶和業(yè)務(wù)組件可以各個(gè)開發(fā),任何業(yè)務(wù)開發(fā)數(shù)據(jù)概覽,門戶都不需要改代碼,只需要界面上配置url即可。這個(gè)異步加載組件已經(jīng)結(jié)束了。這邊門戶需要封裝一封實(shí)現(xiàn)異步組件。父級只需要傳入url即可。這邊還有個(gè)可以優(yōu)化的是,可以把mode優(yōu)先緩存,那么不需要每次都去加載請求。如下:
我們可以看到在門戶的一個(gè)數(shù)據(jù)概覽頁面上加載了多個(gè)異步組件,那么異步組件之間也是可能存在通信的,這樣該如何做呢?因?yàn)楝F(xiàn)在已經(jīng)不是iframe嵌套了,可以通過監(jiān)聽一個(gè)組件,然調(diào)用另一個(gè)組件的方法,這樣確實(shí)可以實(shí)現(xiàn)平級組件間的通信,但這樣勢必不可取的,因?yàn)橐坏┻@樣做了門戶必須要根據(jù)業(yè)務(wù)來輔助,修改代碼來實(shí)現(xiàn)功能。因此這邊借用門戶來生成vue事件總線(空的vue實(shí)例)來實(shí)現(xiàn)。
門戶代碼如下: 在this.$root上掛在一個(gè)事件總線:
created () {
if (!this.$root.eventBus) {
this.$root.eventBus = new Vue()
}
}
然后業(yè)務(wù)組件之間就可以根據(jù)自己的業(yè)務(wù)實(shí)現(xiàn)通信:
組件一和組件二代碼如下:
<template>
<div class="test1">
這是一個(gè)外部組件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>
業(yè)務(wù)組件就可以根據(jù)this.$root.eventBus和vue上的事件傳遞($emit, $on)來實(shí)現(xiàn)相互的通信。
總結(jié):本篇主要借助vue的動態(tài)組件和webpack打包單文件來實(shí)現(xiàn)動態(tài)加載異步組件,通過vue的事件總線掛載在this.$root上來實(shí)現(xiàn)平級組件之間的通信。
拓展方向:這個(gè)方式不僅僅可以應(yīng)用在門戶單個(gè)頁面上的小部件上,同樣如果某個(gè)項(xiàng)目中的頁面文件需要復(fù)用時(shí),不想通過代碼的復(fù)制,同樣可以再那個(gè)文件配置打包單文件配置,打包出的文件在領(lǐng)一個(gè)項(xiàng)目中動態(tài)加載出來即可。這種模式與通用組件的install模式是有點(diǎn)類似的,只是這個(gè)單文件vue不是通用的,但同樣可以達(dá)到打包復(fù)用頁面。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vue2中的keep-alive使用總結(jié)及注意事項(xiàng)
vue2.0提供了一個(gè)keep-alive組件用來緩存組件,避免多次加載相應(yīng)的組件,減少性能消耗。本文給大家介紹vue2中的keep-alive使用總結(jié)及注意事項(xiàng),需要的朋友參考下吧2017-12-12
vue使用html2canvas和jspdf將html轉(zhuǎn)成pdf
在前端開發(fā)中, html轉(zhuǎn)pdf是最常見的需求,下面這篇文章主要給大家介紹了關(guān)于vue如何使用html2canvas和jspdf將html轉(zhuǎn)成pdf的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-03-03
VUE識別訪問設(shè)備是pc端還是移動端的實(shí)現(xiàn)步驟
經(jīng)常在項(xiàng)目中會有支持pc與手機(jī)端需求,并且pc與手機(jī)端是兩個(gè)不一樣的頁面,這時(shí)就要求判斷設(shè)置,下面這篇文章主要給大家介紹了關(guān)于VUE識別訪問設(shè)備是pc端還是移動端的相關(guān)資料,需要的朋友可以參考下2023-05-05
Vue v2.4中新增的$attrs及$listeners屬性使用教程
這篇文章主要給大家介紹了關(guān)于Vue v2.4中新增的$attrs及$listeners屬性的使用方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-01-01
圖文詳解如何在vue3+vite項(xiàng)目中使用svg
SVG指可伸縮矢量圖形,用來定義用于網(wǎng)絡(luò)的基于矢量的圖形,下面這篇文章主要給大家介紹了關(guān)于如何在vue3+vite項(xiàng)目中使用svg的相關(guān)資料,需要的朋友可以參考下2021-11-11

