likes
comments
collection
share

webpack4配置到优化到原理(上)

作者站长头像
站长
· 阅读数 78

前言

  • 前端繁荣发展,工程化已经成为高级前端工程师的必不可少的条件之一,打包构建的发展从gruntfisgluprollupwebpackParcel,技术手段千变万化,

  • 但其实不论任何一项技术或工具,都有五个阶段,

    1. 简单使用(菜鸟)
    2. 熟练掌握(老鸟)
    3. 弄清原理(高手)
    4. 改造优化(大牛)
    5. 创新超越(大神)

这五个阶段越往后是越艰难,但是你越是往后深入就越能透过表象看清它的本质,以在这快速变化的技术手段中站稳,以不变应万变

webpack现在是前端打包构建最流行的工具,那么我们就来好好了解一下它(webpack ^4.42.1)

首先梳理下本文要讲到的内容

  1. 核心概念

    • entry(打包入口)
    • output(打包后文件的处理)
    • loader(对各种资源的处理)
    • plugin (利用插件对webpack进行扩展和增强)
    • mode(针对开发环境和生成环境的区分处理)
  2. 其他常用配置

    • devServer(热更新)
    • resolve(模块解析)
    • optimization(优化)
    • devtool(源码调试)
  3. 优化手段

    • stats分析
    • 速度分析
    • 体积分析
    • tree-shaking
    • scope-hoisting
    • 多进程构建
    • 构建中断处理
    • 并行压缩
    • 预编译资源模块
    • 提升二次构建速度
    • css的tree-shaking
    • webpack图片压缩
    • 动态polufill
  4. 配置总结

  5. webpack原理

  6. loader编写

  7. plugin编写

下面逐个介绍

一. 核心概念

1.entry(打包入口)

定义打包的入口

  • webpack是一个模块打包器,他会把一切资源都当作是模块,模块之间存在依赖关系
  • 入口指示使用哪些模块,根据依赖关系,构成了依赖树,如下图所示
  • 对依赖树进行遍历,最终生成打包后资源 webpack4配置到优化到原理(上)

使用示例

// 简写
module.exports = {
    entry: './src/index.js',
}
// 多入口
module.exports = {
    entry: {
        index: './src/index.js',
        list: './src/index.js',
    },
}

2. output(输出)

编译后文件输出到磁盘的相关配置

// 简写
module.exports = {
    output: {
        filename: '[name]_[chunkhash:8].js' //单个文件名可直接指定,多入口利用占位符保证文件名统一
        path: path.join(__dirname, '../dist') // 写入文件磁盘路径
        publicPath: 'http://cdn.example.com/assets/' 
        //资源使用 CDN ,给所有文件引入模版文件时加上路径前缀
    },
}

占位符

  1. [name]
  • 入口名称
  1. [id]
  • 内部chunk id,例如0,1,2
  1. [hash]
  • 所有文件哈希值相同,只要改变内容跟之前的不一致,所有哈希值都改变
  1. [chunkhash]
  • 不同的entry生成不同的chunkhash
  • 同一个模块,就算将js和css分离,其哈希值也是相同的,修改一处,js和css哈希值都会变
  1. [contenthash]
  • 文件内容不一样,产生的哈希值就不一样
  1. [hash:8]
  • 默认生成20位hash,可自定义截取位数

3. loader(资源解析转换)

webpack 原生只支持js 和json,利用loader,对不同文件类型支持,转换成有效的模块 简单示例

module.exports = {
    module: {
        rules:[
            {
                test: /\/.txt$/, // 指定匹配规则
                use: 'babel-loader' // 指定使用的loader名称
            }
        ]
    }
}

下面介绍几种文件类型的处理以及常用的loader

  1. 解析ES6和JSX
  • babel-loader:js默认是不支持es6 和jsx语法的,
  • .babelrc文件: 设置具体支持的属性方法
{
    test: /\.(j|t)sx?$/,
    use: 'babel-loader',
    exclude: /node_modules/                
},
  1. 解析CSS 项目里使用css文件
  • style-loader:将样式通过style 标签插入模版文件的head当中
  • css-loader: 用于加载.css文件 并且转换成commonjs 对象
      {
        test: /.css$/,
        use: [
          'style-loader',
          'css-loader',
        ],
      },
  1. 解析less
  • less-loader:less转换成css,
  • 其他同上
      {
        test: /.css$/,
        use: [
            'style-loader',
            'css-loader',
            {
                loader: 'less-loader',
                options: { // 可配置属性,修改变量的值,一般利用来修改UI库的主题样式,以下是antd主题样式配置
                    modifyVars: {
                        '@primary-color': '#ec7259',
                    },
                    javascriptEnabled: true,
                },
            },
        ],
      },
  1. 图片和字体解析
  • file-loader:解析图片, 字体等
  • url-loader:也可处理图片和字体,,并可设置较小资源自动base64
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192,
              name: 'static/img/[name].[hash:8].[ext]',// [ext] 文件的后缀名
            },
          },

        ],
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 8192,
          name: 'static/fonts/[name].[hash:8].[ext]',
        },
      },
  1. 移动端适配
  • px2rem-loader: 把px转换成rem,配合lib-flexible使用
    {
        loader: 'px2rem-loader',
        options: {
            remUnit: 75, // 1rem=多少像素
            remPrecision: 8, // rem的小数点后位数
        }
    }
  1. css前缀补齐
  • postcss-loader:用于浏览器适配,某些css3属性浏览器不支持需要加前缀,它会自动针对不同浏览器加不同的属性前缀
    {
        loader: 'postcss-loader',
        options: {
            plugins: () => [autoprefixer()],
        },
    },

4. plugin (利用插件对webpack进行扩展和增强)

  • 解决loader无法完成的事
  • 因为插件可以携带参数/选项,所以要向 plugins属性传入new实例 简单示例
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    plugin: {
        new webpack.ProgressPlugin(),
        new HtmlWebpackPlugin({template: './src/index.html'}) 
    }
}

下面介绍几种常用的plugin

  1. 页面打包
    new HtmlWebpackPlugin({
      filename: '../dist/template/index.html', // 指定生成的模版文件名及路径
      template: path.join(__dirname, '../src/template/index.html'), // 指定要使用的模版文件
      inject: true, // 指定的chunk会自动注入html文件中
      chunks: ['index'], //指定生成的html要使用的chunk
      minify: { // 代码的最小化输出
        collapseWhitespace: true, // 删除空格,但是不会删除SCRIPT、style和textarea中的空格
        preserveLineBreaks: false, // 是否保留换行符
        minifyCSS: true, // css压缩
        minifyJS: true, // js压缩
        removeComments: true, // 删除注释,但是会保留script和style中的注释
      },
    }),
  1. 文件清理
  • 每次打包文件到dist,首先要清理dist内部文件,或直接删除dist文件夹,防止文件重复 我们可利用rimraf dist
  • 但此方式不太优雅,我们可以使用clean-webpack-plugin,清理dist内部文件
new CleanWebpackPlugin(),
  1. css剥离
  • css代码默认打包在js文件中,但有时候css变了,js没变,或者相反,这是不利于缓存的,
  • 我们可以把css剥离出来单独生成文件,去做缓存处理
new MiniCssExtractPlugin({
            filename: '[name]_[contenthash:8].css'
        }),
  1. css压缩
  • 既然css单独剥离出来,就要做压缩,
new OptimizeCssAsssetePlugin({
    assetNameRegExp: /\.css$/g, //文件匹配
    cssProcessor: require('cssnano') // cssnano 压缩和优化的css插件 
}),
  1. 基础库分离
  • 我们常把一些不太变化的静态资源放在cdn上,然后在模版文件里引入
  • 在webpack也提供了插件支持,可直接配置插入
  • 假如我们分离react和react-dom

举例:

const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin')
    plugins: [
        new HtmlWebpackExternalsPlugin({
            externals:[
                {
                    module: 'react',
                    entry: 'https://cdn.cn/16.8.0/react.min.js',
                    global: 'React',
                },
                {
                    module: 'react-dom',
                    entry: 'https://cdn.cn/16.8.0/react-dom.min.js',
                    global: 'ReactDOM',
                }
            ]
        }),

5. mode(针对开发环境和生成环境的区分处理)

  1. mode:对应三个属性,
  • development: 开发模式,
  • production: 生产模式,
  • none: 无

webpack会针对不同环境直接做一些优化工作,例如production模式下会进行,tree-shakingscope-hosting 下面优化会详细介绍

二. 常用配置

6. devServer(热更新)

  1. 简介
  • 远古时期,我们做前端开发时,写一个html文件,在浏览器打开它查看效果,修改时,需手动更新,

  • 后来我们使用热更新,webpack低版本,不提供热更新的支持,我们使用插件http-proxy-middlewarewebpack-hot-middleware,实现热更新,配置比较麻烦

  • 最后webpack把热更新集成在内部,就成了devServer

简单配置如下

  devServer: {
    historyApiFallback: true, // 单页面程序 刷新浏览器会出现404,原因是它通过这个路径(比如: /search/list)来访问后台,所以会出现404,而把historyApiFallback设置为true那么所有的路径都执行index.html

    host: '127.0.0.1', // 域名
    open: true, //支持自动打开浏览器
    hot: true, // 模块热替换,在前端代码变动的时候无需整个刷新页面,只把变化的部分替换掉
    inline: false, // inline选项会为入口页面添加“热加载”功能,即代码改变后重新加载页面
    port: 8080, // 端口
    proxy: proxyConfig._proxy, // 代理后端服务,举例:可本地调试测试接口
    before(app) { // 其他中间件之前, 提供执行自定义中间件
      apiMocker(app, path.resolve('./mocks/mock.js'), // 举例:可用来做mock数据
        proxyConfig._proxy);
    },
  },
  1. 原理

首先来看下简单的流程示意图

webpack4配置到优化到原理(上)

  • webpack compile 将JS编译成bundle.js
  • HMR server 将热更新文件输出给HMR Runtime,HMR -> HotModuleReplacement(热模块替换)
  • Bundle server 提供文件在浏览器的访问
  • HMR Runtime 注入浏览器,更新文件的变化,使浏览器 和 服务器建立一个链接(websocket)
  • bundle.js 构建输出的文件

启动阶段

  • 1 -> 2 -> 3

  • 初始代码经过webpack compiler编译进行打包

  • 编译好的文件传输给bundle server, 它就相当于一个服务器,它使文件以server的方式让浏览器访问

  • files -> webpack Compiler -> Bundle Sever -> bundle.js

更新阶段

  • 1 -> 4 -> 5 -> 6

  • file文件发生变化,经过webpack compiler编译

  • 编译好的文件传输给HMR server,通知浏览器端的HMR Runtime(通常以JSON形式传输)

  • HMR Runtime 更新代码,实现无刷新改变页面内容

  • files -> webpack Compiler -> HMR server -> HMR Runtime -> code

7. resolve(模块解析)

  • 设置模块如何被解析

介绍几个常用的属性用法

  1. alias 创建 import 或 require 的别名,来确保模块引入变得更简单
  2. extensions 自动解析确定的扩展
  3. mainFileds 当从 npm 包中导入模块时,决定在 package.json 中使用哪个字段导入模块
  4. moudles 告诉 webpack 解析模块时应该搜索的目录

举例:

// webpack 配置文件
resolve: {
    alias: {
        Util: path.resolve(__dirname, 'src/util/'),
    },
    mainFileds: ['main'],
    extensions: ['.js', '.jsx', '.json'],
    moudles: [path.resolve(__dirname, 'node_modules')]
  },

//业务文件 component.js
import Utility from '../../util/utility.js';
// 简化写法(不用写文件路径前缀,也不用写引用文件的扩展名)
import Utility from 'Util/utility';

8. optimization(优化)

  • webpack 4 开始,会根据你选择的 mode 来执行不同的优化,不过所有的优化还是可以手动配置和重写 下面介绍几种常用的优化
  1. 提取公共资源
  • 项目多页面的时候,大多数页面使用的基础库或依赖都是一样的,这时每个页面都单独打包一份,对资源是一种浪费,打包后体积较大,页面加载时间长,
  • 所以我们可以把公共资源提取出来单独打包,访问多页面时利用缓存机制,只加载一次,达到优化目的
  • splitChunks,代替之前的CommonsChunkPlugin,公共资源分离vendors

chunks属性特别说明

  • async 对异步引入的文件分离(默认)
  • initial 对同步引入的文件分离
  • all 对所有匹配的文件分离 不论是同步还是异步我们都希望分离出来,所以推荐使用 all
  1. 公共文件分离
  • 一些公共的工具函数类文件,我们可以通过限制被调用的次数来决定是否把它分离出来
  • 利用splitChunks 公共文件分离commons
  1. 提取webpack的模块化信息清单
  • 模块信息清单在每次有模块变更(hash 变更)时都会变更, 所以把这部分代码单独打包出来, 配合后端缓存策略,
  • 避免某个模块的变化导致包含在模块化信息中的模块缓存失效
  • 具体使用runtimeChunk

举例:

  optimization: {
    runtimeChunk: {
      name: 'manifest',
    },
    splitChunks: {
        minSize: 50000 // 分离的包的体积大小
      cacheGroups: {
        vendors: {
          test:  /(react|react-dom)/, //正则匹配要分离的文件
          name: 'vendors',
          chunks: 'all', // 确定对何种引入方式的文件进行分离
          minChunks: 1, // 最小使用的次数
          priority: 10, // 多个缓存组时,需要有优先级排列,优先使用哪个进行分离
        },
        commons: { // 分离公共文件
          name: 'commons',
          chunks: 'all',
          minChunks: 2,
          priority: 5,
        },
      },
    },
  },

9. devtool(源码调试)

  • 控制是否生成,以及如何生成 source map
  • 我们要进行一个配置以方便我们在测试环境进行问题定位,源码调试的增强

关键字定义

  • eval 模块都使用 eval() 包裹执行,并且都有 //@ sourceURL(指向的是原文件index.js,调试的时候,根据sourceUrl找到的index.js文件的)
  • source map 产生.map文件(这个map文件会和原始文件做一个映射,调试的时候,就是通过这个.map文件去定位原来的代码位置的 )
  • cheap 不包含列的信息,(假如代码运行出现了错误,控制台报出了,error,我们点击定位到具体源码的时候,就只能定位到行,而不能定位到具体的列)
  • inline .map文件作为dataUrl嵌入到打包文件,而不单独生成
  • moudle 包含loader的sourcemap(调试的代码不会被转换,会保留原始代码语法)

几种关键字进行组合就形成了具体的用法

不同用法对构建速度是有影响的,基本情况你越清晰容易的看到原始的代码,构建速度就越慢,调试的方便性和构建速度上大家可以自己权衡一下

共有13种用法,详细的请看官方文档

举例

// 开发环境
devtool: 'cheap-module-eval-source-map' // 原始源代码(仅限行)

// 生产环境,一般不进行设置

三. 优化手段

1. 优化分析 stats

构建统计信息

使用举例

// 构建完成后会生成json文件,显示构建的一些信息,时间,各模块的体积等
scripts: {
'build: stats': 'webpack --config build/webpack.prod.config.js --json > stats.json',
}

2. 速度分析

  • 分析每个插件和loader的耗时情况 使用举例
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin')
const smp = new SpeedMeasureWebpackPlugin();
module.exports = smp.wrap(merge(webpackConfigBase, webpackConfigProd));

webpack4配置到优化到原理(上)

图中我们看到了每个插件和loader的耗时情况, 如果耗时较长,会以红字提示,我们就可以具体分析那个地方为什么时间长,可以用别的插件替换之类的去做构建速度优化

3. 体积分析

  • 以图形大小的形式,更加直观的看到各个模块插件所占用的体积
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
  plugins: [ 
    new BundleAnalyzerPlugin({ 
      analyzerPort: 8919 //打包构建后体积分析图展示的窗口
      }),
  ],

运行打包命令,体积分析示意图会自动打开在8919窗口

webpack4配置到优化到原理(上)

图中我们可以看到moment插件占用的空间很大,我们可以对它进行优化

  1. 减小体积(忽略语言包)
  • 我们可以看到为了支持国际化,moment里包含了很多语言包,我们可以利用webpack内置的插件忽略它,在需要的时候按需引入
new webpack.IgnorePlugin(/\.\/locale/, /moment/),
// compoent
import 'moment/locale/zh-cn';
moment.locale('zh-cn');
  1. 替换插件
  • 我们可以用更为轻量的dayjs插件进行替换,它的大小仅为2k

4. tree-shaking

  • 字面意思是摇晃树,就是把树上坏掉的叶子摇下来,就是死码清除(即没有被用到的代码)
  • 某个模块或文件,的某个方法被用到了,整个模块都会被打包都bundle文件中去,tree-shaking会把没有用到的方法去除,在uglify阶段清除
  • 仅支持es6语法 webpack 4 中设置 production 默认开启了此项优化

Dead Code(什么是死码呢?)

  1. 代码不会被执行
  2. 执行结果不会被用到
  3. 代码只会影响死变量

ES6 module 特点:

  • 只能作为模块顶层的语句出现
  • import 的模块名只能是字符串常量
  • import binding 是 immutable的

ES6模块依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析,这就是tree-shaking的基础,让死码清除成为了可能

静态分析就是不执行代码,仅仅从字面的意思上对代码进行分析,ES6之前的模块化,比如我们可以动态require一个模块,只有执行后才知道引用的什么模块,这个就不能通过静态分析去做优化,特别说明import()动态引入也是不支持的

5. scope-hoisting

  1. 问题 webpack构建后存在大量的闭包代码
  • 大量函数闭包包裹代码,导致体积增大
  • 运行代码时函数作用域变多,消耗更多的内存
  1. 举例

引用的文件tools.js

export default 'Hello World';

入口文件index.js

import str from './tools';
console.log(str);

未开启scope-hoisting编译后文件


/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
console.log(_tools__WEBPACK_IMPORTED_MODULE_0__["default"]);
/***/ }),

/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ('Hello World');
/***/ })

可以看到

  • 0表示index模块
  • 1表示tools模块
  • 两个模块就存在两块函数闭包代码,真实的场景会有更多模块

开启scope-hoisting编译后文件

/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
// ESM COMPAT FLAG
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ var tools = ('Hello World');
console.log(tools);

/***/ })

可以看到

  • 函数声明由两个变成了一个,tools.js 中定义的内容被直接注入到了 index.js 对应的模块中
  1. 优点 使用scope-hoisting
  • 代码体积更小,因为函数申明语句会产生大量代码;
  • 代码在运行时因为创建的函数作用域更少了,内存开销也随之变小。
  1. 原理
  • 将所有模块的代码按照引用顺序,放在一个函数作用域内,
  • 重命名一些变量防止命名冲突
  1. 小结
  • webapack4 中mode设置production后也是默认开启的此项优化
  • 使用非 ES6 模块或使用异步 import() 也不会把模块放到同一个函数作用域中去
  • 还要注意得是,只合并被引用了一次的模块,引用多次的还是分成多个闭包,减少代码的冗余度

6. 多进程构建

  • webpack构建是一个涉及文件的读写的过程,如果项目非常复杂,构建时间就会加长,
  • 而webpack运行在nodejs上是单线程模型,同一时间只能处理一个任务
  • 我们是否可以让webpack同时进行多任务处理呢 happypackthread loader给我们提供了方案

由于happypack作者不再维护此项目,同时两者原理大致一致,我们就主要介绍thread loader

  • thread loader由官方提供
  • thread loader放在最上面,就会在最后执行,之前的loader会在一个单独的worker池中运行,
  • 每个 worker 都是一个单独的有 600ms 限制的 node.js 进程,从而实现了多进程的构建,降低构建时间 举例
  rules: [
    {
      test: /.js$/,
      use: [
        {
          loader: 'thread-loader',
          options: {
            workers: 3, // 产生的 worker 的数量,默认是 cpu 的核心数
          }
        }
      ]
  },
]

注意:thread loader 只有在项目庞大复杂的时候才能显著的凸显效果,如果是中小型项目没有必要使用

日志上报

7. 构建异常,中断处理

  • 构建过程中,有时会出现构建异常报错的情况,我们可以通过某些方法捕获,以及自定义一些逻辑
  plugins: [
    // 主动捕获构建错误
    function () {
      this.hooks.done.tap('done', (stats) => {
        if (stats.compilation.errors
          && process.argv.indexOf('--watch' == -1)) {
          console.log('error', stats.compilation.errors);
          // 可以做一个构建系统的日志,在此处上报错误原因
          process.exit(12); // 自定义错误code码
        }
      });
    },
  ],

8. 并行压缩

  • webpack4 推荐使用 terser-webpack-plugin 开启 parallel
    • uglify-webpack-plugin也支持并行压缩,但不支持es6,不做具体介绍,有兴趣的同学自行查询
    optimization: {
        minimizer: [
            new TerserPluginWebpack({
                parallel: 4, // 开启 不主动指定的话,默认数值是当前电脑cpu数量的2倍减1
            })
        ],
    }

9. 分包,预编译资源模块

  1. 问题 之前的分包存在问题
  • externals 分包会打出太多的script标签
  • splitchunk 分包需要一个分析的时间
  1. 解决
  • 预编译资源模块
  • 将react,react-dom redux 等基础包和业务基础包打包成一个文件
  1. 方法
  • dll-plugin进行分包,
  • dllreferenceplugin 对mainfest.json(对分离包的描述)引用,将预编译的模块加载进来
  • 利用add-asset-html-webpack-plugin把生成文件插入模版

举例

// package.json
  "scripts": {
    "dll": "webpack --config build/webpack.dll.js" // 打包前只需执行一次,只要基础包不变则不需要执行
  },
// webpack.dll.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
    entry: { // 指定要分离的包
        library: [ // 基础包
            'react',
            'react-dom',
        ],
    },
    output: {
        filename: "[name]_[hash].dll.js",
        path: path.resolve(__dirname, './library'),
        library: "[name]_[hash]", //包名称 注意此名需和下面的DllPlugin,name名一致
    },
    plugins: [
        new webpack.DllPlugin({
            name: "[name]_[hash]",
            path: path.resolve(__dirname, './library/[name].json')
        })
    ]
}
// webpack.prod.js
plugins: [
  ...
  // 将给定的 JS文件添加到 webpack 配置的文件中,并插入到模版文件中
  new AddAssetHtmlPlugin({
      filepath: path.resolve(__dirname, '../build/library/*.dll.js'),
    }),
  //           
  new webpack.DllReferencePlugin({
      manifest: require('../build/library/library.json')
    }),
]
  1. 总结 【1】使用范围
  • 引用但是不会修改的npm包 【2】优点:
  • 多个包打在了一起
  • DllPlugin 将包含大量复用模块且不会频繁更新的库进行编译,只需要编译一次,提升打包速度

10. 提升二次构建以后的速度

  • 缓存插件 hard-source-webpack-plugin
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
module.exports = {
  plugins: [
    ...
    new HardSourceWebpackPlugin() 
  ]
]}

总结

  • dll 虽然提升打包速度,但是配置复杂
  • 并且vue-clicreate-react-app中并没有使用dll
  • 所以如果只是为了提升打包速度,可以使用hard-source-webpack-plugin替换dll
  • 但如果想把将多个npm包打成一个公共包,dll还是有点用的

11. css的tree-shaking(Remove unused CSS)

  • 清除 css无用代码
  • 早期PurifyCSSPlugin,它主要的作用是可以去除没有用到的CSS代码,类似JS的Tree Shaking,现已不再维护
  • 现在使用purgecss-webpack-plugin,配合mini-css-extract-plugin使用

举例

const PATHS = {
    src: path.join(__dirname, '../src')
  }
plugin: [
          new PurgecssPlugin({	
            paths: glob.sync(`${PATHS.src}/**/*`,  { nodir: true }),	// 注意是绝对路径匹配
          }),
]

12. webpack 图片压缩

  • 使图片压缩更加自动化
  • 基于node库的imagemin
  1. 优点
  • 支持定制选项,
  • 可引入第三方插件
  • 支持多个图片格式
  1. 具体用image-webpack-loader 举例
         {
             test: /.(png|jpg|gif|jpeg)$/,
             use: [
                {
                    loader: 'url-loader',
                    options: {
                        limit: 8192,
                        name:'[name]_[hash:8].[ext]'
                    }
                 },
                 {
                    loader: 'image-webpack-loader',
                    options: {
                      mozjpeg: {
                        progressive: true,
                        quality: 65
                      },
                      optipng: {
                        enabled: false,
                      },
                      pngquant: {
                        quality: [0.65, 0.90],
                        speed: 4
                      },
                      gifsicle: {
                        interlaced: false,
                      },
                      webp: {
                        quality: 75
                      }
                    }
                  },
             ],
         },

13 动态polyfill服务

  1. 问题
  • babel-polyfill 打包时占资源比重较大
  • 能否按需加载呢
  1. polyfill-service
  • 获取浏览器的useragent判断支持情况,
  • 动态的返回浏览器不支持的新特性
  • 官方提供了cdnhttps://polyfill.io/v3/polyfill.min.js
  • 也可以基于官方自建polyfill服务,更加自由的配置属性(比如,指定只判断promise的支持程度) 例如 https://polyfill.alicdn.com/polyfill.min.js?features=Promise
  1. 注意
  • 国内浏览器众多复杂,某些浏览器私自修改了useragent,导致判断不准确
  • 我们可以判断执行错误时加载回全部polyfill,进行一个降级处理

四. 配置总结

我们以实际目标为导向收集整理一下常用webpack配置要做什么事情

1. 基础配置

  1. 解析js
  2. 解析css
  3. 解析less
  4. 解析图片
  5. 解析字体
  6. 前缀补齐
  7. 移动端适配
  8. 目录清理
  9. 页面打包
  10. css抽离
  11. 异常主动捕获

2.提高构建速度

  1. resolve缩小文件的搜索范围
  2. 使用DllPlugin减少基础模块编译次数
  3. thread loader 多进程构建
  4. terser并行压缩
  5. hard-source-webpack-plugin提升二次构建速度
  6. dll分包,预编译资源模块

3.减小构建体积

  1. 公共资源分离
  2. Tree Shaking
  3. scope-hoisting
  4. js,css,字体,图片压缩
  5. 动态polyfill
  6. css的tree-shaking
  7. 代码分割,按需引入,import动态加载

4.提升开发体验

  1. sourcemap源码调试
  2. devserver热更新
  3. 友好错误提示

5. 保证稳定安全

  1. 代码压缩混淆

小结

虽然说了很多关于webpack的配置和优化,但我们还是要根据项目的复杂程度,公司&项目的具体情况,处理遗留代码的难度,来选择性的处理, 有时如果强行使用反而得不偿失

总结

当然最后要记住一切的技术都只是工具,都要以赋能业务,价值产出为目标,打包工具的目标就是

  • 提高工作效率,
  • 提升开发体验,
  • 提升用户体验,
  • 保证稳定安全

参考

  1. 玩转webpack
  2. webpack文档
  3. hard-source-webpack-plugin,webpack DllPlugin配置的代替方案
转载自:https://juejin.cn/post/6844904138044604430
评论
请登录