diff --git a/README.md b/README.md index ee733b6..fc5de2e 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ While there are tools like [tsc](https://www.typescriptlang.org/docs/handbook/co ## 🚀 Usage ```bash -npx mkdist [rootDir] [--src=src] [--dist=dist] [--pattern=glob [--pattern=more-glob]] [--format=cjs|esm] [-d|--declaration] [--ext=mjs|js|ts] +npx mkdist [rootDir] [--src=src] [--dist=dist] [--pattern=glob [--pattern=more-glob]] [--format=cjs|esm] [-d|--declaration] [--ext=mjs|js|ts] [--declarationExt=infer|d.ts|d.mts|d.cts] ``` ## License diff --git a/src/cli.ts b/src/cli.ts index 4acc90c..cf0e764 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -46,6 +46,11 @@ const main = defineCommand({ default: false, alias: ["d"], }, + declarationExt: { + type: "string", + description: "Extensions for type declaration files", + valueHint: "infer|d.ts|d.mts|d.cts", + }, ext: { type: "string", description: "File extension", @@ -90,6 +95,7 @@ const main = defineCommand({ pattern: args.pattern, ext: args.ext, declaration: args.declaration, + declarationExt: args.declarationExt, loaders: args.loaders?.split(","), esbuild: { jsx: args.jsx, diff --git a/src/loader.ts b/src/loader.ts index 29b0545..9073acf 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -21,6 +21,7 @@ export interface OutputFile { */ path: string; srcPath?: string; + srcExtension?: string; extension?: string; contents?: string; declaration?: boolean; @@ -38,6 +39,7 @@ export interface LoaderOptions { ext?: "js" | "mjs" | "cjs" | "ts" | "mts" | "cts"; format?: "cjs" | "esm"; declaration?: boolean; + declarationExt?: "infer" | "d.ts" | "d.mts" | "d.cts"; esbuild?: CommonOptions; postcss?: | false diff --git a/src/loaders/js.ts b/src/loaders/js.ts index e7e79fe..7ab2764 100644 --- a/src/loaders/js.ts +++ b/src/loaders/js.ts @@ -11,25 +11,56 @@ const KNOWN_EXT_RE = /\.(c|m)?[jt]sx?$/; const TS_EXTS = new Set([".ts", ".mts", ".cts"]); export const jsLoader: Loader = async (input, { options }) => { - if (!KNOWN_EXT_RE.test(input.path) || DECLARATION_RE.test(input.path)) { + if (!KNOWN_EXT_RE.test(input.path)) { return; } const output: LoaderResult = []; let contents = await input.getContents(); + const [explicitDtsExtension] = input.srcPath?.match(DECLARATION_RE) ?? []; + const isManualDts = !!explicitDtsExtension; // declaration - if (options.declaration && !input.srcPath?.match(DECLARATION_RE)) { + if (options.declaration || isManualDts) { const cm = input.srcPath?.match(CM_LETTER_RE)?.[0] || ""; - const extension = `.d.${cm}ts`; + + const extension = (() => { + if (options.declarationExt === "infer") { + return ( + { + js: ".d.ts", + mjs: ".d.mts", + cjs: ".d.cts", + ts: ".d.ts", + mts: ".d.mts", + cts: ".d.cts", + }[options.ext?.replace(/^\./, "")] ?? + { + esm: ".d.mts", + cjs: ".d.ts", // TODO: Change it to ".d.cts" in next major version + }[options.format ?? "esm"] + ); + } else if (options.declarationExt) { + return `.${options.declarationExt}`; + } else { + return `.d.${cm}ts`; + } + })(); + output.push({ contents, srcPath: input.srcPath, + srcExtension: isManualDts ? explicitDtsExtension : undefined, path: input.path, extension, + raw: isManualDts, declaration: true, }); + + if (isManualDts) { + return output; + } } // typescript => js diff --git a/src/loaders/vue.ts b/src/loaders/vue.ts index 4e5aaf1..030ca89 100644 --- a/src/loaders/vue.ts +++ b/src/loaders/vue.ts @@ -124,5 +124,5 @@ const scriptLoader = vueBlockLoader({ outputLang: "js", type: "script", exclude: [/\bsetup\b/], - validExtensions: [".js", ".mjs"], + validExtensions: [".js", ".mjs", ".ts", ".mts"], }); diff --git a/src/make.ts b/src/make.ts index c21f5cc..a38dbc1 100644 --- a/src/make.ts +++ b/src/make.ts @@ -92,7 +92,8 @@ export async function mkdist( // Normalize output extensions for (const output of outputs.filter((o) => o.extension)) { const renamed = - basename(output.path, extname(output.path)) + output.extension; + basename(output.path, output.srcExtension ?? extname(output.path)) + + output.extension; output.path = join(dirname(output.path), renamed); // Avoid overriding files with original extension if (outputs.some((o) => o !== output && o.path === output.path)) { diff --git a/test/index.test.ts b/test/index.test.ts index eec3812..8fa37b4 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -192,6 +192,211 @@ describe("mkdist", () => { `); }, 50_000); + describe("mkdist (declarationExt: infer)", () => { + it.each([ + { + format: "cjs", + ext: undefined, + implicitFiles: [ + "dist/foo.js", + "dist/foo.d.ts", + "dist/bar.js", + "dist/bar.d.ts", + "dist/index.js", + "dist/index.d.ts", + "dist/star/index.js", + "dist/star/index.d.ts", + "dist/star/other.js", + "dist/star/other.d.ts", + "dist/types.d.ts", + "dist/bar/index.js", + "dist/bar/index.d.ts", + "dist/bar/esm.js", + "dist/bar/esm.d.ts", + "dist/ts/test1.js", + "dist/ts/test2.js", + "dist/ts/test1.d.ts", + "dist/ts/test2.d.ts", + "dist/components/js.vue.d.ts", + "dist/components/ts.vue.d.ts", + "dist/components/jsx.js", + "dist/components/tsx.js", + "dist/components/jsx.d.ts", + "dist/components/tsx.d.ts", + "dist/dir-export.js", + "dist/dir-export.d.ts", + "dist/components/index.d.ts", + "dist/components/index.js", + "dist/components/script-multi-block.vue", + ], + }, + // not setting the format explicitly should work like esm + ...(["esm", undefined] as const).map((format) => ({ + format, + ext: undefined, + implicitFiles: [ + "dist/foo.mjs", + "dist/foo.d.mts", + "dist/bar.mjs", + "dist/bar.d.mts", + "dist/index.mjs", + "dist/index.d.mts", + "dist/star/index.mjs", + "dist/star/index.d.mts", + "dist/star/other.mjs", + "dist/star/other.d.mts", + "dist/types.d.mts", + "dist/bar/index.mjs", + "dist/bar/index.d.mts", + "dist/bar/esm.mjs", + "dist/bar/esm.d.mts", + "dist/ts/test1.mjs", + "dist/ts/test2.mjs", + "dist/ts/test1.d.mts", + "dist/ts/test2.d.mts", + "dist/components/js.vue.d.mts", + "dist/components/ts.vue.d.mts", + "dist/components/jsx.mjs", + "dist/components/tsx.mjs", + "dist/components/jsx.d.mts", + "dist/components/tsx.d.mts", + "dist/dir-export.mjs", + "dist/dir-export.d.mts", + "dist/components/index.d.mts", + "dist/components/index.mjs", + "dist/components/script-multi-block.vue", + ], + })), + ...(["cjs", "esm", undefined] as const).flatMap((format) => + (["js", "mjs", "cjs", "ts", "mts", "cts"] as const).map((ext) => { + const [srcExt, dtsExt, emitsVue = false] = { + js: ["js", "d.ts", true], + mjs: ["mjs", "d.mts", true], + cjs: ["cjs", "d.cts"], + ts: ["ts", "d.ts", true], + mts: ["mts", "d.mts", true], + cts: ["cts", "d.cts"], + }[ext]; + + return { + format, + ext, + implicitFiles: [ + `dist/foo.${srcExt}`, + `dist/foo.${dtsExt}`, + `dist/bar.${srcExt}`, + `dist/bar.${dtsExt}`, + `dist/index.${srcExt}`, + `dist/index.${dtsExt}`, + `dist/star/index.${srcExt}`, + `dist/star/index.${dtsExt}`, + `dist/star/other.${srcExt}`, + `dist/star/other.${dtsExt}`, + `dist/types.${dtsExt}`, + `dist/bar/index.${srcExt}`, + `dist/bar/index.${dtsExt}`, + `dist/bar/esm.${srcExt}`, + `dist/bar/esm.${dtsExt}`, + `dist/ts/test1.${srcExt}`, + `dist/ts/test2.${srcExt}`, + `dist/ts/test1.${dtsExt}`, + `dist/ts/test2.${dtsExt}`, + `dist/components/jsx.${srcExt}`, + `dist/components/tsx.${srcExt}`, + `dist/components/jsx.${dtsExt}`, + `dist/components/tsx.${dtsExt}`, + `dist/dir-export.${srcExt}`, + `dist/dir-export.${dtsExt}`, + `dist/components/index.${srcExt}`, + `dist/components/index.${dtsExt}`, + `dist/components/script-multi-block.vue`, + ...(emitsVue + ? [ + `dist/components/js.vue.${dtsExt}`, + `dist/components/ts.vue.${dtsExt}`, + ] + : []), + ], + }; + }), + ), + ] as const)( + "format: $format, ext: $ext", + async ({ format, ext, implicitFiles }) => { + const rootDir = resolve(__dirname, "fixture"); + const { writtenFiles } = await mkdist({ + rootDir, + format, + ext, + declaration: true, + declarationExt: "infer", + }); + expect(writtenFiles.sort()).toEqual( + [ + "dist/README.md", + "dist/demo.css", + "dist/components/blank.vue", + "dist/components/js.vue", + "dist/components/script-setup-ts.vue", + "dist/components/ts.vue", + "dist/nested.css", + ...implicitFiles, + ] + .map((f) => resolve(rootDir, f)) + .sort(), + ); + }, + ); + }, 50_000); + + it.each(["d.ts", "d.mts", "d.cts"] as const)( + "mkdist (declarationExt: %s)", + async (declarationExt) => { + const rootDir = resolve(__dirname, "fixture"); + const { writtenFiles } = await mkdist({ + rootDir, + declaration: true, + declarationExt, + }); + expect(writtenFiles.sort()).toEqual( + [ + "dist/README.md", + "dist/demo.css", + "dist/foo.mjs", + `dist/foo.${declarationExt}`, + "dist/index.mjs", + `dist/index.${declarationExt}`, + "dist/star/index.mjs", + `dist/star/index.${declarationExt}`, + "dist/star/other.mjs", + `dist/star/other.${declarationExt}`, + `dist/types.${declarationExt}`, + "dist/components/blank.vue", + "dist/components/js.vue", + `dist/components/js.vue.${declarationExt}`, + "dist/components/script-setup-ts.vue", + "dist/components/ts.vue", + `dist/components/ts.vue.${declarationExt}`, + "dist/components/jsx.mjs", + "dist/components/tsx.mjs", + `dist/components/jsx.${declarationExt}`, + `dist/components/tsx.${declarationExt}`, + "dist/bar/index.mjs", + `dist/bar/index.${declarationExt}`, + "dist/bar/esm.mjs", + `dist/bar/esm.${declarationExt}`, + "dist/ts/test1.mjs", + "dist/ts/test2.mjs", + `dist/ts/test1.${declarationExt}`, + `dist/ts/test2.${declarationExt}`, + "dist/nested.css", + ] + .map((f) => resolve(rootDir, f)) + .sort(), + ); + }, + ); + describe("mkdist (sass compilation)", () => { const rootDir = resolve(__dirname, "fixture"); let writtenFiles: string[];