今天看啥  ›  专栏  ›  132

手写一个 typescript 打包器

132  · 掘金  ·  · 2020-02-25 05:52
阅读 287

手写一个 typescript 打包器

halo,大家好,我是 132,那个啥,俺又出来诈尸啦

这次带来的是一个 ts 打包器的主要思路,最终实现代码先放一下

github.com/yisar/picop…

欢迎 star 和 fork!

背景

最近其实写了几个 typescript 的库,但是打包一直困扰着我,一方面感觉 tsc 比较好用,不想用 rollup,一边 ts 很难打包多文件

typescript 是可以将多文件打包为一个文件的,使用 --outputFlie

  "compilerOptions": {
    "module": "amd",
    "outFile": "./dist/doux.js"
  }
复制代码

这样可以将所有的 ts 文件打包成一个 js 文件,但比较遗憾,只支持 adm 和 systemjs

而我们写库,通常是 umd 格式的,给 typescript 的人发 issue,回复基本上不会支持

所以就萌生了自己写一个打包器的想法

开始

传统的 js 打包器都是分析 AST 然后进行各种的修改,替换,然后根据不同的格式进行包裹,最终产生一个能用的 js 文件

ts 也不例外……和 babel 一样,ts 官方提供了一组 TS Compiler API

看上去非常不错,比 babel 好太多,主要是 babel 生态乱,想干啥就要用各种第三方库

首先,需要创建一个 ts 项目,顺便进行类型检查

let diagnostics = ts.getPreEmitDiagnostics(
  ts.createProgram([entryFile], {
    strict: true,
    target: ts.ScriptTarget.Latest,
    moduleResolution: ts.ModuleResolutionKind.NodeJs
  })
)

if (diagnostics.length) {
  diagnostics.forEach(d => console.log(d.messageText))
  error('Type check Error.')
}
复制代码

首先,我们大概是需要拿到一个文件的 AST,使用 createSource

const source = program.getSourceFile('fixtures/test.ts')

if (source) {
  ts.forEachChild(source, node => {
    if (ts.isFunctionDeclaration(node)) {
      console.log(node.name && node.name.text)
    }
  })
}
复制代码

拿到 source,我们可以根据 kind 进行判断是哪一种代码

可以对应 AST viewer 来使用: TS AST viewer

然后经过一波操作后,需要重新生成 AST

const ast = ts.createVariableDeclarationList([
  ts.createVariableDeclaration(
    ts.createIdentifier('test'),
    ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
    ts.createLiteral('Hello!')
  )
], ts.NodeFlags.Const)

const printer = ts.createPrinter()
const code = printer.printNode(ts.EmitHint.Unspecified, ast, source)
console.log(code)
复制代码

到此,整个思路就捋顺了,不过我们既然是多文件打包,所以还需要根据 import 找到源文件,然后再去检查

case ImportDeclaration:
        let moduleSpecifier = node.moduleSpecifier.getText(source)
        let dep = JSON.parse(moduleSpecifier) as string
        let depPath: string
        if (dep.startsWith('.')) {
          depPath = localModulePath(dep, path)
        }
        pathQueue.push(depPath)
        break
复制代码

找到这个文件,需要再 compile 一次

很好,差不多就是这样啦

总结

  1. 关于 AST

总的来说,做 typescript compiler 远比 js 有前途的多,主要是 js 太乱了真的

其实 AST 也有两种,一种是借助 babel,webpack,ts compiler 生成的 AST,这种的缺点是比较乱,单词比较长

另一种是自己做 AST 的生成,这种就相当于 jsx 插件了,最终可以生成和 vdom 一样干净的树

我个人比较倾向于第二种,但………工作量比较大

  1. 关于转译

编译阶段可以说无所不能,前有各种打包器,做各种的代码打包和优化,后有国内各种小程序转译框架,如 taro,nanachi……

但是事实证明,什么东西适合做什么事情都是预定好了的

taro-next 使用了模拟浏览器 API 的方式,放弃编译思路,转投 runtime

svelte 使用编译思路,一直被 vue 等各种吐槽

所以看个人选择吧




原文地址:访问原文地址
快照地址: 访问文章快照