资讯专栏INFORMATION COLUMN

Vue动态加载异步组件

awesome23 / 2409人阅读

摘要:目前采用动态加载异步组件的方式来实现小组件之间的通信。内容使用过的都应该知道的动态加载组件通过来绑定需要加载的组件。总结本篇主要借助的动态组件和打包单文件来实现动态加载异步组件,通过的事件总线挂载在上来实现平级组件之间的通信。

背景:

目前我们项目都是按组件划分的,然后各个组件之间封装成产品。目前都是采用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
  }) + "

")

  if (stats.hasErrors()) {
    console.log(chalk.red("  Build failed with errors.
"))
    process.exit(1)
  }

  console.log(chalk.cyan("  Build complete.
"))
  console.log(chalk.yellow(
    "  Tip: built files are meant to be served over an HTTP server.
" +
    "  Opening index.html over file:// won"t work.
"
  ))
})

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()
        }
      }

然后业务组件之间就可以根据自己的业务实现通信:
组件一和组件二代码如下:

    
    
    
    
    
    

业务组件就可以根据this.$root.eventBus和vue上的事件传递($emit, $on)来实现相互的通信。

总结:本篇主要借助vue的动态组件和webpack打包单文件来实现动态加载异步组件,通过vue的事件总线挂载在this.$root上来实现平级组件之间的通信。

拓展方向:这个方式不仅仅可以应用在门户单个页面上的小部件上,同样如果某个项目中的页面文件需要复用时,不想通过代码的复制,同样可以再那个文件配置打包单文件配置,打包出的文件在领一个项目中动态加载出来即可。这种模式与通用组件的install模式是有点类似的,只是这个单文件vue不是通用的,但同样可以达到打包复用页面。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/108691.html

相关文章

  • Vue动态组件异步组件

    摘要:动态组件如果我们打算在一个地方根据不同的状态引用不同的组件的话,比如页,那么给我们提供动态组件。实现动态组件的加载。的值可以是一个已经注册的组件的名字或者一个组件的选对象。 动态组件 如果我们打算在一个地方根据不同的状态引用不同的组件的话,比如tab页,那么Vue给我们提供动态组件。 基本使用 Parent.vue {{btn.name}} ...

    nanchen2251 评论0 收藏0
  • 利用VUE异步组件动态加载组件,实现自定义组件顺序、动态绑定传入子组件的props、动态绑定监听子

    摘要:发现了动态组件异步组件这个东西简直是救命啊动态组件异步组件思路分析有了动态组件这个东西之后,我们就可以根据绑定不同的值来渲染不同的组件。每个组件要传给子组件的值和接收子组件的事件也可以动态的绑定上去。 推动我实现这个功能的业务背景 最近接到一个让我很头疼的需求:产品要求我们系统页面上所有的模块都支持顺序的变动。比如有 模块A、B、C、D,可以无序的展示在页面上,我刚听到这个需求的时候我...

    marser 评论0 收藏0
  • VueJS 如何编译服务器端远程模板【异步组件+简单方法】

    摘要:,常规组件,卒。小结总之呢,上面分析了在中编译远程模板的可能性,最后得出了两种方法异步组件,应该是官方的推荐方法动态组件,变通之法,论坛上发现的思路当然如果有其他方法欢迎交流,本文如果有不严谨不正确的地方也欢迎指出本文发自我的,原文链接我的 说明 有些时候你可能需要从后台获取模板,并在前台在自己编译,这在用 AngularJS 1.x 的时候似乎很常见,可以直接用 ng-include...

    褰辩话 评论0 收藏0
  • VueJS 如何编译服务器端远程模板【异步组件+简单方法】

    摘要:,常规组件,卒。小结总之呢,上面分析了在中编译远程模板的可能性,最后得出了两种方法异步组件,应该是官方的推荐方法动态组件,变通之法,论坛上发现的思路当然如果有其他方法欢迎交流,本文如果有不严谨不正确的地方也欢迎指出本文发自我的,原文链接我的 说明 有些时候你可能需要从后台获取模板,并在前台在自己编译,这在用 AngularJS 1.x 的时候似乎很常见,可以直接用 ng-include...

    2i18ns 评论0 收藏0
  • 使用Webpack的代码分离实现Vue加载(译文)

    摘要:当一个的项目体积变得十分庞大的时候,使用的代码分离功能将,或的代码进行分离并按需加载,会极大的提高的首屏加载速度。如果我们使用函数在中返回模块作为载荷,就实现了懒加载。 当一个Vue的项目体积变得十分庞大的时候,使用Webpack的代码分离功能将Vue Components,routes或Vuex的代码进行分离并按需加载,会极大的提高App的首屏加载速度。 showImg(https:...

    SmallBoyO 评论0 收藏0

发表评论

0条评论

awesome23

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<