esbuild-tsc
Advanced tools
Comparing version
137
cjs/index.js
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.default = esbuildPluginTsc; | ||
exports.EsbuildPluginTsc = void 0; | ||
const tslib_1 = require("tslib"); | ||
@@ -8,78 +8,75 @@ const node_fs_1 = tslib_1.__importDefault(require("node:fs")); | ||
const node_util_1 = require("node:util"); | ||
const strip_comments_1 = tslib_1.__importDefault(require("strip-comments")); | ||
const typescript_1 = tslib_1.__importDefault(require("typescript")); | ||
const DECORATOR_PATTERN = /((?<![(\s]\s*['"])@\(?\w*\w\s*(?!;)[(?=\s)])/; | ||
function esbuildPluginTsc(options) { | ||
const tsconfigPath = options?.tsconfigPath || node_path_1.default.join(process.cwd(), './tsconfig.json'); | ||
const tsx = options?.tsx ?? true; | ||
return { | ||
name: 'tsc', | ||
setup(build) { | ||
let parsedTsConfig; | ||
build.onLoad({ filter: tsx ? /\.tsx?$/ : /\.ts$/ }, async (args) => { | ||
if (!parsedTsConfig) { | ||
parsedTsConfig = parseTsConfig(tsconfigPath, process.cwd()); | ||
if (parsedTsConfig.options.sourceMap) { | ||
parsedTsConfig.options.sourceMap = false; | ||
parsedTsConfig.options.inlineSources = true; | ||
parsedTsConfig.options.inlineSourceMap = true; | ||
} | ||
} | ||
// Just return if we don't need to search the file. | ||
if (!(parsedTsConfig?.options?.emitDecoratorMetadata || options?.force)) { | ||
return new EsbuildPluginTsc(options); | ||
} | ||
exports.default = esbuildPluginTsc; | ||
/** | ||
* @class EsbuildPluginTsc | ||
*/ | ||
class EsbuildPluginTsc { | ||
constructor(options) { | ||
this.name = 'tsc'; | ||
this.options = { | ||
tsconfigPath: options?.tsconfigPath || 'tsconfig.json', | ||
filter: options?.filter || /\.tsx?$/, | ||
}; | ||
} | ||
setup(build) { | ||
const parsedTsConfig = this._parseTsConfig(this.options.tsconfigPath, process.cwd()); | ||
if (parsedTsConfig.options?.sourceMap) { | ||
parsedTsConfig.options.sourceMap = false; | ||
parsedTsConfig.options.inlineSources = true; | ||
parsedTsConfig.options.inlineSourceMap = true; | ||
} | ||
const filterFn = typeof this.options.filter === 'function' | ||
? this.options.filter | ||
: undefined; | ||
build.onLoad({ | ||
filter: typeof this.options.filter === 'object' | ||
? this.options.filter | ||
: /\.tsx?$/, | ||
}, async (args) => { | ||
if (filterFn) { | ||
if (!filterFn(args.path, parsedTsConfig)) | ||
return; | ||
} | ||
let fileContent; | ||
try { | ||
fileContent = node_fs_1.default.readFileSync(args.path, 'utf8'); | ||
} | ||
catch (err) { | ||
printDiagnostics({ file: args.path, err }); | ||
return; | ||
} | ||
if (!options?.force) { | ||
// Find the decorator and if there isn't one, return out | ||
const hasDecorator = findDecorators(fileContent); | ||
if (!hasDecorator) { | ||
return; | ||
} | ||
} | ||
const program = typescript_1.default.transpileModule(fileContent, { | ||
compilerOptions: parsedTsConfig.options, | ||
fileName: node_path_1.default.basename(args.path), | ||
}); | ||
return { contents: program.outputText }; | ||
} | ||
else if (!parsedTsConfig?.options?.emitDecoratorMetadata) { | ||
return; | ||
} | ||
const fileContent = node_fs_1.default.readFileSync(args.path, 'utf8'); | ||
const program = typescript_1.default.transpileModule(fileContent, { | ||
compilerOptions: parsedTsConfig.options, | ||
fileName: node_path_1.default.basename(args.path), | ||
}); | ||
}, | ||
}; | ||
} | ||
const findDecorators = (fileContent) => DECORATOR_PATTERN.test((0, strip_comments_1.default)(fileContent)); | ||
function parseTsConfig(tsconfig, cwd = process.cwd()) { | ||
const fileName = typescript_1.default.findConfigFile(cwd, typescript_1.default.sys.fileExists, tsconfig); | ||
// if the value was provided, but no file, fail hard | ||
if (tsconfig !== undefined && !fileName) { | ||
throw new Error(`failed to open '${fileName}'`); | ||
return { contents: program.outputText }; | ||
}); | ||
} | ||
let loadedConfig = {}; | ||
let baseDir = cwd; | ||
if (fileName) { | ||
const text = typescript_1.default.sys.readFile(fileName); | ||
if (text === undefined) | ||
throw new Error(`failed to read '${fileName}'`); | ||
const result = typescript_1.default.parseConfigFileTextToJson(fileName, text); | ||
if (result.error !== undefined) { | ||
printDiagnostics(result.error); | ||
throw new Error(`failed to parse '${fileName}'`); | ||
_parseTsConfig(tsconfig, cwd = process.cwd()) { | ||
const fileName = typescript_1.default.findConfigFile(cwd, typescript_1.default.sys.fileExists, tsconfig); | ||
// if the value was provided, but no file, fail hard | ||
if (tsconfig !== undefined && !fileName) { | ||
throw new Error(`Unable to locate tsconfig'`); | ||
} | ||
loadedConfig = result.config; | ||
baseDir = node_path_1.default.dirname(fileName); | ||
let loadedConfig = {}; | ||
let baseDir = cwd; | ||
if (fileName) { | ||
const text = typescript_1.default.sys.readFile(fileName); | ||
if (text === undefined) | ||
throw new Error(`failed to read '${fileName}'`); | ||
const result = typescript_1.default.parseConfigFileTextToJson(fileName, text); | ||
loadedConfig = result.config; | ||
baseDir = node_path_1.default.dirname(fileName); | ||
} | ||
const parsedTsConfig = typescript_1.default.parseJsonConfigFileContent(loadedConfig, typescript_1.default.sys, baseDir); | ||
if (parsedTsConfig.errors.length) { | ||
this._printDiagnostics(parsedTsConfig.errors); | ||
} | ||
return parsedTsConfig; | ||
} | ||
const parsedTsConfig = typescript_1.default.parseJsonConfigFileContent(loadedConfig, typescript_1.default.sys, baseDir); | ||
if (parsedTsConfig.errors[0]) | ||
printDiagnostics(parsedTsConfig.errors); | ||
return parsedTsConfig; | ||
_printDiagnostics(...args) { | ||
// eslint-disable-next-line no-console | ||
console.log((0, node_util_1.inspect)(args, false, 10, true)); | ||
} | ||
} | ||
function printDiagnostics(...args) { | ||
// eslint-disable-next-line no-console | ||
console.log((0, node_util_1.inspect)(args, false, 10, true)); | ||
} | ||
exports.EsbuildPluginTsc = EsbuildPluginTsc; |
136
esm/index.js
import fs from 'node:fs'; | ||
import path from 'node:path'; | ||
import { inspect } from 'node:util'; | ||
import stripComments from 'strip-comments'; | ||
import typescript from 'typescript'; | ||
const DECORATOR_PATTERN = /((?<![(\s]\s*['"])@\(?\w*\w\s*(?!;)[(?=\s)])/; | ||
export default function esbuildPluginTsc(options) { | ||
const tsconfigPath = options?.tsconfigPath || path.join(process.cwd(), './tsconfig.json'); | ||
const tsx = options?.tsx ?? true; | ||
return { | ||
name: 'tsc', | ||
setup(build) { | ||
let parsedTsConfig; | ||
build.onLoad({ filter: tsx ? /\.tsx?$/ : /\.ts$/ }, async (args) => { | ||
if (!parsedTsConfig) { | ||
parsedTsConfig = parseTsConfig(tsconfigPath, process.cwd()); | ||
if (parsedTsConfig.options.sourceMap) { | ||
parsedTsConfig.options.sourceMap = false; | ||
parsedTsConfig.options.inlineSources = true; | ||
parsedTsConfig.options.inlineSourceMap = true; | ||
} | ||
} | ||
// Just return if we don't need to search the file. | ||
if (!(parsedTsConfig?.options?.emitDecoratorMetadata || options?.force)) { | ||
function esbuildPluginTsc(options) { | ||
return new EsbuildPluginTsc(options); | ||
} | ||
export default esbuildPluginTsc; | ||
/** | ||
* @class EsbuildPluginTsc | ||
*/ | ||
export class EsbuildPluginTsc { | ||
constructor(options) { | ||
this.name = 'tsc'; | ||
this.options = { | ||
tsconfigPath: options?.tsconfigPath || 'tsconfig.json', | ||
filter: options?.filter || /\.tsx?$/, | ||
}; | ||
} | ||
setup(build) { | ||
const parsedTsConfig = this._parseTsConfig(this.options.tsconfigPath, process.cwd()); | ||
if (parsedTsConfig.options?.sourceMap) { | ||
parsedTsConfig.options.sourceMap = false; | ||
parsedTsConfig.options.inlineSources = true; | ||
parsedTsConfig.options.inlineSourceMap = true; | ||
} | ||
const filterFn = typeof this.options.filter === 'function' | ||
? this.options.filter | ||
: undefined; | ||
build.onLoad({ | ||
filter: typeof this.options.filter === 'object' | ||
? this.options.filter | ||
: /\.tsx?$/, | ||
}, async (args) => { | ||
if (filterFn) { | ||
if (!filterFn(args.path, parsedTsConfig)) | ||
return; | ||
} | ||
let fileContent; | ||
try { | ||
fileContent = fs.readFileSync(args.path, 'utf8'); | ||
} | ||
catch (err) { | ||
printDiagnostics({ file: args.path, err }); | ||
return; | ||
} | ||
if (!options?.force) { | ||
// Find the decorator and if there isn't one, return out | ||
const hasDecorator = findDecorators(fileContent); | ||
if (!hasDecorator) { | ||
return; | ||
} | ||
} | ||
const program = typescript.transpileModule(fileContent, { | ||
compilerOptions: parsedTsConfig.options, | ||
fileName: path.basename(args.path), | ||
}); | ||
return { contents: program.outputText }; | ||
} | ||
else if (!parsedTsConfig?.options?.emitDecoratorMetadata) { | ||
return; | ||
} | ||
const fileContent = fs.readFileSync(args.path, 'utf8'); | ||
const program = typescript.transpileModule(fileContent, { | ||
compilerOptions: parsedTsConfig.options, | ||
fileName: path.basename(args.path), | ||
}); | ||
}, | ||
}; | ||
} | ||
const findDecorators = (fileContent) => DECORATOR_PATTERN.test(stripComments(fileContent)); | ||
function parseTsConfig(tsconfig, cwd = process.cwd()) { | ||
const fileName = typescript.findConfigFile(cwd, typescript.sys.fileExists, tsconfig); | ||
// if the value was provided, but no file, fail hard | ||
if (tsconfig !== undefined && !fileName) { | ||
throw new Error(`failed to open '${fileName}'`); | ||
return { contents: program.outputText }; | ||
}); | ||
} | ||
let loadedConfig = {}; | ||
let baseDir = cwd; | ||
if (fileName) { | ||
const text = typescript.sys.readFile(fileName); | ||
if (text === undefined) | ||
throw new Error(`failed to read '${fileName}'`); | ||
const result = typescript.parseConfigFileTextToJson(fileName, text); | ||
if (result.error !== undefined) { | ||
printDiagnostics(result.error); | ||
throw new Error(`failed to parse '${fileName}'`); | ||
_parseTsConfig(tsconfig, cwd = process.cwd()) { | ||
const fileName = typescript.findConfigFile(cwd, typescript.sys.fileExists, tsconfig); | ||
// if the value was provided, but no file, fail hard | ||
if (tsconfig !== undefined && !fileName) { | ||
throw new Error(`Unable to locate tsconfig'`); | ||
} | ||
loadedConfig = result.config; | ||
baseDir = path.dirname(fileName); | ||
let loadedConfig = {}; | ||
let baseDir = cwd; | ||
if (fileName) { | ||
const text = typescript.sys.readFile(fileName); | ||
if (text === undefined) | ||
throw new Error(`failed to read '${fileName}'`); | ||
const result = typescript.parseConfigFileTextToJson(fileName, text); | ||
loadedConfig = result.config; | ||
baseDir = path.dirname(fileName); | ||
} | ||
const parsedTsConfig = typescript.parseJsonConfigFileContent(loadedConfig, typescript.sys, baseDir); | ||
if (parsedTsConfig.errors.length) { | ||
this._printDiagnostics(parsedTsConfig.errors); | ||
} | ||
return parsedTsConfig; | ||
} | ||
const parsedTsConfig = typescript.parseJsonConfigFileContent(loadedConfig, typescript.sys, baseDir); | ||
if (parsedTsConfig.errors[0]) | ||
printDiagnostics(parsedTsConfig.errors); | ||
return parsedTsConfig; | ||
_printDiagnostics(...args) { | ||
// eslint-disable-next-line no-console | ||
console.log(inspect(args, false, 10, true)); | ||
} | ||
} | ||
function printDiagnostics(...args) { | ||
// eslint-disable-next-line no-console | ||
console.log(inspect(args, false, 10, true)); | ||
} |
{ | ||
"name": "esbuild-tsc", | ||
"description": "An esbuild plugin which uses tsc to compile typescript files", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"author": "Panates", | ||
"license": "MIT", | ||
"dependencies": { | ||
"strip-comments": "^2.0.1" | ||
}, | ||
"dependencies": {}, | ||
"type": "module", | ||
@@ -11,0 +9,0 @@ "exports": { |
# esbuild-tsc | ||
An esbuild plugin which uses tsc to compile typescript files | ||
[![NPM Version][npm-image]][npm-url] | ||
[![NPM Downloads][downloads-image]][downloads-url] | ||
[![CircleCI][circleci-image]][circleci-url] | ||
[![Test Coverage][coveralls-image]][coveralls-url] | ||
Note: Originally inspired from [esbuild-plugin-tsc](https://www.npmjs.com/package/esbuild-plugin-tsc), Thanks to *Thomas Schaaf* | ||
## Motivation | ||
## About | ||
Esbuild natively supports typescript files - so if you are not using exotic typescript features this plugin is not meant for you! | ||
An ESBuild plugin which uses tsc to compile typescript files | ||
I wanted to use [`typescripts emitDecoratorMetadata`](https://www.typescriptlang.org/tsconfig#emitDecoratorMetadata) feature, | ||
which is [not supported by esbuild](https://github.com/evanw/esbuild/issues/257). | ||
If you are only using typescript decorators in some files, this plugin allows you to convert only | ||
the files containing decorators and lets esbuild handle all other files. | ||
If for some reason you want to use typescripts compiler for all files | ||
you can simple set the `force` option. | ||
## Motivation | ||
## Basic Usage | ||
Esbuild natively supports typescript files, but not TypeScript decorators. | ||
TypeScript decorators are different from EcmaScript decorators. | ||
If TypeScript decorators are used in your project, tsc should be used to transpile. | ||
1. Install this plugin in your project: | ||
## Installation | ||
@@ -26,19 +25,10 @@ ```sh | ||
2. Add this plugin to your esbuild build script: | ||
## Basic Usage | ||
Javascript: | ||
```diff | ||
+const esbuildPluginTsc = require('esbuild-tsc'); | ||
... | ||
esbuild.build({ | ||
... | ||
plugins: [ | ||
+ esbuildPluginTsc(), | ||
], | ||
}) | ||
``` | ||
Add this plugin to your esbuild build script: | ||
Typescript: | ||
```diff | ||
+import esbuildPluginTsc from 'esbuild-plugin-tsc'; | ||
```ts | ||
import esbuildPluginTsc from 'esbuild-tsc'; | ||
... | ||
@@ -48,18 +38,28 @@ esbuild.build({ | ||
plugins: [ | ||
+ esbuildPluginTsc(), | ||
esbuildPluginTsc(options), | ||
], | ||
}) | ||
}); | ||
``` | ||
## Config | ||
### Options | ||
```typescript | ||
esbuildPluginTsc({ | ||
// If empty, uses tsconfig.json | ||
tsconfigPath?: string, | ||
// If true, force compilation with tsc | ||
force?: boolean, | ||
// If true, enables tsx file support | ||
tsx?: boolean | ||
}) | ||
``` | ||
**_tsconfigPath [string] _**: Path of the tsconfig json file. | ||
**_filter [RegExp | Function]_**: A RegExp or function to filter files. | ||
[npm-image]: https://img.shields.io/npm/v/esbuild-tsc.svg | ||
[npm-url]: https://npmjs.org/package/esbuild-tsc | ||
[circleci-image]: https://circleci.com/gh/panates/esbuild-tsc/tree/master.svg?style=shield | ||
[circleci-url]: https://circleci.com/gh/panates/esbuild-tsc/tree/master | ||
[coveralls-image]: https://img.shields.io/coveralls/panates/esbuild-tsc/master.svg | ||
[coveralls-url]: https://coveralls.io/r/panates/esbuild-tsc | ||
[downloads-image]: https://img.shields.io/npm/dm/esbuild-tsc.svg | ||
[downloads-url]: https://npmjs.org/package/esbuild-tsc | ||
[gitter-image]: https://badges.gitter.im/panates/esbuild-tsc.svg | ||
[gitter-url]: https://gitter.im/panates/esbuild-tsc?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge | ||
[dependencies-image]: https://david-dm.org/panates/esbuild-tsc/status.svg | ||
[dependencies-url]:https://david-dm.org/panates/esbuild-tsc | ||
[devdependencies-image]: https://david-dm.org/panates/esbuild-tsc/dev-status.svg | ||
[devdependencies-url]:https://david-dm.org/panates/esbuild-tsc?type=dev | ||
[quality-image]: http://npm.packagequality.com/shield/esbuild-tsc.png | ||
[quality-url]: http://packagequality.com/#?package=esbuild-tsc |
@@ -1,6 +0,24 @@ | ||
import type { Plugin } from 'esbuild'; | ||
export default function esbuildPluginTsc(options?: { | ||
tsconfigPath?: string; | ||
force?: boolean; | ||
tsx?: boolean; | ||
}): Plugin; | ||
import type { Plugin, PluginBuild } from 'esbuild'; | ||
import typescript from 'typescript'; | ||
declare function esbuildPluginTsc(options?: EsbuildPluginTsc.Options): EsbuildPluginTsc; | ||
export default esbuildPluginTsc; | ||
/** | ||
* @namespace EsbuildPluginTsc | ||
*/ | ||
export declare namespace EsbuildPluginTsc { | ||
interface Options { | ||
tsconfigPath?: string; | ||
filter?: RegExp | ((filename: string, tsconfig: typescript.ParsedCommandLine) => boolean); | ||
} | ||
} | ||
/** | ||
* @class EsbuildPluginTsc | ||
*/ | ||
export declare class EsbuildPluginTsc implements Plugin { | ||
name: string; | ||
options: Required<EsbuildPluginTsc.Options>; | ||
constructor(options?: EsbuildPluginTsc.Options); | ||
setup(build: PluginBuild): void; | ||
protected _parseTsConfig(tsconfig: string, cwd?: string): typescript.ParsedCommandLine; | ||
protected _printDiagnostics(...args: any[]): void; | ||
} |
11286
1.31%0
-100%181
6.47%- Removed
- Removed