webpack学习

文章目录

前言

webpack作为现代前端开发中最火的模块打包工具,广泛应用于前端工程领域,是前端工程师必备的技能之一。
webpack4配置,将会无缝切换到webpack5。

第1章:webpack简介

1.1 webpack是什么?

webpack 是一种前端资源构建工具,一个静态模块打包器(module bundler)。

webpack 看来, 前端的所有资源文件(js/json/css/img/less/…)会构成一个依赖树,在依赖树中的每一个JS或其他资源文件节点都是一个module模块。 它将根据模块的依赖关系进行静态分析,将互相依赖的一条JS资源整合成chunk块,打包生成对应的静态bundle资源。

例如:
我们写的less要编译成css,js中的一些ES6、ES7语法编译成浏览器认识的

在这里插入图片描述

1.2 webpack 五个核心概念

一般为主文件下index.js引入依赖module模块,引进来整合成chunk(块),再对块各项操作,叫做打包,打包后的资源称为 bundles

1.2.1 Entry

入口(Entry)指示 webpack 以哪个文件为入口起点开始打包,分析构建内部依赖图。

指定从哪开始打包

1.2.2 Output

输出(Output)指示 webpack 打包后的资源 bundles 输出到哪里去,以及如何命名。

指定文件的输出

1.2.3 Loader

Loader 让 webpack 能 够 去 处 理 那 些 非 JavaScript 文 件 (webpack 自 身 只 理 解 JavaScript)

翻译less、img等文件给浏览器可以识别

const path = require('path');

// 路径处理方法
function resolve(dir){
  return path.join(__dirname, dir);
}

const config = {
  //...
  module: { 
    noParse: /jquery|lodash/,
    rules: [
      {
        test: /\.js$/i,
        include: resolve('src'),
        exclude: /node_modules/,
        use: [
          'babel-loader',
        ]
      },
      // ...
    ]
  }
};
  • include:符合条件的模块进行解析
  • exclude:排除符合条件的模块,不解析
  • exclude 优先级更高

1.2.4 Plugins

插件(Plugins)可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩, 一直到重新定义环境中的变量等

比如样式文件的压缩处理

1.2.5 Mode

模式(Mode)指示 webpack 使用相应模式的配置。

选项描述特点
development会将 DefinePlugin 中 process.env.NODE_ENV 的值设置 为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin。开发模式,打包更加快速,省了代码优化步骤
production会将 DefinePlugin 中 process.env.NODE_ENV 的值设置 为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 TerserPlugin。生产模式,打包比较慢,会开启 tree-shaking 和 压缩代码

1.3 占位符

占位符解释
ext文件后缀名
name文件名
path文件相对路径
folder文件所在文件夹
hash每次构建生成的唯一 hash 值
chunkhash根据 chunk 生成 hash 值
contenthash根据文件内容生成hash 值
  • hash :任何一个文件改动,整个项目的构建 hash 值都会改变;
  • chunkhash:文件的改动只会影响其所在 chunk 的 hash 值;
  • contenthash:每个文件都有单独的 hash 值,文件的改动只会影响自身的 hash 值;

第2章:webpack 的初体验

2.1 初始化配置

  1. 初始化 package.json 输入指令:
npm init
  1. 下载并安装 webpack 输入指令:
npm install webpack webpack-cli -g  //全局安装
npm install webpack webpack-cli -D  // -D下载开发依赖

2.2 编译打包应用

开发环境指令:

webpack src/js/index.js -o build/js/built.js --mode=development

功能:webpack 能够编译打包 js 和 json 文件,并且能将 es6 的模块化语法转换成 浏览器能识别的语法。

生产环境指令:

webpack src/js/index.js -o build/js/built.js --mode=production

功能:在开发配置功能上多一个功能,压缩代码。

结论:webpack 能够编译打包 js 和 json 文件。 能将 es6 的模块化语法转换成浏览器能识别的语法。 能压缩代码。

问题:不能编译打包 css、img 等文件。 不能将 js 的 es6 基本语法转化为 es5 以下语法。

第3章:webpack 开发环境的基本配置

3.1 创建配置文件

  1. 创建文件 webpack.config.js
  2. 配置内容如下
const { resolve } = require('path'); // node 内置核心模块,用来处理路径问题。
module.exports = { 
    entry: './src/js/index.js', // 入口文件 
    output: { // 输出配置 
       filename: './built.js', // 输出文件名 
       path: resolve(__dirname, 'build/js') // 输出文件路径配置 
    },
    mode: 'development' //开发环境 
}
  1. 运行指令: webpack
  2. 结论: 此时功能与上节一致

3.2 打包样式资源

  • webpack只能识别js资源
  • css-loader来处理 CSS,翻译成js文件。
  • style-loader来将处理好的 css 通过 style 标签的形式添加到页面上
  • less-loader将less文件处理成css文件
  • loader执行顺讯是从下到上,从右到左
npm i css-loader style-loader less-loader less -D

修改配置文件

// resolve 用来拼接绝对路径的方法 
const { resolve } = require('path'); 
module.exports = { // webpack 配置 
// 入口起点 
entry: './src/index.js', 
// 输出 
output: { 
   // 输出文件名 
   filename: 'built.js',
   // 输出路径 
   // __dirname nodejs 的变量,代表当前文件的目录绝对路径
   path: resolve(__dirname, 'build')
},
   // loader 的配置 
   module: { 
      rules: [ 
      // 详细 loader 配置 
      // 不同文件必须配置不同 loader 处理 
      { 
         // 匹配哪些文件 
         test: /\.css$/, 
         // 使用哪些 loader 进行处理 
         use: [ 
            // use 数组中 loader 执行顺序:从右到左,从下到上 依次执行 
            // 创建 style 标签,将 js 中的样式资源插入进行,添加到 head 中生效 
            'style-loader',
            // 将 css 文件变成 commonjs 模块加载 js 中,里面内容是样式字符串 
            'css-loader' 
        ] 
      },
      { 
         test: /\.less$/, 
         use: [ 
           'style-loader', 
           'css-loader', 
           // 将 less 文件编译成 css 文件 
           // 需要下载 less-loader 和 less 
           'less-loader' 
         ] 
      } 
    ] 
   },
   // plugins 的配置
   plugins: [ 
      // 详细 plugins 的配置 
      ],
      // 模式 
      mode: 'development', 
      // 开发模式 
      // mode: 'production' 
}

需要在入口文件中引入css文件

import './main.css';

3.3 打包html资源

  • 通过html-webpack-plugin插件来复制html模板,并将打包后的js或css引入html中

下载依赖:npm install html-webpack-plugin -D

配置:

/*
  loader: 1. 下载   2. 使用(配置loader)
  plugins: 1. 下载  2. 引入  3. 使用
*/
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      // loader的配置
    ]
  },
  plugins: [
    // plugins的配置
    // html-webpack-plugin
    // 功能:默认会创建一个空的HTML,自动引入打包输出的所有资源(JS/CSS)
    // 需求:需要有结构的HTML文件
    new HtmlWebpackPlugin({
      // 复制 './src/index.html' 文件,并自动引入打包输出的所有资源(JS/CSS)
      template: './src/index.html'
    })
  ],
  mode: 'development'
};

需要有结构的HTML文件

new HtmlWebpackPlugin({
      // 复制 './src/index.html' 文件,并自动引入打包输出的所有资源(JS/CSS)
      template: './src/index.html'
    })

3.4 打包图片资源

  • 默认使用url-loader来处理图片,
  • url-loader又依赖于file-loader
npm install --save-dev html-loader url-loader file-loader

修改配置文件

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.less$/,
        // 要使用多个loader处理用use
        use: ['style-loader', 'css-loader', 'less-loader']
      },
      {
        // 问题:默认处理不了html中img图片
        // 处理图片资源
        test: /\.(jpg|png|gif)$/,
        // 使用一个loader
        // 下载 url-loader file-loader
        loader: 'url-loader',
        // loader的配置
        options: {
          // 图片大小小于8kb,就会被base64处理
          // 优点: 减少请求数量(减轻服务器压力)
          // 缺点:图片体积会更大(文件请求速度更慢)
          limit: 8 * 1024,
          // 问题:因为url-loader默认使用es6模块化解析,而html-loader引入图片是commonjs
          // 解析时会出问题:[object Module]
          // 解决:关闭url-loader的es6模块化,使用commonjs解析
          esModule: false,
          // 给图片进行重命名
          // [hash:10]取图片的hash的前10位
          // [ext]取文件原来扩展名
          name: '[hash:10].[ext]'
        }
      },
      {
        test: /\.html$/,
        // 处理html文件的img图片(负责引入img,从而能被url-loader进行处理)
        loader: 'html-loader'
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development'
};

use里面存数组代表引入多个loader,使用一个loader可以用loader

3.5 打包其他资源

引入字体、icon等文件

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      // 打包其他资源(除了html/js/css资源以外的资源)
      {
        // 排除css/js/html资源
        exclude: /\.(css|js|html|less)$/,
        loader: 'file-loader',
        options: {
          name: '[hash:10].[ext]'
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development'
};

3.6 打包前清空上次文件

每次打包的时候,打包目录都会遗留上次打包的文件,为了保持打包目录的纯净,我们需要在打包前将打包目录清空

这里我们可以使用插件 clean-webpack-plugin 来实现

1.安装npm install clean-webpack-plugin -D

2.配置

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 引入插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
  // ...
  plugins:[ // 配置插件
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new CleanWebpackPlugin() // 引入插件
  ]
}

3.6 devServer

  • devServer: 用来自动化(自动编译,自动打开浏览器,自动刷新浏览器)/ 热部署 / 解决开发环境跨域问题
  • 内存中运行,关闭程序后,不会生成任何东西。
  devServer: {
    // 运行代码的目录
    // contentBase: resolve(__dirname, 'build'),  // 已弃用
    static: {
      directory: path.join(__dirname, 'public'),
    },
    // 监视 contentBase 目录下的所有文件,一旦文件变化就会 reload
    watchContentBase: true,
    watchOptions: {
      // 忽略文件
      ignored: /node_modules/
    },
    // 启动gzip压缩
    compress: true,
    // 端口号
    port: 5000,
    // 域名
    host: 'localhost',
    // 自动打开浏览器
    open: true,
    // 开启HMR功能
    hot: true,
    // 不要显示启动服务器日志信息
    clientLogLevel: 'none',
    // 除了一些基本启动信息以外,其他内容都不要显示
    quiet: true,
    // 如果出错了,不要全屏提示~
    overlay: false,
    // 服务器代理 --> 解决开发环境跨域问题
    proxy: {
      // 一旦devServer(5000)服务器接受到 /api/xxx 的请求,就会把请求转发到另外一个服务器(3000)
      '/api': {
        target: 'http://localhost:3000',
        // 发送请求时,请求路径重写:将 /api/xxx --> /xxx (去掉/api)
        pathRewrite: {
          '^/api': ''
        }
      }
    }
  }

运行指令: webpack serve

3.7 基本的开发环境配置

/*
  开发环境配置:能让代码运行
    运行项目指令:
      webpack 会将打包结果输出出去
      webpack serve 只会在内存中编译打包,没有输出
*/

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      // loader的配置
      {
        // 处理less资源
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'less-loader']
      },
      {
        // 处理css资源
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        // 处理图片资源
        test: /\.(jpg|png|gif)$/,
        loader: 'url-loader',
        options: {
          limit: 8 * 1024,
          name: '[hash:10].[ext]',
          // 关闭es6模块化
          esModule: false,
          outputPath: 'imgs'
        }
      },
      {
        // 处理html中img资源
        test: /\.html$/,
        loader: 'html-loader'
      },
      {
        // 处理其他资源
        exclude: /\.(html|js|css|less|jpg|png|gif)/,
        loader: 'file-loader',
        options: {
          name: '[hash:10].[ext]',
          outputPath: 'media'
        }
      }
    ]
  },
  plugins: [
    // plugins的配置
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development',
  devServer: {
    // contentBase: resolve(__dirname, 'build'), // 已弃用
    static: {
      directory: path.join(__dirname, 'public'),
    },
    compress: true,
    port: 3000,
    open: true
  }
};

运行指令: webpack serve

第 4 章:webpack 生产环境的基本配置

4.1 提取 css 成单独文件

  • 默认打包会将css文件打包进js中,会使资源文件太大,造成闪屏现象
  • mini-css-extract-plugin能提取css为单独的文件
  • 下载插件 npm install --save-dev mini-css-extract-plugin
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          // 创建style标签,将样式放入
          // 'style-loader', 
          // 这个loader取代style-loader。作用:提取js中的css成单独文件
          MiniCssExtractPlugin.loader,
          // 将css文件整合到js文件中
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new MiniCssExtractPlugin({
      // 对输出的css文件进行重命名
      filename: 'css/built.css'
    })
  ],
  mode: 'development'
};

4.2 css 兼容性处理

  • 使用 postcss-loader,自动添加 CSS3 部分属性的浏览器前缀
  • 安装 npm install postcss postcss-loader postcss-preset-env -D

1.修改webpack

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

// 设置nodejs环境变量
// process.env.NODE_ENV = 'development';

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          'postcss-loader'
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new MiniCssExtractPlugin({
      filename: 'css/built.css'
    })
  ],
  mode: 'development'
};

2.创建 postcss 配置文件 postcss.config.js

// postcss.config.js
module.exports = {
  plugins: [require('postcss-preset-env')]
}

3.创建 postcss-preset-env 配置文件 .browserslistrc

# 换行相当于 and
last 2 versions # 回退两个浏览器版本
> 0.5% # 全球超过0.5%人使用的浏览器,可以通过 caniuse.com 查看不同浏览器不同版本占有率
IE 10 # 兼容IE 10

4.3 压缩 css

  • optimize-css-assets-webpack-plugin压缩css插件,减少css文件大小
  • 下载 npm install --save-dev optimize-css-assets-webpack-plugin
  • optimize-css-assets-webpack-plugin 在webpack5中已不在友好支持
  • 使用 css-minimizer-webpack-plugin 为webpack5支持版
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // 引入插件
// const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          // 添加loader
          MiniCssExtractPlugin.loader,
          'css-loader',
          {
            loader: 'postcss-loader',
            options: {
              ident: 'postcss',
              plugins: () => [
                // postcss的插件
                require('postcss-preset-env')()
              ]
            }
          }
        ]
      }
    ]
  },
  // 优化 webpack4 以后
  optimization: {
    minimizer: [
      new CssMinimizerPlugin(),
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new MiniCssExtractPlugin({
      filename: 'css/built.css'
    }),
    // 压缩css
    // new OptimizeCssAssetsWebpackPlugin()
  ],
  mode: 'development'
};

4.4 js 语法检查

  • eslint-loader eslint-plugin-import规范JS代码
  • 下载安装包 npm install --save-dev eslint-loader eslint eslint-config-airbnb-base eslint-plugin-import
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      /*
        语法检查: eslint-loader  eslint
          注意:只检查自己写的源代码,第三方的库是不用检查的
          设置检查规则:
            package.json中eslintConfig中设置~
              "eslintConfig": {
                "extends": "airbnb-base"
              }
            airbnb --> eslint-config-airbnb-base  eslint-plugin-import eslint
      */
      {
        test: /\.js$/,
        // 排除node_modules中的代码
        exclude: /node_modules/,
        loader: 'eslint-loader',
        options: {
          // 自动修复eslint的错误
          fix: true
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development'
};

配置 package.json,继承airbnb库中的eslint规则

"eslintConfig": { "extends": "airbnb-base", "env": { "browser": true } }

4.5 js 兼容性处理

  • babel-loader处理ES6等兼容性问题
  • babel还可以将jsx装换成js
  • 下载npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/polyfill core-js
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      /*
        js兼容性处理:babel-loader @babel/core 
          1. 基本js兼容性处理 --> @babel/preset-env
            问题:只能转换基本语法,如promise高级语法不能转换
          2. 全部js兼容性处理 --> @babel/polyfill  
            问题:我只要解决部分兼容性问题,但是将所有兼容性代码全部引入,体积太大了~
          3. 需要做兼容性处理的就做:按需加载  --> core-js
      */  
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          // 预设:指示babel做怎么样的兼容性处理
          presets: [
            [
              '@babel/preset-env',
              {
                // 按需加载
                useBuiltIns: 'usage',
                // 指定core-js版本
                corejs: {
                  version: 3
                },
                // 指定兼容性做到哪个版本浏览器
                targets: {
                  chrome: '60',
                  firefox: '60',
                  ie: '9',
                  safari: '10',
                  edge: '17'
                }
              }
            ]
          ]
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development'
};

解决方案

 1. 基本js兼容性处理 --> @babel/preset-env
            问题:只能转换基本语法,如promise高级语法不能转换
 2. 全部js兼容性处理 --> @babel/polyfill  
            问题:我只要解决部分兼容性问题,但是将所有兼容性代码全部引入,体积太大了~
 3. 需要做兼容性处理的就做:按需加载  --> core-js

4.6 js 压缩

  • 只需将mode换成production,就可以自动压缩js
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  // 生产环境下会自动压缩js代码
  mode: 'production'
};

4.7 HTML 压缩

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      // 压缩html代码
      minify: {
        // 移除空格
        collapseWhitespace: true,
        // 移除注释
        removeComments: true
      }
    })
  ],
  mode: 'production'
};

4.8 生产环境配置

const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

// 定义nodejs环境变量:决定使用browserslist的哪个环境
process.env.NODE_ENV = 'production';

// 复用loader
const commonCssLoader = [
  MiniCssExtractPlugin.loader,
  'css-loader',
  // css兼容性处理
  {
    // 还需要在package.json中定义browserslist
    loader: 'postcss-loader',
    options: {
      ident: 'postcss',
      plugins: () => [require('postcss-preset-env')()]
    }
  }
];

// 1. 向外暴露对象
module.exports = {
  // 2. 配置入口
  entry: './src/js/index.js',
  // 3. 配置出口
  output: {
    // 配置文件名和目录
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [...commonCssLoader]
      },
      {
        test: /\.less$/,
        use: [...commonCssLoader, 'less-loader']
      },
      /*
        正常来讲,一个文件只能被一个loader处理。
        当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:
          先执行eslint 在执行babel
      */
      {
        // 在package.json中eslintConfig --> airbnb
        test: /\.js$/,
        exclude: /node_modules/,
        // 优先执行
        enforce: 'pre',
        loader: 'eslint-loader',
        options: {
          fix: true
        }
      },
      // js兼容性处理
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          presets: [
            [
              '@babel/preset-env',
              {
                useBuiltIns: 'usage',
                corejs: {version: 3},
                targets: {
                  chrome: '60',
                  firefox: '50'
                }
              }
            ]
          ]
        }
      },
      // 处理图片资源
      {
        test: /\.(jpg|png|gif)/,
        loader: 'url-loader',
        options: {
          limit: 8 * 1024,
          name: '[hash:10].[ext]',
          outputPath: 'imgs',
          esModule: false
        }
      },
      // 处理html中的图片资源
      {
        test: /\.html$/,
        loader: 'html-loader'
      },
      // 处理其他资源
      {
        exclude: /\.(js|css|less|html|jpg|png|gif)/,
        loader: 'file-loader',
        options: {
          outputPath: 'media'
        }
      }
    ]
  },
  plugins: [
    // css提取出单独文件
    new MiniCssExtractPlugin({
      filename: 'css/built.css'
    }),
    // css压缩处理
    new OptimizeCssAssetsWebpackPlugin(),
    // html压缩处理
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true
      }
    })
  ],
  mode: 'production'
};

当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序

第 5 章:webpack 优化配置

5.1 HMR (热模块替换)

  • HMR:hot module replacement 热模块替换 / 模块热替换
  • 作用:在开发环境下,一个模块发生变化,只会重新打包这一模块(而不是打包所以模块) 极大提升构建速度
  • 样式文件:可以使用HMR功能:因为style-loader内部实现了
  • JS文件:默认不能使用HMR功能 --> 需要修改js代码,添加支持HMR功能的代码
  • Html文件: 默认不能使用HMR功能.同时会导致问题:html文件不能热更新了。解决:修改entry入口,将html文件引入
  devServer: {
    contentBase: resolve(__dirname, 'build'),
    compress: true,
    port: 3000,
    open: true,
    // 开启HMR功能
    // 当修改了webpack配置,新配置要想生效,必须重新webpack服务
    hot: true
  }

5.2 source-map (开发环境)

source-map:一种提供源代码到构建后代码映射技术(如果构建后代码出错了,通过映射可以追踪到源代码错误)

开发环境下调试

  devtool: 'eval-source-map'

5.3 oneOf (生产环境)

使loader只会匹配一个

  1. 修改配置文件
const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

// 定义nodejs环境变量:决定使用browserslist的哪个环境
process.env.NODE_ENV = 'production';

// 复用loader
const commonCssLoader = [
  MiniCssExtractPlugin.loader,
  'css-loader',
  {
    // 还需要在package.json中定义browserslist
    loader: 'postcss-loader',
    options: {
      ident: 'postcss',
      plugins: () => [require('postcss-preset-env')()]
    }
  }
];

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        // 在package.json中eslintConfig --> airbnb
        test: /\.js$/,
        exclude: /node_modules/,
        // 优先执行
        enforce: 'pre',
        loader: 'eslint-loader',
        options: {
          fix: true
        }
      },
      {
        // 以下loader只会匹配一个
        // 注意:不能有两个配置处理同一种类型文件
        oneOf: [
          {
            test: /\.css$/,
            use: [...commonCssLoader]
          },
          {
            test: /\.less$/,
            use: [...commonCssLoader, 'less-loader']
          },
          /*
            正常来讲,一个文件只能被一个loader处理。
            当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:
              先执行eslint 在执行babel
          */
          {
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader',
            options: {
              presets: [
                [
                  '@babel/preset-env',
                  {
                    useBuiltIns: 'usage',
                    corejs: {version: 3},
                    targets: {
                      chrome: '60',
                      firefox: '50'
                    }
                  }
                ]
              ]
            }
          },
          {
            test: /\.(jpg|png|gif)/,
            loader: 'url-loader',
            options: {
              limit: 8 * 1024,
              name: '[hash:10].[ext]',
              outputPath: 'imgs',
              esModule: false
            }
          },
          {
            test: /\.html$/,
            loader: 'html-loader'
          },
          {
            exclude: /\.(js|css|less|html|jpg|png|gif)/,
            loader: 'file-loader',
            options: {
              outputPath: 'media'
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/built.css'
    }),
    new OptimizeCssAssetsWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true
      }
    })
  ],
  mode: 'production'
};

5.4 缓存

5.4.1.babel的缓存:

  • babel对JS代码进行了一个兼容性的处理,让第二次打包构建速度更快
const config = {
 module: { 
    noParse: /jquery|lodash/,
    rules: [
      {
        test: /\.js$/i,
        include: resolve('src'),
        exclude: /node_modules/,
        use: [
          // ...
          {
            loader: 'babel-loader',
            options: {
              cacheDirectory: true // 启用缓存
            }
          },
        ]
      },
      // ...
    ]
  }
}

5.4.2 cache-loader

  • 缓存一些性能开销比较大的 loader 的处理结果
  • 缓存位置:node_modules/.cache/cache-loader
  • 安装npm i -D cache-loader
const config = {
 module: { 
    // ...
    rules: [
      {
        test: /\.(s[ac]|c)ss$/i, //匹配所有的 sass/scss/css 文件
        use: [
          // 'style-loader',
          MiniCssExtractPlugin.loader,
          'cache-loader', // 获取前面 loader 转换的结果
          'css-loader',
          'postcss-loader',
          'sass-loader', 
        ]
      }, 
      // ...
    ]
  }
}

5.4.3 cache 持久化缓存

通过配置 cache 缓存生成的 webpack 模块和 chunk,来改善构建速度。

const config = {
  cache: {
    type: 'filesystem',
  },
};

5.4.4 资源的缓存

在资源文件名上使用filename: 'css/built.[contenthash:10].css'

  • hash: 每次wepack构建时会生成一个唯一的hash值。
    问题: 因为js和css同时使用一个hash值。
    如果重新打包,会导致所有缓存失效。(可能我却只改动一个文件)
  • chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样
    问题: js和css的hash值还是一样的
    因为css是在js中被引入的,所以同属于一个chunk
  • contenthash: 根据文件的内容生成hash值。不同文件hash值一定不一样
    让代码上线运行缓存更好使用
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/built.[contenthash:10].css'
    }),
    new OptimizeCssAssetsWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true
      }
    })
  ],

5.5 Tree-shaking (树摇)

  • Tree-shaking:剔除没有使用的代码,以降低包的体积
  • webpack 默认支持,需要在 .bablerc 里面设置 model:false,即可在生产环境下默认开启
  • 利用 ES Module 模块化可以进行静态分析的特点来检测模块内容的导出、导入以及被使用的情况,保留 Live Code
  • 消除不会被执行和没有副作用(Side Effect) 的 Dead Code,即 DCE 过程
  • 副作用:函数被调用,同时因为访问了外部数据,尤其是因为对外部数据进行了写操作,从而一定程度地改变了系统环境。

推荐阅读 从过去到现在,聊聊 Tree-shaking

5.6 代码分割

5.6.1 入口点分割

  // 单入口
  // entry: './src/js/index.js',
  entry: {
    // 多入口:有一个入口,最终输出就有一个bundle
    index: './src/js/index.js',
    test: './src/js/test.js'
  },
  output: {
    // [name]:取文件名
    filename: 'js/[name].[contenthash:10].js',
    path: resolve(__dirname, 'build')
  },

5.6.2 optimization.splitChunk 分包配置

  • optimization.splitChunks 是基于 SplitChunksPlugin 插件实现的
  • 如果多个文件使用了同一个依赖,解决同一个依赖被多次打包的问题

webpack 将根据以下条件自动拆分 chunks:

  • 新的 chunk 可以被共享,或者模块来自于 node_modules 文件夹
  • 新的 chunk 体积大于 20kb(在进行 min+gz 之前的体积)
  • 当按需加载 chunks 时,并行请求的最大数量小于或等于 30
  • 当加载初始化页面时,并发请求的最大数量小于或等于 30

默认配置

module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async', // 有效值为 `all`,`async` 和 `initial`
      minSize: 20000, // 生成 chunk 的最小体积(≈ 20kb)
      minRemainingSize: 0, // 确保拆分后剩余的最小 chunk 体积超过限制来避免大小为零的模块
      minChunks: 1, // 拆分前必须共享模块的最小 chunks 数。
      maxAsyncRequests: 30, // 最大的按需(异步)加载次数
      maxInitialRequests: 30, // 打包后的入口文件加载时,还能同时加载js文件的数量(包括入口文件)
      enforceSizeThreshold: 50000,
      cacheGroups: { // 配置提取模块的方案
        defaultVendors: {
          test: /[\/]node_modules[\/]/,
          priority: -10,
          reuseExistingChunk: true,
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
};

项目配置

const config = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: { // 配置提取模块的方案
        default: false,
        styles: {
            name: 'styles',
            test: /\.(s?css|less|sass)$/,
            chunks: 'all',
            enforce: true,
            priority: 10,
          },
          common: {
            name: 'chunk-common',
            chunks: 'all',
            minChunks: 2,
            maxInitialRequests: 5,
            minSize: 0,
            priority: 1,
            enforce: true,
            reuseExistingChunk: true,
          },
          vendors: {
            name: 'chunk-vendors',
            test: /[\\/]node_modules[\\/]/,
            chunks: 'all',
            priority: 2,
            enforce: true,
            reuseExistingChunk: true,
          },
         // ... 根据不同项目再细化拆分内容
      },
    },
  },
}

5.7 lazy loading (懒加载)

通常使用import()实现JS代码的懒加载,import()是个语法糖,返回值是一个Promise对象,意味着这需要异步处理,你可以在.then()中拿到真正的模块。

  • lazy loading (懒加载):当文件需要时才加载
  • prefetch (预获取):浏览器空闲的时候进行资源的拉取
  • preload (预加载):提前加载后面会用到的关键资源
  • 正常加载可以认为是并行加载(同一时间加载多个文件)
// 点击时,才会去加载test.js
document.getElementById('btn').onclick = function() {
  import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {
    console.log(mul(4, 5));
  });
};

React中使用懒加载代码分割实现效率提升

const Dashboard = lazy(() => import('components/Dashboard'));
const Settings = lazy(() => import('components/Settings'));
const Configurations = lazy(() => import('components/Configurations'));

<Suspense fallback={<div>Loading...</div>}>
              <Switch>
                <Route path="/dashboard">
                  <Dashboard/>
                </Route>
                <Route path="/settings">
                  <Settings/>
                </Route>
                <Route path="/configuration">
                  <Configurations/>
                </Route>
              </Switch>
            </Suspense>

5.8 多进程配置

  • 优化打包速度,使用thread-loader开启多进程打包
  • 在小型项目中,开启多进程打包反而会增加时间成本,因为启动进程和进程间通信都会有一定开销。
  • 配置在 thread-loader 之后的 loader 都会在一个单独的 worker 池(worker pool)中运行

1.下载npm install --save-dev thread-loader

const path = require('path');

// 路径处理方法
function resolve(dir){
  return path.join(__dirname, dir);
}

const config = {
  //...
  module: { 
    noParse: /jquery|lodash/,
    rules: [
      {
        test: /\.js$/i,
        include: resolve('src'),
        exclude: /node_modules/,
        use: [
          {
            loader: 'thread-loader', // 开启多进程打包
            options: {
              worker: 3,
            }
          },
          'babel-loader',
        ]
      },
      // ...
    ]
  }
};

5.9 优化 resolve(解析) 配置

5.9.1 alias 设置别名

alias 用的创建 importrequire 的别名,用来简化模块引用,项目中基本都需要进行配置。

const path = require('path')
...
// 路径处理方法
function resolve(dir){
  return path.join(__dirname, dir);
}

 const config  = {
  ...
  resolve:{
    // 配置别名
    alias: {
      '~': resolve('src'),
      '@': resolve('src'),
      'components': resolve('src/components'),
    }
  }
};

配置完成之后,我们在项目中就可以

// 使用 src 别名 ~ 
import '~/fonts/iconfont.css'

// 使用 src 别名 @ 
import '@/fonts/iconfont.css'

// 使用 components 别名
import footer from "components/footer";

5.9.2 externals

  • externals防止将某些库打包
const config = {
  //...
  externals: {
    jquery: 'jQuery',
  },
};

从 CDN 引入 jQuery,而不是把它打包

 <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>

5.10 dll (动态链接库)

  • 使用dll技术,对某些库(第三方库:jquery、react、vue…)进行单独打包
  • 当你运行 webpack 时,默认查找 webpack.config.js 配置文件
  • 需要运行 webpack.dll.js 文件
  • webpack5已不支持

5.11 webpack性能优化

  1. 开发环境性能优化
    • 优化打包构建速度
      HMR(一个模块发生变化,只修改这一个模块)
    • 优化代码调试
      source-map(提供一种源代码到构建后代码的映射技术)
  2. 生产环境性能优化
    • 优化打包构建速度
      oneOf(使loader只会匹配一个资源)
      babel缓存(优化打包构建速度,处理JS兼容性)
      多进程打包(提升打包速度)
      externals(某些库不打包,通过CDN链接进来)
      dll(先把库打包好,后面不打包这个库)
    • 优化代码运行的性能
      缓存(hash:每次打包都会变-chunkhash:同一个chunk相同-contenthash:根据文件内容生成,内容不变就不会变)
      tree shaking (去除程序中未使用的代码。前提:必须使用es6,开启生产环境。通过sideEffect注释掉不被去除的代码)
      code split(optimization:将node_modules中代码单独打包一个bundle)
      懒加载/预加载(懒加载:需要的时候才会加载。预加载:空闲的时候就会加载)
      pwa(离线可以访问)

第 6 章:webpack 配置详情

6.1 entry

  • 起点或是应用程序的起点入口。
  entry: 入口起点
    1. string --> './src/index.js'
      单入口
      打包形成一个chunk。 输出一个bundle文件。
      此时chunk的名称默认是 main
    2. array  --> ['./src/index.js', './src/add.js']
      多入口
      所有入口文件最终只会形成一个chunk, 输出出去只有一个bundle文件。
        --> 只有在HMR功能中让html热更新生效~
    3. object
      多入口
      有几个入口文件就形成几个chunk,输出几个bundle文件
      此时chunk的名称是 key

      --> 特殊用法
        {
          // 所有入口文件最终只会形成一个chunk, 输出出去只有一个bundle文件。
          index: ['./src/index.js', './src/count.js'], 
          // 形成一个chunk,输出一个bundle文件。
          add: './src/add.js'
        }

6.2 output 输出

指示 webpack 如何去输出、以及在哪里输出你的「bundleasset 和其他你所打包或使用 webpack 载入的任何内容

  output: {
    // 文件名称(指定名称+目录)
    filename: 'js/[name].js',
    // 输出文件目录(将来所有资源输出的公共目录)
    path: resolve(__dirname, 'build'),
    // 所有资源引入公共路径前缀 --> 'imgs/a.jpg' --> '/imgs/a.jpg'
    publicPath: '/',
    chunkFilename: 'js/[name]_chunk.js', // 非入口chunk的名称
    // library: '[name]', // 整个库向外暴露的变量名
    // libraryTarget: 'window' // 变量名添加到哪个上 browser
    // libraryTarget: 'global' // 变量名添加到哪个上 node
    // libraryTarget: 'commonjs'
  },

6.3 module(模块)

  • 决定了如何处理项目中的不同类型的模块
  module: {
    rules: [
      // loader的配置
      {
        test: /\.css$/,
        // 多个loader用use
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.js$/,
        // 排除node_modules下的js文件
        exclude: /node_modules/,
        // 只检查 src 下的js文件
        include: resolve(__dirname, 'src'),
        // 优先执行
        enforce: 'pre',
        // 延后执行
        // enforce: 'post',
        // 单个loader用loader
        loader: 'eslint-loader',
        options: {}
      },
      {
        // 以下配置只会生效一个
        oneOf: []
      }
    ]
  },

6.4 resolve 解析

  • 设置模块如何被解析。
  • 用于设置别名、省略文件路径后缀名、解析模块目录设置
  // 解析模块的规则
  resolve: {
    // 配置解析模块路径别名: 优点简写路径 缺点路径没有提示
    alias: {
      // 配置css中的绝对路径
      $css: resolve(__dirname, 'src/css')
    },
    // 配置省略文件路径的后缀名
    extensions: ['.js', '.json', '.jsx', '.css'],
    // 告诉 webpack 解析模块是去找哪个目录
    modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
  }

6.5 devServer 开发服务

  • 用于开启开发环境配置
  • 开启HMR热部署、解决开发环境跨域问题
  devServer: {
    // 运行代码的目录
    contentBase: resolve(__dirname, 'build'),
    // 监视 contentBase 目录下的所有文件,一旦文件变化就会 reload
    watchContentBase: true,
    watchOptions: {
      // 忽略文件
      ignored: /node_modules/
    },
    // 启动gzip压缩
    compress: true,
    // 端口号
    port: 5000,
    // 域名
    host: 'localhost',
    // 自动打开浏览器
    open: true,
    // 开启HMR功能
    hot: true,
    // 不要显示启动服务器日志信息
    clientLogLevel: 'none',
    // 除了一些基本启动信息以外,其他内容都不要显示
    quiet: true,
    // 如果出错了,不要全屏提示~
    overlay: false,
    // 服务器代理 --> 解决开发环境跨域问题
    proxy: {
      // 一旦devServer(5000)服务器接受到 /api/xxx 的请求,就会把请求转发到另外一个服务器(3000)
      '/api': {
        target: 'http://localhost:3000',
        // 发送请求时,请求路径重写:将 /api/xxx --> /xxx (去掉/api)
        pathRewrite: {
          '^/api': ''
        }
      }
    }
  }

6.6 optimization 优化

  • 从 webpack 4 开始,会根据你选择的 mode 来执行不同的优化,不过所有的优化还是可以手动配置和重写。
  • 常用来配置压缩方案、代码切割
  optimization: {
    splitChunks: {
      chunks: 'all'
    },
    // 将当前模块的记录其他模块的hash单独打包为一个文件 runtime
    // 解决:修改a文件导致b文件的contenthash变化
    runtimeChunk: {
      name: entrypoint => `runtime-${entrypoint.name}`
    },
    minimizer: [
      // 配置生产环境的压缩方案:js和css
      new TerserWebpackPlugin({
        // 开启缓存
        cache: true,
        // 开启多进程打包
        parallel: true,
        // 启动source-map
        sourceMap: true
      })
    ]
  }

6.7 devtool 开发工具

  • 此选项控制是否生成,以及如何生成 source map
  // source-map 映射
  devtool: 'eval-source-map'

第 7 章:webpack5 介绍和使用

此版本重点关注以下内容:

  • 通过持久缓存提高构建性能.
  • 使用更好的算法和默认值来改善长期缓存.
  • 通过更好的树摇和代码生成来改善捆绑包大小.
  • 清除处于怪异状态的内部结构,同时在 v4 中实现功能而不引入任何重大更改.
  • 通过引入重大更改来为将来的功能做准备,以使我们能够尽可能长时间地使用 v5.

下载

  • npm i webpack@next webpack-cli -D

自动删除 Node.js Polyfills

早期,webpack 的目标是允许在浏览器中运行大多数 node.js 模块,但是模块格局发生了变化,许多模块用途现在主要是为前端目的而编写的。webpack <= 4 附带了许多 node.js 核心模块的 polyfill,一旦模块使用任何核心模块(即 crypto 模块),这些模块就会自动应用。

尽管这使使用为 node.js 编写的模块变得容易,但它会将这些巨大的 polyfill 添加到包中。在许多情况下,这些 polyfill 是不必要的。

webpack 5 会自动停止填充这些核心模块,并专注于与前端兼容的模块。

迁移:

  • 尽可能尝试使用与前端兼容的模块。
  • 可以为 node.js 核心模块手动添加一个 polyfill。错误消息将提示如何实现该目标。

Chunk 和模块 ID

添加了用于长期缓存的新算法。在生产模式下默认情况下启用这些功能。

chunkIds: "deterministic", moduleIds: "deterministic"

Chunk ID

你可以不用使用 import(/* webpackChunkName: "name" */ "module") 在开发环境来为 chunk 命名,生产环境还是有必要的

webpack 内部有 chunk 命名规则,不再是以 id(0, 1, 2)命名了

Tree Shaking

  1. webpack 现在能够处理对嵌套模块的 tree shaking
// inner.js
export const a = 1;
export const b = 2;

// module.js
import * as inner from './inner';
export { inner };

// user.js
import * as module from './module';
console.log(module.inner.a);

在生产环境中, inner 模块暴露的 b 会被删除

  1. webpack 现在能够多个模块之前的关系
import { something } from './something';

function usingSomething() {
  return something;
}

export function test() {
  return usingSomething();
}

当设置了"sideEffects": false时,一旦发现test方法没有使用,不但删除test,还会删除"./something"

  1. webpack 现在能处理对 Commonjs 的 tree shaking

Output

webpack 4 默认只能输出 ES5 代码

webpack 5 开始新增一个属性 output.ecmaVersion, 可以生成 ES5 和 ES6 / ES2015 代码.

如:output.ecmaVersion: 2015

SplitChunk

// webpack4
minSize: 30000;
// webpack5
minSize: {
  javascript: 30000,
  style: 50000,
}

Caching

// 配置缓存
cache: {
  // 磁盘存储
  type: "filesystem",
  buildDependencies: {
    // 当配置修改时,缓存失效
    config: [__filename]
  }
}

缓存将存储到 node_modules/.cache/webpack

监视输出文件

之前 webpack 总是在第一次构建时输出全部文件,但是监视重新构建时会只更新修改的文件。

此次更新在第一次构建时会找到输出文件看是否有变化,从而决定要不要输出全部文件。

默认值

  • entry: "./src/index.js
  • output.path: path.resolve(__dirname, "dist")
  • output.filename: "[name].js"

更多内容

https://github.com/webpack/changelog-v5

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值