TS Macro - TS 版的 Volar 插件

1,401 阅读2分钟

介绍

TS Macro是一个基于Volar.jsVSCode插件提供修改TS的能力。 最开始做这个项目的想法是两个月前我与智子面基的时候他提出来的,直到前段时间我做defineStyle的时候发现如果想要实现语法高亮需要写一个textmate配置文件并写死在vscode插件的package.json里,我才开始写这个插件。

没有这个插件之前,需要先使用@vue-macros/volar/script-sfcTSX文件头部添加<script lang="tsx">把它转成Vue文件后才能在@vue/language-tools里运行。这在Vue项目里运行的很好,没有问题。但这么强大的功能如果能够像vite一样提供一个插件系统给所有基于TSTSX的框架使用那就太好了。所以这个项目没有内置任何插件,所有的能力都通过插件的形式交给用户去配置。

仓库地址

github.com/ts-macro/ts…

使用方式

  • 安装 VSCode插件

  • tsconfig.json的同级目录下创建tsm.config.ts配置文件。

    ts-macro 支持从 vite.config.ts 里自动注册插件 就像 xxx.d.ts 一样。
    对于插件的作者 需要抛出一个 volar 文件 ts-macro 就会自动加载这个插件 并与 vite 插件共享 userOptionsExample
    对于插件的使用者 只需要安装 TS Macrovscode 插件 可以不用写 tsm.config.ts 了。

  • 编写第一个插件,声明一个defineStyle类型。

// tsm.config.ts
export default {
  plugins: [
    {
      name: 'ts-macro-define-style',
      resolveVirtualCode({ codes }){
        // 在每个 TS 文件结尾声明一个 defineStyle 函数
        codes.push(`declare function defineStyle<T>(style: string): T;`)
      }
    }
  ]
}

编写defineStyle插件

在这里我们使用createPlugin来定义插件,还可以兼容Vue完整实现的代码

安装 ts-macro

pnpm add ts-macro

编写插件

// define-style.ts
import { createPlugin, replaceSourceRange } from 'ts-macro'

export default createPlugin<{ macro: string } | undefined>(
  ( 
    { 
     ts,
     compilerOptions, 
     vueCompilerOptions // 用于设置 userOptions 👇 只有在 vue 插件里才有值
    },
    userOptions = vueCompilerOptions?.defineStyle ?? { macro: 'defineStyle' }
  ) => ({
    name: 'ts-macro-define-style',
    resolveVirtualCode({ ast, codes, source }){
      // 1. 在每个 TS 文件结尾声明一个 defineStyle 函数
      codes.push(`declare function defineStyle<T>(style: string): T;`)

      // 2. 遍历 ast
      walkAst(ast)
      
      function walkAst(
        node: import('typescript').Node,
        parent?: import('typescript').Node,
      ) {
        if (
          ts.isCallExpression(node) &&
          ts.isIdentifier(node.expression) &&
          node.expression.text === userOptions.macro
        ) {
          // 3. 找到 defineStyle 的范型位置后插入自定义类型.
          replaceSourceRange( // 类似于的 Array.splice 方法
            codes,
            source, // 在vue文件里才有值: 'script' | 'scriptSetup' | undefined
            node.arguments.pos - 1,
            node.arguments.pos - 1,
            // 应该用正则表达式找出 css 里所有的 class,这里为了简单起见就先写死了。
            '<{ foo: string }>', 
          )
        }

        ts.forEachChild(node, (child) => {
          walkAst(child, node)
        })
      }
    }
  })
)

使用方式

  • TS Macro里使用
// tsm.config.ts
import defineStyle from './define-style.ts'

export default {
  plugins: [
    defineStyle({
      macro: 'defineStyle'
    })
  ]
}
  • Vue里使用,需要先发布到npm,并安装到package.json里。
// tsconfig.json
{
  // ...
  "vueCompilerOptions": {
    "plugins": [
      "define-style",
    ],
    "defineStyle": {
      "macro": "defineStyle"
    }
  }
}

效果

image.png

TSC

使用tsmc代替tsc编译TS。

  • 安装
pnpm add @ts-macro/tsc -D
  • package.json里使用
{
  "scripts": {
    "typecheck": "tsmc --noEmit"
  }
}

更多例子

react项目里使用vue指令和vue宏。

github.com/ts-macro/ts…

结尾

最后还感谢这些伟大的项目,让我少踩了好多坑。