最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Esbuild 入口文件及启动过程|源码解读

    正文概述 掘金(五柳)   2021-05-06   799

    前言

    又回到了经典的一句话:“先知其然,而后使其然”。相信很多同学都知道了 esbuild,其以飞快的构建速度闻名于众。并且,esbuild 作者 Evan Wallace 也在官网的 FAQ专门介绍了为什么 esbuild 会这么快?(有兴趣的同学可以自行了解 esbuild.github.io/faq/)

    那么,回到今天本文,将会从 esbuild 源码的目录结构入手,围绕以下 2 点和大家一起走进 esbuild 底层的世界:

    • 初识 Esbuild 构建的入口
    • Esbuild 构建的入口做了什么

    1 初识 Esbuild 构建的入口

    在 Go 中,是以 package (包)来划分模块,每个 Go 的应用程序都需要包含一个入口 package main,即 main.go 文件。那么,显然 esbuild 本身也是一个 Go 应用,即它的入口文件同样也是 main.go 文件。

    而对于 esbuild,它的目录结构:

    |—— cmd
    |—— docs
    |—— images
    |—— internal
    |—— lib
    |—— npm
    |—— pkg
    |—— require
    |—— scripts
    .gitignore
    go.mod
    go.sum
    Makefile
    README.md
    version.txt
    

    似乎一眼望去,并没有我们想要的 main.go 文件,那么我们要怎么找到整个应用的入口?

    学过 C 的同学,应该知道 Make 这个构建工具,它可以用于执行我们定义好的一系列命令,来实现某个构建目标。并且,不难发现的是上面的目录结构中有一个 Makefile 文件,它则是用来注册 Make 命令的。

    而在 Makefile 文件中注册规则的基础语法会是这样:

    <target> : <prerequisites> 
    [tab]  <commands>
    

    这里,我们来分别认识一下各个参数的含义:

    • target 构建的目标,即使用 Make 命令的目标,例如 make 某个目标名
    • prerequisites 前置条件,通常是一些文件对应的路径,一旦这些文件发生变动,在执行 Make 命令时,就会进行重新构建,反之不会
    • tab 固定的语法格式要求,命令 commands 的开始必须为一个 tab
    • commands 命令,即执行 Make 命令构建某个目标时,对应会执行的命令

    那么,下面我们来看一下 esbuild 中 Makefile 文件中的内容:

    ESBUILD_VERSION = $(shell cat version.txt)
    
    # Strip debug info
    GO_FLAGS += "-ldflags=-s -w"
    
    # Avoid embedding the build path in the executable for more reproducible builds
    GO_FLAGS += -trimpath
    
    esbuild: cmd/esbuild/version.go cmd/esbuild/*.go pkg/*/*.go internal/*/*.go go.mod
    	CGO_ENABLED=0 go build $(GO_FLAGS) ./cmd/esbuild
    
    test:
    	make -j6 test-common
    
    # These tests are for development
    test-common: test-go vet-go no-filepath verify-source-map end-to-end-tests js-api-tests plugin-tests register-test node-unref-tests
    
    # These tests are for release (the extra tests are not included in "test" because they are pretty slow)
    test-all:
    	make -j6 test-common test-deno ts-type-tests test-wasm-node test-wasm-browser lib-typecheck
    ....
    

    可以看到,在 Makefile 文件中注册了很多规则。而我们经常使用的 esbuild 命令,则对应着这里的 esbuild 目标。

    根据上面对 Makefile 的介绍以及结合这里的内容,我们可以知道的是 esbuild 命令的核心是由 cmd/esbuild/version.go cmd/esbuild/*.gopkg/*/*.gointernal/*/*.go go.mod 这三部分相关的文件实现的。

    那么,通常执行 make esbuild 命令,其本质上是执行命令:

    CGO_ENABLED=0 go build $(GO_FLAGS) ./cmd/esbuild
    

    下面,我们来分别看一下这个命令做了什么(含义):

    CGO_ENABLED=0

    CGO_ENABLED 是 Go 的环境(env)信息之一,我们可以用 go env 命令查看 Go 支持的所有环境信息。

    而这里将 CGO_ENABLED 设为 0 是为了禁用 cgo,因为默认情况下,CGO_ENABLED1,也就是开启 cgo 的,但是 cgo 是会导入一些包含 C 代码的文件,那么也就是说最后编译的结果会包含一些外部动态链接,而不是纯静态链接

    那么,这个时候大家可能会思考外部动态链接静态链接之间的区别是什么?为什么需要纯静态链接的编译结果?

    这是因为外部动态链接会打破你最后编译出的程序对平台的适应性。因为,外部动态链接存在一定的不确定因素,简单的说也许你现在构建出来的应用是可以用的,但是在某天外部动态链接的内容发生了变化,那么很可能会对你的程序运行造成影响。

    go build $(GO_FLAGS) ./cmd/esbuild

    go build $(GO_FLAGS) ./cmd/esbuild 的核心是 go build 命令,它是用于编译源码文件、代码包、依赖包等操作,例如我们这里是对 ./cmd/esbuild/main.go 文件执行编译操作。

    到这里,我们就已经知道了 esbuild 构建的入口是 cmd/esbuild/main.go 文件了。那么,接下来就让我们看一下构建的入口都做了哪些事情?

    2 Esbuild 构建的入口做了什么?

    虽然,Esbuild 构建的入口 cmd/esbuild/main.go 文件的代码总共才 268 行左右。但是,为了方便大家理解,这里我将拆分为以下 3 点来分步骤讲解:

    • 基础依赖的 package 导入
    • --help 的文字提示函数的定义
    • main 函数具体都做了哪些

    2.1 基础依赖的 package 导入

    首先,是基础依赖的 package 导入,总共导入了 8 个 package

    import (
    	"fmt"
    	"os"
    	"runtime/debug"
    	"strings"
    	"time"
    
    	"github.com/evanw/esbuild/internal/api_helpers"
    	"github.com/evanw/esbuild/internal/logger"
    	"github.com/evanw/esbuild/pkg/cli"
    )
    

    这 8 个 package 分别对应的作用:

    • fmt 用于格式化输出 I/O 的函数
    • os 提供系统相关的接口
    • runtime/debug 提供程序在运行时进行调试的功能
    • strings 用于操作 UTF-8 编码的字符串的简单函数
    • time 用于测量和展示时间
    • github.com/evanw/esbuild/internal/api_helpers 用于检测计时器是否正在使用
    • github.com/evanw/esbuild/internal/logger 用于格式化日志输出
    • github.com/evanw/esbuild/pkg/cli 提供 esbuild 的命令行接口

    2.2 --help 的文字提示函数的定义

    任何一个工具都会有一个 --help 的选项(option),用于告知用户能使用的具体命令。所以,esbuild 的 --help 文字提示函数的定义也具备同样的作用,对应的代码(伪代码):

    var helpText = func(colors logger.Colors) string {
    	return `
    ` + colors.Bold + `Usage:` + colors.Reset + `
      esbuild [options] [entry points]
    
    ` + colors.Bold + `Documentation:` + colors.Reset + `
      ` + colors.Underline + `https://esbuild.github.io/` + colors.Reset + `
    
    ` + colors.Bold
      ...
    }
    

    这里会用到我们上面提到的 logger 这个 packageColors 结构体,它主要用于美化在终端输出的内容,例如加粗(Bold)、颜色(RedGreen):

    type Colors struct {
    	Reset     string
    	Bold      string
    	Dim       string
    	Underline string
    
    	Red   string
    	Green string
    	Blue  string
    
    	Cyan    string
    	Magenta string
    	Yellow  string
    }
    

    而使用 Colors 结构体创建的变量会是这样:

    var TerminalColors = Colors{
    	Reset:     "\033[0m",
    	Bold:      "\033[1m",
    	Dim:       "\033[37m",
    	Underline: "\033[4m",
    
    	Red:   "\033[31m",
    	Green: "\033[32m",
    	Blue:  "\033[34m",
    
    	Cyan:    "\033[36m",
    	Magenta: "\033[35m",
    	Yellow:  "\033[33m",
    }
    

    2.3 main 函数主要都做了哪些

    在前面,我们也提及了每个 Go 的应用程序都必须要有一个 main package,即 main.go 文件来作为应用的入口。而在 main.go 文件内也必须声明 main 函数,来作为 package 的入口函数。

    那么,作为 esbuild 的入口文件的 main 函数,主要是做这 2 件事:

    1. 获取输入的选项(option),并进行处理

    使用我们上面提到的 os 这个 package 获取终端输入的选项,即 os.Args[1:]。其中 [1:] 表示获取数组从索引为 1 到最后的所有元素构成的数组。

    然后,会循环 osArgs 数组,每次会 switch 判断具体的 case,对不同的选项,进行相应的处理。例如 --version 选项,会输出当前 esbuild 的版本号以及退出:

    fmt.Printf("%s\n", esbuildVersion)
    os.Exit(0)
    

    这整个过程对应的代码会是这样:

    osArgs := os.Args[1:]
    argsEnd := 0
    for _, arg := range osArgs {
      switch {
      case arg == "-h", arg == "-help", arg == "--help", arg == "/?":
        logger.PrintText(os.Stdout, logger.LevelSilent, os.Args, helpText)
        os.Exit(0)
    
      // Special-case the version flag here
      case arg == "--version":
        fmt.Printf("%s\n", esbuildVersion)
        os.Exit(0)
        ...
      default:
        osArgs[argsEnd] = arg
        argsEnd++
      }
    }
    

    并且,值得一提的是这里会重新构造 osArgs 数组,由于选项是可以一次性输入多个的, 但是 osArgs 会在后续的启动构建的时候作为参数传入,所以这里处理过的选项会在数组中去掉。

    2. 调用 cli.Run(),启动构建

    对于使用者来说,我们切实关注的是使用 esbuild 来打包某个应用,例如使用 esbuild xxx.js --bundle 命令。而这个过程由 main 函数最后的自执行函数完成。

    该函数的核心是调用 cli.Run() 来启动构建过程,并且传入上面已经处理过的选项。

    func() {
      ...
      exitCode = cli.Run(osArgs)
    }()
    

    并且,在正式开启构建之前,会根据继续处理前面的选项相关的逻辑,具体会涉及到 CPU 跟踪、堆栈的跟踪等,这里不作展开介绍,有兴趣的同学自行了解。

    结语

    好了,到这里我们就大致过了一遍 esbuild 构建的入口文件相关源码。站在没接触过 Go 的同学角度看可能稍微有点晦涩,并且有些分支逻辑,文中并没有展开分析,这会在后续的文章中继续展开。但是,总体上来看,打开一个新的窗户看到了不一样的风景,这不就是我们作为工程师所希望经历的嘛 ?。最后,如果文中存在表达不当或错误的地方,欢迎各位同学提 Issue~

    点赞 ?

    通过阅读本篇文章,如果有收获的话,可以点个赞,这将会成为我持续分享的动力,感谢~


    下载网 » Esbuild 入口文件及启动过程|源码解读

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元