diff --git a/package.json b/package.json index ef6b0c7..7e6312b 100644 --- a/package.json +++ b/package.json @@ -57,11 +57,14 @@ "sass": "^1.70.0", "typescript": "^5.3.3", "unbuild": "^2.0.0", + "vue": "^3.3.4", + "vue-tsc": "^1.8.5", "vitest": "^1.2.1" }, "peerDependencies": { "sass": "^1.69.7", - "typescript": ">=5.3.3" + "typescript": ">=5.3.3", + "vue-tsc": "^1.8.5" }, "peerDependenciesMeta": { "sass": { @@ -69,6 +72,9 @@ }, "typescript": { "optional": true + }, + "vue-tsc": { + "optional": true } }, "packageManager": "pnpm@8.14.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc1bd1e..89a95b4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -88,6 +88,12 @@ devDependencies: vitest: specifier: ^1.2.1 version: 1.2.1(@types/node@20.11.5)(sass@1.70.0) + vue: + specifier: ^3.3.4 + version: 3.4.15(typescript@5.3.3) + vue-tsc: + specifier: ^1.8.5 + version: 1.8.27(typescript@5.3.3) packages: @@ -1061,6 +1067,118 @@ packages: pretty-format: 29.7.0 dev: true + /@volar/language-core@1.11.1: + resolution: {integrity: sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==} + dependencies: + '@volar/source-map': 1.11.1 + dev: true + + /@volar/source-map@1.11.1: + resolution: {integrity: sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==} + dependencies: + muggle-string: 0.3.1 + dev: true + + /@volar/typescript@1.11.1: + resolution: {integrity: sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==} + dependencies: + '@volar/language-core': 1.11.1 + path-browserify: 1.0.1 + dev: true + + /@vue/compiler-core@3.4.15: + resolution: {integrity: sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==} + dependencies: + '@babel/parser': 7.23.6 + '@vue/shared': 3.4.15 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.0.2 + dev: true + + /@vue/compiler-dom@3.4.15: + resolution: {integrity: sha512-wox0aasVV74zoXyblarOM3AZQz/Z+OunYcIHe1OsGclCHt8RsRm04DObjefaI82u6XDzv+qGWZ24tIsRAIi5MQ==} + dependencies: + '@vue/compiler-core': 3.4.15 + '@vue/shared': 3.4.15 + dev: true + + /@vue/compiler-sfc@3.4.15: + resolution: {integrity: sha512-LCn5M6QpkpFsh3GQvs2mJUOAlBQcCco8D60Bcqmf3O3w5a+KWS5GvYbrrJBkgvL1BDnTp+e8q0lXCLgHhKguBA==} + dependencies: + '@babel/parser': 7.23.6 + '@vue/compiler-core': 3.4.15 + '@vue/compiler-dom': 3.4.15 + '@vue/compiler-ssr': 3.4.15 + '@vue/shared': 3.4.15 + estree-walker: 2.0.2 + magic-string: 0.30.5 + postcss: 8.4.33 + source-map-js: 1.0.2 + dev: true + + /@vue/compiler-ssr@3.4.15: + resolution: {integrity: sha512-1jdeQyiGznr8gjFDadVmOJqZiLNSsMa5ZgqavkPZ8O2wjHv0tVuAEsw5hTdUoUW4232vpBbL/wJhzVW/JwY1Uw==} + dependencies: + '@vue/compiler-dom': 3.4.15 + '@vue/shared': 3.4.15 + dev: true + + /@vue/language-core@1.8.27(typescript@5.3.3): + resolution: {integrity: sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@volar/language-core': 1.11.1 + '@volar/source-map': 1.11.1 + '@vue/compiler-dom': 3.4.15 + '@vue/shared': 3.4.15 + computeds: 0.0.1 + minimatch: 9.0.3 + muggle-string: 0.3.1 + path-browserify: 1.0.1 + typescript: 5.3.3 + vue-template-compiler: 2.7.16 + dev: true + + /@vue/reactivity@3.4.15: + resolution: {integrity: sha512-55yJh2bsff20K5O84MxSvXKPHHt17I2EomHznvFiJCAZpJTNW8IuLj1xZWMLELRhBK3kkFV/1ErZGHJfah7i7w==} + dependencies: + '@vue/shared': 3.4.15 + dev: true + + /@vue/runtime-core@3.4.15: + resolution: {integrity: sha512-6E3by5m6v1AkW0McCeAyhHTw+3y17YCOKG0U0HDKDscV4Hs0kgNT5G+GCHak16jKgcCDHpI9xe5NKb8sdLCLdw==} + dependencies: + '@vue/reactivity': 3.4.15 + '@vue/shared': 3.4.15 + dev: true + + /@vue/runtime-dom@3.4.15: + resolution: {integrity: sha512-EVW8D6vfFVq3V/yDKNPBFkZKGMFSvZrUQmx196o/v2tHKdwWdiZjYUBS+0Ez3+ohRyF8Njwy/6FH5gYJ75liUw==} + dependencies: + '@vue/runtime-core': 3.4.15 + '@vue/shared': 3.4.15 + csstype: 3.1.3 + dev: true + + /@vue/server-renderer@3.4.15(vue@3.4.15): + resolution: {integrity: sha512-3HYzaidu9cHjrT+qGUuDhFYvF/j643bHC6uUN9BgM11DVy+pM6ATsG6uPBLnkwOgs7BpJABReLmpL3ZPAsUaqw==} + peerDependencies: + vue: 3.4.15 + dependencies: + '@vue/compiler-ssr': 3.4.15 + '@vue/shared': 3.4.15 + vue: 3.4.15(typescript@5.3.3) + dev: true + + /@vue/shared@3.4.15: + resolution: {integrity: sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==} + dev: true + /acorn-jsx@5.3.2(acorn@8.11.3): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1497,6 +1615,10 @@ packages: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} dev: true + /computeds@0.0.1: + resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==} + dev: true + /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true @@ -1623,6 +1745,14 @@ packages: dependencies: css-tree: 2.2.1 + /csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + dev: true + + /de-indent@1.0.2: + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + dev: true + /debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -2666,6 +2796,11 @@ packages: function-bind: 1.1.2 dev: true + /he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + dev: true + /hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} dev: true @@ -3182,6 +3317,13 @@ packages: brace-expansion: 2.0.1 dev: true + /minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: true + /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} dev: true @@ -3266,6 +3408,10 @@ packages: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} dev: true + /muggle-string@0.3.1: + resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==} + dev: true + /nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -3491,6 +3637,10 @@ packages: lines-and-columns: 1.2.4 dev: true + /path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + dev: true + /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -4694,6 +4844,41 @@ packages: - terser dev: true + /vue-template-compiler@2.7.16: + resolution: {integrity: sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==} + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + dev: true + + /vue-tsc@1.8.27(typescript@5.3.3): + resolution: {integrity: sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==} + hasBin: true + peerDependencies: + typescript: '*' + dependencies: + '@volar/typescript': 1.11.1 + '@vue/language-core': 1.8.27(typescript@5.3.3) + semver: 7.5.4 + typescript: 5.3.3 + dev: true + + /vue@3.4.15(typescript@5.3.3): + resolution: {integrity: sha512-jC0GH4KkWLWJOEQjOpkqU1bQsBwf4R1rsFtw5GQJbjHVKWDzO6P0nWWBTmjp1xSemAioDFj1jdaK1qa3DnMQoQ==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@vue/compiler-dom': 3.4.15 + '@vue/compiler-sfc': 3.4.15 + '@vue/runtime-dom': 3.4.15 + '@vue/server-renderer': 3.4.15(vue@3.4.15) + '@vue/shared': 3.4.15 + typescript: 5.3.3 + dev: true + /which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} dependencies: diff --git a/src/make.ts b/src/make.ts index 6c10e8d..1e5b597 100644 --- a/src/make.ts +++ b/src/make.ts @@ -9,6 +9,7 @@ import { Loader, } from "./loader"; import { getDeclarations } from "./utils/dts"; +import { getVueDeclarations } from "./utils/vue-dts"; import { LoaderName } from "./loaders"; export interface MkdistOptions extends LoaderOptions { @@ -76,13 +77,11 @@ export async function mkdist( // Generate declarations const dtsOutputs = outputs.filter((o) => o.declaration && !o.skip); if (dtsOutputs.length > 0) { - const declarations = await getDeclarations( - new Map(dtsOutputs.map((o) => [o.srcPath, o.contents || ""])), - { - addRelativeDeclarationExtensions: - options.addRelativeDeclarationExtensions, - }, - ); + const vfs = new Map(dtsOutputs.map((o) => [o.srcPath, o.contents || ""])); + const declarations = Object.create(null); + for (const loader of [getVueDeclarations, getDeclarations]) { + Object.assign(declarations, await loader(vfs, options)); + } for (const output of dtsOutputs) { output.contents = declarations[output.srcPath] || ""; } diff --git a/src/utils/dts.ts b/src/utils/dts.ts index 436c3ee..463264b 100644 --- a/src/utils/dts.ts +++ b/src/utils/dts.ts @@ -37,6 +37,14 @@ export async function getDeclarations( const program = ts.createProgram(inputFiles, compilerOptions, tsHost); await program.emit(); + return extractDeclarations(vfs, inputFiles, opts); +} + +export function extractDeclarations( + vfs: Map, + inputFiles: string[], + opts?: GetDeclarationsOptions, +) { const output: Record = {}; for (const filename of inputFiles) { @@ -60,6 +68,8 @@ export async function getDeclarations( } } output[filename] = contents; + + vfs.delete(filename); } return output; diff --git a/src/utils/vue-dts.ts b/src/utils/vue-dts.ts new file mode 100644 index 0000000..72c48cd --- /dev/null +++ b/src/utils/vue-dts.ts @@ -0,0 +1,82 @@ +import { createRequire } from "node:module"; +import { CompilerOptions } from "typescript"; +import { MkdistOptions } from "../make"; +import { extractDeclarations } from "./dts"; + +const require = createRequire(import.meta.url); + +const compilerOptions: CompilerOptions = { + allowJs: true, + declaration: true, + incremental: true, + skipLibCheck: true, + strictNullChecks: true, + emitDeclarationOnly: true, +}; + +export async function getVueDeclarations( + vfs: Map, + opts?: MkdistOptions, +) { + const fileMapping = getFileMapping(vfs); + const srcFiles = Object.keys(fileMapping); + const originFiles = Object.values(fileMapping); + if (originFiles.length === 0) { + return; + } + + const vueTsc = await import("vue-tsc") + .then((r) => r.default || r) + .catch(() => undefined); + if (!vueTsc) { + console.warn( + "[mkdist] Please install `vue-tsc` to generate Vue SFC declarations.", + ); + return; + } + + // Inside vue-tsc, `require` is used instead of `import`. In order to override `ts.sys`, it is necessary to import it in the same way as vue-tsc for them to refer to the same file. + const ts = + require("typescript") as typeof import("typescript/lib/tsserverlibrary"); + + const tsHost = ts.createCompilerHost(compilerOptions); + + const _tsSysWriteFile = ts.sys.writeFile; + ts.sys.writeFile = (filename, content) => { + vfs.set(filename, content); + }; + const _tsSysReadFile = ts.sys.readFile; + ts.sys.readFile = (filename, encoding) => { + if (vfs.has(filename)) { + return vfs.get(filename); + } + return _tsSysReadFile(filename, encoding); + }; + + const program = vueTsc.createProgram({ + rootNames: srcFiles, + options: compilerOptions, + host: tsHost, + }); + + try { + program.emit(); + } finally { + ts.sys.writeFile = _tsSysWriteFile; + ts.sys.readFile = _tsSysReadFile; + } + + return extractDeclarations(vfs, originFiles, opts); +} + +const SFC_EXT_RE = /\.vue\.[cm]?[jt]s$/; + +function getFileMapping(vfs: Map): Record { + const files: Record = Object.create(null); + for (const [srcPath] of vfs) { + if (SFC_EXT_RE.test(srcPath)) { + files[srcPath.replace(SFC_EXT_RE, ".vue")] = srcPath; + } + } + return files; +}