Skip to content

Instantly share code, notes, and snippets.

@reosablo
Last active November 16, 2023 18:09
Show Gist options
  • Select an option

  • Save reosablo/c679a6cff340c5a6c007c2e6e697d7f6 to your computer and use it in GitHub Desktop.

Select an option

Save reosablo/c679a6cff340c5a6c007c2e6e697d7f6 to your computer and use it in GitHub Desktop.
esubild plugin for Typia
// @ts-check
/**
* @file esbuild plugin for transforming TypeScript code using Typia
*
* CAUTION: this plugin will break the output source map.
*
* @example
* ```js:build.mjs
* import { build } from "esbuild";
* import typia from "./esbuild-plugin-typia.mjs";
*
* await build({
* entryPoints: ["./src/main.ts"],
* outfile: "./dist/main.js",
* bundle: true,
* format: "esm",
* platform: "node",
* // sourcemap: "inline",
* plugins: [typia()],
* });
* ```
*/
import ts from "typescript";
import { transform } from "typia/lib/transform.js";
function getCompilerOptions() {
const configPath = ts.findConfigFile("./", ts.sys.fileExists);
if (configPath === undefined) {
return undefined;
}
const { config } = ts.readConfigFile(configPath, ts.sys.readFile);
const { options } = ts.parseJsonConfigFileContent(config, ts.sys, configPath);
return options;
}
/**
* same function exists in [TypiaProgrammer.ts](https://github.com/samchon/typia/blob/master/src/programmers/TypiaProgrammer.ts)
*
* @param {string} content
*/
function emend(content) {
if (
content.indexOf("typia.") === -1 ||
content.indexOf("import typia") !== -1 ||
content.indexOf("import * as typia") !== -1
) return content;
return `import typia from "typia";\n\n${content}`;
}
/**
* esbuild plugin for transforming TypeScript code using Typia
*
* CAUTION: this plugin will break the output source map.
*
* @param {object} [options] - An optional object containing plugin options.
* @param {RegExp} [options.filter=/\.[cm]?ts$/] - A regular expression used to filter the files that should be transformed.
*
* @returns {import("esbuild").Plugin}
*/
export default function typia(options = {}) {
/** @type {ts.CompilerOptions | undefined} */
let compilerOptions;
/** @type {import("esbuild").OnLoadOptions} */
const onLoadOptions = { filter: options.filter ?? /\.[cm]?ts$/ };
return {
name: "typia",
setup(build) {
build.onLoad(onLoadOptions, ({ path }) => {
// similar process exists in TypiaProgrammer.ts
// CREATE PROGRAM
compilerOptions ??= getCompilerOptions() ?? {};
const typiaPlugin = /** @type {?*[]} */ (compilerOptions.plugins)?.find(
(p) => p.transform === "typia/lib/transform",
);
const program = ts.createProgram([path], compilerOptions);
const sourceFile = program.getSourceFile(path);
if (sourceFile === undefined) {
throw new Error("source file not found");
}
// DO TRANSFORM
/** @type {ts.Diagnostic[]} */
const diagnostics = [];
const typiaTransformer = transform(program, typiaPlugin, {
addDiagnostic: (diag) => diagnostics.push(diag),
});
const transformedFile = ts.transform(sourceFile, [typiaTransformer])
.transformed
.find((file) => file.fileName === path);
// TRACE ERRORS
if (diagnostics.length > 0) {
throw new Error(ts.formatDiagnostics(diagnostics, {
getCanonicalFileName: (path) => path,
getCurrentDirectory: ts.sys.getCurrentDirectory,
getNewLine: () => ts.sys.newLine,
}));
}
if (transformedFile === undefined) {
throw new Error("transformed file not found");
}
// EMIT TRANSFORMED FILE CONTENTS
const printer = ts.createPrinter();
const code = printer.printFile(transformedFile);
return { contents: emend(code), loader: "ts" };
});
},
};
}
{
"private": true,
"license": "CC0-1.0",
"peerDependencies": {
"esbuild": "*",
"typescript": "*",
"typia": ">=5"
},
"devDependencies": {
"esbuild": "^0.19.5",
"typescript": "^5.2.2",
"typia": "^5.2.6"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment