這篇文章主要介紹了Vue動態加載異步組件的方法,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
背景:
目前我們項目都是按組件劃分的,然後各個組件之間封裝成產品。目前都是採用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不是通用的,但同樣可以達到打包複用頁面。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持神馬文庫。