Vue-Cli3插件实战一:vue-cli-plugin-dll

栏目: 编程语言 · 发布时间: 5年前

内容简介:关于模块预编译,网上的教程及webpack配置攻略非常多,没有经验的读者可参考webpack dllPlugin。在前端项目迭代到中后期或者依赖第三方模块体积较大时,模块预编译可有效提升注意:本文封装的

vue-cli3 版本的发布距今已经过了大半年,前后迭代了50多个版本,终于趋于稳定;这里不得不得感叹vue开源团队对vue技术栈的倾力贡献,使得vue社区的前端工程化实践又向前迈了一大步。相比 vue-cli2 版本的'大锅混',三版本的插件系统卓识令人惊艳了一把,因此组内也在第一时间迁移了 vue-cli3 ,本文算是对插件系统的一次探索与学习,也算是一次抛砖引玉,期待后面继续更新推出优秀的插件并将开发插件的经验总结开源出来。

插件开发背景

关于模块预编译,网上的教程及webpack配置攻略非常多,没有经验的读者可参考webpack dllPlugin。在前端项目迭代到中后期或者依赖第三方模块体积较大时,模块预编译可有效提升 webpack 构建速度,但不同项目需要预编译的模块不同,以及配置细节也不同,所以借助 vue-cli3 封装成 vue-plugin-dll 插件,将构建逻辑封装在插件内部,对外开放预编译的配置项,这样可以使前端开发更专注于业务。

注意:本文封装的 vue-cli-plugin-dll 未发布到npm中,仅提供了开发插件的思路和总结。

模块预编译原理

webpack.dllPlugin 本质是将大量复用模块且不会频繁更新的库进行预编译,且只需要编译一次,编译完成后产出指定文件(可以称为动态链接库)。在之后的构建过程中不会再对这些模块进行编译,而是直接使用DllReferencePlugin来引用动态链接库的代码,因此可以提高构建速度。一般可以将第三方模块进行预编译,如 vue、vue-router、vuex 等,只要这些依赖模块不更新,就不需要再重新编译。

项目对比

在封装 vue-cli-plugin-dll 插件之前,需要探索一下模块预编译对前端项目的影响有多大。 这里实验对比了两个项目:

vue-cli3
vue-cli3

改造前现状

开发环境,未预先运行dll脚本进行预编译

构建次数 第一次 第二次 第三次 第四次 平均用时
vue-init 2997ms 3561ms 2867ms 2935ms 3078ms
sellgoods 21449ms 16601ms 22480ms 22600ms 20782ms

生产环境,未预先运行dll脚本进行预编译

构建次数 第一次 第二次 第三次 第四次 平均用时 构建包大小
vue-init 3736ms 3713ms 3647ms 3800ms 3724ms 122.99 KB
sellgoods 52.09s 38.77s 39.78s 47.82s 44.615s 2.54 MB

改造后现状

其中 files 指定了需要提前预编译的模块 list

// vue-init 预编译列表
files: [
    'vue/dist/vue.runtime.esm.js',
    'vue-router',
    'vuex'
]

// sellgoods 预编译列表
files: [
    'vue/dist/vue.runtime.esm.js',
    'vue-router',
    'vuex',
    'axios',
    'element-ui',
    'nprogress',
    'qs',
    'resize-observer-polyfill',
    'lodash'
]
复制代码

开发环境,运行dll脚本提前预编译

构建次数 第一次 第二次 第三次 第四次 平均用时
vue-init 2723ms 2849ms 2799ms 2774ms 2786ms
sellgoods 16115ms 16432ms 16479ms 15131ms 16039ms

生产环境,运行dll脚本提前预编译

构建次数 第一次 第二次 第三次 第四次 平均用时 构建包大小
vue-init 3057ms 2936ms 3708ms 2877ms 3144ms 25.06 KB
sellgoods 27.93s 27.60s 27.72s 27.10s 27.58s 1.51 MB

结果分析

实际上,影响webpack构建速度的因素存在很多,比如硬件设施、webpack配置是否合理、代码分割策略等等。这里只针对预编译(创建动态软链)这一种情况的优化做了分析。

同时为了结果的可行性分析,这里剔除了异常数据,仅对优化与未优化两种结果的数据进行对比来进行讨论。

从生产环境的构建时间可以看到:

  • vue-init:开发环境下构建平均耗时 3078ms;优化后平均耗时2786ms,速度提升10%左右;
  • sellgoods:开发环境下构建平均耗时20782ms;优化后平均耗时16039ms,速度提升30%左右。

从生产环境的构建时间可以看到:

  • vue-init:生产环境下构建平均耗时3724ms,优化后平均耗时3144ms,构建产出包大小由122.99 KB缩减到25.06 KB,速度提升18%左右。
  • sellgoods:生产环境下构建平均耗时44.615s,优化后平均耗时27.58s,构建产出包大小由2.54 MB缩减到1.51 MB,速度提升60%左右。

注:构建产物减少不意味着浏览器加载资源变少,而是减少的部分被提前预编译,以script标签形式在index.html中引入。

结论:

针对同一工程的不同环境下而言,预编译对生产环境的构建提升速度明显

从vue-init和sellgoods二者的生产环境与开发环境进行对比可以看到,不考虑硬件设施和其它因素影响的情况下,生产环境下的效率提升要比开发环境提升效率高出一倍左右。

预编译的模块体积越大,构建提升效率越高

将sellgoods与vue-init进行横向比较,vue-init项目是脚手架的初始项目,只添加了vue、vue-router、vuex等依赖库;而sellgoods项目已进行到中后期,相对于vue-init而言,代码量及依赖的库要多很多,其中以 element-ui 最为明显。从结果可以看到,sellgoods无论是生产环境还是开发环境下,预编译对构建效率的提升都要比vue-init明显。

通用化方案

实际上, webpack.dllPlugin 配置门槛很低,但没有必要在每个工程中配置一遍,或者将底层配置开放给业务人员。这里选择了封装 vue-cli-plugin-dll 插件并发布到内网 npm 源中,供其他项目自由引用,下面详细介绍如果一步步开放 vue-cli3 插件。

插件开发文档可见:vue插件开发指南

1.构建插件目录

├── generator
├    └── index.js
├── service
├    ├── base.js
├    └── dll.js
├── index.js
└── package.json
复制代码

2.开发 generator

const { red, green } = require('chalk');

module.exports = (api, options, rootOptions) => {
  api.extendPackage({
    scripts: {
      dll: 'vue-cli-service dll'
    },
    vue: {
      pluginOptions: {
        dll: {
          // 文件名
          entry: 'vendor',
          // 文件输出路径
          filePath: './public/vendor',
          // 预编译包
          files: ['vue/dist/vue.runtime.esm.js', 'vue-router', 'vuex'],
          // 是否保留历史编译记录
          noCache: true
        }
      }
    }
  });
};
复制代码

generator 对外暴露一个函数,对内接受一个api工具类( GeneratorAPI )负责对工程做偏好设置。这里我们借助 extendPackage 方法向 package.json 文件注入 dll 指令,以及 dll 插件的初始化配置。如果建立项目的时候勾选了 useConfigFiles ,那么 vue 属性下的配置将会被注入到 vue.config.js 文件中。

3.开发service(index.js)

module.exports = (api, ops) => {
  require('./service/base')(api, ops);
  require('./service/dll')(api, ops);
};

module.exports.defaultModes = {
  dll: 'production'
};
复制代码

service 也对外暴露一个函数,并接受api工具类( PluginAPI )负责对webpack作更新配置。 这里我们将webpack配置进行解耦, base 配置公共 webpack 逻辑,创建动态软链;而 dll 负责预编译模块逻辑。

4.开发dll指令

const { red, green } = require('chalk');

module.exports = (api, ops = {}) => {
  api.registerCommand(
    'dll',
    {
      description: '第三方模块预编译',
      usage: 'vue-cli-service dll'
    },
    async args => {
      const Config = require('webpack-chain');
      const webpack = require('webpack');
      const fs = require('fs-extra');
      const path = require('path');
      const {
        log,
        done,
        logWithSpinner,
        stopSpinner
      } = require('@vue/cli-shared-utils');

      logWithSpinner(green('Building dll files to public vendor'));

      const config = new Config();
      const pluginOptions = ops.pluginOptions || {};
      const root = api.getCwd();
      const dllConfig = pluginOptions.dll;

      if (!dllConfig) {
        log();
        log(red('缺失dll文件配置'));
        log();
        process.exit(0);
      }

      function resolve(dir) {
        return path.resolve(root, dir);
      }
      function hasVendor(filePath) {
        return fs.existsSync(resolve(filePath));
      }

      // 默认打到public/vendor文件夹里
      const {
        entry = 'vendor',
        filePath = `./public/${entry}`,
        files,
        noCache = true
      } = dllConfig;

      if (files.length) {
        files.forEach(oneOf => config.entry(entry).add(oneOf));
      }

      config.output
        .path(resolve(filePath))
        .filename('[name].dll.[hash:8].js')
        .library('[name]_[hash]')
        .end();

      if (noCache) {
        // 清空vendor缓存
        config.when(hasVendor(filePath), () => {
          fs.removeSync(resolve(filePath));
        });
      }

      config
        .plugin('DllPlugin')
        .use(require('webpack/lib/DllPlugin'), [
          {
            name: '[name]_[hash]',
            path: path.join(root, filePath, '[name]-manifest.json'),
            context: root
          }
        ])
        .end();

      const result = config.toConfig();
      webpack(result, (err, stats) => {
        stopSpinner(false);
        if (err) {
          log();
          log(red(err));
          log();
          return false;
        }
        done(green('Build complete'));
      });
    }
  );
};

复制代码

这里借助 registerCommand 方法注册 dll 指令,与 generator 中扩展的脚本前后呼应,在 dll 方法中,核心使用 webpack/lib/DllPlugin 插件预编译模块,并产生缓存文件,供其他环境配置使用。

5.开发base.js

module.exports = (api, ops) => {
  const webpack = require('webpack');
  const path = require('path');
  const fs = require('fs');
  const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
  const root = api.getCwd();

  function resolve(dir) {
    return path.resolve(root, dir);
  }

  if (ops && ops.pluginOptions) {
    const { entry = 'vendor', filePath = `./public/${entry}` } =
      ops.pluginOptions.dll || {};
    const outputPath = path.basename(filePath) || entry;
    if (fs.existsSync(path.join(filePath, `${entry}-manifest.json`))) {
      api.configureWebpack(config => {
        config.plugins.push(
          new webpack.DllReferencePlugin({
            context: root,
            manifest: require(resolve(`${filePath}/${entry}-manifest.json`))
          }),
          new AddAssetHtmlPlugin({
            filepath: resolve(`${filePath}/*.js`),
            publicPath: `./${outputPath}`,
            outputPath: `./${outputPath}`
          })
        );
      });
    }
  }
};
复制代码

在插件安装完毕之后,运行 yarn dll 指令,即可将预编译的包及缓存打到 public/vendor 目录下,这时还需为其他环境(如开发和生产环境)配置动态软链,忽略预编译模块的构建。在 base.js 中借助 configureWebpack 方法将创建动态软链的配置更新到最终版的 webpack 配置中(也可使用 chainWebpack )。

总结

至此,一个初步的 vue-cli-plugin-dll 插件开发完毕,具备了预编译模块的功能,但扔有很多的不足,比如未开放预编译模块的 loader 或者 plugin 定制功能等,这里仅是一次插件封装的尝试。

最后:欢迎大牛或者有经验的前端从业人员对本文有误内容不吝指导。

转载请注明出处,十分感谢!


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

游戏之旅

游戏之旅

云风 / 电子工业出版社 / 2005-12-01 / 46.00

这是一本非常有特色的计算机编程学习书籍。其特色就在于它将作者十余年来对游戏编程的所思、所感、所悟与编程理论知识相结合,褪去了纯理论的教学理念,使读者在前人的学习过程中吸取学习经验和教训,将计算机基础知识和高级编程技术不知不觉地融入自己的头脑中。 本书忠实地记录了作者十余年来对游戏编程的所思、所感、所悟。全书按照作者本人学习和实践的过程,带着读者从基础的计算机知识到高级的编程技......一起来看看 《游戏之旅》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

MD5 加密
MD5 加密

MD5 加密工具

SHA 加密
SHA 加密

SHA 加密工具