60行代码实现一个基于esbuild按需加载的loader(一)

简介: 大家好,我是Fly, 最近研究的技术就是esbuild,一个新技术的 出现必然是解决是某些问题的, 这不我就打算用它来进行项目的编译速度进行操刀了, 前端对于tsx 的编译方式 无非是两种,

大家好,我是Fly, 最近研究的技术就是esbuild,一个新技术的 出现必然是解决是某些问题的, 这不我就打算用它来进行项目的编译速度进行操刀了, 前端对于tsx 的编译方式 无非是两种,


  1. 第一种就是 使用我们 很熟悉的babel 配合对应的presets  「@babel/preset-typescript」  然后进行 编译转换js


  1. 第二种 就是 由社区提供的一些loader, 对于tsx 的文件进行编译, 比如  「ts-loader」


  1. 还有一些比较新的技术, 「可以在开发环境玩玩, 生产环境慎用」, 比如使用swc 和 esbuild 对tsx 文件进行编译。还是看公司的业务场景,esbuild 没法编译成es5 ,没法做类型检查, 然后对于ts的一些语法不支持,等缺点。


esbuild



esbuild 号称下一代前端的打包工具,我们看一张图就是知道他的速度了


image.png


对于three.js 项目的打包 esbuild 的打包速度,竟然是webpack5 的100 倍都不止, 这速度看了谁不喜欢???多快哦, 看下esbuild支持的特性:


  1. 极速,无需缓存, 这里就是 在现阶段的打包的过程中, 对于AST 的分析, 反复去做, 每一个工具链, 可能都会利用AST 做一点事情,这就会导致大量的内存占用。而esbuild共用同一份AST 节点数据,提高内存利用率。


  1. 使用go 语言开发, 充分利用cpu 多核的优势, 使用多线程打包, 线程之间共享数据。


  1. esbuild的解析器可以同时处理ES6和CommonJS的模块。它不会去区分ES6和其他的模块,因此可以让你在同一个文件中同时使用ES6和CommonJS的语法。随着esm 的流行,未来可能是一种趋势, 但是老的包有的是基于commonJS 的, 要把common js 类型的包 转成esm, 或者esm 转换成 commonjs. esm的模式 打包出来的bundle, 更加高效, 运行时开销也小。


  1. 还有 作用域提升 已经 tree-shaking 这些特性


swc


swc 本身工具的定位有类似于 babel, 不过是基于rust 写的,esbuild 定位有点类似于webpack。我们看下图:



image.png


越高表示越好, swc 的性能是 babel、typescript、esbuild 在es2015 语法 6倍多, 由于这是官网的截图, 没有经过实际项目验证, 不过我说一个数据, 对于大型react 项目, 我使用esbuild-loader 和 swc-loader 构建的速度, 整体速度的差距是不大的。swc不是本篇文章的重点, 接下来继续分析esbuild


esbuild-loader



既然esbuild, 辣么快, 那么社区肯定有人就会想到, esbuild 如何与当前前端工程化项目去做结合, 提升开发体验。现阶段我所知道的有下面这些


  1. esbuild-jest 检测单元测试, 使用esbuild的能力 来缩短跑单侧的时间


  1. esbuild-eslint  我们项目由于eslint 的规则 十分多, 子路由大概10多个, 模块关系依赖复杂,跑一次lint, 时间都是非常慢, 如果借助esbuild 的能力,美滋滋。


  1. 还有一个就是编写一个loader 去解析tsx, 解析tsx这个loader, 在webpack 的编译时间, 往往都是十分耗时的,优化这段部分,整个webpack的启动时间就会大幅度加快。


目前所做的都是开发环境的优化, 没有在生产环境去操作过, 如果使用,请慎重,等vite 啥时候生产环境 不采用rollup ,保持开发环境 和生产环境的打包方式一致了, vite 的项目 还是具有代表性的, 唯一的担心, 就是esbuild 打包出来的bundle 和 webpack 打包出来的bundle 不一致, 出现问题就背大锅了, fly 建议 内部项目 或者个人学习项目, 在技术层面可以激进点,坏了, 可以修, 但是线上项目,服务很多用户的, 还是要慎重。所以现阶段 webpack 任然是最棒的工具, 繁荣的社区, 几乎问题都可以解决,项目没有升级webpack5的, wepack5 的持久化缓存, 模块联邦、lazyCompilation、 都是令人惊艳的重大更新。


好的下面我们就开始编写一个webpack 的loader, 本篇文章不会详细介绍loader 的编写不是本文的重点, 不会编写webpack loader的同学, 请自行学习。「esbuild-import-loader」  主要是有下面有两个功能


  1. 第一个 使用esbuild 进行编译tsx


  1. 实现组件库的按需加载格式


安装相关依赖



主要的依赖有


  1. esbuild   这个主要是核心依赖, 主要利用 transform 这个api  esbuild 提供的AST 接口少之又少, 如果你的项目重度依赖 babel , 那么在技术选型的过程中, 不建议使用 esbuild, 因为esbuild, 提供的AST 接口很少, esbuild 的作者本身 追求极致性能,  你可以使用swc, swc 你可以理解为 更快一点的 babel, 不过是基于rust, 相对于js 有着天然的优势 。可以看下官网的移植指南,结合项目综合去考虑。

image.png


  1. 由于esbuild, 没有提供AST 的接口, 所以对于AST 的 处理 我们 还是使用babel 相关的包 去做处理。 「@babel/parser , @babel/traverse, @babel/type  @babel/generator」


定义接口类型




关于接口的设计其实很简单,  在 esbuild Transform 接口的类型上, 进行增加


import { TransformOptions } from 'esbuild'
export type EsbuildImportLoader = Omit<TransformOptions, 'sourcemap' | 'sourcefile'> & {
  libraryName: string
  customStyle?: (importName: string) => string
  customName: (importName: string) => string
}


T除了 sourcemap  和 sourcefile  这两个属性


「sourcefile」 这个属性:


当使用没有文件名的输入时,此选项设置文件名。当使用转换 API 和使用带有标准输入的构建 API 时会发生这种情况。配置的文件名反映在错误消息和源映射中。如果未配置,则文件名默认为


let fs = require('fs')
let js = fs.readFileSync('app.js', 'utf8')
require('esbuild').transformSync(js, {
  sourcefile: 'example.js',
  sourcemap: 'inline',
})


sourcemap  就是我们前端理解的 源码映射, 我们写loader 完全没必要 由外面传了,

直接通过loader 的上下文拿对应的map其实就可以了。


TransformOptions 到底有哪些 类型呢 ???

有很多类型, 感兴趣的同学 去 这个网址 进行 查看  https://esbuild.github.io/api/#sourcemap

我选几个有代表性的属性 进行讲解:


Format


定义转换的文件 格式: 有  iife、commonjs 、esm。我们一一进行试验。


IIFE


let js = 'alert("我是FLY")'
let out = require('esbuild').transformSync(js, {
  format: 'iife',
})
process.stdout.write(out.code)


我们执行 ts-node 看控制台的输出


image.png

CommonJS


继续实验commonjs 的


let js = 'export default "test"'
let out = require('esbuild').transformSync(js, {
  format: 'cjs',
})
process.stdout.write(out.code)


image.png


打包结果 是对 esm 用一个 「toCommonJS」 进行包裹


ESM


let js = 'module.exports = "test"'
let out = require('esbuild').transformSync(js, {
  format: 'esm',
})
process.stdout.write(out.code)


image.png


但是 同样一段代码 esm 打包出来的 代码更少,用了一个 commonJS 函数包裹  这也证明了 esbuild  在esm 和commonjs 之间的能力。


相关文章
|
7天前
|
JavaScript 前端开发
Babel 插件的作用是什么?
Babel 插件的作用是什么?
|
23天前
|
开发框架 自然语言处理 JavaScript
babel 原理,怎么写 babel 插件
【10月更文挑战第23天】要深入理解和掌握如何编写 Babel 插件,需要不断实践和探索,结合具体的项目需求和代码结构,灵活运用相关知识和技巧。你还可以进一步扩展和深入探讨各个方面的内容,提供更多的实例和细节,以使文章更加丰富和全面。同时,关注 Babel 插件开发的最新动态和研究成果,以便及时了解其发展和变化。
|
4月前
|
JavaScript 前端开发
前端 JS 经典:Vite 打包优化
前端 JS 经典:Vite 打包优化
250 0
|
6月前
|
JavaScript
【第12期】Vue3+TypeScript+Vite中动态引入图片等静态资源
【第12期】Vue3+TypeScript+Vite中动态引入图片等静态资源 c
1236 0
|
4月前
|
前端开发 开发者
深入解析Vite.js源码
【7月更文挑战第1天】Vite.js 深入解析:以其无bundle开发、动态ES模块加载提升开发效率;本地HTTP服务器配合WebSocket实现热更新;按需加载减少资源占用;预构建优化生产环境性能;基于Rollup的插件系统增强灵活性。Vite,一个创新且高效的前端构建工具。
83 0
|
前端开发 JavaScript Java
前端——使用RequireJS的r.js打包压缩模块
前端——使用RequireJS的r.js打包压缩模块
|
自然语言处理 前端开发 JavaScript
Babel 的工作原理以及怎么写一个 Babel 插件
Babel 的工作原理以及怎么写一个 Babel 插件
211 0
|
6月前
|
JavaScript 前端开发
vue-loader是什么?使用它的用途有哪些?怎么使用?
vue-loader是什么?使用它的用途有哪些?怎么使用?
87 0
|
6月前
|
JavaScript 前端开发
vue项目打包后使用reverse-sourcemap反编译到源码(详解版)
vue项目打包后使用reverse-sourcemap反编译到源码(详解版)
812 0