Skip to content
Updated:

大宝典-工程化

Table of contents

Open Table of contents

194. Webpack 作用

Webpack 是一个现代的前端模块打包工具,它用于构建和优化 Web 应用程序的前端资源,包括 JavaScript、Css、图片、字体等。Webpack 的主要目标是将项目的所有依赖项(模块、资源文件)打包到一个或多个最终的静态文件中,以便在浏览器中加载。改善前端开发的工作流程,提高代码的可维护性和性能解决了模块化、资源管理、性能优化和自动化等多个关键问题。

195. Webpack 构建流程

  1. 读取配置文件:Webpack 首先会读取项目中的配置文件,通常是 webpack.config.js,该配置文件包含了构建过程中的各种设置,如入口文件、输出目录、加载器 (Loaders)、插件 (Plugins) 等。

  2. 解析入口文件:Webpack 会根据配置文件中定义的入口点 (entry points) 来解析应用程序的依赖关系。入口文件通常是应用程序的主要 javaScript 文件,但也可以有多个入口点。

  3. 依赖解析:Webpack 分析入口文件和其依赖的模块,构建一个依赖关系图,以确定哪些模块依赖于其他模块,以及它们之间的依赖关系。

  4. Loader 处理:Webpack 使用加载器来处理不同类型的资源文件,如 CSS、图片、字体等。加载器允许开发人员在构建过程中转换这些资源文件,以便将它们整合到最终的输出文件中。

  5. Plugin 处理:Webpack 提供了插件系统,插件用于执行各种任务,如代码压缩、资源优化、HTML 生成、热模块替换 (HMR) 等。插件可以根据需要自定义 Webpack 的构建过程。

  6. 生成输出文件:Webpack 根据入口文件和依赖关系图生成一个或多个输出文件。这些输出文件包括 javaScript 文件、CSS 文件、图片、字体等资源文件。

  7. 优化和压缩:Webpack 可以进行各种优化,包括代码压缩、Tree Shaking、懒加载等以减小包的大小并提高性能。

  8. 生成 Source Maps:Webpack 可以生成 Source Maps,以便在开发中进行调试。Source Maps 是一种映射文件,将最终输出文件映射回原始源代码。

  9. 输出到指定目录: 最终的构建结果被输出到配置文件中指定的目录中,通常是一个名为”dist”的目录。输出文件的命名和目录结构也可以根据配置进行自定义。

  10. 完成构建过程:Webpack 构建过程完成后,它会生成构建报告,包括构建成功或失败的信息,输出文件的大小等统计信息。

196. Webpack 热更新原理

  1. 监控文件变化:Webpack 的开发服务器会监控项目中所有的块文件,包括:JS 文件、CSS 文件、模板文件等。
  2. 模块热替换: 当你在代码中做出更改并保存时,Webpack 检测到文件变化,会首先通过热替换插件 (Hot Module Replacement Plugin) 生成新的模块代码。
  3. 构建更新的模块: 生成的新模块代码会被构建成一个独立的文件或数据块,
  4. 通知客户端:Webpack 开发服务器会将更新的模块代码的信息发送到浏览器。
  5. 浏览器端处理: 浏览器接收到更新的模块信息后,会在不刷新页面的情况下通过热替换运行时 (Hot Module Replacement Runtime) 替换相应的模块。
  6. 应用程序状态保持: 热更新还可以保持应用程序的状态。当修改代码不会丢失已有的数据、用户登录状态等。
  7. 回调处理: 允许在模块更新时执行自定义的回调函数,可以处理特定的逻辑,以确保模块更新后的正确性。

197. Webpack 常用 Loader

198. Webpack 常用 Plugin

199. Loader 和 Plugin 的区别

在 Webpack 中,Loader 和 Plugin 是两个重要的概念,它们用于不同的目的并在 Webpack 构建过程中发挥不同的作用。

总结

简单来说,Loader 处理文件转换,Plugin 处理构建过程中的各种任务。两者结合使用,使 Webpack 具有强大的灵活性和扩展性。

200. 写一个 Loader

编写一个自定义的 Webpack Loader 需要实现一个 Node.js 模块,该模块导出一个函数。这是一个基本的 Loader 示例,该 Loader 将文件内容转换为大写字母。

  1. 创建 Loader 文件

    在项目根目录下创建一个名为 uppercase-loader.js 的文件:

    // uppercase-loader.js
    
    module.exports = function (source) {
      // 将源文件内容转换为大写
      const result = source.toUpperCase();
      return result;
    };
  2. 配置 Webpack

    在项目根目录下创建一个 webpack.config.js 文件:

    // webpack.config.js
    
    const path = require("path");
    
    module.exports = {
      mode: "development",
      entry: "./src/index.js",
      output: {
        path: path.resolve(__dirname, "dist"),
        filename: "bundle.js",
      },
      module: {
        rules: [
          {
            test: /\.txt$/,
            use: path.resolve(__dirname, "uppercase-loader.js"),
          },
        ],
      },
    };

201. 写一个 Plugin

编写一个自定义的 Webpack Plugin 需要创建一个 JavaScript 类,并定义一个 apply 方法,该方法在 Webpack 构建过程中会被调用。

// advanced-plugin.js

class AdvancedPlugin {
  apply(compiler) {
    // 监听编译开始的钩子
    compiler.hooks.compile.tap("AdvancedPlugin", () => {
      console.log("编译开始。..");
    });

    // 监听编译完成的钩子
    compiler.hooks.done.tap("AdvancedPlugin", stats => {
      console.log("编译完成!");
    });
  }
}

module.exports = AdvancedPlugin;

webpack.config.js 中使用 AdvancedPlugin

const AdvancedPlugin = require("./advanced-plugin");

module.exports = {
  // ... 其他配置
  plugins: [new AdvancedPlugin()],
};

202. Webpack 构建速度提升

提升 Webpack 构建速度可以从以下几个方面入手:

  1. 优化 Loader 配置

    • 缓存:使用 cache-loaderbabel-loadercacheDirectory 选项来缓存编译结果。
      {
        loader: 'babel-loader',
        options: {
          cacheDirectory: true,
        },
      }
    • 限制作用范围:通过 includeexclude 选项限制 Loader 的作用范围。
      {
        test: /\.js$/,
        include: path.resolve(__dirname, 'src'),
        use: 'babel-loader',
      }
  2. 分离开发和生产环境配置

    使用 webpack-merge 将开发和生产环境的配置文件分离,确保只有必要的插件在相应环境下被加载。

  3. 减少解析时间

    • 模块别名:使用 resolve.alias 配置来缩短模块路径,减少模块查找时间。
      resolve: {
        alias: {
          '@': path.resolve(__dirname, 'src'),
        },
      }
    • 文件扩展名:明确指定要解析的文件扩展名,减少文件解析次数。
      resolve: {
        extensions: ['.js', '.jsx', '.json'],
      }
  4. DLLPlugin

    使用 DllPlugin 将不常变动的依赖单独打包,可以显著提高构建速度。

    const webpack = require("webpack");
    
    module.exports = {
      // ...
      plugins: [
        new webpack.DllPlugin({
          name: "[name]",
          path: path.resolve(__dirname, "dist/[name]-manifest.json"),
        }),
      ],
    };
  5. HappyPack

    使用 HappyPack 将任务分解到多个子进程中并行处理,减少总编译时间。

    const HappyPack = require("happypack");
    
    module.exports = {
      // ...
      module: {
        rules: [
          {
            test: /\.js$/,
            use: "happypack/loader?id=js",
          },
        ],
      },
      plugins: [
        new HappyPack({
          id: "js",
          loaders: ["babel-loader"],
        }),
      ],
    };
  6. Thread-loader

    利用 thread-loader 开启多进程编译。

    {
      test: /\.js$/,
      use: [
        'thread-loader',
        'babel-loader',
      ],
    }
  7. 优化插件

    • terser-webpack-plugin:在生产环境下使用更快的压缩插件。

      const TerserPlugin = require("terser-webpack-plugin");
      
      module.exports = {
        optimization: {
          minimize: true,
          minimizer: [new TerserPlugin()],
        },
      };
    • webpack-parallel-uglify-plugin:并行压缩 JS 文件。

      const ParallelUglifyPlugin = require("webpack-parallel-uglify-plugin");
      
      module.exports = {
        plugins: [
          new ParallelUglifyPlugin({
            uglifyJS: {
              output: {
                comments: false,
              },
              compress: {
                warnings: false,
              },
            },
          }),
        ],
      };
  8. 持久化缓存

    Webpack 5 引入了持久化缓存,可以显著减少重复构建时间。

    module.exports = {
      cache: {
        type: "filesystem",
      },
    };
  9. 缩小编译范围

    • Tree Shaking:移除无用代码。
      module.exports = {
        optimization: {
          usedExports: true,
        },
      };
  10. 其他工具

    • speed-measure-webpack-plugin:衡量构建时间,找出瓶颈。

      const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
      const smp = new SpeedMeasurePlugin();
      
      module.exports = smp.wrap({
        // webpack config
      });
    • webpack-bundle-analyzer:分析打包后的文件,优化体积。

      const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
      
      module.exports = {
        plugins: [new BundleAnalyzerPlugin()],
      };

203. Webpack 神奇注释

Webpack 的“神奇注释”(Magic Comments)是一种内联注释,它们可以用来提供额外的构建指令,从而优化打包过程和改善开发体验。

  1. 动态导入 (Dynamic Imports)

    动态导入允许你按需加载模块,可以用 import() 函数结合神奇注释实现代码拆分和懒加载。

    • webpackChunkName 指定拆分后生成的 chunk 的名称。

      import(/* webpackChunkName: "my-chunk-name" */ "./module");
    • webpackMode 指定导入模式,可以是 lazylazy-onceeager

      import(/* webpackMode: "lazy" */ "./module"); // 默认模式
      import(/* webpackMode: "eager" */ "./module"); // 立即加载
    • webpackPrefetch 指示浏览器在空闲时预取资源,优化性能。

      import(/* webpackPrefetch: true */ "./module");
    • webpackPreload 指示浏览器立即加载资源,优先级高于 webpackPrefetch

      import(/* webpackPreload: true */ "./module");
    • webpackInclude 和 webpackExclude 用于正则表达式匹配,包含或排除特定文件。

      import(
        /* webpackInclude: /\.js$/ */
        /* webpackExclude: /\.test\.js$/ */
        "./module"
      );
  2. 注释中的魔法

    在注释中添加魔法注释,可以在代码分割和异步加载时提供更多的控制。

    • webpackIgnore 忽略特定的动态导入。

      import(/* webpackIgnore: true */ "./module");

下面是一个使用各种神奇注释的示例:

// 使用 webpackChunkName 指定 chunk 名称
import(/* webpackChunkName: "login" */ "./login").then(module => {
  const login = module.default;
  login();
});

// 使用 webpackMode 设置懒加载模式
import(/* webpackMode: "eager" */ "./dashboard").then(module => {
  const dashboard = module.default;
  dashboard();
});

// 使用 webpackPrefetch 指示浏览器预取资源
import(/* webpackPrefetch: true */ "./analytics").then(module => {
  const analytics = module.default;
  analytics();
});

// 使用 webpackPreload 指示浏览器立即加载资源
import(/* webpackPreload: true */ "./performance").then(module => {
  const performance = module.default;
  performance();
});

// 使用 webpackInclude 和 webpackExclude 进行正则匹配
import(
  /* webpackInclude: /\.js$/ */
  /* webpackExclude: /\.test\.js$/ */
  "./components"
).then(module => {
  const components = module.default;
  components.init();
});

// 使用 webpackIgnore 忽略特定的动态导入
import(/* webpackIgnore: true */ "./config");

204. Webpack 分包案例

目的:尽量按改动频率来区分,利用浏览器缓存

  1. vendor: 第三方 lib 库,基本不会改动,除非依赖版本升级
  2. common: 业务组件代码的公共部分提取出来,改动较少
  3. entry: 不同页面的 entry 里业务组件代码的差异部分,会经常改动

205. Webpack vs Vite

Webpack: 一个打包工具(对标 Rollup), 静态构建,在项目工程化,依赖,打包,构建等过程发挥作用

Vite: 一个更上层的工具链方案(对标 Webpack + 针对 web 的常用配置 + webpack-dev-server)。旨在提供快速的开发体验,使用 ES modules 和现代浏览器特性来实现即时开发,不需要预构建或编译

206. Babel 的原理

js 的编译工具,将新版本的 js(ES6+) 转换为向后兼容的 js 代码,以便在旧版 js 引擎上运行

工作原理如下:

  1. 解析(parsing): 将输入的 js 代码解析成抽奖语法树(AST,将代码分解成语法树节点,以便后续的分析和转换)

  2. 转换(Transformation): 在这一步,Babel 会根据配置的插件和预设,对 AST 进行各种转换操作。这些转换可能包括将现代的 ES6+ 语法转换为 ES5 语法,移除类型注解(如 Flow 或 TypeScript),或添加 polyfill 以支持新的标准库功能。例如,@babel/plugin-transform-arrow-functions 插件会将箭头函数转换为普通函数

  3. 生成(Generation): 转换后的 AST 会被重新生成为 JavaScript 代码。在生成过程中,Babel 会输出与转换后的 AST 对应的代码字符串。这个新生成的代码是向后兼容的,可以在较旧的 JavaScript 环境中运行

207. 模块化和组件化的区别

模块化

  1. 重点:主要关注代码的组织和封装,将代码分割成小的独立单元(模块),每个模块通常负责特定功能或任务
  2. 特点:通过导入和导出语法来定义模块之间的依赖关系;

组件化

  1. 重点:主要关注构建用户界面和交互。将用户界面的不同部分拆分成可重用的组件,每个组件包含特定的 UI 元素和交互逻辑
  2. 特点:通常使用组件库或框架来创建组合和渲染可重用的 UI 组件

208. CommonJS vs ESM

CommonJS 通常用于服务端(Node.js),在浏览器环境需要使用工具转译或打包

ESM(ECMAScript Modules)模块是浏览器原生支持的,可以直接在浏览器中使用

  1. 加载方式

    • CommonJS: 同步加载,模块在运行时(runtime)加载,按需加载
    • ESM: 可异步加载 <script type='module' />
  2. 执行时机

    • CommonJS: 在第一次 require 时执行
    • ESM: 加载和解析时执行
  3. 依赖关系

    • CommonJS: 动态的,意味着模块可以在运行时根据条件加载不同的依赖
    • ESM: 静态的,解析时加载,代码执行前就被加载,依赖关系在模块加载前就确定
  4. 导出方式

    • CommonJS: requiremodule.export,可到处任务类型的值,包含函数,对象,类等
    • ESM: exportimport关键字。导出时要明确指定导出的变量,函数或类,导入也需要明确指定
  5. 全局共享

    • **CommonJS: **模块有自己的作用域,不会污染全局作用域
    • **ESM: **默认是严格模式(strict mode),变量不会污染全局作用域,模块内部的变量不会被提升
  6. 静态分析

    • **CommonJS: **模块的依赖关系无法在编译时静态分析,这对一些工具的性能和优化产生了挑战
    • **ESM: **模块的依赖关系可以在编译时进行静态分析,这有助于提高性能,比如 tree-shaking

在 Node.js 中使用 ESM,可以将文件扩展名改为 .mjs,在 package.json 文件中添加 “type”: “module”。

209. SSR vs CSR

服务端渲染 (SSR)

服务端渲染是指在服务器上生成完整的 HTML 页面,然后将其发送到客户端浏览器进行展示。传统的 Web 开发多采用这种方式。

  1. 特点:

    1. 初始加载快:由于服务器直接返回完整的 HTML,客户端在接收到页面时无需额外的 JavaScript 处理,因此初始加载时间较短。
    2. SEO 友好:搜索引擎爬虫能够直接读取和索引服务器生成的 HTML 内容,有利于搜索引擎优化。
    3. 更少的客户端负担:服务器完成了大部分渲染工作,客户端只需渲染已经生成的 HTML。
  2. 优缺点:

    • 优点

      • 初始加载速度快。
      • 对 SEO 友好。
      • 适合内容驱动的网站。
    • 缺点

      • 服务器压力大,需要更多的计算资源。
      • 动态更新交互较慢,因为每次都需要重新请求和渲染页面。

客户端渲染 (CSR)

客户端渲染是指在客户端(通常是浏览器)上使用 JavaScript 动态生成和更新页面内容。最常见的例子是单页应用程序 (SPA)。

  1. 特点:

    1. 初始加载慢:客户端需要先下载 HTML 框架和大量的 JavaScript 文件,然后执行这些脚本以生成内容,因此初始加载时间较长。
    2. 更好的用户体验:一旦加载完成,页面之间的导航和更新速度更快,因为不需要每次都从服务器请求完整的页面。
    3. 动态交互强:适合复杂的动态交互场景,可以实现更丰富的用户体验。
  2. 优缺点:

    • 优点

      • 更好的用户体验,页面交互更流畅。
      • 适合复杂的单页应用程序。
      • 服务器负担较轻,减少了服务器的计算压力。
    • 缺点

      • 初始加载时间较长。
      • SEO 不友好,搜索引擎爬虫可能无法抓取动态生成的内容。
      • 对低端设备和慢速网络不友好。

总结

SSR vs CSR

特点SSRCSR
初始加载时间较快较慢
SEO 友好性
用户体验初始加载快,但后续导航较慢初始加载慢,但后续导航和交互更流畅
服务器负担
适用场景内容驱动的网站,如博客和新闻网站交互丰富的单页应用程序,如社交媒体平台

现代 Web 开发中,许多应用会结合 SSR 和 CSR 的优点,采用混合渲染的方式。往往是首屏 SSR,保证首页渲染速度,次屏 CSR,保证用户交互体验。例如,Next.js 是一个 React 框架,它支持页面级别的 SSR 和 CSR,允许开发者根据具体需求选择合适的渲染方式。

210. SPA vs MPA vs SSG

单页应用(SPA)

单页应用(Single Page Application, SPA)是一种 Web 应用架构,整个应用只有一个 HTML 页面,通过动态加载和替换页面内容来实现不同的视图和交互。

  1. 特点

    1. 单一页面结构:所有的页面内容都在一个 HTML 文件中,通过 JavaScript 动态更新页面。
    2. 客户端渲染:大部分渲染工作由客户端(浏览器)完成,使用框架如 React、Vue、Angular 等。
    3. 快速导航:页面切换和内容更新不需要重新加载整个页面,用户体验流畅,路由跳转是基于特定的实现(如 react-router 等)而非原生浏览器的文档跳转
    4. 状态管理:通常需要管理复杂的前端状态,可以使用 Redux、Vuex 等状态管理库。
  2. 优缺点

    • 优点

      • 流畅的用户体验,页面切换速度快。
      • 减少服务器请求次数,降低服务器压力。
      • 更适合交互性强的应用,如社交媒体、单页仪表盘等。
    • 缺点

      • 首次加载时间较长,需要下载大量的 JavaScript 文件。
      • SEO 不友好,需要额外的配置和工具(如服务器端渲染或预渲染)来改善。
      • 对低端设备和慢速网络不友好。

多页应用(MPA)

多页应用(Multi Page Application, MPA)是一种传统的 Web 应用架构,每个页面都有一个独立的 HTML 文件,每次导航时都会请求新的 HTML 页面。

  1. 特点

    1. 多页面结构:每个页面都是独立的 HTML 文件,通过服务器请求加载新页面。
    2. 服务端渲染:大部分渲染工作由服务器完成,生成完整的 HTML 页面发送给客户端。
    3. 独立页面加载:每次页面切换都会请求新的 HTML 页面,重新加载页面内容。不同页面路由切换有原生浏览器文档跳转。
    4. 简单的状态管理:每个页面独立,不需要复杂的前端状态管理。
  2. 优缺点

    • 优点

      • 初次加载速度快,每个页面可以单独优化和缓存。
      • 对 SEO 友好,搜索引擎能够轻松抓取和索引页面内容。
      • 适合内容驱动的网站,如博客、新闻网站、企业官网等。
    • 缺点

      • 页面切换时需要重新加载整个页面,用户体验不如 SPA 流畅。
      • 服务器请求次数多,可能增加服务器压力。
      • 前后端代码分离度较低,可能需要更多的开发和维护工作。

静态网站生成(SSG)

静态网站生成(Static Site Generation, SSG)是一种 Web 应用架构,在构建时生成静态 HTML 页面,用户请求时直接提供这些预生成的页面。

  1. 特点

    1. 构建时生成静态文件:在构建阶段将所有页面生成静态 HTML 文件,部署到静态服务器上。
    2. 快速加载:由于页面是静态的,加载速度非常快,不需要额外的服务器处理。
    3. SEO 友好:静态 HTML 文件天然对搜索引擎友好,容易被爬虫抓取和索引。
    4. 适合内容更新频率低的网站:适用于内容较为固定,不需要频繁更新的网站。
  2. 优缺点

    • 优点

      • 加载速度快,性能好。
      • 对 SEO 友好,搜索引擎爬虫容易抓取内容。
      • 维护成本低,部署简单。
    • 缺点

      • 不适合频繁更新的内容,构建过程较慢。
      • 动态功能和复杂交互实现较困难,需要依赖客户端渲染或 API 调用。

总结

特点SPAMPASSG
页面结构单一页面,动态内容多页面,静态内容预生成静态页面
渲染方式客户端渲染服务端渲染构建时生成静态文件
页面导航快速,流畅较慢,需要重新加载页面快速,因为是静态文件
初次加载时间较长较短较短
SEO 友好性较差,需要额外配置较好,天然支持非常好
适用场景交互性强的应用,如社交媒体、单页仪表盘等内容驱动的网站,如博客、新闻网站等内容固定的网站,如博客、文档等
状态管理复杂,需要使用状态管理库简单,每个页面独立简单,页面内容固定