标签

Webpack

Webpack是一个模块打包工具。它的主要目的是为了在浏览器中使用而打包JavaScript文件,但它也能转换、打包或打包几乎任何资源或资产。Webpack会取模块及其依赖关系,并生成代表这些模块的静态资源。Webpack支持扩展性,并在网络架构和性能方面推广最佳实践。

Webpack
前端5月28日 03:28
Webpack 有哪些优化手段?Webpack 有哪些优化手段? 从构建速度和产物体积两个方向回答,面试中最常考察的优化点如下: ## 构建速度优化 ### 1. 持久化缓存 ```js // webpack.config.js module.exports = { cache: { type: 'filesystem', // Webpack 5 支持 buildDependencies: { config: [__filename] } } } ``` 首次构建后缓存写入 `node_modules/.cache/webpack`,二次构建可减少 60%-80% 时间。修改配置文件会自动失效。 ### 2. 缩小 Loader 处理范围 ```js module.exports = { module: { rules: [{ test: /\.js$/, include: path.resolve(__dirname, 'src'), // 只处理 src 目录 exclude: /node_modules/, use: 'babel-loader' }] } } ``` 不配置 `include` 时,Babel 会遍历 `node_modules` 中所有 JS 文件,构建时间翻倍。 ### 3. 多线程编译 - `thread-loader`:将耗时的 loader 放到 worker 池中并行执行 - `terser-webpack-plugin` 开启 `parallel: true`:并行压缩 JS - Webpack 5 原生支持 `parallel` 配置 ### 4. 优化模块解析 ```js module.exports = { resolve: { extensions: ['.ts', '.tsx', '.js'], // 按使用频率排列,减少尝试次数 alias: { '@': path.resolve(__dirname, 'src') }, // 别名减少路径查找 }, module: { noParse: /lodash|jquery/, // 跳过已打包库的解析 } } ``` `noParse` 告诉 Webpack 这些库没有 `import/require`,不需要解析依赖关系。 ### 5. 使用更快的编译器 - `swc-loader` 替代 `babel-loader`(Rust 实现,速度快 20 倍以上) - `esbuild-loader` 替代 `terser-webpack-plugin`(Go 实现) ### 6. DLL 预编译(Webpack 4 适用) ```js // webpack.dll.config.js new webpack.DllPlugin({ name: '[name]', path: path.join(__dirname, 'dll', '[name]-manifest.json') }) ``` 将 React、Vue 等不变库提前编译成 DLL,开发构建跳过。Webpack 5 的持久化缓存已基本替代此方案。 ## 产物体积优化 ### 1. 代码分割(Code Splitting) **splitChunks — 提取公共依赖:** ```js module.exports = { optimization: { splitChunks: { chunks: 'all', cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, name: 'vendors', chunks: 'all' } } } } } ``` 将 `node_modules` 中的库打包到独立 chunk,利用浏览器缓存。 **动态 import() — 按需加载:** ```js const Home = React.lazy(() => import('./Home')); const About = React.lazy(() => import('./About')); ``` 路由级懒加载,首屏只加载当前路由代码。 > splitChunks 是配置层面的自动提取,动态 import() 是代码层面的手动分割,两者配合使用。 ### 2. Tree Shaking ```js module.exports = { mode: 'production', // 自动开启 optimization: { usedExports: true, minimize: true, } } ``` **前提条件:** - 必须使用 ESM(`import/export`),不能用 CommonJS(`require`) - `babel-loader` 配置 `"modules": false` 防止转译为 CJS - `package.json` 标记 `"sideEffects": false` 告知安全移除未使用导出 **为什么需要 ESM?** ESM 的 `import/export` 是静态声明,编译时可确定依赖关系;CommonJS 的 `require` 是运行时调用,可以在 `if` 中使用,打包工具无法静态分析。 ### 3. Scope Hoisting(作用域提升) ```js module.exports = { optimization: { concatenateModules: true, // 生产模式默认开启 } } ``` 将模块合并到同一个闭包中,减少函数声明和闭包数量,体积可减小 5%-10%。 ### 4. 压缩 - JS:`terser-webpack-plugin`(Webpack 5 生产模式默认启用) - CSS:`css-minimizer-webpack-plugin` ```js const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); module.exports = { optimization: { minimizer: [ new CssMinimizerPlugin(), ], } } ``` ### 5. externals 排除大型库 ```js module.exports = { externals: { react: 'React', 'react-dom': 'ReactDOM' } } ``` 将 React、Lodash 等通过 CDN 引入,不打包进 bundle,显著减小产物体积。 ### 6. 图片与资源优化 - 小于 8KB 的图片转 base64 内联(Webpack 5 的 `asset/inline`) - 大图使用 `image-webpack-loader` 压缩或转 WebP ```js { test: /\.(png|jpg)$/, type: 'asset', parser: { dataUrlCondition: { maxSize: 8 * 1024 } // 8KB 以下内联 } } ``` ### 7. IgnorePlugin 排除无用模块 ```js new webpack.IgnorePlugin({ resourceRegExp: /^\.\/locale$/, contextRegExp: /moment$/ }) ``` 忽略 moment.js 的 locale 文件,体积减少约 200KB。 ## 构建分析工具 | 工具 | 用途 | |------|------| | `webpack-bundle-analyzer` | 可视化分析产物体积,找出大依赖 | | `speed-measure-webpack-plugin` | 统计各 loader/plugin 耗时 | | `webpack --profile --json > stats.json` | 导出构建统计数据 | ## Webpack 5 新特性补充 - **Module Federation**:跨应用共享模块,实现微前端,避免重复打包公共依赖 - **持久化缓存**:替代 DLL 方案,配置更简单 - **Asset Modules**:内置资源模块,替代 `url-loader`/`file-loader` - **更好的 Tree Shaking**:支持嵌套的 Tree Shaking 和内部模块的 Tree Shaking ## 追问 ### splitChunks 和动态 import 有什么区别? `splitChunks` 是 Webpack 配置层面的自动优化,根据策略提取公共依赖(如 `node_modules` 中的库),开发者无需手动干预。动态 `import()` 是代码层面的手动分割,由开发者决定哪些模块按需加载。两者互补:splitChunks 处理共享依赖,动态 import 处理路由级懒加载。 ### Tree-Shaking 为什么需要 ESM? ESModule 的 `import/export` 是静态声明,在编译阶段就能确定模块的依赖关系和哪些导出被使用。CommonJS 的 `require` 是运行时调用,可以写在 `if` 语句、循环或函数中,打包工具无法在编译时判断哪些代码会被执行,因此无法安全移除未使用的代码。此外,需确保 Babel 不将 ESM 转译为 CJS(配置 `"modules": false`),并在 `package.json` 中声明 `"sideEffects": false`。 ### webpack 的 cache 缓存了什么?为什么能加快构建? 缓存了每个模块的 loader 处理结果和编译产物。首次构建后写入磁盘(`node_modules/.cache/webpack`),二次构建时 Webpack 检查文件时间戳和内容 hash,只重新编译变化的模块及其依赖链上的模块,未变化的模块直接使用缓存。典型场景:修改一个组件文件,只重新编译该文件和引用它的文件,其余数千模块跳过,构建时间从 30s 降到 5s 以内。Webpack 5 的 `filesystem` 缓存相比 Webpack 4 的 `memory` 缓存,重启开发服务器后缓存不丢失。 ### Module Federation 解决了什么问题? 多个独立构建的应用之间共享模块,避免重复打包相同依赖。典型场景:微前端架构中,主应用和子应用都依赖 React,传统方案各自打包 React,用 Module Federation 可以在运行时共享一份 React,减少总体加载量。核心配置是 `exposes`(暴露模块)和 `remotes`(消费远程模块),实现应用间的模块级复用。
前端5月27日 01:10
webpack 的热更新原理是什么?详细聊聊热更新的流程HMR(Hot Module Replacement)的核心流程: 1. **webpack-dev-server 启动 WebSocket 服务**,和浏览器建立长连接 2. 文件变化 → webpack 重新编译 → 生成两个文件:`[hash].hot-update.json`(变更模块清单)和 `[hash].hot-update.js`(变更模块代码) 3. 通过 WebSocket 推送 hash 事件给浏览器 4. 浏览器用 AJAX 拉 `hot-update.json`,用 JSONP 拉 `hot-update.js` 5. HMR runtime 找到变更的模块,执行 `module.hot.accept` 注册的回调 6. 如果模块没有注册 accept,HMR runtime 向上冒泡,直到找到 accept 或刷新整个页面 Vue-loader、style-loader 等 loader 内部自动调用了 `module.hot.accept`,所以改 .vue 文件能无刷新更新。 ## 追问 ### 为什么需要两步拉取(json + js)? `hot-update.json` 先确认哪些模块变了,如果当前页面没有引用变更模块,就不需要拉 `hot-update.js`。另外 json 由 webpack-dev-server 直接提供(内存文件系统),js 则走 JSONP 来避免跨域问题。 ### React Fast Refresh 和普通 HMR 有什么区别? React Fast Refresh 在 HMR 基础上做了 React 组件的"签名"匹配——换掉的组件如果有相同的 hooks 调用和组件的导出签名,就保留下面的组件状态(state 不丢失)。普通 HMR 只是替换模块代码,状态会重置。 ### HMR 失效了怎么排查? - `module.hot.accept` 是否注册(检查对应 loader 是否配置正确) - 文件路径大小写不一致(macOS 不敏感但 webpack 敏感) - 配置文件中 `hot: true` 和 `HotModuleReplacementPlugin` 是否启用
前端2024年8月5日 12:48
Webpack 的详细工作流程Webpack是一个现代JavaScript应用程序的静态模块打包器,它主要的工作就是分析项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(如TypeScript等),并将其转换和打包为合适的格式供浏览器使用。 Webpack的工作流程主要分为以下几个阶段: ### 初始化 在Webpack启动后,它会从配置文件(默认是 `webpack.config.js`)中读取配置的参数,合并命令行传递过来的参数,形成最终的配置对象。 ### 编译(Compiling) Webpack开始编译整个项目。在这个阶段,Webpack会根据配置中的入口(Entry)开始递归解析所有依赖项。配置文件中的 `entry`属性用于定义入口文件,可能是一个或多个。 ### 构建(Building) 针对每个依赖项,Webpack会使用对应的loader去处理文件,例如使用 `babel-loader`来处理JavaScript文件,`css-loader`处理CSS文件,`file-loader`处理图片等资源。Loaders的定义让Webpack能够去处理非JavaScript文件(Webpack本身只理解JavaScript)。 ### 生成(Output) 经过加载和转换,Webpack会根据配置中的 `output`部分,把处理过的文件生成到文件系统中。通常是在项目的 `dist`目录下生成 `bundle.js`或者其他自定义名称的文件。 ### 优化(Optimizing) 在生成出来的文件中,Webpack可以进行代码压缩、分割代码以实现按需加载等优化操作。这通过配置 `plugins`来实现,比如 `UglifyJsPlugin`、`SplitChunksPlugin`等。 ### 输出(Emitting) 将所有的资源文件输出到指定目录下,此时,Webpack的工作就算是完成了。 ### 示例 例如,您有一个项目,其入口文件是 `src/index.js`。Webpack会解析这个文件,并解析出这个文件依赖的模块。假如 `index.js`中依赖了 `src/print.js`,Webpack会继续解析 `print.js`的依赖。 假设 `index.js`中还使用了ES6的语法和 `.scss`样式文件,那么在构建阶段,Webpack会使用 `babel-loader`将ES6代码转换为ES5代码,使用 `sass-loader`将SCSS文件转换为CSS文件,并且结合 `css-loader`和 `style-loader`将CSS代码注入到JavaScript中,这样就可以通过JavaScript将样式添加到DOM上。 在优化阶段,可能会有插件去检查代码,去除未引用的代码(dead code),压缩混淆输出的文件,以减少文件大小和提高加载速度。 最终,在输出阶段,Webpack会在 `dist`目录下输出 `bundle.js`,其中包含了所有的应用程序代码,以及所有的样式打包成的CSS代码。 这就是Webpack的一个基本工作流程。它的强大之处在于可扩展性,通过配置文件和插件系统,可以适应各种复杂的项目需求。
前端2024年6月24日 16:43
webpack 的 loader 的作用是什么?如何自定义一个loader?### webpack 的 loader 的作用是什么? webpack 的 loader 有着非常关键的作用。在 webpack 打包过程中,loader 负责处理源代码文件,并且将其转换成 webpack 能够处理的模块。由于 webpack 本身只理解 JavaScript,所以当我们需要在 JavaScript 代码中导入 CSS、图片或者其他非 JavaScript 文件时,就需要用到 loader。 例如,如果我们想在 JavaScript 中导入一个 CSS 文件,我们会使用 `style-loader` 和 `css-loader`。这两个 loader 分别做两件事情: 1. `css-loader` 会处理 `import` 和 `url()` 等,就像 JavaScript 模块一样处理 CSS 文件。 2. `style-loader` 会将计算后的 CSS 注入到页面的 `<style>` 标签中。 其他常用的 loader 还包括: - `babel-loader`:将 ES6+ 代码转换成兼容性更好的 JavaScript 代码。 - `file-loader`:处理文件导入,将文件输出到输出目录,并返回文件的 URL。 - `url-loader`:像 `file-loader` 但在文件大小低于指定的限制时会返回 Data URL。 - `sass-loader`:将 SASS/SCSS 文件编译成 CSS。 - 等等。 Loader 可以链式调用,每个 loader 将上一个处理的结果传递给下一个 loader,直到最后输出 webpack 能够处理的 JavaScript 模块。 ### 如何自定义一个 loader? 自定义一个 loader 需要遵循一定的步骤: 1. **创建一个 JavaScript 函数**: - 这个函数将接收源文件的内容作为参数。 - 函数内部,可以使用任何 JavaScript 代码来处理输入的内容。 - 函数最后需要返回处理后的内容,或者使用 `this.callback` 方法异步返回处理结果和可能的 map。 2. **遵守 loader 的规则**: - 接收源文件的内容作为参数。 - 不修改原始内容,返回一个新的字符串或者 Buffer。 - 在需要时,可以通过 `this.async` 返回一个异步函数。 - 使用 `this.cacheable` 标记 loader 的结果是否可缓存。 - 通过 `this.loaders` 访问配置中的其他 loaders。 3. **导出这个函数**: - 使用 `module.exports` 导出这个 loader 函数。 4. **在 webpack 配置中使用你的 loader**: - 在 `module.rules` 中添加一个规则来使用你的 loader。 例子: 假设我们要创建一个简单的 markdown loader,将 markdown 转换为 HTML。 ```javascript const marked = require('marked'); module.exports = function (markdown) { // 将 markdown 转换成 HTML const html = marked(markdown); // 返回 HTML 字符串 return `module.exports = ${JSON.stringify(html)}`; }; ``` 然后在 webpack 配置文件中使用这个 loader: ```javascript module.exports = { // ... module: { rules: [ { test: /\.md$/, use: [ { loader: 'html-loader' }, { loader: path.resolve('./path/to/your/custom-markdown-loader.js') } ] } ] } // ... }; ``` 注意,这里我们使用了 `html-loader` 来处理我们自定义 loader 输出的 HTML 字符串,这样做可以确保 HTML 字符串也遵循 webpack 的模块系统。 通过这种方式,开发者可以根据项目需求创建特定功能的 loader,以适应各种类型的文件处理场景。