Vue動態加載異步組件

背景:

目前咱們項目都是按組件劃分的,而後各個組件之間封裝成產品。目前都是採用iframe直接嵌套頁面。項目中咱們仍是會碰到一些通用的組件跟業務之間有通訊,這種狀況下iframe並非最好的選擇,iframe存在跨域的問題,固然是postMessage仍是能夠通訊的,但也並不是是最好的。目前有這麼一個場景:門戶須要製做通用的首頁和數據概覽頁面,首頁和數據概覽頁面經過小部件來自由拼接。業務組件在製做的時候只須要提供各個模塊小部件的url就能夠了,但是若是小部件之間還存在聯繫呢?那麼iframe是很差的。目前採用Vue動態加載異步組件的方式來實現小組件之間的通訊。固然門戶也要提供一個通訊的基線:Vue事件總線(空的Vue實例對象)。css

內容:

使用過vue的都應該知道vue的動態加載組件components:html

Vue經過is來綁定須要加載的組件。那麼咱們如今須要的就是如何打包組件,若是經過複製業務組件內部的代碼,那麼這種就須要把依賴所有找齊,並複製過去(不少狀況下會漏下某個圖片或css等),這種方式是比較low的,不方便維護。所以咱們須要經過webpack來打包單個vue文件成js,這邊一個vue打包成一個js,不需壓代碼分割,css分離。由於component加載時只須要加載一個文件便可。打包文件配置以下:
首先在package.json加入打包命令:vue

"scripts": {
   ...
   "build-outCMP": "node build/build-out-components.js"
 },

Build-out-components.js文件:node

'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文件配置以下webpack

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文件配置以下:ios

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),還有自動刪除以前打包的文件。
目前的文件目錄爲git

clipboard.png

經過打包生產文件:github

clipboard.png

在static下outComponents文件夾內的js文件。(最終打包須要打包到dist下面,這邊作測試先打包在static文件下,方便後續動態組件ajax獲取組件使用)web

門戶的小部件是經過配置url,和調整佈局來生產的。所以業務組件至此已經完成了。只須要提供對門戶暴露的url便可。
接下來就是門戶這邊加載動態組件的實現了。門戶這邊就相對簡單了。看以下圖配置:

門戶經過component的動態組件來實現加載異步組件,經過ajax請求剛纔打包的url,而後實例化函數new Function來賦值給mode(new Function之因此分紅2部,是所以效驗規則的問題,可忽略)。以下圖演示
圖片描述
這樣就實現了動態加載異步組件了。門戶和業務組件能夠各個開發,任何業務開發數據概覽,門戶都不須要改代碼,只須要界面上配置url便可。這個異步加載組件已經結束了。這邊門戶須要封裝一封實現異步組件。父級只須要傳入url便可。這邊還有個能夠優化的是,能夠把mode優先緩存,那麼不須要每次都去加載請求。以下:ajax

咱們能夠看到在門戶的一個數據概覽頁面上加載了多個異步組件,那麼異步組件之間也是可能存在通訊的,這樣該如何作呢?由於如今已經不是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不是通用的,但一樣能夠達到打包複用頁面。

相關文章
相關標籤/搜索