webpack学习笔记

81 阅读7分钟

webpack是什么

webpack是一个现代JavaScript应用程序的静态模块打包工具,他能解决各个模块之间的依赖,构建依赖关系图,把各个模块按照特定的规则和顺序组织起来,最终合并成一个或者多个js文件,这一过程就是模块打包.

webpack基本结构

  • entry: 入口配置
  • output: 出口配置
  • module: 模块loader配置
  • plugins: 插件
  • mode: 模式
  • resolve: 解析配置
  • devtool: source-map配置
  • watch: 监听开关
  • watchOptions: 监听配置
  • devServer: webpack-dev-server配置

webpack打包流程

webpack打包可分为三个阶段:初始化参数,编译阶段,输出阶段

初始化阶段

  • 初始化参数:读取配置文件,shell,npm script等参数,合并参数
  • 开始编译: 使用得到的参数初始化Compiler对象,加载所有配置的插件,执行run方法进行编译
  • 确定入口: 根据配置文件找出所有入口 编译阶段
  • 编译模块:从入口文件出发,串行调用所有配置的loader对模块进行翻译,再找出该模块依赖的模块,递归进行编译,知道所有模块编译完成
  • 编译完成: 编译完成,得到每个模块最终的内容和依赖关系 输出阶段
  • 输出资源列表: 根据入口和模块之间的依赖关系,组装成一个个包含多个模块的Chunk,再把Chunk转换成一个单独的文件加入输出列表
  • 输出完成: 根据配置的路径和文件名,把文件内容写入文件系统

webpack-loader

  • loader的职责是单一的,只需要完成一种转换, 一个源文件需要多次转换,则需要多个loader串行处理,loader的调用是链式的, 从右往左的,第一个loader拿到源文件转换后,会将转换的结果传递给下一个loader,最后一个loader转换完成会将结果输出给webpack,loader只需要关注输入输出结果即可,保持单一原则

webpack-plugin

  • webpack 就像一条生产线,要经过一些列处理流程后才能将源文件转换成输出结果,这条生产线上的每个流程职责都是单一的,多流程之间存在依赖关系,只有完成当前流程后,才能交给下一个流程去处理,而插件就像是插入到生产线中的一个功能,在特定ed时机对生产线的资源做出处理.
  • webpack这种事件流机制应用了观察者模式,依赖于tapable,webpack运行生命周期中会广播出很多事件,plugin可以监听这些事件也只需要关心这些事件,在合适的时机调用来改变输出结果.
  • webpack-plugin必须提供apply方法,webpack内部会遍历所有插件,调用apply将compiler实例传给plugin
  • webpack通过 compiler和compilation 与plugin连接 compiler包含了webpack运行时的所有配置信息,compilation包含了当前模块资源,编译后的资源文件等.

webpack优化

构建速度优化

指标: speed-measure-webpack-plugin

  1. exclude,include, noParse, IgnorePulgin缩小构建范围
  2. 多进程打包 happypack/thread-loader
  3. 缓存,cache-loader/cacheDirectory
  4. Dll动态连接库 注意:DLL缓存是大大缩短了首次构建时间,像之前的cache-loader优化都是缩短rebuild时间

构建体积优化

指标:webpack-bundle-analyzer

  1. optimization.splitChunks分包,抽离公共模块
  2. css压缩
  3. 去除死代码 terser-webpack-plugin
  4. babel配置, 配置@babel/plugin-transform-runtime防止所需的helper函数重复注入
  5. tree-shaking terser-webpack-plugin示例
const TerserPlugin = require('terser-webpack-plugin');

const config = {
 // 生产模式下tree-shaking才生效
 mode: 'production',
 optimization: {
  // Webpack 将识别出它认为没有被使用的代码,并在最初的打包步骤中给它做标记。
  usedExports: true,
  minimizer: [
   // 删除死代码的压缩器
   new TerserPlugin({...})
  ]
 }
};

什么时候才会触发tree-shaking?

tree-shaking条件:import/export导入导出的静态模块(ES6模块)+无副作用 thread-loader示例:

把 thread-loader 放置在其它 loader 之前,那么它之后的 loader 就会在一个单独的 worker 池中运行。
rules: [ 
    { 
        test: /\.jsx?$/, 
        use: ['thread-loader', 'cache-loader', 'babel-loader'] 
    } 
] 

cacheDirectory示例

rules: [
      {
            test: /\.(j|t)sx?$/,
            use: [
              {
                loader: 'babel-loader',
                options: {
                  cacheDirectory: true,
                },
              }
       }
 ]

cache-loader示例:

rules: [
    {
        test: /\.(css)$/,
        use: [
          { loader: 'style-loader' },
          { loader: 'cache-loader' },
          { loader: 'css-loader' },
          { loader: 'postcss-loader' }
        ]
      }
]

HMR热更新配置及其原理热更新原理:

  1. 启动webpack-dev-server后,浏览器和客户端使用websocket实现长连接;
  2. webpack监听源文件的变化,当监听到文件内容变化后,webpack重新编译,每次编译都会生成hash值,已改动模块的json文件,已改动模块代码的js文件;
  3. 编译完成后通过socket向客户端推送当前编译的hash戳
  4. 客户端的websocket监听到有文件改动推送过来的hash戳,会和上一次对比,一致则走缓存,不一致则通过ajax和jsonp向服务端获取最新资源
  5. 使用内存文件系统,去替换有修改的内容实现局部刷新

wbepack 常用loader及其配置

babel-loader babel/core babel/present-env

@babel/runtime + @babel/plugin-transform-runtime 配套使用。 沙箱,提取所有页面所需的helper函数到一个包里,避免重复注入

babel配置:
babel-loader, babel/core, babel/preset-env
{
  test: /\.m?js$/,
  exclude: /(node_modules|bower_components)/,
  include: path.resolve(__dirname,'src'),
  use: {
    loader: "babel-loader",
    options: {
      // 也可以配置.babelrc文件
      // babel提供的插件预设, 允许使用babel插件
      presets: ["@babel/preset-env"],
      plugins: [
        ["@babel/plugin-proposal-decorators", { legacy: true }], // 支持装饰器写法
        ["@babel/plugin-proposal-class-properties"], // 支持class写法
      ],
    },
  },
},

.babelr常用配置

{
    // "plugins": ["@babel/plugin-transform-arrow-functions", "@babel/plugin-transform-destructuring"]    // 一个个插件不如使用preset-env就可以一次性全部引入
    // "presets": ["@babel/preset-env"]
    
    "presets": [
    "@babel/preset-env", 
    {
               "modules": false,
               "useBuiltIns": "entry", // 按需引入:在入口处把所有ie8以上浏览器不支持api的polyfill引入进来
                                       // 'usage',其功能更为强大,它会扫描你的代码,只有你的代码用到了哪个新的api,                        它才会引入相应的polyfill。【试验状态,谨慎使用】
               "targets": "ie >= 8" // 只有ie8以上版本浏览器不支持的语法才会被转换
    }
    '@babel/preset-react',
    '@babel/preset-typescript',
    ],
    "plugins": [
        "@babel/plugin-syntax-dynamic-import", //动态导入
        ["@babel/plugin-transform-runtime", {
            "corejs": 2
        }]                                // 所有的helper函数抽离到一个包中,由所有的文件共同引用则可以减少可观的代码量。也可以为你的代码创建一个sandboxed environment(沙箱环境),这在你编写一些类库等公共代码的时候尤其重要。
    ] 
}

样式处理(style-loader,css-loader, less-loader,postcss-loader)

  • postcss-loader + autoprefixer插件,可以为我们的样式添加前缀-webkit-,-ms- 提高兼容性
// 样式处理
style-loader, css-loader, less-loader,postcss-loader
// 自动添加css前缀 配置
// 使用 autoprefixer 插件
{
  test: /\.css$/i,
  use: [
    MiniCssExtractPlugin.loader,
    {
      loader: "css-loader",
    },
    {
      loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: [
            [
              "autoprefixer", // postcss-loader + autoprefixer + package.json (browserslist), 来设置css3前缀
            ],
          ],
        },
      },
    },
  ],
},
// package.json
"browserslist": [
"last 2 versions",
"> 1%",
"iOS 7",
"last 3 iOS versions"
]

文件处理(url-loader, file-loader)

TS转换js (awesome-typescript-loader)

react解析

  1. 结合babel,config中配置预设presen支持;
  2. 结合ts,config里配置"compilerOptions": { "jsx": "react" // 开启 jsx ,支持 React }", 入口文件需要更改为tsx,需要安装types/react @types/react-dom tsConfig示例
{
  "presets": ["es2015", "stage-2", "react"],
  "plugins": [
    "react-hot-loader/babel",
    "transform-function-bind",
    "transform-class-properties",
    "transform-export-extensions",
    ],
    "env": {
      "backend": {
        "plugins": [
          [ "webpack-loaders",
            { "config": "./webpack.config.babel.js"
            , "verbose": true
            }
          ]
        ]
      }
    }
  }
}
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        user: {
          loader: 'babel-loader?cacheDirectory=true', // cacheDirectory用于缓存babel的编译结果,加快重新编译的速度
          options: {
            presets: ['@babel/preset-env'],
            plugins: [
              '@babel/plughin-transform-runtime',
              '@babel/plugin-transform-modules-commonjs'
            ]
          }
        }
      }
    ]
  }
}

webpack常用plugin

打包html文件 html-webpack-plugin

引入html-webpack-plugin插件
const HtmlWebpackPlugin = require("html-webpack-plugin"); // 打包html文件
plugin中配置
plugins: [
	  new HtmlWebpackPlugin({
      template: "./src/index.html",
      filename: "index.html",
      minify: true, // 最小化
      hash: true,
    }),
]

抽离压缩css: MiniCssExtractPlugin, CssMinimizerPlugin

const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 抽离css
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); // 压缩css
plugins: [
	new MiniCssExtractPlugin({
      filename: 'css/main.css' // css分类打包  
    }), // css单独打包
]
optimization: {
  // 优化项
  minimizer: [
    // 压缩工具优化
    new CssMinimizerPlugin(),
    new UglifyJsPlugin(), // 使用了minimizer, 貌似必须要配置uglifyJsPlugin
  ],
  }

每次打包前清除dist文件,防止缓存

const { CleanWebpackPlugin } = require("clean-webpack-plugin"); // 每次build 清楚dist文件
new CleanWebpackPlugin(), // 每次打包前清除dist文件

ESlint配置

// 配置eslint插件
const ESLintPlugin = require('eslint-webpack-plugin'); // eslint
new ESLintPlugin(), // eslint
// 配置.eslintrl.json文件
{
 "parser": "babel-eslint", // 使用babel-eslint来解析 可以防止使用babel解析高级语法(es2015等)	后的eslint的报错
...
}

webpack-merge: 提供公共配置,分离生产和开发环境配置文件

使用 webpack-merge来合并webpack.common.js 配置到对应环境配置文件
// wabpack.dev.js
const { merge }  = require('webpack-merge')
const common = require('./webpack.common')

const devWebpackCfg = {
  mode: 'development',
  devServer: {
      port: 3000,
      progress: true,
      contentBase: "./dist",
      // open: true // 自动打开浏览器
      proxy: {
        '/api': { // dev-server解决开发跨域, 以'/api开头的将代理到dev-server,然后在代理到服务器'
          target: 'http://localhost:3000',
          pathRewrite: {'^/api' : ''},// 并不是每个接口都是/api开头的,需要将api替换''
        }
      }
  },
}

module.exports  = merge([common, devWebpackCfg])

definePlugin: 写入环境变量

new webpack.DefinePlugin({
  PRODUCTION: JSON.stringify(true), 
  VERSION: JSON.stringify('5fa3b9'),
  BROWSER_SUPPORTS_HTML5: true,
  TWO: '1+1',
  'typeof window': JSON.stringify('object'),
  'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
});

optimize-css-assets-webpack-plugin:合并相同的css样式文件

ignorePlugin忽略文件,防止被打包进去

plugins: [
 // moment自带edlocale包很大, 很多语言包是用不到的,但是依然会打包进去,使用webpack ignorePlugin 忽略语言包
  new webpack.IgnorePlugin({
    resourceRegExp: /^\.\/locale$/, // 匹配.locale
    contextRegExp: /moment$/, // 匹配目录,在moment中匹配 .locale
  }),
]

动态连接库 DllPlugin & DllReferencePlugin

// 1.webpack.dll.conf.js
module.exports = {
	entry: {
    	vendor: ['react','react-dom']
    },
    output: {
    	path: path.resolve(__dirname,'dist', 'dll'),
        filename: '[name]_dll.js',
        library: '[name]_library' //定义动态链接库库名
    },
    plugins: [
    	new webpack.DllPlugin({
        	name: '[name]_library', // 和library要一致
            path: path.resolve(__dirname, './dist/dll', 'manifest.json'), //资源映射文件路径
        })
    ]
}
// 2.在webpack.conf.js中引用dll
  plugins: [
     // 引用动态链接库
    new webpack.DllReferencePlugin({
        manifest: path.resolve(__dirname, './dist/dll', 'manifest.json')
    })
  ]
// 3.在index.html中引入
 <script src="./dll/vendor.dll.js"></script>

多进程打包HappyPack

const Happypack = require('happypack');
  {
      test: /\.m?js$/,
      exclude: /(node_modules|bower_components)/,
      include: path.resolve(__dirname, 'src'),
      use: ['Happypack/loader?id=js'], // 这里使用Happypack来代替babel-loader, 标注id值
  },
  plugins: [
      new Happypack({
          id: 'js', // 这里和loader中标椎的id值相同
          use: [{
              loader: "babel-loader", // babel-loader配置在这里
              options: {
              // babel提供的插件预设, 允许使用babel插件
              presets: ["@babel/preset-env", "@babel/preset-react"],
              plugins: [
                  ["@babel/plugin-proposal-decorators", { legacy: true }], // 支持装饰器写法
                  ["@babel/plugin-proposal-class-properties"], // 支持class写法
                  ['@babel/plugin-syntax-jsx']
              ],
              },
          }]
      }),
  ]

CopyWebpackPlugin,复制静态资源到dist

resolve优化

  • extensions: 配置扩展后缀,使用require和import时可以省略后缀
  • alias: 别名,简化引用
  • modules:限制第三方包查询范围
  • mainFiles: 配置模块优先查找文件名
  • mainFields: 配置第三方插件的package.json的main字段

其他优化

  • optimization: 抽离公共代码