【前端学习笔记】Webpack

1.介绍

Webpack 是一个现代 JavaScript 应用程序的静态模块打包工具,它将 JavaScript、CSS、图片、字体等资源文件打包成一个或多个静态文件,以供浏览器使用。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源。

基本概念

1.入口(Entry)

入口起点(entry point) 指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。

默认值是 ./src/index.js,但你可以通过在 webpack configuration 中配置 entry 属性,来指定一个(或多个)不同的入口起点。

入口是 Webpack 处理项目的起点。它指定了 Webpack 应该从哪个文件开始分析和构建模块依赖图。一般来说,入口文件是项目的主要 JavaScript 文件。

2.输出(output)

output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。输出配置指定了 Webpack 打包后文件的存放位置和文件名称。通常,输出目录会存放在 dist 目录中,文件名称可以通过配置来定制。

output.filename 和 output.path 属性 告诉 webpack bundle 的名称,以及想要 bundle 生成(emit)到哪里。

3.加载器(loader)

webpack 默认只能理解 JavaScript 和 JSON 文件,loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。Loaders 是 Webpack 的一个强大功能,它让你能够转换和处理各种类型的文件(如 CSS、图片、TypeScript、SASS 等),让它们可以作为模块导入到 JavaScript 文件中。

4.插件(Plugins)

Plugins 用来执行各种各样的任务,比如优化输出文件、注入环境变量、生成 HTML 文件等。它们在 Webpack 构建过程中执行各种操作。插件允许你在构建过程中做一些额外的操作(例如:代码压缩、生成 HTML 文件、环境变量注入等)。Webpack 插件通过 webpack 对象的 plugins 属性进行配置。

5.模块(Modules)

Webpack 的核心理念是"一切皆模块"。无论是 JavaScript、CSS、图片,还是字体文件等,都可以被视作模块。在 Webpack 中,模块是应用的基本单位,Webpack 通过模块化来处理所有资源。Webpack 会通过依赖图找到所有模块,进行打包。

6.模式(mode)

通过选择 development, production 或 none 之中的一个,来设置 mode 参数,可以启用 webpack 内置在相应环境下的优化。其默认值为 production。

  • development 模式 主要关注开发过程中的效率和可调试性,提供详细的错误信息和调试支持,构建速度较快,但输出的代码未经过压缩,体积较大。
  • production 模式 主要关注优化性能和代码体积,启用代码压缩、树摇、分离代码等优化手段,构建速度较慢,但输出的代码小且高效,更适合用于生产环境。

7.开发服务器(Development Server)

Webpack 提供了一个内置的开发服务器 webpack-dev-server,用于在开发过程中提供热更新、自动刷新等功能。可以通过配置 devServer 来启用这个功能。

typescript 复制代码
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development',  // 设置模式
  entry: './src/index.js',  // 入口文件
  output: {
    filename: 'bundle.js',  // 输出文件名
    path: path.resolve(__dirname, 'dist'),  // 输出目录
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: 'babel-loader',
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
    }),
  ],
  devServer: {
    contentBase: './dist',  // 开发服务器提供的静态文件目录
    hot: true,  // 启用热更新
  },
};

2.入口Entry

它指定了打包过程的入口文件。entry 配置项的主要作用是告诉 Webpack 从哪个文件开始打包。Webpack 会从这个文件开始,递归地分析所有依赖,最终将这些依赖和文件打包成最终的输出。默认情况下,entry 可以是一个字符串,表示单一的入口文件;也可以是一个对象,表示多个入口文件。entry: string | [string]

用于描述入口的对象。可以使用如下属性:

  • dependOn: 当前入口所依赖的入口。它们必须在该入口被加载前被加载。
  • filename: 指定要输出的文件名称。可以是字符串、模板或函数。
    • [name]:入口文件的名称
    • [contenthash]:基于文件内容生成的哈希值,用于缓存优化
    • [id]:模块的 ID
    • [chunkhash]:模块内容生成的哈希值,用于缓存优化
  • import: 指定启动时需要加载的模块。
  • library: 指定 library 选项,为当前 entry 构建一个 library。用于将打包的代码暴露为一个库,允许其他 JavaScript 环境(比如浏览器、Node.js 或其他框架)使用。libraryTarget:指定库的输出目标,这里使用了 umd(通用模块定义)格式,兼容多种模块化系统。
  • runtime: 处理 Webpack 生成的运行时代码,并将其提取到单独的文件中。如果设置了,就会创建一个新的运行时 chunk。在 webpack 5.43.0 之后可将其设为 false 以避免一个新的运行时 chunk。
  • publicPath: 当该入口的输出文件在浏览器中被引用时,为它们指定一个公共 URL 地址。指定资源的公共 URL 地址,即所有通过 Webpack 打包的资源(如 JavaScript、CSS、图片等)在浏览器中引用时的基础 URL。它允许你在不同的环境下(开发、生产等)动态地配置资源的加载路径。

1.单入口配置

最简单的形式是通过指定一个文件路径,告诉 Webpack 从哪个文件开始构建。

typescript 复制代码
module.exports = {
  entry: './src/index.js',  // 单一入口文件
  output: {
    filename: 'bundle.js',  // 输出的文件
    path: path.resolve(__dirname, 'dist'),  // 输出目录
  },
};

2.多入口配置

如果一个项目有多个入口文件,例如一个多页面应用(MPA),你可以使用一个对象来指定多个入口点。每个入口文件会生成一个独立的输出文件。

typescript 复制代码
module.exports = {
  entry: {
    app: './src/app.js',
    admin: './src/admin.js',
  },
  output: {
    filename: '[name].js',  // 使用入口文件名作为输出文件名
    path: path.resolve(__dirname, 'dist'),
  },
};

3.动态入口

你也可以使用一个函数来动态返回入口配置。这在根据不同的环境或条件来设置入口文件时非常有用。

typescript 复制代码
module.exports = (env) => {
  return {
    entry: env.production ? './src/index.prod.js' : './src/index.dev.js',
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'dist'),
    },
  };
};

4.数组形式

也可以为同一个入口点配置多个模块。通过将多个文件放入一个数组中,你可以告诉 Webpack 从多个文件开始打包。Webpack 会将这些文件合并为一个入口文件。

typescript 复制代码
module.exports = {
  entry: ['./src/file_1.js', './src/file_2.js'],
  output: {
    filename: 'bundle.js',
  },
};

3.输出(output)

在 Webpack 中,output 配置项用于指定打包后的文件如何输出。它控制了 Webpack 构建的输出路径、文件名称、以及其他相关的配置,决定了如何组织和存储打包后的文件。

output 配置项的主要属性

  1. path:指定了输出文件的目标目录。它必须是一个绝对路径(相对路径会引发错误)。 这个路径定义了 Webpack 打包后的文件存放位置。

    js 复制代码
    const path = require('path');
    module.exports = {
      output: {
        path: path.resolve(__dirname, 'dist'), // 输出文件存放目录
      },
    };
  2. filename
    filename 属性用于设置输出文件的名称。它可以使用模板变量(例如:[name][hash][chunkhash] 等)来根据文件的特征动态命名。 通常,filename 会用于定义 JavaScript 文件的名称。

    js 复制代码
    module.exports = {
      output: {
        filename: 'bundle.js', // 输出文件名
      },
    };

    你也可以使用模板字符串来生成文件名,以便支持缓存和版本控制:

    js 复制代码
    module.exports = {
      output: {
        filename: '[name].[contenthash].js', // 输出文件名包含哈希值,便于缓存
      },
    };
    • [name]:表示入口的名称。
    • [hash]:表示整个构建的哈希值,常用于生成唯一文件名。
    • [chunkhash]:表示特定 chunk(代码块)的哈希值,适用于按需加载的模块,能更精确地控制缓存。
    • [contenthash]:表示文件内容的哈希值,适用于文件内容变化时修改文件名,通常用在生产环境中。
  3. publicPath:用于指定加载资源(如 JS、CSS、图片等)时的 URL 路径。它可以是一个相对路径或绝对路径,指示浏览器从哪里加载资源。如果你在服务器上部署文件时需要设置路径,这个配置很有用。可以确保在服务器上资源文件能够正确加载,特别是在有 CDN 或特定路径需求时。

    js 复制代码
    module.exports = {
      output: {
        publicPath: '/assets/', // 所有资源都会通过 /assets/ 路径加载
      },
    };
    • 例如,假设你的文件结构是:

      /assets/js/bundle.js
      /assets/css/style.css
      

      那么在 HTML 中引入这些资源时,会自动加上 /assets/ 前缀。

    • 对于 CDN 资源,可以配置为:

      js 复制代码
      output: {
        publicPath: 'https://cdn.example.com/', // CDN 地址
      },
  4. librarylibraryTarget:Webpack 可以将构建结果暴露为可复用的库,适用于开发 JavaScript 库和工具。

    • library 配置项用于指定输出的代码如何暴露给全局作用域,通常在构建库时使用。你可以选择将代码暴露为一个变量、一个对象,或者其他目标。
    • libraryTarget 用于指定输出文件的模块格式。它的值可以是 varumdcommonjs 等。
    js 复制代码
    module.exports = {
      output: {
        library: 'MyLibrary', // 使得打包后的文件可以通过 MyLibrary 变量访问
        libraryTarget: 'umd', // 输出的文件兼容多种模块格式
      },
    };
    • libraryTarget 可以设置为:
      • var:将库暴露为全局变量(适用于浏览器)。
      • umd:通用模块定义,支持浏览器、CommonJS 和 AMD 模块。
      • commonjs:以 CommonJS 模式输出(适用于 Node.js 环境)。
  5. chunkFilename:指定了异步加载的代码块(chunk)生成的文件名。它通常和代码拆分(Code Splitting)一起使用,将代码分割成多个文件,按需加载。

    js 复制代码
    module.exports = {
      output: {
        chunkFilename: '[name].[chunkhash].js', // 非入口文件(按需加载的文件)命名
      },
    };

    chunkFilename 主要用于动态加载的模块文件。它会生成类似于 main.chunk.jsvendor.chunk.js 这样的文件。

  6. pathinfo:是一个布尔值,决定是否在输出的文件中包含模块的信息(如文件的路径信息)。这对于调试很有帮助,但会增加文件体积,因此在生产环境中一般会禁用。

    js 复制代码
    module.exports = {
      output: {
        pathinfo: true, // 输出文件中包含模块的路径信息
      },
    };

    在开发模式下,pathinfo 默认为 true,会在控制台或源代码中显示有关模块的信息。生产环境中,通常会禁用该选项以减小文件体积。

4.加载器(loader)

Webpack 默认只处理 JavaScript 文件,但在现代前端开发中,我们往往需要处理各种不同类型的文件,比如:CSS 文件、图片(如 PNG、JPG、SVG 等)、字体(如 TTF、WOFF 等)、TypeScript 文件、SASS/LESS 文件、JSON 文件以及其他自定义文件格式。加载器通过将这些资源转换为 Webpack 能够处理的模块,使得 Webpack 能够打包、优化这些资源。

Webpack 通过加载器将不同格式的文件转换成模块,整个过程可以理解为:

  1. 解析文件:当 Webpack 遇到某个文件时,它根据配置的规则判断该文件应该使用哪个加载器进行处理。
  2. 执行加载器:加载器会对文件内容进行转换。例如,使用 babel-loader 转换 ES6+ 代码为 ES5,或者用 css-loader 将 CSS 文件转换为 JavaScript 模块。
  3. 返回处理后的内容:加载器会返回处理后的内容,Webpack 会将其继续处理,直到所有依赖的模块都被解析完毕。
  • loader 支持链式调用。链中的每个 loader 会将转换应用在已处理过的资源上。一组链式的 loader 将按照相反的顺序执行。链中的第一个 loader 将其结果(也就是应用过转换后的资源)传递给下一个 loader,依此类推。最后,链中的最后一个 loader,返回 webpack 所期望的 JavaScript。
  • loader 可以是同步的,也可以是异步的。
  • loader 运行在 Node.js 中,并且能够执行任何操作。
  • loader 可以通过 options 对象配置(仍然支持使用 query 参数来设置选项,但是这种方式已被废弃)。
js 复制代码
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/, // 处理 .css 文件
        use: ['style-loader', 'css-loader'], // 使用 css-loader 和 style-loader
      },
      {
        test: /\.js$/, // 处理 .js 文件
        exclude: /node_modules/, // 排除 node_modules 文件夹中的文件
        use: 'babel-loader', // 使用 babel-loader 进行转换
      },
    ],
  },
};

常见的加载器

  1. babel-loader(用于 JavaScript 转译)

    • babel-loader 用于将 ES6+ 代码转译为浏览器兼容的 ES5 代码。
    • 配合 Babel 使用,通常需要配合 .babelrc 配置文件或 babel.config.js 文件来指定使用的转译规则。

    示例配置:

    js 复制代码
    {
      test: /\.js$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-env'], // 使用 @babel/preset-env 转译 ES6+ 语法
        },
      },
    }
  2. css-loader(用于处理 CSS)

    • css-loader 用于解析和转换 CSS 文件。它会将 CSS 转换成一个 JavaScript 模块,允许你通过 import 语句导入 CSS 文件。

    示例配置:

    js 复制代码
    {
      test: /\.css$/,
      use: ['style-loader', 'css-loader'], // 先用 css-loader 解析,再用 style-loader 将其注入到 HTML 文件中
    }
  3. style-loader(用于将 CSS 注入到 HTML 中)

    • style-loader 用于将 JavaScript 中的 CSS 插入到 HTML 文件的 <style> 标签中。它通常和 css-loader 一起使用。

    示例配置:

    js 复制代码
    {
      test: /\.css$/,
      use: ['style-loader', 'css-loader'],
    }
  4. sass-loaderless-loader(用于处理 SASS/LESS)

    • sass-loaderless-loader 分别用于处理 SASS 和 LESS 文件,并将它们转换为 CSS。使用时通常还需要配合 css-loaderstyle-loader

    示例配置(处理 SASS 文件):

    js 复制代码
    {
      test: /\.scss$/,
      use: ['style-loader', 'css-loader', 'sass-loader'], // 顺序很重要
    }
  5. file-loaderurl-loader(用于处理文件)

    • file-loader 用于将文件复制到输出目录,并返回文件的 URL。它常用于图片、字体等资源的处理。
    • url-loader 类似于 file-loader,但它可以将小于指定大小的文件转为 base64 编码,减少网络请求。

    示例配置(处理图片):

    js 复制代码
    {
      test: /\.(png|jpg|gif)$/,
      use: 'file-loader', // 使用 file-loader 处理图片文件
    }

    示例配置(使用 url-loader,将图片小于 8kb 的转换为 base64):

    js 复制代码
    {
      test: /\.(png|jpg|gif)$/,
      use: 'url-loader?limit=8192', // 小于 8KB 的图片将转为 base64
    }
  6. html-loader(用于处理 HTML 文件中的资源)

    • html-loader 用于将 HTML 文件中的资源(如图片、字体)转换为合适的路径。它会将 HTML 文件中的静态资源引入到打包结果中。

    示例配置:

    js 复制代码
    {
      test: /\.html$/,
      use: 'html-loader', // 处理 HTML 文件中的资源
    }
  7. json-loader(用于加载 JSON 文件)

    • json-loader 用于加载 JSON 文件。自 Webpack 2 版本起,JSON 文件已经被 Webpack 内置支持,因此不需要额外的 loader,除非使用特殊的配置或版本。

    示例配置:

    js 复制代码
    {
      test: /\.json$/,
      use: 'json-loader', // 不常用,Webpack 2 以上版本已内建支持
    }
  8. ts-loaderawesome-typescript-loader(用于 TypeScript 文件)

    • ts-loaderawesome-typescript-loader 用于将 TypeScript 文件(.ts/.tsx)转译为 JavaScript 文件。

    示例配置:

    js 复制代码
    {
      test: /\.tsx?$/,
      use: 'ts-loader', // 使用 ts-loader 处理 TypeScript 文件
    }

加载器的执行顺序

当 Webpack 处理一个文件时,它会o从右到左(或从下到上)地取值(evaluate)/执行(execute)配置的加载器。Webpack 会从 use 数组的 右向左 执行加载器(如果有多个加载器时)。这意味着,首先应用数组中的最后一个加载器,然后向前应用加载器。需要注意的是,加载器的顺序非常重要,先执行的加载器处理内容较为原始,后执行的加载器处理的是更高层次的内容。

例如,处理 scss 文件时:

  1. sass-loader:将 SASS 编译为 CSS。
  2. css-loader:将 CSS 转换为 JavaScript 模块。
  3. style-loader:将最终的 CSS 插入到 HTML 文件中。

自定义 loader

自定义 loader是一个 Node.js 模块,它导出一个函数来处理文件内容。

js 复制代码
module.exports = function (source) {
  // 对文件内容进行处理
  return `export default ${JSON.stringify(source)}`;
};

然后在 Webpack 配置中使用自定义 loader:

js 复制代码
module: {
  rules: [
    {
      test: /\.txt$/,
      use: path.resolve(__dirname, 'my-loader.js') // 使用自定义 loader
    }
  ]
}

5.插件(Plugins)

插件目的在于解决 loader 无法实现的其他事。Webpack 插件是一个具有 apply 方法的 JavaScript 对象。apply 方法会被 webpack compiler 调用,并且在 整个 编译生命周期都可以访问 compiler 对象。

插件不同于加载器(Loader),加载器主要负责转换资源文件,而插件则可以在构建过程中执行更复杂的任务,比如优化、打包管理、环境配置、文件生成等。通过插件,开发者可以干预 Webpack 的构建过程,实现从构建流程的各个环节对项目进行深入的定制和优化。

插件通过 Webpack 提供的 生命周期钩子(Hooks) 与构建过程进行交互。在构建过程中,Webpack 会触发一系列的事件,插件通过监听这些事件并执行相应的操作来完成工作。

插件通常是在 Webpack 配置文件中通过 plugins 数组进行配置,插件的调用是同步或异步的,具体取决于插件的设计。

插件通常是通过 new PluginName() 的方式来初始化,并传入必要的配置参数。WebPack 配置文件中的 plugins 是一个数组,所有的插件实例都在这个数组中。

js 复制代码
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  // ...其他配置
  plugins: [
    new CleanWebpackPlugin(), // 在每次构建之前清理输出目录
    new HtmlWebpackPlugin({
      template: './src/index.html', // 使用自定义的 HTML 模板
      inject: 'body', // 将脚本标签插入到 <body> 中
    }),
  ],
};

常见插件

  1. HtmlWebpackPlugin

    • HtmlWebpackPlugin 用于简化 HTML 文件的创建,特别是在 Webpack 打包时自动插入所有生成的资源文件(如 JS、CSS)。
    • 它能够自动生成一个 index.html 文件,并将输出的 JS 和 CSS 文件插入到该文件中。

    示例配置:

    js 复制代码
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    module.exports = {
      plugins: [
        new HtmlWebpackPlugin({
          template: './src/index.html', // 自定义模板
          filename: 'index.html', // 输出文件名
          inject: 'body', // 将所有 JS 脚本标签插入到 <body> 中
        }),
      ],
    };
  2. CleanWebpackPlugin

    • CleanWebpackPlugin 插件用于在每次构建前清空输出目录。这样可以确保不会留下旧的文件,避免生成无用文件。
    • 它通常与 output.path 配合使用。

    示例配置:

    js 复制代码
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    
    module.exports = {
      plugins: [
        new CleanWebpackPlugin(), // 清理 dist 目录
      ],
    };
  3. MiniCssExtractPlugin

    • MiniCssExtractPlugin 用于将 CSS 从 JavaScript 文件中提取出来,生成独立的 CSS 文件。这对于生产环境中的性能优化非常有用,特别是当你有大量的 CSS 时。

    示例配置:

    js 复制代码
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    
    module.exports = {
      module: {
        rules: [
          {
            test: /\.css$/,
            use: [MiniCssExtractPlugin.loader, 'css-loader'],
          },
        ],
      },
      plugins: [
        new MiniCssExtractPlugin({
          filename: '[name].[contenthash].css', // 输出文件名包含哈希值
        }),
      ],
    };
  4. TerserWebpackPlugin

    • TerserWebpackPlugin 是 Webpack 默认用于压缩 JavaScript 代码的插件。它可以删除无用的代码,减小输出文件的体积。
    • 默认情况下,在 production 模式下启用此插件。

    示例配置:

    js 复制代码
    const TerserWebpackPlugin = require('terser-webpack-plugin');
    
    module.exports = {
      optimization: {
        minimize: true, // 开启代码压缩
        minimizer: [new TerserWebpackPlugin()],
      },
    };
  5. CopyWebpackPlugin

    • CopyWebpackPlugin 用于将静态资源从源目录复制到输出目录(比如复制图片、字体等文件)。

    示例配置:

    js 复制代码
    const CopyWebpackPlugin = require('copy-webpack-plugin');
    
    module.exports = {
      plugins: [
        new CopyWebpackPlugin({
          patterns: [
            { from: 'src/assets', to: 'assets' }, // 将 src/assets 目录复制到 dist/assets
          ],
        }),
      ],
    };
  6. BundleAnalyzerPlugin

    • BundleAnalyzerPlugin 用于可视化 Webpack 打包生成的文件大小,有助于发现哪些模块占用了较多的空间,从而进行性能优化。

    示例配置:

    js 复制代码
    const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
    
    module.exports = {
      plugins: [
        new BundleAnalyzerPlugin(), // 启动 Bundle 分析工具
      ],
    };
  7. DefinePlugin

    • DefinePlugin 用于在代码中创建全局常量,通常用于区分不同的环境(如开发环境和生产环境)。
    • 比如,我们可以通过这个插件在代码中注入 process.env.NODE_ENV,以便进行条件渲染。

    示例配置:

    js 复制代码
    const webpack = require('webpack');
    
    module.exports = {
      plugins: [
        new webpack.DefinePlugin({
          'process.env.NODE_ENV': JSON.stringify('production'), 
        }),
      ],
    };

Webpack 插件通过监听 Webpack 提供的 钩子(hooks) 来与构建流程进行交互。Webpack 的构建过程是由多个阶段组成的,插件可以在这些阶段中执行不同的任务。插件通常使用 Webpack Compiler APIWebpack Compilation API 来访问构建过程中的各个阶段。

  • apply() :插件的核心方法。所有插件都需要实现 apply 方法,这个方法会在插件实例化时被调用。在 apply 方法中,插件可以通过 Webpack 提供的钩子和事件与构建过程进行交互。
  • 钩子 :Webpack 提供了多个钩子供插件使用,如 emitafterCompiledone 等。插件可以在这些钩子中添加自定义逻辑。

6.模块(Modules)

在 Webpack 中,模块是构成应用程序的基本单元。任何在 Webpack 配置中需要被打包的资源,都被视为模块。

  1. 模块解析: Webpack 会从项目的入口文件开始,递归地分析文件中的依赖关系。它会逐步识别所有 import 或 require 引入的模块,并解析这些模块的内容。
  2. 使用加载器(Loader): Webpack 处理每个模块时,使用加载器来转换非 JavaScript 模块(如 CSS、图片、SASS 等)。加载器会根据配置的规则,逐步将不同类型的文件转换为 Webpack 可以理解的模块。
  3. 模块打包: Webpack 会收集所有模块,建立模块之间的依赖图,然后将这些模块和依赖关系打包成最终的输出文件。Webpack 会通过 代码分割(Code Splitting) 来决定将哪些模块打包成单独的文件。

Webpack 会从入口文件开始,递归地解析每个模块的依赖关系。所有模块通过 import 或 require 引入其他模块时,Webpack 会追踪这些依赖,并将它们组织成一个 依赖图(Dependency Graph)。

Webpack 不仅会解析和打包这些模块,还会根据配置的优化策略进行代码分割(Code Splitting)。这样可以将代码分成多个独立的文件,按需加载,优化页面的加载性能。

入口代码分割: Webpack 会根据入口文件,将不同的模块打包成独立的文件。比如,在多个页面之间共享一些代码时,Webpack 会将共享的代码提取成单独的文件。

动态导入(Dynamic Imports): 通过动态导入,Webpack 可以在代码运行时按需加载模块,减少初始加载的包体积。动态导入使用 import() 函数来实现。

在 Webpack 中,每个模块的内容都会被缓存,以便在后续的构建过程中加速构建速度。Webpack 会基于模块的 内容哈希(content hash) 来判断是否需要重新构建模块,这样能够避免不必要的重复构建,提升构建效率。

持久化缓存(Persistent Caching):从 Webpack 5 开始,Webpack 支持持久化缓存(通过硬盘缓存),这样即使是开发环境中的模块,也能在下一次构建时快速加载。

为什么要使用代码分割?

  1. 减少初次加载时间:如果所有代码都打包成一个文件,用户访问时必须等待整个文件加载完才能使用应用。通过代码分割,只有用户当前需要的部分才会被加载,从而加速页面的首次加载。
  2. 按需加载:通过动态导入,WebPack 可以根据用户操作或需求,加载页面所需的特定模块。这对于大型应用非常重要,可以避免加载不必要的资源。
  3. 缓存优化:当代码发生变化时,整个打包文件都会更新并重新下载。通过将不常变化的公共代码(如第三方库)提取为单独的文件,浏览器可以缓存这些文件,避免不必要的重新加载。
  4. 提高模块复用:通过将公共代码提取成独立的文件,多个页面或入口点可以复用这些模块,避免重复加载相同的内容。

7.模式(mode)

在 Webpack 中,mode 是一个非常重要的配置项,它用于告诉 Webpack 你希望构建过程的目标环境,以便 Webpack 根据该模式优化输出内容。mode 主要有三个可选值:development、production 和 none。每个模式都有不同的优化策略和行为,目的是根据不同的构建需求(开发还是生产环境)来调整 Webpack 的行为,从而提高构建性能和优化输出结果。

  1. development(开发模式)

    在开发模式下,Webpack 主要关注的是构建速度和开发过程中的调试体验,因此它会做以下优化:

    • Source Maps:Webpack 会生成源映射(source maps),这样你可以在浏览器中查看原始的源代码,而不仅仅是打包后的代码。源映射使得调试过程更加简便。
    • 不压缩代码:为了便于调试,Webpack 默认不会压缩 JavaScript 文件,以保留代码的可读性。
    • 更快的构建速度:Webpack 会做一些优化,使得构建过程更加快速,但牺牲了一些代码体积的优化。
    • 更详细的错误和警告信息:开发模式下,Webpack 会提供更详细的错误提示和警告信息,帮助开发者快速定位问题。
  2. production(生产模式)

    在生产模式下,Webpack 的主要目标是优化代码体积和性能,因此它会做以下优化:

    • 代码压缩:Webpack 会使用 TerserPlugin 来压缩 JavaScript 代码,从而减小文件体积。
      优化打包体积:Webpack 会启用一些优化策略来删除无用的代码(比如死代码、未使用的模块等),以减少最终打包文件的大小。
    • 去除开发相关的代码:在生产环境下,Webpack 会去除一些只对开发有用的功能,比如调试信息、警告和源映射等。
    • 自动启用 ModuleConcatenationPlugin:这个插件会将多个模块合并成一个函数调用,减少运行时的开销,提高执行效率。
    • 使用更精简的错误和警告输出:在生产环境下,Webpack 会将错误和警告信息压缩和精简,以提高构建效率和减少输出日志的冗余。
  3. none(无模式)

    none 模式基本上禁用 Webpack 的所有默认优化配置。在这种模式下,Webpack 不会自动启用任何优化,它会执行最基础的打包工作,适合一些特殊的需求或当你希望完全控制优化策略时使用。

    通常,如果你不希望 Webpack 根据模式自动进行优化,或者你想要在自己的配置中完全控制构建过程,那么你可以使用 none 模式。

8.开发服务器(Development Server)

Webpack 的开发服务器(webpack-dev-server)通过搭建一个本地服务器,提供热更新(Hot Module Replacement, HMR)、自动刷新(Auto Refresh)等功能,从而大大提升开发效率。

开发服务器的作用

  1. 实时预览和自动刷新:开发服务器可以实时查看文件变动,当源代码文件(如 JS、CSS)发生更改时,浏览器会自动刷新或更新页面,避免手动刷新,提升开发体验。

  2. 热模块替换(Hot Module Replacement, HMR):Webpack Dev Server 可以在不重新加载整个页面的情况下,仅替换更改的模块。这样,开发者在修改代码时,不仅可以看到修改结果,还能保留页面状态,比如表单数据或滚动位置等。

  3. 简化本地开发:它提供了一个本地开发环境,使得开发者能够像在生产环境中一样运行应用,只是它的构建速度更快,且具备开发过程中所需要的热更新、自动刷新等功能。

  4. 代理和跨域支持:在开发过程中,很多时候需要与后端 API 进行交互。Webpack Dev Server 支持代理功能,可以将开发服务器的请求转发到后台 API,解决跨域问题。

要在 Webpack 项目中使用开发服务器,需要先安装 webpack-dev-server,然后在 Webpack 配置文件中进行相关配置。在 webpack.config.js 文件中,添加 devServer 配置项,来启用开发服务器功能。

js 复制代码
module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  devServer: {
    contentBase: path.join(__dirname, 'dist'),  // 指定静态文件的根目录
    port: 9000,  // 指定开发服务器的端口号
    open: true,  // 启动后自动打开浏览器
    hot: true,   // 启用热模块替换
  },
};

在这个基本配置中,最重要的部分是 devServer 配置项。以下是常用的配置项和含义:

  • contentBase :指定开发服务器的静态文件目录,默认是 ./public。它告诉服务器从哪里提供静态资源。通常设置为 dist,即构建后的输出目录。

  • port :设置开发服务器的端口号,默认为 8080,你可以根据需要修改为其他端口。

  • open :设置为 true 时,开发服务器启动后会自动在浏览器中打开应用。默认是 false

  • hot:启用热模块替换(HMR)。当代码修改时,Webpack 只会替换修改的模块,而不会重新加载整个页面,从而保持应用的状态。

  • historyApiFallback :对于使用 HTML5 History API 的单页面应用(SPA),如果在浏览器中直接访问某个路径时返回 404 错误,可以通过此配置让开发服务器始终返回 index.html。例如,应用路由是基于 Hash 的时候,刷新页面可能会导致找不到资源。

    js 复制代码
    devServer: {
      historyApiFallback: true,
    }
  • proxy:设置代理,解决跨域问题。在开发时,可能需要与后端 API 进行交互。此时,可以通过配置代理将某些请求转发到指定的后端服务器。

    js 复制代码
    devServer: {
      proxy: {
        '/api': 'http://localhost:3000', // 将 /api 请求代理到后端的 3000 端口
      },
    }
  • inline :启用内联模式。在内联模式下,Webpack 会将脚本注入到页面中,通常用于调试。现在大多数情况下不再需要此配置,因为 devServer 默认启用内联模式。

  • watchContentBase:启用该选项后,Webpack 会监控静态资源文件(如 HTML、CSS 等)的变化,自动重新加载页面。

  • compress:启用 gzip 压缩,压缩服务器响应的文件。在开发环境中,可以减少网络传输的大小,提升性能。

    js 复制代码
    devServer: {
      compress: true,  // 启用 gzip 压缩
    }

热模块替换(HMR)

热模块替换(HMR)是 Webpack 的一个重要特性,能在应用运行时只替换已更改的模块,而不是重新加载整个页面。它不仅能加速开发过程,还能保持应用的状态(如用户输入的数据、滚动条位置等)。Webpack Dev Server 默认启用了 HMR。

要使用 HMR,除了启用 hot: true 配置外,还需要在应用中引入 HMR 相关的 API。例如,在 JavaScript 中使用 module.hot.accept() 处理模块的更新:

js 复制代码
if (module.hot) {
  module.hot.accept('./module.js', function() {
    console.log('Module updated!');
  });
}

production 模式下,HMR 不会默认启用,因为生产环境下,代码通常经过压缩和优化,不需要实时更新。HMR 主要用于开发环境。

启动开发服务器

webpack.config.js 配置好 devServer 后,可以通过命令启动开发服务器:

bash 复制代码
npx webpack serve

或者,如果你在 package.json 中配置了 scripts,可以直接使用 npm 或 yarn 启动开发服务器:

json 复制代码
{
  "scripts": {
    "start": "webpack serve --config webpack.config.js"
  }
}

然后,在浏览器中访问 http://localhost:9000 即可看到运行中的应用。

Webpack Dev Server 并不完全模拟生产环境,它主要是为开发环境提供快速的构建、实时预览和调试功能。它并不会自动进行生产环境的优化(如代码压缩、去除死代码等)。虽然它有一些功能可以模拟生产环境的行为(如代理、热更新等),但其主要目标是提高开发效率。

相关推荐
饮长安千年月2 小时前
Linksys WRT54G路由器溢出漏洞分析–运行环境修复
网络·物联网·学习·安全·机器学习
红花与香菇2____2 小时前
【学习笔记】Cadence电子设计全流程(二)原理图库的创建与设计(上)
笔记·嵌入式硬件·学习·pcb设计·cadence·pcb工艺
天宇&嘘月2 小时前
web第三次作业
前端·javascript·css
小王不会写code2 小时前
axios
前端·javascript·axios
发呆的薇薇°3 小时前
vue3 配置@根路径
前端·vue.js
luckyext3 小时前
HBuilderX中,VUE生成随机数字,vue调用随机数函数
前端·javascript·vue.js·微信小程序·小程序
小小码农(找工作版)3 小时前
JavaScript 前端面试 4(作用域链、this)
前端·javascript·面试
一天八小时4 小时前
Docker学习进阶
学习·docker·容器
前端没钱4 小时前
前端需要学习 Docker 吗?
前端·学习·docker
前端郭德纲4 小时前
前端自动化部署的极简方案
运维·前端·自动化