webpack优化实践(遇到很奇葩的问题)

简介: 本文主要讲webpack优化实践



1、先上打包前后的优化效果图




2、优化背景


  • webpack 的构建过程太花时间
  • webpack 打包的结果体积太大
  • 作者的项目是webpack 2的老项目(项目已上线很久,不太敢突然的升级webpack版本,以防有意想不到的问题)


3、优化操作


1. 用include或exclude来避免不必要的转义


module: {
    rules: [{
        test: /\.js$/,
        use: {
             loader: 'babel-loader',
        },
        // 规避了对庞大的 node_modules 文件夹或者 bower_components 文件夹的处理
        exclude: /(node_modules|bower_components)/,
        include: resolve('source')
    }]
}


2. 开启缓存将转译结果缓存至文件系统


module: {
    rules: [{
        test: /\.js$/,
        use: {
            // 开启缓存
            loader: 'babel-loader?cacheDirectory=true'
        },
        exclude: /(node_modules|bower_components)/,
        include: resolve('source')
    }]
}


3. DllPlugin/DLLReferencePlugin优化处理


处理第三方库有多种方式,externals、CommonsChunkPlugin、splitChunks、DllPlugin/DLLReferencePlugin 。我们先说说各种的优劣势


3.1 externals


防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)。


<script src="https://unpkg.com/element-ui@2.0.8/lib/index.js"></script>
module.exports= {
    externals: {
        ElementUi: 'ELEMENT' // 主要element-ui保留出来的全局变量是ELEMENT
    }
}
import ElementUi from 'ElementUi';


  • 优点:操作比较简单,支持多种设置方式,也可以自定义行为。
  • 缺点:操作不是很灵活,需要设置多步,并且会出现意想不到的情况。下面我会介绍到。


3.2 CommonsChunkPlugin


通过将公共模块拆出来,最终合成的文件能够在最开始的时候加载一次,便存到缓存中供后续使用。这个带来速度上的提升,因为浏览器会迅速将公共的代码从缓存中取出来, 而不是每次访问一个新页面时,再去加载一个更大的文件。(但是新的webpack中已经被移除,使用的是SplitChunksPlugin)


new webpack.optimize.CommonsChunlPlugin({
      name: string, // or
      names: string[],
      // 这是 common chunk 的名称。已经存在的 chunk 可以通过传入一个已存在的 chunk 名称而被选择。
      // 如果一个字符串数组被传入,这相当于插件针对每个 chunk 名被多次调用
      // 如果该选项被忽略,同时 `options.async` 或者 `options.children` 被设置,所有的 chunk 都会被使用,
      // 否则 `options.filename` 会用于作为 chunk 名。
      // When using `options.async` to create common chunks from other async chunks you must specify an entry-point
      // chunk name here instead of omitting the `option.name`.
      filename: string,
      // common chunk 的文件名模板。可以包含与 `output.filename` 相同的占位符。
      // 如果被忽略,原本的文件名不会被修改(通常是 `output.filename` 或者 `output.chunkFilename`)。
      // This option is not permitted if you're using `options.async` as well, see below for more details.
      minChunks: number|Infinity|function(module, count) -> boolean,
      // 在传入  公共chunk(commons chunk) 之前所需要包含的最少数量的 chunks 。
      // 数量必须大于等于2,或者少于等于 chunks的数量
      // 传入 `Infinity` 会马上生成 公共chunk,但里面没有模块。
      // 你可以传入一个 `function` ,以添加定制的逻辑(默认是 chunk 的数量)
      chunks: string[],
      // 通过 chunk name 去选择 chunks 的来源。chunk 必须是  公共chunk 的子模块。
      // 如果被忽略,所有的,所有的 入口chunk (entry chunk) 都会被选择。
      children: boolean,
      // 如果设置为 `true`,所有公共 chunk 的子模块都会被选择
      deepChildren: boolean,
      // 如果设置为 `true`,所有公共 chunk 的后代模块都会被选择
      async: boolean|string,
      // 如果设置为 `true`,一个异步的  公共chunk 会作为 `options.name` 的子模块,和 `options.chunks` 的兄弟模块被创建。
      // 它会与 `options.chunks` 并行被加载。
      // Instead of using `option.filename`, it is possible to change the name of the output file by providing
      // the desired string here instead of `true`.
      minSize: number,
      // 在 公共chunk 被创建立之前,所有 公共模块 (common module) 的最少大小。
});


  • 优点:最终合成的文件能够在最开始的时候加载一次,便存到缓存中供后续使用。这个带来速度上的提升。
  • 缺点:每次构建时都会重新构建一次 vendor。


3.3 splitChunks


webpack4中支持了零配置的特性,同时对块打包也做了优化,CommonsChunkPlugin已经被移除了,现在是使用optimization.splitChunks代替。


// webpack.config.js
splitChunks: {
    chunks: "async",
    minSize: 30000,
    minChunks: 1,
    maxAsyncRequests: 5,
    maxInitialRequests: 3,
    automaticNameDelimiter: '~',
    name: true,
    cacheGroups: {
        vendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: -10
        },
      default: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true
        }
    }
}


  • 优点:使用SplitChunksPlugin是Webpack 4的默认行为,可能你设置一下chunks: 'all'就足够了,打包速度更快了。
  • 缺点:SplitChunksPlugin是Webpack 4的特性,Webpack 4之前不能操作,比如我当前优化项目,webpack 2。


3.4 DllPlugin/DLLReferencePlugin


DllPlugin:是基于 Windows 动态链接库(dll)的思想被创作出来的。这个插件会把第三方库单独打包到一个文件中,这个文件就是一个单纯的依赖库。这个依赖库不会跟着你的业务代码一起被 重新打包,只有当依赖自身发生版本变化时才会重新打包。

DLLReferencePlugin:是基于 Windows 动态链接库(dll)的思想被创作出来的。这个插件会把第三方库单独打包到一个文件中,这个文件就是一个单纯的依赖库。这个依赖库不会跟着你的业务代码。


一起被重新打包,只有当依赖自身发生版本变化时才会重新打包。配置步骤如下:


  1. 新增一个配置文件webpack.dll.config.js


const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
    entry: {
        // 依赖的库数组
        vendor: [
            'vue/dist/vue.esm.js',
            'axios',
            // 'element-ui',
            'vue-router',
            'vuex',
            'v-viewer',
            'vue-photo-preview',
            'mockjs'
        ]
    },
    output: {
        path: path.join(__dirname, 'htdocs/dist'),
        filename: '[name].js',
        library: '[name]_[hash]',
    },
    plugins: [
        new CleanWebpackPlugin(['htdocs/dist/vendor-manifest.json', 'htdocs/dist/vendor.js']),
        new webpack.DllPlugin({
            // DllPlugin的name属性需要和libary保持一致
            name: '[name]_[hash]',
            path: path.join(__dirname, 'htdocs/dist', '[name]-manifest.json'),
            // context需要和webpack.config.js保持一致
            context: __dirname,
        }),
    ],
}


  1. 配置package.json命令,npm run dll 这个命令就会生成两个文件,一个是vendor-manifest.json,一个是vendor.js。 vendor.js 不必解释,是我们第三方库打包的结果。这个多出来的 vendor-manifest.json,则用于描述每个第三方库对应的具体路径下。


/ package.json
"scripts": {
    "dll": "webpack -p --progress --config ./webpack.dll.config.js" 
    },


  1. 配置webpack.config.js


module.exports = {
    plugins: [
         new webpack.DllReferencePlugin({
            context: __dirname,
            // manifest就是我们第一步中打包出来的json文件
            manifest: require('./htdocs/dist/vendor-manifest.json'),
        })       
    ]
}


  1. 引入vendor.js,引入vendor.js有两种方式


  • 第一种:直接在index.html。,这种方式简单粗暴,针对没有vendor.js没有hash值的,完全ok,但是如果你打包的vendor.js有hash,每次重新 npm run dll之后如果生成了新的hash,就要手动改这个路径了。
  • 第二种:add-asset-html-webpack-plugin,该插件会将给定的JS或CSS文件添加到Webpack知道的文件中,并将其放入html-webpack-plugin注入到生成的html 的资产列表中。将插件添加 到您的配置中。(但是需要注意的是此插件需要html-webpack-plugin@^2.10.0。在webpack 4+之后,需要先注册AddAssetHtmlPlugin后才 使用内部使用的钩子,而先前版本的webpack对此 并不关心。HtmlWebpackPluginhtml-webpack-plugin-before-html-generation。)


但是我在操作的时候遇到一个很奇葩的坑,element-ui被dllPlugins之后,el-table不生效,是的不生效,页面也么有报错,样式都还存在,但是就是页面展示不出来table。

我看到element-ui的issues上也有人遇到这样的情况,但是现在还没有一个解决方案,很难受。



如果不能dll elemenet-ui,打包出来的index文件,还是很大的,怎么办。



所以我想到了一个办法就是,利用externals来处理element-ui(操作步骤如上),index有80%的大小是element-ui打包之后占据的。但是externals来处理却发现了另外一个问题。index.js打包的文件是小了很多 但是,有一个相应的js文件的大小反而变的很大,这样操作得不偿失。



网上有很多解决办法,但是都没有根治我这个问题,最后的解决方案是,单独的使用table,项目重写table。这样的方法也有一个弊端,就是当前只是发现了table不能正常使用,但是如果还有其他 组件不行,也需要重写,这样的成本就很好了。所以,最后项目选择是不打包element-ui(无奈之举)。期待这个问题被解决。


4. happypack-将laoder转换为多线程


webpack 是单线程的,就算此刻存在多个任务,你也只能排队一个接一个地等待处理。这是 webpack 的缺点,好在我们的 CPU 是多核的,Happypack 会充分释放 CPU 在多核并发方面的优势, 帮我们把任务分解给多个子进程去并发执行,大大提升打包效率。

HappyPack 的使用方法也非常简单,只需要我们把对 loader 的配置转移到 HappyPack 中去就好,我们可以手动告诉 HappyPack 我们需要多少个并发的进程:


const HappyPack = require("happypack");
// 手动创建进程池
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
module.exports = {
    plugins: [
        new HappyPack({
            // 这个HappyPack的“名字”就叫做happyBabel,和楼上的查询参数遥相呼应
            id: 'happyBabel',
            // 指定进程池
            threadPool: happyThreadPool,
            // 输出日志
            verbose: true,
            loaders: ['babel-loader?cacheDirectory']
        }),
    ]
};


5. webpack-parallel-uglify-plugin替换uglifyjs-webpack-plugin


这个插件可以帮助具有许多入口点的项目加速构建。随Webpack提供的uglifyjs插件在每个输出文件上按顺序运行。这个插件与每个可用CPU的一个线程并行运行uglify。这可能会导致显著减少构 建时间,因为最小化是CPU密集型的。简单点来说就是顺序运行改为并行,所以大大提升了构建速度。


// const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
module.exports = {
    plugins: [
        new ParallelUglifyPlugin({
            cacheDir: 'cache_dir',//用作缓存的可选绝对路径。如果未提供,则不使用缓存。
            sourceMap: config.build.productionSourceMap, //可选布尔值。是否为压缩后的代码生成对应的Source Map(浏览器可以在调试代码时定位到源码位置了),这会减慢编译速度。默认为false
            uglifyJS: { 
               output: { 
                   comments: false,//是否保留代码中的注释,默认为保留 
               }, 
               warnings: true,//是否在UglifyJS删除没有用到的代码时输出警告信息,默认为false 
               compress: { 
                   drop_console: true,//是否删除代码中所有的console语句,默认为false 
                   collapse_vars: true,//是否内嵌虽然已经定义了,但是只用到一次的变量, 默认值false 
                   reduce_vars: true,//是否提取出现了多次但是没有定义成变量去引用的静态值,默认为false 
               } 
            },
        })
    ]
};



6. 路由按需加载


作者优化这个项目之前,开发人员也是分组打包,但是是一个路由打包一个js文件,导致最后的结果就是,生产了60+的js文件,在进行批量发布时,哪速度叫一个感人,作者进行了一个简单的操作优化, 就是根据名称分组打包,最后打包下来只有10+的js,并且我发现,60+文件打包的大小比10+文件的总体大小要大一些。哈哈哈,无意间发现的。


7. 多使用函数式编程


这个优化其实就是一个日常需要注意的问题,但是能间接的提升webpack的打包效率、打包大小的优化。

  • 为什么? 因为在webpack 2开始,是集成了tree-shaking。在Vue的3.0版本,有一个重大的改变就是Vue采用了函数式编程。 函数式更方便tree-shaking(删除无用的方法,例如:100个方法,只使用了1个方法,打包的时候的时候只打包一个方法,删除没有使用的方法),在Vue2.0暴露出去的是一个类, 不利于tree-shaking。在Vue3.0中,使用函数式,全部暴露的是函数,使用哪个函数就是import哪个函数,更方便tree-shaking。减少了代码体积。在简单项目中Vue的打包体积可以降低到10KB左右。


所以我们在日常的操作中多使用函数式编程,来提升webpack的打包效率和打包大小。


4、遗留问题


element-ui被dllPlugins之后,el-table不生效。求大神解决。项目依赖版本如下:

目录
相关文章
|
3月前
|
缓存 前端开发 数据可视化
Webpack Bundle Analyzer:深入分析与优化你的包
Webpack Bundle Analyzer是一款可视化工具,帮助分析Webpack构建结果,找出占用空间较大的模块以便优化。首先需安装Webpack和Webpack Bundle Analyzer,接着在`webpack.config.js`中配置插件。运行Webpack后,会在`dist`目录生成`report.html`,展示交互式图表分析包大小分布。为优化可采用代码分割、Tree Shaking、压缩插件、加载器优化、模块懒加载、代码预热、提取公共库、使用CDN、图片优化、利用缓存、避免重复模块、使用Source Maps、优化字体和图标、避免全局样式污染以及优化HTML输出等策略。
131 3
|
12天前
|
缓存 前端开发 JavaScript
前端性能优化:Webpack与Babel的进阶配置与优化策略
【10月更文挑战第28天】在现代Web开发中,Webpack和Babel是不可或缺的工具,分别负责模块打包和ES6+代码转换。本文探讨了它们的进阶配置与优化策略,包括Webpack的代码压缩、缓存优化和代码分割,以及Babel的按需引入polyfill和目标浏览器设置。通过这些优化,可以显著提升应用的加载速度和运行效率,从而改善用户体验。
32 6
|
14天前
|
缓存 监控 前端开发
前端工程化:Webpack与Gulp的构建工具选择与配置优化
【10月更文挑战第26天】前端工程化是现代Web开发的重要趋势,通过将前端代码视为工程来管理,提高了开发效率和质量。本文详细对比了Webpack和Gulp两大主流构建工具的选择与配置优化,并提供了具体示例代码。Webpack擅长模块化打包和资源管理,而Gulp则在任务编写和自动化构建方面更具灵活性。两者各有优势,需根据项目需求进行选择和优化。
46 7
|
13天前
|
缓存 前端开发 JavaScript
前端工程化:Webpack与Gulp的构建工具选择与配置优化
【10月更文挑战第27天】在现代前端开发中,构建工具的选择对项目的效率和可维护性至关重要。本文比较了Webpack和Gulp两个流行的构建工具,介绍了它们的特点和适用场景,并提供了配置优化的最佳实践。Webpack适合大型模块化项目,Gulp则适用于快速自动化构建流程。通过合理的配置优化,可以显著提升构建效率和性能。
26 2
|
18天前
|
前端开发 JavaScript
手敲Webpack 5:React + TypeScript项目脚手架搭建实践
手敲Webpack 5:React + TypeScript项目脚手架搭建实践
|
29天前
|
前端开发 JavaScript 开发者
构建工具对比:Webpack与Rollup的前端工程化实践
【10月更文挑战第11天】本文对比了前端构建工具Webpack和Rollup,探讨了它们在模块打包、资源配置、构建速度等方面的异同。通过具体示例,展示了两者的基本配置和使用方法,帮助开发者根据项目需求选择合适的工具。
21 3
|
5月前
|
缓存 JavaScript 前端开发
探讨如何通过一系列优化策略来提升TypeScript与Webpack的构建性能。
【6月更文挑战第11天】本文探讨了优化TypeScript与Webpack构建性能的策略。理解Webpack的解析、构建和生成阶段是关键。优化包括:调整tsconfig.json(如关闭不必要的类型检查)和webpack.config.js选项,启用Webpack缓存,实现增量构建,代码拆分和懒加载。通过这些方法,可以提升构建速度,提高开发效率。
78 0
|
3月前
|
前端开发 JavaScript 开发者
Angular与Webpack协同优化:打造生产级别的打包配置——详解从基础设置到高级代码拆分和插件使用
【8月更文挑战第31天】在现代前端开发中,优化应用性能和加载时间至关重要,尤其是对于使用Angular框架的项目。本文通过代码示例详细展示了如何配置Webpack,以实现生产级别的打包优化。从基础配置到生产环境设置、代码拆分,再到使用加载器与插件,每个步骤都旨在提升应用效率,确保快速加载和稳定运行。通过这些配置,开发者能更好地控制资源打包,充分发挥Webpack的强大功能。
79 0
|
4月前
|
缓存 JSON 前端开发
Webpack打包优化实践
【7月更文挑战第17天】Webpack的打包优化是一个持续的过程,需要开发者根据项目的实际情况选择合适的优化策略。通过拆分代码、压缩代码、使用Tree Shaking、优化加载器配置、利用缓存以及进行性能分析,我们可以有效提升Webpack的打包效率和应用的加载
|
6月前
|
缓存 资源调度 监控
Webpack 5新特性详解与性能优化实践
Webpack 5通过确定性的Chunk ID、模块ID和导出ID实现了长期缓存,这意味着相同的输入将始终产生相同的输出。这样,当你的用户再次访问更新后的网站时,浏览器可以重用旧的缓存,而不是重新下载所有资源。
81 2