自定义 loader

BlackBerry,2 min read

最近看到了 pirates (opens in a new tab) 库,对 Node 的 require 函数进行了劫持,进行了统一的封装。

esbuild, rollup, esno 等都依赖这个库。

下面来看下自定义加载器,可以干什么,比如:可以让 Node 直接运行 ts 文件。

Node 里面内置了 三种加载器,分别是 node, json, js

以 js 加载器为示例看下写法

Module._extensions['.js'] = function (module, filename) {
  // If already analyzed the source, then it will be cached.
  const cached = cjsParseCache.get(module)
  let content
  if (cached?.source) {
    content = cached.source
    cached.source = undefined
  } else {
    content = fs.readFileSync(filename, 'utf8')
  }
  // ...
  module._compile(content, filename)
}

上面总共就两个步骤

  1. 读取文件内容
  2. 编译 js 代码

我们也照葫芦画瓢模仿一下

hijack.js
const fs = require('fs')
const Module = require('module')
const { transformSync } = require('esbuild')
 
Module._extensions['.ts'] = function (module, filename) {
  const content = fs.readFileSync(filename, 'utf8')
  const { code } = transformSync(content, {
    sourcefile: filename,
    sourcemap: 'both',
    loader: 'ts',
    format: 'cjs',
  })
  module._compile(code, filename)
}

写完之后如何运行用 node 运行 ts 之前加载我们这部分的代码呢。

node 有提供这方面的命令

node --help | grep preload
index.ts
// 创建一个 ts 文件测试一下
const str: string = 'hello world'
console.log(str)
node -r ./hijack.js index.ts

运行成功我们可以看到命令行输出了 hello world。

我们再用 pirates 封装的标准重新写一下。

pirates.js
const addHook = require('pirates').addHook
const { transformSync } = require('esbuild')
 
addHook(
  (code, filename) => {
    const { code: result } = transformSync(code, {
      sourcefile: filename,
      sourcemap: 'both',
      loader: 'ts',
      format: 'cjs',
    })
    return result
  },
  {
    exts: ['.ts'],
  }
)
node -r ./pirates.js index.ts

pirates (opens in a new tab) 源码不到 100 行,就是替换了原有的 module._compile 函数

© BlackBerry.