emit-volar-types.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. import { resolve } from "path"
  2. import { Module } from "@nuxt/types"
  3. import ts from "typescript"
  4. import chokidar from "chokidar"
  5. const { readdir, writeFile } = require("fs").promises
  6. function titleCase(str: string): string {
  7. return str[0].toUpperCase() + str.substring(1)
  8. }
  9. async function* getFilesInDir(dir: string): AsyncIterable<string> {
  10. const dirents = await readdir(dir, { withFileTypes: true })
  11. for (const dirent of dirents) {
  12. const res = resolve(dir, dirent.name)
  13. if (dirent.isDirectory()) {
  14. yield* getFilesInDir(res)
  15. } else {
  16. yield res
  17. }
  18. }
  19. }
  20. async function getAllVueComponentPaths(): Promise<string[]> {
  21. const vueFilePaths: string[] = []
  22. for await (const f of getFilesInDir("./components")) {
  23. if (f.endsWith(".vue")) {
  24. const componentsIndex = f.split("/").indexOf("components")
  25. vueFilePaths.push(`./${f.split("/").slice(componentsIndex).join("/")}`)
  26. }
  27. }
  28. return vueFilePaths
  29. }
  30. function resolveComponentName(filename: string): string {
  31. const index = filename.split("/").indexOf("components")
  32. return filename
  33. .split("/")
  34. .slice(index + 1)
  35. .filter((x) => x !== "index.vue") // Remove index.vue
  36. .map((x) => x.split(".vue")[0]) // Remove extension
  37. .filter((x) => x.toUpperCase() !== x.toLowerCase()) // Remove non-word stuff
  38. .map((x) => titleCase(x)) // titlecase it
  39. .join("")
  40. }
  41. function createTSImports(components: [string, string][]) {
  42. return components.map(([componentName, componentPath]) => {
  43. return ts.factory.createImportDeclaration(
  44. undefined,
  45. undefined,
  46. ts.factory.createImportClause(
  47. false,
  48. ts.factory.createIdentifier(componentName),
  49. undefined
  50. ),
  51. ts.factory.createStringLiteral(componentPath)
  52. )
  53. })
  54. }
  55. function createTSProps(components: [string, string][]) {
  56. return components.map(([componentName]) => {
  57. return ts.factory.createPropertySignature(
  58. undefined,
  59. ts.factory.createIdentifier(componentName),
  60. undefined,
  61. ts.factory.createTypeQueryNode(ts.factory.createIdentifier(componentName))
  62. )
  63. })
  64. }
  65. function generateTypeScriptDef(components: [string, string][]) {
  66. const statements = [
  67. ...createTSImports(components),
  68. ts.factory.createModuleDeclaration(
  69. undefined,
  70. [ts.factory.createModifier(ts.SyntaxKind.DeclareKeyword)],
  71. ts.factory.createIdentifier("global"),
  72. ts.factory.createModuleBlock([
  73. ts.factory.createInterfaceDeclaration(
  74. undefined,
  75. undefined,
  76. ts.factory.createIdentifier("__VLS_GlobalComponents"),
  77. undefined,
  78. undefined,
  79. [...createTSProps(components)]
  80. ),
  81. ]),
  82. ts.NodeFlags.ExportContext |
  83. ts.NodeFlags.GlobalAugmentation |
  84. ts.NodeFlags.ContextFlags
  85. ),
  86. ]
  87. const source = ts.factory.createSourceFile(
  88. statements,
  89. ts.factory.createToken(ts.SyntaxKind.EndOfFileToken),
  90. ts.NodeFlags.None
  91. )
  92. const printer = ts.createPrinter({
  93. newLine: ts.NewLineKind.LineFeed,
  94. })
  95. return printer.printFile(source)
  96. }
  97. async function generateShim() {
  98. const results = await getAllVueComponentPaths()
  99. const fileComponentNameCombo: [string, string][] = results.map((x) => [
  100. resolveComponentName(x),
  101. x,
  102. ])
  103. const typescriptString = generateTypeScriptDef(fileComponentNameCombo)
  104. await writeFile(resolve("shims-volar.d.ts"), typescriptString)
  105. }
  106. const module: Module<{}> = async function () {
  107. if (!this.nuxt.options.dev) return
  108. await generateShim()
  109. chokidar.watch(resolve("../components/")).on("all", async () => {
  110. await generateShim()
  111. })
  112. }
  113. export default module