webpack入门与进阶:基础配置与热更新原理(二)

209 阅读4分钟

一、核心概念

1、entry

entry:指定打包入口(指定打包的源代码)

  • 单入口: entry 是一个字符串
modules.exports = {
    entry: "./src/index.js",
}
  • 多入口文件:entry 是一个对象
modules.exports = {
    entry: {
        index: "./src/index.js",
        search: "./scr/search.js"
    },
}

2、output

output 用来告诉 webpack 如何将编译后的文件输出到磁盘。(指定打包输出的结果代码)

  • 单入口配置
modules.exports = {
    entry: "./src/index.js",
    output: {
        filename: "bundle.js",
        path: __dirname + '/dist'
    }
}
  • 多入口配置
module.exports = {
  entry: {
    index: './src/index.js',
    search: './src/search.js'
  },
  output: {
    path: path.join(__dirname, 'dist'),
    // 使用 [name]占位符
    filename: '[name].js'
  },
  mode: 'production'
}

使用 [name]占位符,通过占位符确保文件名称的唯一
占位符详细见文件指纹部分

3、Loaders

  • 处理webpack不能识别的文件
  • 开箱即用只支持JS和JSON两种文件类型
  • 在loader中添加其他文件类型,可转换成有效模块
  • 本身是一个函数,接受源文件作为参数,返回转换的结果
常见的Loaders
名称描述
babel-loader转换ES6、ES7等JS新特性语法
css-loader支持.css文件的加载和解析
less-loader将less文件转换成css
ts-loader将TS转换成JS
file-loader进行图片、字体等打包
raw-loader将文件以字符串的形式导入
thread-loader多进程打包JS和CSS
用法
modules.exports = {
    entry: "./src/index.js",
    output: {
        filename: "bundle.js",
        path: __dirname + '/dist'
    },
    module: {
        rules: [
        // test:指定匹配规则 ,use 指定使⽤的 loader 名称
            { test: /\.txt$/,use: 'raw-loader' }
        ]
    }
}

4、Plugins

  • 增强 webpack 的功能,优化打包输出,资源管理、环境变量注入
  • 作用于整个构建过程
常见的Plugins

image.png

用法

plugins 为一个数组,将所有的 plugins 放在数组里即可。

5、mode

用来指定当前构建的环境变量:production、development和none
设置mode,可以使用webpack内置的函数,默认值是production

module.exports = {
  entry: {
    index: './src/index.js',
    search: './src/search.js'
  },
  output: {
    path: path.join(__dirname, 'dist'),
    // 使用 [name]占位符
    filename: '[name].js'
  },
  mode: 'production',
}

image.png

二、资源解析

Babel 是一个 JavaScript 的转译器。它的主要功能就是把 ES2015+ 的代码转换为 ES5 或更低版本的 JavaScript 代码。有了 Babel,您就可以使用最新的 JavaScript 语法,不用太担心浏览器的兼容问题。

babel 两个重要的部分 pluginspresets

  • plugins:一个 plugins 对应一个功能
  • presets:是一系列 plugins 的集合。(在babel编译之前,babel需要知道你的编译规则,到底是以什么样的规范去编译。)

webpack 中配置 babel 的相关参考文档:

Webpack 配置 Babel

webpack怎么配置babel?

Webpack中使用babel的知识点整理

Webpack和Babel

1、解析ES6

解析 es6 需要安装的插件

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

新建.babelrc文件,配置babel preset

{
  "presets": [
    "@babel/preset-env"
  ]
}

在webpack.config.js 中配置 babel

module.exports = {
  entry: {
    index: './src/index.js',
    search: './src/search.js'
  },
  output: {
    path: path.join(__dirname, 'dist'),
    // 使用 [name]占位符
    filename: '[name].js'
  },
  mode: 'production',
  module: {
      rules: [
        {
          test: /\.js$/,
          use: 'babel-loader'
        }
      ]
  }
}

2、解析React JSX

下载相关插件

npm i react react-dom @babel/preset-react -D

增加 reactbabel preset配置

//.babelrc 
{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react"
  ]
}

举个🌰:

1、新建search.js文件:

'use strict';
import React from 'react';
import ReactDom from 'react-dom';

class Search extends React.Component{
  render() {
    return <div>Search Text</div>
  }
}

ReactDom.render(
  <Search/>,
  document.getElementById('root')
)

2、通过 npm run build打包后,在dist目录下新建一个search.html文件,引入打包后的search.js文件,会显示“Search Text”,说明编译成功。

3、search.html文件;

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="root"></div>

</body>
<script src="./search.js" type="text/javascript"></script>

</html>

3、解析CSS

  • css-loader 用于加载 .css 文件,并且转换成 commonjs 对象(加载时机:代码里有引入css文件)
  • style-loader 将样式通过 <style></style> 标签插入到 head 中 安装插件
npm i style-loader css-loader -D

在webpack中配置:(webpack.config.js)

module.export = {
    ...
    ...
    module: {
        rules: [
            {
                test: /\.js$/,
                use: 'babel-loader'
            },
            {
                test: /\.css$/,
                // 注意先后顺序
                use: ['style-loader', 'css-loader']
            },
        ]
    }
}

配置css相关的loader需要注意先后顺序

  • loader的调用是链式调用,执行顺序是从右往左调用
  • 原理是:先通过css-loader解析 css ,然后将解析好的 css 传递给style-loader,将 css 插入到 head 中
  • 编写顺序:style-loader =》 css-loader

4、解析Less和Sass

less-loader,用于将 less 转转成 css 安装插件

npm i less less-loader -D

在 webpack 中配置:(webpack.config.js)

module.export = {
    ...
    ...
    module: {
        rules: [
            {
                test: /\.js$/,
                use: 'babel-loader'
            },
            {
                test: /\.css$/,
                // 注意先后顺序
                use: ['style-loader', 'css-loader']
            },
            {
                test: /\.less$/,
                // 注意先后顺序
                use: ['style-loader', 'css-loader', 'less-loader']
            },
        ]
    }
}

5、解析图片

使用 url-loader 处理图片

npm i url-loader -D

在webpack中配置:(webpack.config.js)

module.export = {
    ...
    ...
    module: {
        ...
        ...
        rules: [
            {
                test: /\.(png|jpg|gif|jpeg)$/,
                use: 'url-loader'
            },
        ]
    }
}

6、解析字体

使用file-loader处理文件

npm i file-loader -D

在webpack中配置:(webpack.config.js)

module.export = {
    ...
    ...
    module: {
        ...
        ...
        rules: {
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/,
                use: ['file-loader']
            },
        }
    }
}

三、webpack文件监听

源代码发生改变时,自动重新构建出新的输出文件。
webpack开启自动监听的模式,有两种方式:

  • 启动 webpack 命令时,带上 --watch 参数
  • 配置 webpack.config.js 中设置 watch:true

image.png 启动:npm run watch

页面改变,手动刷新,页面会刷新。

缺陷:需要手动刷新浏览器

文件监听的原理

image.png

四、webpack的自动编译

为了完成自动编译,webpack提供了三种可选的方式:

  • Webpack watch mode
  • Webpack-dev-server
  • Webpack-dev-middleware

1、watch mode

  • 在该模式下,webpack依赖图中所有文件,只要有一个发生了更新,那么代码将被重新编译
  • 不需要手动npm run build
  • 没有自动刷新浏览的功能
  • 开启方式:
    • ①在配置文件中增加配置项:"watch": true
    • ②在启动webapck的命令中,添加--watch标识
 "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "watch": "webpack --watch"
  },

2、WDS

webpack.config.js中配置 webpack-dev-server简称 WDS,可实现热更新的效果。

下载:

npm install --save webpack-dev-server

webpack.config.js中的配置

 "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "dev": "webpack-dev-server --open"
  },

打包更新命令:

npm run dev

--open :意思是构建完成,自动开启浏览器

WDS 热更新特点

  • ①不刷新浏览器,不输出文件,而是放在内存中(构建速度快)

  • webpack-dev-server 配置,会自动添加HotModuleReplacementPlugin 插件

    HotModuleReplacementPlugin作用:

    • webpack 构建出来的 bundle.js 本身是不具备热更新的能力的
    • HotModuleReplacementPlugin 的作用就是将 HMR runtime 注入到 bundle.js,使得 bundle.js 可以和 HMR server 建立 websocket 的通信连接 image.png
  • ③ webpack-dev-server v4.0.0+ 和 webpack >= v5.0.0和 webpack-cli >= v4.7.0 只需要配置package.json

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode=production --config webpack.prod.js",
    "dev": "webpack-dev-server --mode=development --config webpack.dev.js",
    "watch": "webpack --watch webpack.dev.js"
  },

举个例子:
在日常开发过程中,通常会按不同开发环境进行打包配置,这里按区分开发环境和生成环境进行简单配置:

package.json 配置

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode=production --config webpack.prod.js",
    "dev": "webpack-dev-server --mode=development --config webpack.dev.js",
    "watch": "webpack --watch webpack.dev.js"
  },

说明:

  • webpack-dev-server --mode=development --config webpack.dev.js:mode是development时,打包的配置文件是webpack.dev.js,可通过命令:npm run dev打包执行
  • webpack --mode=production --config webpack.prod.js:mode是production时,打包的配置文件是webpack.prod.js,可通过命令:npm run build打包执行

image.png

开发环境配置文件:webpack.dev.js

'use strict';

const path = require('path');
module.exports = {
  entry: {
    index: './src/index.js',
    search: './src/search.js'
  },
  output: {
    path: path.join(__dirname, 'dist'),
    // 使用 [name]占位符
    filename: '[name].js'
  },
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'babel-loader'
      },
      // loader的调用是链式调用,执行顺序是从右往左调用,所以先通过css-loader解析css,然后将解析好的css通传递给style-loader,将css插入到head中
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'less-loader']
      },
      {
        test: /\.(png|jpg|gif|jpeg)$/,
        use: 'url-loader'
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf|otf)$/,
        use: [
          'file-loader'
        ]
      }
    ]
  }
}

生成环境配置文件: webpack.prod.js

'use strict';

const path = require('path');
module.exports = {
  entry: {
    index: './src/index.js',
    search: './src/search.js'
  },
  output: {
    path: path.join(__dirname, 'dist'),
    // 使用 [name]占位符  设置文件指纹
    filename: '[name]_[chunkhash:8].js'
  },
  mode: 'production',
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'babel-loader'
      },
      // loader的调用是链式调用,执行顺序是从右往左调用,所以先通过css-loader解析css,然后将解析好的css通传递给style-loader,将css插入到head中
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'less-loader']
      },
      {
        test: /\.(png|jpg|gif|jpeg)$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              // 设置文件指纹
              name: '[name]_[hash:8][ext]'
            }
          }
        ]
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf|otf)$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              // 设置文件指纹
              name: '[name]_[hash:8][ext]'
            }
          }
        ]
      }
    ]
  }
}

3、WDM

  • webpack-dev-middleware (WDM)是一个封装器,它可以把webpack处理过的文件发送到一个server(服务器)

    • webpack-dev-server在内部使用了它,然而它也可以作为一个单独的package来使用,以便根据需求进行更多自定义配置
    • 搭配一个服务器来使用它,比如express.
    • npm install --save express webpack-dev-middleware
  • 编写Server.js

const express = require("express")
const webpack = require("webpack")
const webpackDevMiddleware = require("webpack-dev-middleware")

const  app = express()
const config = require("./webpack.config")
const compiler = webpack(config)

app.use(webpackDevMiddleware(compiler,{
    publicPath:config.output.publicPath
}),()=>{
    console.log("这里是回调函数")
})

app.listen(3000,()=>{
    console.log("Server running")
})
  • Node Server.js即可运行起一个服务,并监听文件更改和刷新浏览器。

五、热更新原理

1、HMR

webpack-dev-server 配置,会自动添加HotModuleReplacementPlugin (简称HMR)插件。

HotModuleReplacementPlugin作用:

  • webpack 构建出来的 bundle.js 本身是不具备热更新的能力的
  • HotModuleReplacementPlugin 的作用就是将 HMR runtime 注入到 bundle.js,使得 bundle.js 可以和 HMR server 建立 websocket 的通信连接

2、热更新原理分析

image.png

  • Wepack Compile:是webpack的编译器,将JS源代码编译成Bundle.js(最终打包输出的文件)
  • Bundle Server:提供文件在浏览器的访问。将编译好的 Bundle.js 文件通过服务器的方式访问,比如:localhaost:8080/bundle.js。也就是我们平时能够正常通过 localhost 访问我们本地网站的原因
  • HMR Server(服务端):将热更新的文件(变化的 js 模块)通过 websocket 的消息通知HMR Runtime(浏览器端)
  • HMR Runtime(浏览器端):
    • 用于接收 HMR Server 传递的模块数据,浏览器端可以看到 .hot-update.json 的文件过来。
    • 在开发的打包阶段,HMR Runtime 会被注入到浏览器的 bundle.js 里面,此时,HMR Runtime 会跟服务器建立一个 websocket 连接,收到更新数据时,会自动更新文件。
  • Bundle.js:构建输出的文件。
  • HotModuleReplacementPlugin的作用:webpack 构建出来的 bundle.js 本身是不具备热更新的能力的,HotModuleReplacementPlugin 的作用就是将 HMR runtime 注入到 bundle.js,使得bundle.js可以和HMR server建立websocket的通信连接
  • webpack-dev-server和hot-module-replacement-plugin之间的关系:
    • webpack-dev-server(WDS)的功能提供 bundle server的能力,就是生成的 bundle.js 文件可以通过 localhost://xxx 的方式去访问,另外 WDS 也提供 livereload(浏览器的自动刷新)。
    • hot-module-replacement-plugin 的作用是提供 HMR 的 runtime,并且将 runtime 注入到 bundle.js 代码里面去。一旦磁盘里面的文件修改,那么 HMR server(服务端) 会将有修改的 js module 信息发送给 HMR runtime(客户端),然后 HMR runtime 去局部更新页面的代码。因此这种方式可以不用刷新浏览器。
    • 单独写两个包也是出于功能的解耦来考虑的。简单来说就是:hot-module-replacement-plugin 包给 webpack-dev-server 提供了热更新的能力。

3、热更新的过程

启动阶段:

  • 在文件系统里进行编译,将初始代码经过webpack complier进行打包。
  • 打包完成之后,将编译好的文件传输给bundle server(服务器),bundle server可以让打包好的 bundle.js 文件通过服务器的方式进行访问(localhaost:8080/bundle.js)。
  • 图中展示:1 -> 2 -> A -> B 文件更新阶段:(本地文件改变,更新)
  • 本地开发,文件更新之后,文件系统变化后,代码会经过webpack compiler进行编译。
  • 编译之后的代码发送给HMR Server(服务端),HMR Server会知道哪些js源代码模块进行了改变,并将改变的模块通过json的格式进行传输,传输给HMR Runtime(浏览器端)。
  • HMR Runtime会局部更新代码,并且不需要刷新页面,因为有hot-module-replacement-plugin插件
  • 图中展示:1 -> 2 -> 3 -> 4

参考文档:

六、文件指纹

定义:打包后输出的文件名的后缀。

常见的文件指纹:

  • ①Hash:和整个项目的构建相关,只要项目文件有修改,整个项目构建的hash值就会更改
  • ②Chunkhash:和 webpack 打包的 chunk(模块)有关,不同的 entry 会生成不同的 chunkhash 值。某个文件发生变化,不会影响其他的问题件。(js文件指纹采用)
module.export = {
    ...
    output: {
        path: path.join(__dirname, 'dist'),
        // 使用 [name]占位符  设置文件指纹
        filename: '[name]_[chunkhash:8].js'
      },
}
  • ③Contenthash:根据文件内容来定义 hash ,文件内容不变,则 contenthash 不变。(css文件指纹采用)

设置文件指纹时,占位符对应表: image.png

举个例子-css文件指纹的设置

安装插件

npm i mini-css-extract-plugin -D

webpack.prod.js

// webpack.prod.js

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.export = {
    ...
    // 压缩css
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                  // style-loader是把css插入到header里,而MiniCssExtractPlugin是将css提取到一个独立的文件里,所以两者存在冲突
                  // 'style-loader',
                  MiniCssExtractPlugin.loader,
                  'css-loader'
                ]
            },
        ]
    },
    plugins: [
        // 设置css文件指纹
        new MiniCssExtractPlugin({
            // name 文件名称占位符
            // contenthash:8 采用contenthash的前八位
          filename: '[name]_[contenthash:8]'
        })
      ]
}

七、代码压缩

JS文件压缩

webpack 内置了 uglifyjs-webpack-plugin 插件进行js压缩

CSS文件压缩

github.com/webpack-con…

安装optimize-css-assets-webpack-plugin和cssnano(css预处理器)

npm install cssnano -D

安装css压缩插件时会报错

image.png 解决方法一:降低npm版本
解决方法二:

npm install optimize-css-assets-webpack-plugin -D -- force

// 或者
npm install optimize-css-assets-webpack-plugin -D --legacy-peer-deps

css压缩配置:

// webpack.prod.js
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');

module.exports = {
    plugins: [
        new MiniCssExtractPlugin({
          filename: '[name]_[contenthash:8].css'
        }),
        // css压缩
        new OptimizeCSSAssetsPlugin(),
  ]
}

打包后(npm run build),就可以看到压缩后的css文件
备注:webpack5.0使用的插件官方文档

HTML文件的压缩

官方文档

插件安装:

npm i html-webpack-plugin -D

html压缩配置

// webpack.prod.js
const HtmlWebpackPlugin = require('html-webpack-plugin');

plugins: [
        new MiniCssExtractPlugin({
            filename: '[name]_[contenthash:8].css'
        }),
        new OptimizeCSSAssetsPlugin(),
        // html压缩配置 一个页面对应一个HtmlWebpackPlugin
        new HtmlWebpackPlugin({
           // 模板所在位置
          template: path.join(__dirname, 'src/index.html'),
          // 指定打包后的文件名称
          filename: 'index.html',
          // 指定生成的html需要哪些chunk
          chunks: ['index'],
          // 打包后的chunk会自动注入到html中
          inject: true,
          minify: {
            html5: true,
            collapseWhitespace: true,
            preserveLineBreaks: false,
            minifyCSS: true,
            minifyJS: true,
            removeComments: false
          }
       }),
       // html压缩配置 一个页面对应一个HtmlWebpackPlugin
       new HtmlWebpackPlugin({
          template: path.join(__dirname, 'src/search.html'),
          filename: 'search.html',
          chunks: ['search'],
          inject: true,
          minify: {
            html5: true,
            collapseWhitespace: true,
            preserveLineBreaks: false,
            minifyCSS: true,
            minifyJS: true,
            removeComments: false
          }
       }) 
  ]

执行npm run build 会生成经过压缩的文件