Webpack详解

Webpack

Webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler) 。它允许开发者将项目中的资源(如 JavaScript、CSS、图片等)视为模块,通过分析和处理这些模块之间的依赖关系,将它们打包成一个或多个 bundle(捆绑包),这些 bundle 可以在浏览器中加载和执行。Webpack 提高了开发效率,简化了前端项目的构建流程,是现代前端开发不可或缺的工具之一。

Why Webpack?

Webpack通过提供模块化开发、依赖管理、资源优化、开发便捷性、易于集成、构建流程标准化和支持多种目标等特性,极大地提高了前端开发的效率和项目的可维护性。

使用Webpack有多个重要原因,这些原因主要涉及到前端开发中的效率、模块化、依赖管理、资源优化等方面。以下是使用Webpack的几个关键理由:

  1. 模块化开发

    Webpack支持ES6模块化语法以及CommonJS、AMD等模块化标准,使得开发者能够以模块化的方式组织代码。这有助于代码的可维护性、可重用性和可扩展性。通过将代码分割成多个模块,开发者可以更容易地理解和维护大型项目。

  2. 依赖管理

    Webpack能够自动分析项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的扩展语言(如SCSS, TypeScript等),并将其转换和打包为合适的格式供浏览器使用。它还能智能地处理模块之间的依赖关系,确保它们按照正确的顺序加载和执行。

  3. 资源优化

    Webpack提供了丰富的loader和plugin,可以对项目中的资源进行各种优化处理。例如,通过Babel loader可以将ES6代码转换为兼容当前浏览器的ES5代码;通过CSS loader和style loader可以将CSS文件注入到DOM中;通过UglifyJSPlugin可以对打包后的文件进行压缩,减少文件体积,加快加载速度。

  4. 开发便捷性

    Webpack提供了开发服务器(webpack-dev-server)和热模块替换(HMR)等功能,使得开发者在开发过程中能够实时预览更改,而无需手动刷新浏览器。这大大提高了开发效率,减少了重复性工作。

  5. 易于集成

    Webpack易于与其他工具和库集成,如React、Vue、Angular等前端框架,以及ESLint、Prettier等代码质量工具。这使得开发者可以在一个统一的构建系统中完成项目的构建、测试、部署等流程。

  6. 构建流程标准化

    使用Webpack可以标准化项目的构建流程,无论是小型项目还是大型项目,都可以通过配置文件来定义项目的构建规则。这使得团队成员能够更容易地理解和参与项目的构建过程,提高了团队协作的效率。

  7. 支持多种目标

    Webpack不仅可以用于构建浏览器端的应用,还可以通过适当的配置和插件,支持Node.js环境下的服务器端渲染(SSR)、Electron桌面应用等多种目标。这使得Webpack成为了一个非常灵活和强大的构建工具。

工作原理

Webpack 的工作原理可以概括为以下几个主要步骤:

  1. 初始化参数 :Webpack 从配置文件(如 webpack.config.js)和命令行参数中读取和合并配置,确定最终的打包参数。

  2. 开始编译:使用上一步得到的参数初始化 Compiler 对象,并加载所有配置的插件。Compiler 对象执行 run 方法,开始执行编译过程。

  3. 确定入口 :根据配置文件中的 entry 字段找出所有的入口文件。这些入口文件是 Webpack 打包的起点。

  4. 编译模块:从入口文件开始,Webpack 会递归地分析每个模块的依赖,并使用配置的 Loader 对这些模块进行翻译和转换。Loader 是一种转换器,可以将各种资源(如 CSS、图片等)转换为 Webpack 能够处理的模块。

  5. 完成模块编译:在所有的模块都被 Loader 转换后,Webpack 会得到每个模块的最终内容和它们之间的依赖关系图。

  6. 输出资源:Webpack 根据入口和模块之间的依赖关系,将模块组合成一个个包含多个模块的 Chunk(代码块)。每个 Chunk 可以是一个独立的文件,也可以被组合成最终的 bundle。然后,Webpack 将这些 Chunk 转换成单独的文件,并加入到输出列表中。

  7. 输出完成:最后,Webpack 根据配置确定输出的路径和文件名,将文件内容写入到文件系统中。

简单的说就是分析代码,找到"require"、"exports"、"define"等关键词,并替换成对应模块的引用。 在一个配置文件中,指明对某些文件进行编译、压缩、组合等任务。把你的项目当成一个整体,通过一个给定的主文件 (index.js),webpack将从这个文件开始找到你的项目的所有的依赖文件,使用loaders处理他们,最后打包为一个浏览器可以识别的js文件。

基本能力

处理依赖

方便引用第三方模块,让模块更容易复用、避免全局注入导致的冲突、避免重复加者加载不必要的模块载。

Webpack通过读取入口文件、解析文件并生成AST、遍历AST并处理依赖、构建依赖图、打包输出等步骤来处理项目中的依赖,并最终生成可供浏览器或Node.js环境运行的bundle文件。这一过程充分利用了AST的抽象能力,使得Webpack能够准确地理解和处理复杂的项目依赖关系。

1. 初始化与配置
  • Webpack从配置文件(如webpack.config.js)中读取配置信息,包括入口文件(entry)、输出配置(output)、模块规则(module rules)等。
  • 根据配置信息,Webpack初始化一个Compiler对象,这个对象将控制整个构建过程。
2. 读取入口文件
  • Webpack从配置的入口文件开始,读取这个文件的内容。
3. 解析入口文件并生成AST
  • 使用Webpack内置的解析器或配置的loader(如babel-loader)对入口文件进行解析。
  • 解析过程中,源码会经过词法分析(Lexical Analysis)和语法分析(Syntax Analysis)两个阶段,最终生成AST。
    • 词法分析:将源代码字符串转换为一系列标记(tokens)的集合。
    • 语法分析:使用这些标记来构建AST,AST是一个树状结构,表示了源代码的语法结构。
4. 遍历AST并处理依赖
  • Webpack使用遍历器(如Babel的@babel/traverse)来遍历AST。
  • 在遍历过程中,Webpack会识别出所有的依赖声明(如importrequire语句)。
  • 对于每个依赖,Webpack会递归地执行上述步骤(读取文件、解析文件、生成AST、遍历AST并处理依赖),直到所有依赖都被处理完毕。
5. 依赖图构建
  • 通过上述递归过程,Webpack会构建一个依赖图(Dependency Graph),这个图表示了项目中所有模块之间的依赖关系。
6. 打包输出
  • 在依赖图构建完成后,Webpack会根据配置中的输出配置,将项目中的所有模块打包成一个或多个bundle文件。
  • 这些bundle文件包含了项目的所有代码,并且已经通过loader进行了必要的转换和优化。
7. 插件介入
  • 在Webpack的构建过程中,还可以通过插件(Plugins)来介入和修改构建流程。
  • 插件可以在Webpack生命周期的不同阶段执行自定义的逻辑,如打包优化、资源管理等。
模块化

前端模块化-webpack打包工具-CSDNVue入门技能树

AMD、CMD、CommonJs的优缺点,区别?_amd cmd的优劣势-CSDN博客

CommonJS模块
//moduleA.js
// 导出变量  
module.exports.name = 'Alice';  
  
// 或者导出函数  
function sayHello() {  
  return 'Hello, CommonJS!';  
}  
module.exports.sayHello = sayHello;


//main.JS
// 导入moduleA模块  
const moduleA = require('./moduleA');  
  
console.log(moduleA.name); // 输出: Alice  
console.log(moduleA.sayHello()); // 输出: Hello, CommonJS!

优点

  1. 简单易用:CommonJS的模块系统相对简单,易于理解和使用。
  2. 支持服务器端:Node.js等服务器端JavaScript环境广泛支持CommonJS。
  3. 丰富的生态系统:npm等包管理工具为CommonJS模块提供了丰富的第三方库和工具。

缺点

  1. 同步加载 :CommonJS模块是同步加载的,这在服务器端通常不是问题,但在浏览器端可能会导致性能问题。因此,在浏览器端使用CommonJS模块时,通常需要通过打包工具(如Webpack)进行转换和优化
  2. 浏览器兼容性:浏览器默认不支持CommonJS模块系统,需要通过工具进行转换才能在浏览器中使用。
ES6模块
// 导出变量和函数  
export let name = 'Alice';  
export function sayHello() {  
  console.log('Hello, ES6 Modules!');  
}  
  
// 默认导出  
export default function createGreeting(name) {  
  return `Hello, ${name}!`;  
}
----------------------------------------------
// 导入具名导出的变量和函数  
import { name, sayHello } from './moduleA.js';  
  
console.log(name); // 输出: Alice  
sayHello(); // 输出: Hello, ES6 Modules!  
  
// 导入默认导出的函数,并重命名  
import createGreeting as greet from './moduleB.js';  
  
console.log(greet('Bob')); // 输出: Hello, Bob!
打包
  • 各种loader与插件:loader加载各种资源、babel把ES6+转化为ES5-,eslint可以检查编译时的各种错误。
//常见loader
1. css-loader & style-loader
{  
  test: /\.css$/,  
  use: ['style-loader', 'css-loader']  
}
2. less-loader & sass-loader
{  
  test: /\.s[ac]ss$/i,  
  use: ['style-loader', 'css-loader', 'sass-loader']  
}
3.babel-loader
{  
  test: /\.(js|jsx)$/,  
  exclude: /node_modules/,  
  use: {  
    loader: 'babel-loader',  
    options: {  
      presets: ['@babel/preset-env']  
    }  
  }  
}
4.file-loader & url-loader
{  
  test: /\.(png|jpe?g|gif)$/i,  
  use: [  
    {  
      loader: 'url-loader',  
      options: {  
        limit: 8192, // 小于8KB的图片会被base64处理  
        outputPath: 'images/' // 输出目录  
      }  
    }  
  ]  
}
5. ts-loader & awesome-typescript-loader
{  
  test: /\.tsx?$/,  
  use: 'ts-loader',  
  exclude: /node_modules/  
}
  • 合并代码:把各个分散的模块集中打包成大文件,减少HTTP的链接的请求次数,配合uglify.js可以减少、优化代码的体积
  • 分块(好渲染)

打包中常见核心概念

摇树

Tree-shaking 它的名字来源于通过摇晃(shake)JavaScript代码的抽象语法树(AST),是一种用于优化JavaScript代码的技术,主要用于移除未被使用的代码,使得最终生成的代码包含应用程序中实际使用的部分。

Webpack摇树VS Vite摇树

摇树失败原因

1、代码引入没用import(ES6)

使用了CommonJS模块化规范

  • CommonJS采用require()module.exports进行模块导入导出,这种方式是动态导入且同步加载的,无法在编译时确定所依赖的模块,因此Webpack在进行摇树时可能会忽略这些模块。

必须用 import 导入,导出用 esm 或者 commonjs 都行

2、代码没开启摇树

  • 在Webpack的optimization配置中,需要设置usedExportstrue来标记无用代码。此外,还需要配合代码压缩插件(如terser-webpack-plugin)来移除这些无用代码。

3、副作用(sideEffects)

  • webpack.config.js中设置sideEffectstrue时,Webpack会检查第三方包的package.json中的sideEffects字段。如果设置不当或Webpack无法准确判断代码中是否存在副作用,则可能导致摇树失败。
  • 代码存在副作用(修改全局变量、修改外部作用域的行为)等副作用会导致Webpack无法安全地移除未使用的代码,因为这些代码可能会对外部产生影响。

4、babel配置preset-env没写module:false参数码没用import引入

  • 使用babel-loader时,如果配置了@babel/preset-env且未禁用ESM到CommonJS的转换,可能会导致Webpack拿到的是以CommonJS组织的代码,从而无法进行摇树。

Chunk

webpack的本质是把多个js模块合并到一个js中,即一个入口得到一个输出js文件(bundle.js)。但是导致的问题是,如果这个bundle.js文件很大,那么浏览器请求的时候,导致请求时间很长,首屏长时间白屏。所以优化手段就是把bundle.js文件拆分成多个小的js文件,同时请求,首屏当然就更快渲染显示。所以入口文件,chunk文件,输出文件三者的关系从原来的一个入口文件对应一个chunk最后输出一个bundle文件改变为一个入口文件对应多个chunk最后输出多个bundle文件

获得Chunk方法

在Webpack中,一个"chunk"通常指的是构建过程中生成的一个或多个bundle(即打包后的文件)。这些chunks可以包含应用程序的代码、第三方库、懒加载的代码块等。了解如何获取或操作这些chunks对于进行代码分割、懒加载优化等场景非常有用。

1. 运行时获取chunks

在客户端(浏览器)中,Webpack提供了__webpack_require__.e函数来动态加载(即懒加载)chunks。但直接获取已加载或可用的chunks列表在运行时通常不是Webpack的直接功能,因为这取决于你的代码分割策略和懒加载逻辑。

然而,你可以通过一些间接的方式来获取关于chunks的信息,比如:

  • 通过Webpack的manifest文件:Webpack可以生成一个manifest文件,它包含了关于各个chunk的元数据(如文件名、包含的模块等)。虽然这不是直接在客户端运行时获取,但你可以在构建过程中解析这个文件来获取chunks的信息。

  • 使用Webpack的Stats对象 :在构建过程中,你可以通过Webpack的stats对象来获取关于构建结果的详细信息,包括chunks的信息。这通常用于构建后分析或优化。

2. 构建时获取chunks

在Webpack的构建过程中,你可以通过Webpack的API或插件来访问和修改chunks。这通常是通过编写Webpack插件来完成的,因为Webpack插件可以访问到Webpack的编译(compilation)和构建(compilation.hooks)过程。

例如,你可以编写一个Webpack插件,监听compilation.hooks.additionalChunkAssetscompilation.hooks.optimizeChunkAssets等钩子,以获取或修改chunks。

class MyPlugin {  
  apply(compiler) {  
    compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {  
      const chunks = compilation.chunks;  
      chunks.forEach(chunk => {  
        console.log(`Chunk name: ${chunk.name}`);  
        console.log(`Chunk files: ${chunk.files}`);  
        // 这里可以进一步处理chunks  
      });  
      callback();  
    });  
  }  
}  
  
module.exports = MyPlugin;

热更新

Webpack热更新(Hot Module Replacement,简称HMR)是Webpack提供的一种在开发过程中实现模块动态替换和更新的功能,而无需刷新整个页面。这种技术能够极大地提升开发效率,允许开发者在保持应用状态的同时,实时预览修改的效果。

一、热更新的基本原理
  1. 启动Webpack开发服务器
    • 当启动Webpack开发服务器(如webpack-dev-server)时,会创建一个WebSocket或轮询连接,用于与浏览器建立通信通道。
    • 开发服务器通常将打包后的文件存储在内存中,以提高访问速度。
  2. 捕获模块变化
    • 使用Webpack的热更新插件(如HotModuleReplacementPlugin)来捕获模块的变化。
    • 当源代码文件发生变化时,Webpack会重新编译这些文件,并生成新的模块。
  3. 生成更新补丁
    • Webpack将变化的模块及其依赖模块打包成一个或多个更新补丁(update chunk)。
    • 这些补丁包含了模块的新代码和必要的元数据。
  4. 发送更新补丁
    • 通过之前建立的WebSocket或轮询连接,Webpack将更新补丁发送给浏览器。
  5. 浏览器接收并处理更新补丁
    • 浏览器接收到更新补丁后,使用热更新的运行时(Hot Module Replacement Runtime)来解析和处理这些补丁。
    • 运行时根据更新补丁对模块进行动态替换和更新,同时尽可能地保持应用的状态。
二、热更新的优势
  • 快速反馈:开发者可以在修改代码后立即看到修改效果,无需手动刷新页面。
  • 保持应用状态:在模块替换过程中,应用的状态得以保留,开发者可以继续与应用进行交互。
  • 提高开发效率:减少了调试和错误修复的时间,提升了开发效率。
相关推荐
小曲曲32 分钟前
接口上传视频和oss直传视频到阿里云组件
javascript·阿里云·音视频
学不会•2 小时前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
EasyNTS3 小时前
H.264/H.265播放器EasyPlayer.js视频流媒体播放器关于websocket1006的异常断连
javascript·h.265·h.264
活宝小娜4 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点4 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow4 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o4 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
刚刚好ā5 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
沉默璇年6 小时前
react中useMemo的使用场景
前端·react.js·前端框架
yqcoder6 小时前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript