前端工程化构建相关概念整理(入门级解释)

本文会很少涉及具体构建工具的配置,主要辨析工程化构建的相关概念以及简单介绍常用的构建工具。

为什么需要工程化构建?

首先我们必须知道浏览器只能读懂原生html,js和css语言,浏览器无法直接读懂前端框架,例如Vue、React、Angular,还有其他样式语言,例如less,sass。因此我们需要构建工具将源码打包成浏览器能够识别的最终文件。

构建环境

我们有两种场景需要将源码进行构建:

  1. 开发环境,用于本地开发调试,一般构建工具会启动本地服务,供开发者在修改代码后,及时看到页面更新。
  2. 生产环境,将源码打包压缩混淆,最终形成部署在服务器上的源文件。

开发环境和生产环境的构建目的不同。

  • 开发环境,应该以便于开发者调试为目的

    • 期望本地服务的启动速度足够快
    • 当开发者修改代码,期望在页面上实时热更新(HMR)
    • 报错时,能够变与调试
  • 生产环境,有更好的客户体验

    • 优化缓存
    • 更小的生产包
    • 代码混淆与压缩

生产环境的构建方式一般都是bundle模式的(snowpack生产环境构建也是unbundle的),而开发环境的构建方式分为两派:bundle 和 unbundle

模块化支持

我猜你肯定会想,那么bundle是什么?别急在了解unbundle之前,我们先辨析标题中的这几个概念,首先我们明确CJS、AMD、UMD,ESM都是js的模块化规范。

起初,js语言并没有导入导出的概念,所有变量和函数都在全局定义,这会导致全局环境被污染,也不利于维护(想想就很混乱,你声明了var name;可能在其他开发者的代码里被使用,你访问到的变量name的值可能不是你预期的。)为了使js支持模块化,一下模块化方法诞生

CJS

CommonJS的缩写,CJS的代码看起来如下:

//importing 
const doSomething = require('./doSomething.js'); 

//exporting
module.exports = function doSomething(n) {
  // do something
}
  • 通过module.export导出模块
  • 通过require(‘xxx’)引入模块

特点:

  • nodejs使用CJS规范,多用于后端开发
  • CJS导入模块是同步的
  • CJS导入模块采用的是值拷贝。
  • CJS不能直接被浏览器解析,需要先被构建打包

AMD

AMD, 即Asynchronous Module Definition, 异步模块定义,顾名思义,它采用异步模式加载模块。代码示例:

// 长这样
define(['dep1', 'dep2'], function (dep1, dep2) {
    //Define the module value by returning a value.
    return function () {};
});

// 或者这样
// "simplified CommonJS wrapping" https://requirejs.org/docs/whyamd.html
define(function (require) {
    var dep1 = require('dep1'),
        dep2 = require('dep2');
    return function () {};
});
  • 异步加载模块(顾名思义),回调函数中写依赖于这个模块的语句。
  • 适用于前端
  • 缺点是它的代码看起来不是很直观

UMD

Universal Module Definition,通用模块定义模式,代码看起来如下:

(function (root, factory) {
    if (typeof define === "function" && define.amd) {
        define(["jquery", "underscore"], factory);
    } else if (typeof exports === "object") {
        module.exports = factory(require("jquery"), require("underscore"));
    } else {
        root.Requester = factory(root.$, root._);
    }
}(this, function ($, _) {
    // this is where I defined my module implementation

    var Requester = { // ... };

    return Requester;
}));

特点:

  • 既适用于前端,也适用于后端
  • UMD更像是配置多个模块系统的模式,像Rollup/Webpack这样的构建工具会使用UMD当作后备模块

ESM

ES Modules, ES推出的模块化规范。

import {foo, bar} from './myLib';
import React from 'react';
...

export default function() {
  // your Function
};
export const function1() {...};
export const function2() {...};
  • 现在多数浏览器能够直接解析ESM规范的语法

  • 兼备CJS的直观性与AMD的异步性

  • 对于Tree-shaking有较好的支持,CJS并不能较好的支持。(Tree-shaking简言之,构建后对于模块依赖图中没用到的模块进行剪枝(去掉))

  • HTML可引入ESM规范的文件,只需要type=“module”

    <script type="module">
      import {func1} from 'my-lib';
    
      func1();
    </script>
    

bundle和unbundle是什么?

一个例子理解bundle模式构建过程

这里引用【webpack 中,module,chunk 和 bundle 的区别是什么?】的例子:

假设我们的项目结构是这样的:

src/
├── index.css
├── index.html # 这个是 HTML 模板代码
├── index.js  # 入口文件1
├── common.js
└── utils.js  #入口文件2

除了html文件以外,各文件的依赖关系如下:

  1. index.js中依赖了index.css和common.js,untils.js文件独立。

  2. 构建配置如下(webpack配置)

    {
        entry: {
            index: "../src/index.js",
            utils: '../src/utils.js',
        },
        output: {
            filename: "[name].bundle.js", // 输出 index.js 和 utils.js
        },
        module: {
            rules: [
                {
                    test: /\.css$/,
                    use: [
                        MiniCssExtractPlugin.loader, // 创建一个 link 标签
                        'css-loader', // css-loader 负责解析 CSS 代码, 处理 CSS 中的依赖
                    ],
                },
            ]
        }
        plugins: [
            // 用 MiniCssExtractPlugin 抽离出 css 文件,以 link 标签的形式引入样式文件
            new MiniCssExtractPlugin({
                filename: 'index.bundle.css' // 输出的 css 文件名为 index.css
            }),
        ]
    }
    
    
    • 入口文件有两个: index.js和utils.js
    • plugins配置中,将css文件抽离为单独的css文件

在bundle模式下构建,构建工具以入口文件为起点,分析各个源文件之间的依赖关系,形成一个模块依赖图,具有依赖关系的文件将放在一个chunk里面,然后根据一定的策略打包后生成bundle。

具体一些,上面的例子的构建过程如下:
在这里插入图片描述

  • index.css, common.js, index.js,这3个文件具有依赖关系,合并为一个chunk;utils.js单独形成一个chunk
  • 一般来说一个chunk对应一个bundle,除非配置一定的策略将chunk分解为多个bundle

现在我们能够明确3个概念:

  • module:我们写的个一个一个的文件(含有import、export) 就是module
  • chunk:webpack对module分析,根据引用关系生产chunk(我理解有依赖关系的放在一个chunk里
  • bundle:经过加载和编译的最终源文件,可直接在浏览器中运行,一般来说一个chunk对应一个bundle,除非有在webpack中特殊配置

一个简单直接的理解:我们直接写出来的是 module,webpack 处理时是 chunk,最后生成浏览器可以直接运行的 bundle。

bundle模式特点

bundle模式最显著的特点就在于,它需要以入口文件为起点,首先分析文件之间的依赖关系,生成模块依赖图。然后根据一定的策略将这个模块依赖图分解为多个 bundle,多个 bundle 之间也存在依赖关系。

基于bundle模式的DevServer

在这里插入图片描述

unbundle模式是什么样的?

unbundle主要用于开发模式构建。它的产生得益于现代浏览器大多数能够直接解析ESM模块化代码,它跳过了解析依赖打包、构建模块依赖图的步骤,将模块依赖解析交给浏览器。当浏览器解析到需要什么依赖,就通过http请求获得相应的模块。

省去了 解析源文件的依赖关系、构建模块依赖图、tree shaking、将模块依赖图分离成 bundle步骤,unbundle模式仍然需要预构建和源文件转换:

  1. 预构建

    为什么要预构建?

    一是为了将第三方库转换成ESM格式,二是为了防止浏览器出现的瀑布流请求情况,影响性能,还需要对符合 ESM 规范的第三方库的多个文件做合并处理,减少 http 请求数量。

  2. 源文件转换

    将ts jsx, vue…格式文件转换成js和css

    不同的是,bundle 模式下转换过程是在 build 过程中完成,是打包构建耗时较久的罪魁祸首之一。而 unbundle 模式下,转换过程是在浏览器发起请求以后,由 server 端借助 middleware 中的 plugin 完成的。由于转换过程也需要消耗一定的时间,导致 unbundle 模式下,首屏和懒加载的性能会有一定的影响。

较为形象的图:
在这里插入图片描述

常见的构建工具

webpack

  • webpack是主流的传统构建工具,它的构建机制属于bundle模式。
  • 它采用CJS规范

Esbuild

  • javascript bundle打包工具,可以将JavaScriptTypeScript代码打包分发在网页上运行。但其打包速度却是webpack的10~100倍。
  • 采用ESM规范

Rollup

  • ESM规范

vite

vite在开发环境使用unbundle基于ESM的DevServer,生产环境使用rollup打包。

基于ESM的DevServer原理如下:

在这里插入图片描述

首先启动开发服务器,访问入口文件(通常是index.html),根据入口文件去访问相应的路由,根据路由去请求相应的依赖模块。没有参与的路由不会影响构建过程。

预编译过程:

Vite启动一个 koa 服务器拦截这些请求,并在后端进行相应的处理将项目中使用的文件通过简单的分解与整合,然后再以ESM格式返回返回给浏览器。

底层使用基于esbuild的预编译

源文件转换过程:

vite底层使用esbuild做源文件转换

HMR热更新过程:

Vite 通过 chokidar 来监听文件系统的变更,只用对发生变更的模块重新加载, 只需要精确的使相关模块与其临近的 HMR边界连接失效即可,这样HMR 更新速度就不会因为应用体积的增加而变慢。

其他概念:

koa服务器:

chokidar:

HMR热更新:

参考文章

深入理解Vite核心原理

漫谈构建工具(七):最近对前端构建工具的一些理解(二)

webpack 中,module,chunk 和 bundle 的区别是什么?

What the heck are CJS, AMD, UMD, and ESM in Javascript?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值