diff --git a/src/cli.ts b/src/cli.ts index a0909cd..041db7d 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -85,6 +85,10 @@ const main = defineCommand({ type: "string", description: "Target environment (esbuild)", }, + sourcemap: { + type: "string", + description: "Emit sourcemap (esbuild)", + }, }, async run({ args }) { const { writtenFiles } = await mkdist({ @@ -103,6 +107,7 @@ const main = defineCommand({ jsxFragment: args.jsxFragment, minify: args.minify, target: args.target, + sourcemap: args.sourcemap, }, } as MkdistOptions); diff --git a/src/loaders/js.ts b/src/loaders/js.ts index e7e79fe..8f87b50 100644 --- a/src/loaders/js.ts +++ b/src/loaders/js.ts @@ -1,5 +1,6 @@ import { transform } from "esbuild"; import jiti from "jiti"; +import { basename, extname } from "pathe"; import type { Loader, LoaderResult } from "../loader"; @@ -18,6 +19,7 @@ export const jsLoader: Loader = async (input, { options }) => { const output: LoaderResult = []; let contents = await input.getContents(); + let sourceMapping = ""; // declaration if (options.declaration && !input.srcPath?.match(DECLARATION_RE)) { @@ -33,16 +35,28 @@ export const jsLoader: Loader = async (input, { options }) => { } // typescript => js + const sourcemap = + options.esbuild?.sourcemap === "linked" + ? "external" + : options.esbuild?.sourcemap; if (TS_EXTS.has(input.extension)) { - contents = await transform(contents, { + const result = await transform(contents, { ...options.esbuild, + sourcemap, + sourcefile: input.srcPath, loader: "ts", - }).then((r) => r.code); + }); + contents = result.code; + sourceMapping = result.map; } else if ([".tsx", ".jsx"].includes(input.extension)) { - contents = await transform(contents, { + const result = await transform(contents, { loader: input.extension === ".tsx" ? "tsx" : "jsx", ...options.esbuild, - }).then((r) => r.code); + sourcemap, + sourcefile: input.srcPath, + }); + contents = result.code; + sourceMapping = result.map; } // esm => cjs @@ -60,6 +74,22 @@ export const jsLoader: Loader = async (input, { options }) => { extension = options.ext.startsWith(".") ? options.ext : `.${options.ext}`; } + // sourcemap + if (options.esbuild?.sourcemap && sourceMapping !== "") { + if (options.esbuild.sourcemap !== "inline") { + output.push({ + contents: sourceMapping, + path: input.path, + extension: `${extension}.map`, + }); + } + + if (options.esbuild.sourcemap === "linked") { + const sourceMappingURL = `${basename(input.path, extname(input.path))}${extension}.map`; + contents += `\n//# sourceMappingURL=${sourceMappingURL}`; + } + } + output.push({ contents, path: input.path, diff --git a/test/index.test.ts b/test/index.test.ts index f2dc0dd..d0c5fbc 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -257,6 +257,71 @@ describe("mkdist", () => { `); }, 50_000); + it("mkdist (emit sourcemaps)", async () => { + const rootDir = resolve(__dirname, "fixture"); + const { writtenFiles } = await mkdist({ + rootDir, + esbuild: { + sourcemap: "linked", + }, + }); + + expect(writtenFiles.sort()).toEqual( + [ + "dist/README.md", + "dist/bar.mjs", + "dist/bar.mjs.map", + "dist/demo.css", + "dist/dir-export.mjs", + "dist/dir-export.mjs.map", + "dist/foo.mjs", + "dist/foo.d.ts", + "dist/index.mjs", + "dist/index.mjs.map", + "dist/types.d.ts", + "dist/star/index.mjs", + "dist/star/index.mjs.map", + "dist/star/other.mjs", + "dist/star/other.mjs.map", + "dist/components/index.mjs", + "dist/components/index.mjs.map", + "dist/components/blank.vue", + "dist/components/js.vue", + "dist/components/script-multi-block.vue", + "dist/components/script-multi-block.vue.mjs.map", + "dist/components/script-setup-ts.vue", + "dist/components/script-setup-ts.vue.mjs.map", + "dist/components/ts.vue", + "dist/components/ts.vue.mjs.map", + "dist/components/jsx.mjs", + "dist/components/jsx.mjs.map", + "dist/components/tsx.mjs", + "dist/components/tsx.mjs.map", + "dist/bar/index.mjs", + "dist/bar/index.mjs.map", + "dist/bar/esm.mjs", + "dist/ts/test1.mjs", + "dist/ts/test1.mjs.map", + "dist/ts/test2.mjs", + "dist/ts/test2.mjs.map", + "dist/nested.css", + "dist/prop-types/index.mjs", + "dist/prop-types/index.mjs.map", + ] + .map((f) => resolve(rootDir, f)) + .sort(), + ); + + expect(await readFile(resolve(rootDir, "dist/index.mjs"), "utf8")).toMatch( + /\n\/\/# sourceMappingURL=index.mjs.map$/, + ); + + expect( + JSON.parse(await readFile(resolve(rootDir, "dist/bar.mjs.map"), "utf8")) + .sourcesContent[0], + ).toMatch(await readFile(resolve(rootDir, "src/bar.ts"), "utf8")); + }); + describe("mkdist (sass compilation)", () => { const rootDir = resolve(__dirname, "fixture"); let writtenFiles: string[];