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

前言

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

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

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

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

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

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

  1. 核心概念java

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

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

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

  5. webpack原理css3

  6. loader编写

  7. plugin编写

下面逐个介绍

一. 核心概念

1.entry(打包入口)

定义打包的入口

  • webpack是一个模块打包器,他会把一切资源都看成是模块,模块之间存在依赖关系
  • 入口指示使用哪些模块,根据依赖关系,构成了依赖树,以下图所示
  • 对依赖树进行遍历,最终生成打包后资源

使用示例

// 简写
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. 原理

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

webpack热更新流程示意图

  • 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));
复制代码

速度分析示意图

图中咱们看到了每一个插件和loader的耗时状况, 若是耗时较长,会以红字提示,咱们就能够具体分析那个地方为何时间长,能够用别的插件替换之类的去作构建速度优化

3. 体积分析

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

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

体积分析示意图

图中咱们能够看到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的配置和优化,但咱们仍是要根据项目的复杂程度,公司&项目的具体状况,处理遗留代码的难度,来选择性的处理, 有时若是强行使用反而得不偿失

总结

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

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

关于webpack原理loader&plugin请看 webpack4配置到优化到原理(下)

参考

  1. 玩转webpack
  2. webpack文档
  3. hard-source-webpack-plugin,webpack DllPlugin配置的代替方案