初识webpack——揭开神秘的“面纱”

1,706 阅读7分钟

本文从webpack的作用入手进行阐述,然后又根据webpack一些基本配置,一步一步来揭开webpack的神秘“面纱”。本文可作为webpack入门篇来学习,更多的也是引领新手去了解webpack

webpack是什么

image.png

webpack官网一进去便能看到上面这张图,这张图便解释了webpack的作用。webpack是一个静态模块打包工具,项目中使用的每个文件都是一个模块,这些模块间还有一些依赖关系,经过webpack打包后,输出为静态资源。

因此也能看出Webpack的缺点或者说局限,是只能用于采用模块化开发的项目。

核心概念

  • 入口(entry):指示webpack构建应该从哪个文件开始。可以指定一个或多个入口
  • 输出(output):告诉webpack构建完后所创建的bundle(打包后的文件)的输出路径以及怎么命名文件
  • loader:转换文件内容,让webpack能处理其他类型文件。
  • 插件(plugin):扩展webpack的功能
  • bundlewebpack打包后生成的文件默认名,一般用来指代打包后的文件
  • chunk:打包后的文件抽离出来的“分支”,或者理解为代码块。例如,使用多个入口起点或使用像 CommonsChunkPlugin 这样的插件会生成多个chunk

webpack构建流程是怎样的

  • 初始化参数:从配置文件webpack.config.jsshell命令中的配置读取与合并参数
  • 开始编译:用上一步得到的参数初始化Compiler对象,加载所有配置的插件,执行对象的run方法开始编译
  • 确定入口:根据配置的entry找出所有的入口文件
  • 编译模块:从入口文件出发,调用所有配置的loader对模块进行转换,再找出该模块依赖的模块,再递归本步骤,直到所有入口依赖的文件都经过了本步骤的处理,编译完后得到了每个文件转换后的最终内容以及它们之间的依赖关系
  • 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的Chunk,再把每个Chunk转换成一个单独的文件加入到输出列表
  • 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,生成文件。

image.png

Webpack 启动后会从入口 Entry 里配置的模块开始递归解析入口文件依赖的所有其他模块。每找到一个模块,就会根据对应模块配置的 Loader 去找出对应的转换规则,对模块进行转换后,再解析出当前模块依赖的模块。这些模块会以入口为单位进行分组,一个入库和其所有依赖的模块被分到一个组也就是一个chunk。最后 Webpack 会把所有 chunk 转换成文件输出。在整个流程中 Webpack 会在恰当的时机执行 Plugin 里定义的逻辑。

配置文件构成

下面以这个示例文件来介绍webpack配置文件的构成(更多配置请移步官网)

// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { resolve } = require("path");

module.exports = {
  mode: "development", // 当前环境,可选值为development,production或none,默认production
  entry: "./src/index.js", // 入口配置
  output: { // 输出配置
    filename: "bundle.[hash:8].js", // 打包后生成的js文件名
    path: path.join(__dirname, "dist"), // 打包文件输出目录
  },
  devtool: 'inline-source-map', // source map配置
  devServer: { // 本地开发配置
    static: path.resolve(__dirname, "src/assets"), // 静态文件目录
    compress: true, // 是否启用 gzip 压缩
    port: 8080, // 端口号
    hot: true // 是否开启热更新
  },
  resolve: { // 解析配置
    alias: { // 配置别名
      "~": resolve("src"),
      "@": resolve("src"),
      components: resolve("src/components"),
    },
    //如果用户引入模块时不带扩展名,那么webpack就会按照extensions配置的数组从左到右的顺序去尝试解析
    extensions: [".js", ".json", ".wasm"], 
    
  },
  module: {
    rules: [ // 这里配置loader
      {
        test: /\.(c|s[ac])ss$/i,
        use: [
          // 'style-loader',
          MiniCssExtractPlugin.loader,
          "css-loader",
          "postcss-loader",
          "sass-loader",
        ],
      },
    ],
  },
  plugins: [  // 这里配置plugin
    new HtmlWebpackPlugin({
      template: "src/index.html",
    }),
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({
      filename: "[name].[hash:8].css",
    }),
  ],
};

下面会详细介绍一下这些配置信息。

entry

打包的入口文件 ,进入入口文件后,webpack会找出有哪些模块和库是入口文件所依赖的。

使用方式

entry的默认值是./src/index.js,我们可以手动配置入口文件:

// webpack.config.js
module.exports = {
  entry: "./src/index.js", // 入口配置
}

多个入口

上面这种只适用于单个入口的配置,如果要配置多个入口,entry要设置为对象的形式:

// webpack.config.js
module.exports = {
  entry: {
    app: "./src/index.js",
    home: "./src/home.js",
  }, // 入口配置
}

output

输出的文件位置和名称,告诉webpack在哪里输出他所创建的bundle,并且怎么命名。

使用方式

不同于entry,我们的输出只能有一个配置。

// webpack.config.js
module.exports = {
  output: { // 输出配置
    filename: "bundle.[hash:8].js", // 打包后生成的js文件名
    path: path.join(__dirname, "dist"), // 打包文件输出目录
  },
}

占位符

在给输出的文件命名的时候,我们经常会用到占位符。常用占位符如下:

占位符解释
ext文件后缀名
name文件名
path文件相对路径
hash每次构建生成的唯一 hash
chunkhash根据 chunk 生成 hash
contenthash根据文件内容生成 hash

上例中,我们配置输出的文件名filename: "bundle.[hash:8].js",可以看到生成的文件名中有一串8位数的hash码。

image.png

loader

webpack 只能理解 JavaScriptJSON 文件,若要让它识别其他类型的文件,需要使用webpackloader机制。

使用方式

无论我们使用loader还是plugin,在用之前都要安装依赖。以css-loader为例:

npm i css-loader -D

loader两种使用方式:

  • 配置方式(推荐) 在 webpack.config.js 文件中指定 loader
// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(c|s[ac])ss$/i, // 用正则去匹配要用该 loader 转换的样式文件
        // 这里loader会从右到左开始解析,sass-loader -> css-loader -> style-loader
        use: ['style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true
            }
          },
          'sass-loader' 
        ]
      }
    ]
  }
}
// 使用 `!` 将资源中的 loader 分开
import Styles from 'style-loader!css-loader?modules!./styles.css';

常用loader一览表

loader作用
css-loaderwebpack能加载css文件
style-loader把样式加载到页面上,将处理好的 css 通过 style 标签的形式添加到页面上
postcss-loader自动添加css3部分属性的浏览器前缀
sass-loaderwebpack能加载scsssass文件
file-loader处理图片文件引入问题,将图片复制到指定目录,默认是dist
url-loader依赖file-loader,当图片小于limit值时,会将图片转为base64编码,大于limit值时依然使用file-loader进行拷贝
img-loader压缩图片
babel-loader解决兼容性,es6转换为es5,开启缓存等
thread-loader开启多进程打包
cache-loader缓存一些性能开销比较大的loader的处理结果
ts-loader处理typescript语言

注:webpack5已经内置了一些基础loader,比如file-loaderurl-loader,我们在使用的时候不必再安装它们了。

Plugin

Plugin 是用来扩展 Webpack 功能的,解决 loader 无法实现的其他事,通过在构建流程里注入钩子实现,它给 Webpack 带来了很大的灵活性。

插件本质是一个具有 apply 方法的 JavaScript 对象。apply 方法会被 webpackcompiler 调用,并且在整个编译生命周期都可以访问 compiler 对象。

使用方式

  • 配置方式
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装
const webpack = require('webpack'); // 访问内置的插件
const path = require('path');

module.exports = {
  ...
  plugins: [
    new webpack.ProgressPlugin(),
    new HtmlWebpackPlugin({ template: './src/index.html' }),
  ],
};
  • Node API方式 在使用 Node API 时,还可以通过配置中的 plugins 属性传入插件
const webpack = require('webpack'); // 访问 webpack 运行时(runtime)
const configuration = require('./webpack.config.js');

let compiler = webpack(configuration);

new webpack.ProgressPlugin().apply(compiler);

compiler.run(function (err, stats) {
  // ...
});

常用plugin一览表

plugin作用
html-webpack-plugin把打包后的资源文件(比如js或者css)自动引入到html文件中
mini-css-extract-plugin/extract-text-webpack-plugin将样式以css文件的形式引入到页面
clean-webpack-plugin打包前将打包目录清空
hard-source-webpack-plugin微模块提供中间缓存,重复构建时间大约减少80%,webpack5中已经内置了模块缓存
webpack-bundle-analyzer打包结果分析,文件大小,各模块依赖关系等
optimize-css-assets-webpack-plugin压缩css体积
terser-webpack-plugin压缩jswebpack5中已经内置了,直接引用即可
purgecss-webpacl-plugin会单独提取css并清除用不到的css
split-chunks-plugin分包插件
progress-plugin自定义编译过程的进度报告

本地开发配置

在本地开发的过程中,我们有这么几个诉求:

  1. 开启本地服务,提供 HTTP 服务而不是使用本地文件预览;
  2. 修改文件后自动刷新网页,做到实时更改;
  3. 支持 Source Map,以方便调试。

webpack-dev-server能帮我们开启本地服务,可以通过 http://localhost 或者本机ip地址去访问本地项目。

配置本地开发环境

安装webpack-dev-server

npm i webpack-dev-server -D

配置启动命令

// package.json
  "scripts": {
    "dev": "webpack serve --mode development",
  }

执行:npm run dev启动项目,这样就能通过http服务访问项目了

image.png

模块热替换

模块热替换(简称HMR)能做到无需重新加载整个页面的情况下,将更新过的模块替换老的模块。这在我们开发的时候能提高开发效率,不用每次修改完文件后再执行一次npm run build

从 webpack-dev-server v4.0.0 开始,热模块替换是默认开启的。如果要手动开启需要在启动DevServer时带上参数--hot,或者在webpack.config.js里面配置hot: true

// webpack.config.js// 
module.exports = {
// ...
  devServer: { // 本地开发配置
    static: path.resolve(__dirname, "src/assets"), // 静态文件目录
    compress: true, // 是否启用 gzip 压缩
    port: 8080, // 端口号
    hot: true // 是否开启热更新
  },
}

支持source map

webpack打包后,很难从打包后的文件追踪到错误信息或者警告信息在源代码中的原始位置,source map功能就是来解决这一问题的,它可以将编译后的代码映射回原始源代码,这样我们本地开发的时候就能准确定位到问题的位置了。

// webpack.config.js// 
module.exports = {
// ...
  devtool: 'inline-source-map', // source map配置
}

devtool的可选值还有其他类型,详细介绍请移步devtool

参考