Webpack5 内容整理

219 阅读20分钟

webpack简介

BiliBili 视频地址: 尚硅谷2020最新版Webpack5实战教程

环境参数(本机)

Nodejs v14.15.0

webpack: "^5.11.0",

webpack-cli: "^4.2.0",

webpack-dev-server: "^3.11.0"

webpack

webpack 是一种前端资源构建工具,一个静态模块打包器(module bundler)。 在webpack 看来, 前端的所有资源文件(js/json/css/img/less/...)都会作为模块处理。 它将根据模块的依赖关系进行静态分析,打包生成对应的静态资源(bundle)。

webpack 五个核心概念

名称描述
Entry入口
Output出口
Loader转换
Plugins插件
Mode模式

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

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

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

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

  • 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。
    能让代码优化上线
    运行的环境

压缩(常用)--- 一般都是用插件

兼容性处理---- 一般都是用各种loader处理

package.json配置

postcss高版本有错误, 稳定3.0.0可以使用

//package.json配置

{
  "name": "webpack_code",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.12.10",
    "@babel/polyfill": "^7.12.1",
    "@babel/preset-env": "^7.12.11",
    "add-asset-html-webpack-plugin": "^3.1.3",
    "babel": "^6.23.0",
    "babel-loader": "^8.2.2",
    "core-js": "^3.8.1",
    "css-loader": "^5.0.1",
    "eslint": "^7.16.0",
    "eslint-config-airbnb-base": "^14.2.1",
    "eslint-loader": "^4.0.2",
    "eslint-plugin-import": "^2.22.1",
    "file-loader": "^6.2.0",
    "html-loader": "^1.3.2",
    "html-webpack-plugin": "^4.5.0",
    "less": "^4.0.0",
    "less-loader": "^7.1.0",
    "mini-css-extract-plugin": "^1.3.3",
    "optimize-css-assets-webpack-plugin": "^5.0.4",
    "postcss-loader": "^3.0.0",
    "postcss-preset-env": "^6.7.0",
    "style-loader": "^2.0.0",
    "terser-webpack-plugin": "^5.0.3",
    "thread-loader": "^3.0.1",
    "url-loader": "^4.1.1",
    "webpack": "^5.11.0",
    "webpack-cli": "^4.2.0",
    "webpack-dev-server": "^3.11.0",
    "workbox-webpack-plugin": "^6.0.2"
  },
  "dependencies": {
    "jquery": "^3.5.1"
  },
  "browserslist": {
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ],
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ]
  },
  "eslintConfig": {
    "extends": "airbnb-base",
    "env": {
      "browser": true
    }
  },
  "sideEffects": [
    "*.css"
  ]
}

webpack配置规则

1.entry配置

入口起点

  • string './src/index.js' ----------使用最多

    • 单入口
    • 打包形成一个chunk ,输出一个bundle文件
    • 此时chunk的名称默认是main
  • array ['./src/index.js', './src/add.js'] ------------使用较多

    • 多入口
    • 所有入口文件最终形成一个chunk ,输出一个bundle文件,默认也叫main
    • 作用只有在HMR功能中让html热更新生效
  • object{index: './src/index.js',add: './src/add.js'}

    • 多入口(名称: 路径)
    • 有几个入口就有几个chunk,输出几个bundle文件
    • chunk的名称是key
  • 特殊用法(复用性)

    {
        index: ['./src/index.js', './src/add.js'],
        count: './src/count.js'
    }
    

多入口打包 示例:

//多入口。 多出口 打包
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: {
    index: ['./src/index.js', './src/count.js'], //index文件中有两个js文件内容
    add: './src/add.js'  //生成的add文件中只有add.js内容
  },
  output: {
    filename: '[name].js',
    path: resolve(__dirname, 'build')
  },
  plugins: [new HtmlWebpackPlugin()],
  mode: 'development'
};

2.output配置

  output: {
    // 文件名
    filename: '[name].js',
    // 文件构建的路径
    path: resolve(__dirname, 'build'),
    // 所有输出资源的公共路径前缀, '/'一般用于生产环境
    publicPath: './',
    // 非入口chunk 的名称,由于有多个chunk,可以重命名
    chunkFilename: 'js/[name]_chunk.js',
    // 整个库向外暴露的变量名
    library: '[name]',
    // 变量名添加到哪个上 window,global,commonjs
    // library: '[name]', // 整个库向外暴露的变量名
    // libraryTarget: 'window' // 变量名添加到哪个上 browser
    // libraryTarget: 'global' // 变量名添加到哪个上 node
    // libraryTarget: 'commonjs'
  }

示例


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

module.exports = {
  entry: './src/index.js',
  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'
  },
  plugins: [new HtmlWebpackPlugin()],
  mode: 'development'
};

3.module配置

常见配置规则:

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

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'js/[name].js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      // loader的配置
      {
        test: /\.css$/,
        // 多个loader用use
        // loader: 'style-loader'   //单个loader使用
        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: []
      }
    ]
  },
  plugins: [new HtmlWebpackPlugin()],
  mode: 'development'
};

4.resolve配置

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

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/[name].js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [new HtmlWebpackPlugin()],
  mode: 'development',
  // 解析模块的规则
  resolve: {
    // 配置解析模块路径别名: 优点简写路径 缺点路径没有提示
    alias: {
      $css: resolve(__dirname, 'src/css')
    },
    // 配置省略文件路径的后缀名,默认是.js, 引入的时候可以简写, 例如 import main from 'home',相当于home/index.js
    extensions: ['.js', '.json', '.jsx', '.css'],
    // 告诉 webpack 解析模块是去找哪个目录
    modules: [resolve(__dirname, '../../node_modules'), 'node_modules']//第一个是相对路径,第二个是防止前一个失效
  }
};

5.devServer配置

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

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/[name].js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [new HtmlWebpackPlugin()],
  mode: 'development',
  resolve: {
    alias: {
      $css: resolve(__dirname, 'src/css')
    },
    extensions: ['.js', '.json', '.jsx', '.css'],
    modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
  },
  devServer: {
    // 运行代码的目录
    contentBase: resolve(__dirname, 'build'),
    // 监视 contentBase 目录下的所有文件,一旦文件变化就会 reload
    watchContentBase: true,
    watchOptions: {
      // 忽略文件
      ignored: /node_modules/
    },
    // 启动gzip压缩
    compress: true,
    // 端口号
    port: 3000,
    // 域名
    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.Optimization配置

关于Chunk的配置 和修改压缩的配置

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

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/[name].[contenthash:10].js',
    path: resolve(__dirname, 'build'),
    chunkFilename: 'js/[name].[contenthash:10]_chunk.js'
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [new HtmlWebpackPlugin()],
  mode: 'production',
  resolve: {
    alias: {
      $css: resolve(__dirname, 'src/css')
    },
    extensions: ['.js', '.json', '.jsx', '.css'],
    modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
  },
  optimization: {
    splitChunks: {
      chunks: 'all' //一般只要设置为all即可
      // 默认值,可以不写~
      /* minSize: 30 * 1024, // 分割的chunk最小为30kb
      maxSize: 0, // 最大没有限制
      minChunks: 1, // 要提取的chunk最少被引用1次
      maxAsyncRequests: 5, // 按需加载时并行加载的文件的最大数量
      maxInitialRequests: 3, // 入口js文件最大并行请求数量
      automaticNameDelimiter: '~', // 名称连接符
      
      
      name: true, // 可以使用命名规则
      cacheGroups: {
        // 分割chunk的组
        // node_modules文件会被打包到 vendors 组的chunk中。--> vendors~xxx.js
        // 满足上面的公共规则,如:大小超过30kb,至少被引用一次。
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          // 优先级
          priority: -10
        },
        default: {
          // 要提取的chunk最少被引用2次
          minChunks: 2,
          // 优先级
          priority: -20,
          // 如果当前要打包的模块,和之前已经被提取的模块是同一个,就会复用,而不是重新打包模块
          reuseExistingChunk: true
        } 
      }
      
      */
    },
    // 将当前模块的记录其他模块的hash单独打包为一个文件 runtime
    // 解决:修改a文件导致b文件的contenthash变化
    //重要!!
    runtimeChunk: {
      name: entrypoint => `runtime-${entrypoint.name}`
    },
    minimizer: [
      // 配置生产环境的压缩方案:js和css
      //注册一下
      new TerserWebpackPlugin({
        // 开启缓存
        cache: true,
        // 开启多进程打包
        parallel: true,
        // 启动source-map
        sourceMap: true
      })
    ]
  }
};

webPack开发环境配置 dev

1.webpack初体验

安装

npm i webpack webpack-cli -D

使用

#开发模式
webpack ./src/index.js -o ./build/built.js --mode=development
#生成模式
webpack ./src/index.js -o ./build/built.js --mode=production

未配置前,只能处理js/json文件,不能处理css/img等其他资源

配置模板

// file:webpack.config.js   webpack的配置文件
const {resolve} = require('path')
module.exports = {
  // 入口起点
  entry: './src/index.js',
  // 出口
  output: {
    filename: 'built.js',
    path: resolve(__dirname, 'build') 
  },
  // loader的配置
  module: {
    rules: [
    // 详细loader配置
    ]
  },
  // plugins的配置
  plugins: [
  ],
  // 模式
  mode: 'development' //开发模式
  // mode: 'production' //生产模式
}

2.打包CSS样式资源

安装

npm i style-loader css-loader -D
npm i less-loader -D

配置

use数组中loader执行顺序:从右到左,从下到上 依次执行

/*
  webpack.config.js  webpack的配置文件
    作用: 指示 webpack 干哪些活(当你运行 webpack 指令时,会加载里面的配置)

    所有构建工具都是基于nodejs平台运行的~模块化默认采用commonjs。
*/

// 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'
}
 

打包

webpack

运行

webpack serve

3. 打包html资源

安装

npm i html-webpack-plugin -D

引入

// file:webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')

使用

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

module.exports = {
  entry: './src/index.js',
  publicPath: '',
  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'
};

4.打包图片资源

安装

npm i url-loader file-loader html-loader -D

配置

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

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'built.js',
    publicPath: '',
    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',
        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'
};

5.打包其他资源

file-loader 前面已经安装

配置

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

module.exports = {
  entry: './src/index.js',
  output: {
    publicPath:'',
    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'
};

6.devServer 热更新

作用

开发服务器,自动编译,自动刷新,自动打开浏览器

只会在内存中编译打包,不会有任何输出

安装

npm i webpack-dev-server -D

配置

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

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'built.js',
    publicPath:'',
    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',

  // 开发服务器 devServer:用来自动化(自动编译,自动打开浏览器,自动刷新浏览器~~)
  // 特点:只会在内存中编译打包,不会有任何输出
  // 启动devServer指令为:webpack serve即可
+  devServer: {
    // 项目构建后路径
+   contentBase: resolve(__dirname, 'build'),
    // 启动gzip压缩
+    compress: true,
    // 端口号
+    port: 3000,
    // 自动打开浏览器
+    open: true
  }
};

使用

webpack serve

7.开发环境配置汇总

./src/文件结构

│ index.html │ ├───css │ iconfont.css │ index.less │ ├───imgs │ angular.jpg │ react.png │ vue.jpg │ ├───js │ index.js │ └───media iconfont.eot iconfont.svg iconfont.ttf iconfont.woff

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

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

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    publicPath:'',
    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,//否则url-loader会按照commonJS处理
          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'),
    compress: true,
    port: 3000,
    open: true,
    hot:true  //重要,只更新改变的文件
  }
};

webPack生产环境配置 production

1.提取css成单独文件

生成单独的CSS文件, 而不是嵌入在js文件中。

mini-css-extract-plugin取代了style-loader。作用:提取js中的css成单独文件

安装

npm i mini-css-extract-plugin -D

引入

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

2.css的兼容性处理

安装

npm i postcss-loader postcss-preset-env

配置

在package.json中


"browserslist": {
开发环境 --> 设置node环境变量:process.env.NODE_ENV = development
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
生产环境:默认是看生产环境
"production": [
">0.2%",
"not dead",
"not op_mini all"
]
}
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',
          /*
            css兼容性处理:postcss --> postcss-loader postcss-preset-env

            帮postcss找到package.json中browserslist里面的配置,通过配置加载指定的css兼容性样式
          */
          // 使用loader的默认配置
          // 'postcss-loader',
          // 修改loader的配置
          {
            loader: 'postcss-loader',
            options: {
              ident: 'postcss',
              plugins: () => [
                // postcss的插件
                require('postcss-preset-env')()
              ]
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new MiniCssExtractPlugin({
      filename: 'css/built.css'
    })
  ],
  mode: 'development'
};

3.压缩css

安装

npm i optimize-css-assets-webpack-plugin -D

引入

const OptimizeCssAssetsWebpackPlugin= require('optimize-css-assets-webpack-plugin')

配置

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')

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

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

4.js语法检查

注:有很多可以用的风格,如:aribnb风格, 或js- standard-style风格

github.com/airbnb/java…

Package.json中进行配置

package.json中要进行配置

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

安装:

1.Airbnb风格

npm i 
      eslint-loader 
      eslint
      eslint-config-airbnb-base
      eslint-plugin-import 
      -D

2.Standard风格

npm install 
            eslint-loader 
            eslint
            eslint-config-standard 
            eslint-plugin-standard 
            eslint-plugin-promise 
            eslint-plugin-import 
            eslint-plugin-node -D

配置

// file: package.json
"eslintConfig": {
  "extends": "standard"
}
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$/,
        exclude: /node_modules/,
        // 确保所有的eslint优先执行
        enforce: 'pre',
        loader: 'eslint-loader',
        options: {
          // 自动修复eslint的错误
          fix: true
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development'
};

想让特殊行EsLint失效(下一行不进行eslint检查) -------使用较少

加eslint-disable-next-line


function add(x, y) {
  return x + y;
}

// 下一行eslint所有规则都失效(下一行不进行eslint检查)
// eslint-disable-next-line
console.log(add(2, 5));

5.js兼容性处理

​ 兼容处理有三种

  • @babel/preset-env 基本js兼容处理 问题:只能转换基本语法,如promise等高级语法,不能转换

  • @babel/polyfill 全部兼容处理

    问题:会将所有的代码做兼容处理,但体积太大, 大约400k以上

  • core-js 按需加载,----推荐

安装

1.基本的语法降级

npm i babel-loader 
      @babel/core 
      @babel/preset-env -D

2.Promise, async等处理

npm i @babel/polyfill -D

//需要再使用的地方 做顶部引入  // import '@babel/polyfill';

3.结合基本babel, 使用Core.js 进行按需加载

npm i babel-loader 
	  core-js
      @babel/core 
      @babel/preset-env -D

配置

// 第一种 增加loader,即module中的rules
{
    test: /\.js$/,
    exclude: /node_modules/,
    loader: 'babel-loader',
    options: {
   		presets: ['@babel/preset-env']
    }
}
// 第二 直接在js文件中增加
import '@babel/polyfill'
// 第三种 增加loader,即module中的rules
{
  test: /\.js$/,
  exclude: /node_modules/,
  loader: 'babel-loader',
  options: {
    // presets: ['@babel/preset-env']
    presets: [
      ['@babel/preset-env',
      {
        // // 按需加载
        useBuiltIns: 'usage',
        // 指定core-js版本
        corejs: {version: 3},
        // 指定兼容性到哪个版本浏览器
        targets: {
          chrome: '60',
          firefox: '60',
          ie: '9',
          safari: '10',
          edge: '17'
        }
      }]
    ]
  }
}

6.js压缩

// file: webpack.config.js 只需要切换成生产模式即可
  // 生产环境下会自动压缩js代码
  mode: 'production'

7.html压缩

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

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

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';//development

// 复用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: [
      {
        test: /\.css$/,
        use: [...commonCssLoader]
      },
      {
        test: /\.less$/,
        use: [...commonCssLoader, 'less-loader']
      },
      /*
        正常来讲,一个文件只能被一个loader处理。
        当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:
          先执行eslint 在执行babel
      */
      {
        // 在package.json中eslintConfig --> airbnb
        test: /\.js$/,
        exclude: /node_modules/,
        // 确保所有的eslint优先执行
        enforce: 'pre',
        loader: 'eslint-loader',
        options: {
          fix: true
        }
      },
      // 语法降级, 使用babel和core.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
        }
      },
      {
        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'
};

重要! WebPack优化配置

1.简介

# webpack性能优化
* 开发环境性能优化
* 生产环境性能优化

## 开发环境性能优化
* 优化打包构建速度
  * HMR
* 优化代码调试
  * source-map

## 生产环境性能优化
* 优化打包构建速度
  * oneOf
  * babel缓存
  * 多进程打包
  * externals
  * dll
* 优化代码运行的性能
  * 缓存(hash-chunkhash-contenthash)
  * tree shaking
  * code split
  * 懒加载/预加载
  * pwa
  

2.HMR功能

HMR: hot module replacement 热模块替换,一个文件修改后,只重新构建该文件,而不是全部构建

提升构建速度

样式文件:可以使用HMR功能:因为style-loader内部实现了~

js文件:默认不能使用HMR功能 --> 需要修改js代码,添加支持HMR功能的代码

​ 注意:HMR功能对js的处理,只能处理非入口js文件的其他文件。

html文件: 默认不能使用HMR功能.同时会导致问题:html文件不能热更新了 (不用做HMR功能,入口就一个)

​ 解决:修改entry入口,将html文件引入

开启热功能

// file: webpack.config.js
devServer: {
    contentBase: resolve(__dirname, 'build'),
    compress: true,
    port: 3000,
    open: true,
    // 开启HMR功能
    hot: true
}
  • 样式文件:默认有HMR功能,因为style-loader内部实现了

  • js文件:默认无HMR功能

    // file:./src/js/index.js   入口js文件下方添加
    //引入了./print.js
    if (module.hot) {
      // 一旦 module.hot 为true,说明开启了HMR功能。 --> 让HMR功能代码生效
      module.hot.accept('./print.js', function() {
        // 方法会监听 print.js 文件的变化,一旦发生变化,其他模块不会重新打包构建。
        // 会执行后面的回调函数
        print();
      });
    }
    
  • html文件:默认无HMR功能,而且会导致无法热更新

    // file: webpack.config.js
    entry: ['./src/js/index.js', './src/index.html']
    

3. Source-map

找到源代码的错误信息和错误位置

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

  source-map:外部                   提供 错误代码准确信息 和 源代码的错误位置

  inline-source-map:内联            只生成一个内联source-map   提供错误代码准确信息 和 源代码的错误位置

  hidden-source-map:外部            错误代码错误原因,但是没有错误位置      。  // 不能追踪源代码错误,只能提示到构建后代码的错误位置

  eval-source-map:内联              每一个文件都生成对应的source-map,都在eval          提供错误代码准确信息 和 源代码的错误位置

  nosources-source-map:外部         错误代码准确信息, 但是没有任何源代码信息

  cheap-source-map:外部             提供 错误代码准确信息 和 源代码的错误位置 ,只能精确到行

  cheap-module-source-map: 外部      提供 错误代码准确信息 和 源代码的错误位置 

  

module会将loader的source map加入

内联 和 外部的区别:1. 外部生成了文件,内联没有 2. 内联构建速度更快



  开发环境:速度快,调试更友好

   速度快(eval>inline>cheap>...)

​    eval-cheap-souce-map

​    eval-source-map

   调试更友好 

​    souce-map

​    cheap-module-souce-map

​    cheap-souce-map



   --> eval-source-map / eval-cheap-module-souce-map



  生产环境:源代码要不要隐藏? 调试要不要更友好

   内联会让代码体积变大,所以在生产环境不用内联

   nosources-source-map 全部隐藏

   hidden-source-map 只隐藏源代码,会提示构建后代码错误信息



   --> source-map / cheap-module-souce-map

使用

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

module.exports = {
  entry: ['./src/js/index.js', './src/index.html'],
  output: {
    filename: 'js/built.js',
    publicPath:'',
    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'),
    compress: true,
    port: 3000,
    open: true,
    hot: true
  },
  devtool: 'eval-source-map'
};

参数

[inline-|hidden-eval-][nosourses-][cheap-[module-]]source-map

属性存放位置错误原因错误位置
source-map外部源文件源文件
inline-source-map内联源文件源文件
eval-source-map内联源文件(+hash)源文件
hidden-source-map外部构建文件构建文件
nosources-source-map外部源文件
cheap-source-map外部源文件源文件(1行)
cheap-module-source-map外部源文件源文件(1行)

开发环境推荐:eval-source-map 速度快(eval>inline>cheap>...)

生产环境推荐:source-map / cheap-source-map / hidden-source-map 都可以

4.oneOf

只匹配一个loader,例如CSS/LESS , 不用全部都匹配,优化打包构建速度

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. Babel缓存

  • babel缓存(第二次打包构建速度更快)

设置cacheDirectory: true开启哈希缓存,

使用contentHash 较好,, 推荐

// 修改js-loader中的options
{
	test: /\.js$/,
	exclude: /node_modules/,
	loader: 'babel-loader',
	options: {
	// 开启babel缓存,第二次构建时读取缓存
		cacheDirectory: true
	}
}
  • 加哈希值

    • filename: 'js/built.[contenthash:10].js',

    ​ //加哈希值,确保文件更新后再次读取,如未变动,哈希值不会变,也就不会重新加载,直接读取缓存

    • filename: 'css/built.[contenthash:10].css
      babel缓存
    
       cacheDirectory: true
    
       --> 让第二次打包构建速度更快
    
      文件资源缓存
    
       hash: 每次wepack构建时会生成一个唯一的hash值。
    
    ​    问题: 因为js和css同时使用一个hash值。
    
    ​     如果重新打包,会导致所有缓存失效。(可能我却只改动一个文件)
    
       chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样
    
    ​    问题: js和css的hash值还是一样的
    
    ​     因为css是在js中被引入的,所以同属于一个chunk
    
       contenthash: 根据文件的内容生成hash值。不同文件hash值一定不一样  
    
       --> 让代码上线运行缓存更好使用
    
  

* 文件资源缓存(让上线运行缓存更好使用)

  * hash 每次webpack会生成唯一的hash值,如果只改动一个文件,打包后会导致所有缓存失效
  * chunkhash 如果打包来自同一个chunk,则hash值相同
  * contenthash 根据内容生成hash值。

  
  // 在生成的文件名中加上不同hash值
  output: {
      filename: 'js/built.[contenthash:10].js',
      path: resolve(__dirname, 'build')
    }
  
  new MiniCssExtractPlugin({
        filename: 'css/built.[contenthash:10].css'
      })

完整代码

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.[contenthash:10].js',
    //加哈希值,确保文件更新后再次读取,如未变动,哈希值不会变,也就不会重新加载,直接读取缓存
    publicPath:'',
    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'
                    }
                  }
                ]
              ],
              // 开启babel缓存
              // 第二次构建时,会读取之前的缓存
              cacheDirectory: true
            }
          },
          {
            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.[contenthash:10].css'
    }),
    new OptimizeCssAssetsWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true
      }
    })
  ],
  mode: 'production',
  devtool: 'source-map'
};

6. Tree-shaking

可以用于 去除无用代码

前提:必须使用es6模块化import, module.export ,开启production环境

在Package.json中设置"sideEffects": false 所有代码都没有副作用(都可以进行tree shaking)

问题:可能会把css / @babel/polyfill (副作用)文件干掉

若要跳过css等其他文件, package.json中设置:

"sideEffects": ["*.css", "*.less"]

7. code split 代码分割

  • 多入口---指定每一个入口,麻烦

    const { resolve } = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    module.exports = {
      // 单入口
      // 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')
      },
      plugins: [
        new HtmlWebpackPlugin({
          template: './src/index.html',
          minify: {
            collapseWhitespace: true,
            removeComments: true
          }
        })
      ],
      mode: 'production'
    };
    
  • optimization--- 进行代码分割

    const { resolve } = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    module.exports = {
      // 单入口
      // entry: './src/js/index.js',
      entry: {
        index: './src/js/index.js',
        test: './src/js/test.js'
      },
      output: {
        // [name]:取文件名
        filename: 'js/[name].[contenthash:10].js',
        path: resolve(__dirname, 'build')
      },
      plugins: [
        new HtmlWebpackPlugin({
          template: './src/index.html',
          minify: {
            collapseWhitespace: true,
            removeComments: true
          }
        })
      ],
      /*
        1. 可以将node_modules中代码单独打包一个chunk最终输出
        2. 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk
      */
      optimization: {
        splitChunks: {
          chunks: 'all'
        }
      },
      mode: 'production'
    };
    
    
  • 纯粹的单入口

  • 通过js代码,让某个文件被单独打包成一个chunk , import动态导入语法:能将某个文件单独打包

如果希望固定每次打包后更新同一个chunks, 可以指定chunks iD,
import(/* webpackChunkName: 'test' */'./test')
  .then(({ mul, count }) => {
    // 文件加载成功~
    // eslint-disable-next-line
    console.log(mul(2, 5));
  })
  .catch(() => {
    // eslint-disable-next-line
    console.log('文件加载失败~');
  });

8.Lazy Loading 懒加载

懒加载:文件需要使用时才加载

预加载(慎用): 会在使用之前空闲时提前加载js, 调用的时候直接读取缓存,速度快**,但兼容性差, IE/移动端 会需要大量调试**

懒加载:当文件需要使用时才加载~ 预加载 prefetch:会在使用之前,提前加载js文件 正常加载可以认为是并行加载(同一时间加载多个文件)

预加载 prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源

  • 在JS文件中加载
// file: ./src/js/index.js

document.getElementById('btn').onclick = function() {
  import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {
    console.log(mul(4, 5));//20
  });
};

9.PWA技术

渐进式网络开发应用程序(离线可访问)

安装插件

npm i workbox-webpack-plugin -D

引入

const WorkboxWebpackPlugin = require('workbox-webpack-plugin')

配置

// file: webpack.config.js
  plugins: [
    new WorkboxWebpackPlugin.GenerateSW({
      /*
        1. 帮助serviceworker快速启动
        2. 删除旧的 serviceworker
        生成一个 serviceworker 配置文件~
      */
      clientsClaim: true,
      skipWaiting: true
    })
  ],

使用,在index.js文件中设置

// file: ./src/js/index.js
// 注册serviceworker
// 处理兼容性问题
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker
      .register('/service-worker.js')
      .then(() => {
        console.log('sw注册成功了');
      })
      .catch(() => {
        console.log('sw注册失败了');
      });
  });
}

服务器搭建(serviceWorker只能在服务器中使用)

# 安装全局serve
npm i serve -g
# 启动服务器,将build目录下所有资源作为静态资源暴露出去
serve -s build

可能出现的问题,

  • 1.Eslint

    eslint不认识 window、navigator全局变量

      解决:需要修改package.json中eslintConfig配置
    
       "env": {
    
    ​    "browser": true // 支持浏览器端全局变量
    
       }
    
  • sw代码必须运行在服务器上

    npm i serve -g
    serve -s build 启动服务器,将build目录下所有资源作为静态资源暴露出去
    

10.多进程打包

只有工作消耗时间比较长,才需要多进程打包

​ 开启多进程打包。

​ 进程启动大概为600ms,进程通信也有开销。

​ 只有工作消耗时间比较长,才需要多进程打包

正常来讲,一个文件只能被一个loader处理。

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

先执行eslint 在执行babel

安装

npm i thread-loader -D

提供给babel配置

// file: webpack.config.js  ->添加loader
          {
            test: /\.js$/,
            exclude: /node_modules/,
            use: [
            //开启多进程
              {
                loader: 'thread-loader',
                options: {
                  workers: 2 // 进程2个
                }
              },
              {
                loader: 'babel-loader',
                options: {
                  presets: [
                    [
                      '@babel/preset-env',
                      {
                        useBuiltIns: 'usage',
                        corejs: { version: 3 },
                        targets: {
                          chrome: '60',
                          firefox: '50'
                        }
                      }
                    ]
                  ],
                  // 开启babel缓存
                  // 第二次构建时,会读取之前的缓存
                  cacheDirectory: true
                }
              }
            ]
          },

11.externals

忽略npm文件的打包

在index.html文件中用<script>进行引入</script>

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

配置

// file: webpack.config.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'
    })
  ],
  mode: 'production',
  externals: {
    // 拒绝jQuery被打包进来,npm包名
    jquery: 'jQuery',
    // react:'React'
  }
};

重新引入

<!-- file: index.html -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>

12. DLL 单独库文件

动态连接库,将库打包单独打包

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

打包命令

1.先打包dll文件
webpack --config webpack.dll.js
2.打包整个项目
webpack

安装

npm i add-asset-html-webpack-plugin -D

新建webpack.dll.js 配置文件

例如: 单独处理jQuery包/ 单独处理react, react-dom ,react-router-dom等文件

const { resolve } = require('path');
const webpack = require('webpack');

module.exports = {
  entry: {
    // 最终打包生成的[name] --> jquery
    // ['jquery'] --> 要打包的库是jquery
    jquery: ['jquery'],
    react:['react','react-dom','react-router-dom']
  },
  output: {
    filename: '[name].js',
    path: resolve(__dirname, 'dll'),
    library: '[name]_[hash:10]' // 打包的库里面向外暴露出去的内容叫什么名字
  },
  plugins: [
    // 打包生成一个 manifest.json --> 提供和jquery映射
    new webpack.DllPlugin({
      name: '[name]_[hash:10]', // 映射库的暴露的内容名称
      path: resolve(__dirname, 'dll/manifest.json') // 输出文件路径
    })
  ],
  mode: 'production'
};

由于DLL文件已经打包过node包jQuery了, 那么webpack.config.json就不用再打包相同的文件了

对Webpack.config.json进行处理

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

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    // 告诉webpack哪些库不参与打包,同时使用时的名称也得变~
+    new webpack.DllReferencePlugin({
+      manifest: resolve(__dirname, 'dll/manifest.json')
+    }),

    // 将某个文件打包输出去,并在html中自动引入该资源
+   new AddAssetHtmlWebpackPlugin({
+      filepath: resolve(__dirname, 'dll/jquery.js')
+    })
  ],
  mode: 'production'
};

进行打包

# 先生成单独打包的文件
webpack --config webpack.dll.js
# 再进行打包构建
webpack

13.优化性能总结

开发环境性能优化

  • 优化打包构建速度
    • HMR
  • 优化代码调试
    • source-map

生产环境性能优化

  • 优化打包构建速度
    • oneOf
    • babel缓存
    • 多进程打包
    • externals 忽略打包
    • dll 单独打包
  • 优化代码运行的性能
    • 缓存(hash-chunkhash-contenthash)
    • tree shaking 树摇,去除无用的代码, 例如没有调用的函数
    • code split 代码分割
    • 懒加载/预加载
    • pwa 离线访问

webPack 5注意点

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

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

1.下载

  • npm i webpack webpack-cli -D

2.自动删除 Node.js Polyfills

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

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

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

迁移:

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

3.Chunk 和模块 ID

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

chunkIds: "deterministic", moduleIds: "deterministic"

4.Chunk ID

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

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

5.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

6.Output

webpack 4 默认只能输出 ES5 代码

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

如:output.ecmaVersion: 2015

7.SplitChunk

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

8.Caching

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

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

9.监视输出文件

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

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

10.默认值

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

11.更多内容

github.com/webpack/cha…