emit-volar-types.ts 3.7 KB

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