Webpack
Webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。它允许开发者将项目中的资源(如 JavaScript、CSS、图片等)视为模块,通过分析和处理这些模块之间的依赖关系,将它们打包成一个或多个 bundle(捆绑包),这些 bundle 可以在浏览器中加载和执行。Webpack 提高了开发效率,简化了前端项目的构建流程,是现代前端开发不可或缺的工具之一。
Why Webpack?
Webpack通过提供模块化开发、依赖管理、资源优化、开发便捷性、易于集成、构建流程标准化和支持多种目标等特性,极大地提高了前端开发的效率和项目的可维护性。
使用Webpack有多个重要原因,这些原因主要涉及到前端开发中的效率、模块化、依赖管理、资源优化等方面。以下是使用Webpack的几个关键理由:
模块化开发:
Webpack支持ES6模块化语法以及CommonJS、AMD等模块化标准,使得开发者能够以模块化的方式组织代码。这有助于代码的可维护性、可重用性和可扩展性。通过将代码分割成多个模块,开发者可以更容易地理解和维护大型项目。依赖管理:
Webpack能够自动分析项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的扩展语言(如SCSS, TypeScript等),并将其转换和打包为合适的格式供浏览器使用。它还能智能地处理模块之间的依赖关系,确保它们按照正确的顺序加载和执行。资源优化:
Webpack提供了丰富的loader和plugin,可以对项目中的资源进行各种优化处理。例如,通过Babel loader可以将ES6代码转换为兼容当前浏览器的ES5代码;通过CSS loader和style loader可以将CSS文件注入到DOM中;通过UglifyJSPlugin可以对打包后的文件进行压缩,减少文件体积,加快加载速度。开发便捷性:
Webpack提供了开发服务器(webpack-dev-server)和热模块替换(HMR)等功能,使得开发者在开发过程中能够实时预览更改,而无需手动刷新浏览器。这大大提高了开发效率,减少了重复性工作。易于集成:
Webpack易于与其他工具和库集成,如React、Vue、Angular等前端框架,以及ESLint、Prettier等代码质量工具。这使得开发者可以在一个统一的构建系统中完成项目的构建、测试、部署等流程。构建流程标准化:
使用Webpack可以标准化项目的构建流程,无论是小型项目还是大型项目,都可以通过配置文件来定义项目的构建规则。这使得团队成员能够更容易地理解和参与项目的构建过程,提高了团队协作的效率。支持多种目标:
Webpack不仅可以用于构建浏览器端的应用,还可以通过适当的配置和插件,支持Node.js环境下的服务器端渲染(SSR)、Electron桌面应用等多种目标。这使得Webpack成为了一个非常灵活和强大的构建工具。
工作原理
Webpack 的工作原理可以概括为以下几个主要步骤:
初始化参数:Webpack 从配置文件(如
webpack.config.js
)和命令行参数中读取和合并配置,确定最终的打包参数。开始编译:使用上一步得到的参数初始化 Compiler 对象,并加载所有配置的插件。Compiler 对象执行 run 方法,开始执行编译过程。
确定入口:根据配置文件中的
entry
字段找出所有的入口文件。这些入口文件是 Webpack 打包的起点。编译模块:从入口文件开始,Webpack 会递归地分析每个模块的依赖,并使用配置的 Loader 对这些模块进行翻译和转换。Loader 是一种转换器,可以将各种资源(如 CSS、图片等)转换为 Webpack 能够处理的模块。
完成模块编译:在所有的模块都被 Loader 转换后,Webpack 会得到每个模块的最终内容和它们之间的依赖关系图。
输出资源:Webpack 根据入口和模块之间的依赖关系,将模块组合成一个个包含多个模块的 Chunk(代码块)。每个 Chunk 可以是一个独立的文件,也可以被组合成最终的 bundle。然后,Webpack 将这些 Chunk 转换成单独的文件,并加入到输出列表中。
输出完成:最后,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会识别出所有的依赖声明(如
import
和require
语句)。- 对于每个依赖,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!
优点:
- 简单易用:CommonJS的模块系统相对简单,易于理解和使用。
- 支持服务器端:Node.js等服务器端JavaScript环境广泛支持CommonJS。
- 丰富的生态系统:npm等包管理工具为CommonJS模块提供了丰富的第三方库和工具。
缺点:
- 同步加载:CommonJS模块是同步加载的,这在服务器端通常不是问题,但在浏览器端可能会导致性能问题。因此,在浏览器端使用CommonJS模块时,通常需要通过打包工具(如Webpack)进行转换和优化。
- 浏览器兼容性:浏览器默认不支持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
配置中,需要设置usedExports
为true
来标记无用代码。此外,还需要配合代码压缩插件(如terser-webpack-plugin
)来移除这些无用代码。
3、副作用(sideEffects)
- 在
webpack.config.js
中设置sideEffects
为true
时,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.additionalChunkAssets
或compilation.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提供的一种在开发过程中实现模块动态替换和更新的功能,而无需刷新整个页面。这种技术能够极大地提升开发效率,允许开发者在保持应用状态的同时,实时预览修改的效果。
一、热更新的基本原理
- 启动Webpack开发服务器:
- 当启动Webpack开发服务器(如webpack-dev-server)时,会创建一个WebSocket或轮询连接,用于与浏览器建立通信通道。
- 开发服务器通常将打包后的文件存储在内存中,以提高访问速度。
- 捕获模块变化:
- 使用Webpack的热更新插件(如HotModuleReplacementPlugin)来捕获模块的变化。
- 当源代码文件发生变化时,Webpack会重新编译这些文件,并生成新的模块。
- 生成更新补丁:
- Webpack将变化的模块及其依赖模块打包成一个或多个更新补丁(update chunk)。
- 这些补丁包含了模块的新代码和必要的元数据。
- 发送更新补丁:
- 通过之前建立的WebSocket或轮询连接,Webpack将更新补丁发送给浏览器。
- 浏览器接收并处理更新补丁:
- 浏览器接收到更新补丁后,使用热更新的运行时(Hot Module Replacement Runtime)来解析和处理这些补丁。
- 运行时根据更新补丁对模块进行动态替换和更新,同时尽可能地保持应用的状态。
二、热更新的优势
- 快速反馈:开发者可以在修改代码后立即看到修改效果,无需手动刷新页面。
- 保持应用状态:在模块替换过程中,应用的状态得以保留,开发者可以继续与应用进行交互。
- 提高开发效率:减少了调试和错误修复的时间,提升了开发效率。