-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
fffe2e1
commit ca8c9b0
Showing
10 changed files
with
486 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@rnm/tscx": patch | ||
--- | ||
|
||
feat: init |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,70 @@ | ||
# tscx(WIP) | ||
# TSCX | ||
|
||
A tsc wrapper with many convenient features. | ||
[![](https://img.shields.io/npm/l/@rnm/tscx.svg)](https://github.com/rnmjs/tscx/blob/main/LICENSE) | ||
[![](https://img.shields.io/npm/v/@rnm/tscx.svg)](https://www.npmjs.com/package/@rnm/tscx) | ||
[![](https://img.shields.io/npm/dm/@rnm/tscx.svg)](https://www.npmjs.com/package/@rnm/tscx) | ||
[![](https://img.shields.io/librariesio/release/npm/@rnm/tscx)](https://www.npmjs.com/package/@rnm/tscx) | ||
[![](https://packagephobia.com/badge?p=@rnm/tscx)](https://packagephobia.com/result?p=@rnm/tscx) | ||
|
||
A `tsc` wrapper with many convenient features. Bring the [nodemon](https://www.npmjs.com/package/nodemon) + JavaScript development experience to TypeScript. | ||
|
||
## Background | ||
|
||
When we are using JavaScript, we usually run `nodemon main.js`. Then, application will automatically restart when js file changes. It's a great development experience. Why can't TypeScript? The reason is the compilation (tsc). Because of this, some edge cases are inconvenient just using `tsc`. For example: | ||
|
||
- Remove the output folder before compilation started | ||
- Copy non-ts files to output folder after compilation finished | ||
- Execute app entrance file immediately once compilation finished | ||
- Watch source files, repeat steps above and restart the app | ||
|
||
Now you can run one line of command to solve the problems. Better development experience! | ||
|
||
```sh | ||
npx tscx --project tsconfig.build.json --remove --copyfiles --watch --exec bootstrap.js | ||
``` | ||
|
||
Happy hacking! | ||
|
||
## Highlight | ||
|
||
- Same usages as `tsc` with few additional options. | ||
- Remove output folder before every compilation. | ||
- Copy non-ts files to output folder after every compilation. | ||
- Execute js file after compilation success. | ||
- Respect `tsconfig.json`. | ||
- ESM. | ||
|
||
## Install | ||
|
||
```sh | ||
npm install typescript @nrm/tscx -D | ||
``` | ||
|
||
## Usages | ||
|
||
```sh | ||
# Equivalent to `npx tsc` | ||
$ npx tscx | ||
|
||
# Equivalent to `npx tsc --project tsconfig.build.json --watch` | ||
$ npx tscx --project tsconfig.build.json --watch | ||
|
||
# Remove output folder before compilation and then compile ts code. | ||
$ npx tscx --remove | ||
|
||
# Compile ts code and then copy non-ts files to output folder after compilation. | ||
$ npx tscx --copyfiles | ||
|
||
# Compile ts code and execute bootstrap.js after successful compilation. | ||
$ npx tscx --exec bootstrap.js | ||
|
||
# Compile ts code in watch mode and execute bootstrap.js after every successful compilation. | ||
$ npx tscx --project tsconfig.build.json --watch --exec bootstrap.js | ||
|
||
# Remove => Compile => Copy => Execute => Edit any file to repeat it | ||
$ npx tscx --project tsconfig.build.json --remove --copyfiles --watch --exec bootstrap.js | ||
``` | ||
|
||
## License | ||
|
||
MIT |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import path from "node:path"; | ||
import process from "node:process"; | ||
import chokidar, { type FSWatcher } from "chokidar"; | ||
import { Compiler, type CompilerOptions } from "./compiler.js"; | ||
|
||
interface TscxOptions extends CompilerOptions { | ||
watch: boolean; | ||
} | ||
|
||
export class Action { | ||
private readonly compiler; | ||
private watcher?: FSWatcher; | ||
constructor(private readonly options: TscxOptions) { | ||
this.compiler = new Compiler(options); | ||
} | ||
|
||
private setupWatcher() { | ||
const include = this.compiler.getInclude() ?? []; | ||
const watchFiles = | ||
include.length <= 0 | ||
? [process.cwd()] | ||
: include | ||
.map((i) => path.resolve(process.cwd(), i)) | ||
.concat(path.resolve(process.cwd(), this.options.project)); | ||
|
||
this.watcher = chokidar.watch(watchFiles, { | ||
ignored: ["**/node_modules/**", "**/.git/**", this.compiler.getOutDir()], | ||
ignoreInitial: true, | ||
}); | ||
this.watcher | ||
.on("add", (filepath) => this.cb(filepath)) | ||
.on("unlink", (filepath) => this.cb(filepath)) | ||
.on("change", (filepath) => this.cb(filepath)) | ||
.on("ready", () => this.cb()); | ||
} | ||
|
||
private cb(filepath?: string) { | ||
console.log("Recompile for the file updated", filepath); | ||
if ( | ||
!filepath || | ||
path.resolve(process.cwd(), filepath) !== | ||
path.resolve(process.cwd(), this.options.project) | ||
) { | ||
return this.compiler.exec(); | ||
} | ||
|
||
try { | ||
this.compiler.refreshTsConfig(); | ||
} catch (e) { | ||
console.warn( | ||
"Refresh ts config fail. You can ignore this small warning.", | ||
e, | ||
); | ||
return; | ||
} | ||
this.watcher | ||
?.close() | ||
.then(() => { | ||
this.setupWatcher(); | ||
}) | ||
.catch((e) => { | ||
console.error("Close watcher fail!", e); | ||
process.exit(1); | ||
}); | ||
} | ||
|
||
start() { | ||
if (!this.options.watch) { | ||
this.compiler.exec(); | ||
return; | ||
} | ||
|
||
this.setupWatcher(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import fs from "node:fs/promises"; | ||
import path from "node:path"; | ||
import { fileURLToPath } from "node:url"; | ||
import { Command } from "commander"; | ||
import { Action } from "../action.js"; | ||
|
||
const version: string = JSON.parse( | ||
await fs.readFile( | ||
path.resolve( | ||
path.dirname(fileURLToPath(import.meta.url)), | ||
"..", | ||
"..", | ||
"package.json", | ||
), | ||
"utf8", | ||
), | ||
).version; | ||
|
||
new Command() | ||
.name("tscx") | ||
.version(version) | ||
.description("The TypeScript Compiler. Run `tsc` under the hood.") | ||
.option( | ||
"-p, --project <path>", | ||
"Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json'.", | ||
"tsconfig.json", | ||
) | ||
.option("-w, --watch", "Watch input files.", false) | ||
.option( | ||
"-r, --remove", | ||
"Remove output folder before before every compilation.", | ||
false, | ||
) | ||
.option( | ||
"-c, --copyfiles", | ||
"Copy non-ts files to output folder after every compilation.", | ||
false, | ||
) | ||
.option( | ||
"-e, --exec <path>", | ||
"Execute the specified js file after compilation success", | ||
) | ||
.action((options) => { | ||
new Action(options).start(); | ||
}) | ||
.parse(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import fs from "node:fs/promises"; | ||
import path from "node:path"; | ||
import process from "node:process"; | ||
|
||
/** | ||
* Copy non-ts/non-js files to outDir | ||
* @param rootDir absolute path | ||
* @param outDir absolute path | ||
*/ | ||
async function copyfiles(rootDir: string, outDir: string) { | ||
rootDir = path.resolve(rootDir); | ||
outDir = path.resolve(outDir); | ||
async function walkDir(dir: string, cb: (filepath: string) => Promise<void>) { | ||
await Promise.all( | ||
(await fs.readdir(dir)) | ||
.map((filepath) => path.resolve(dir, filepath)) | ||
.map(async (filepath) => { | ||
if ((await fs.stat(filepath)).isDirectory()) { | ||
if ( | ||
!filepath.startsWith(outDir) && | ||
!filepath.endsWith(`${path.sep}node_modules`) | ||
) { | ||
await walkDir(filepath, cb); | ||
} | ||
} else { | ||
if (!/\.(js|cjs|mjs|jsx|ts|cts|mts|tsx)$/.test(filepath)) { | ||
await cb(filepath); | ||
} | ||
} | ||
}), | ||
); | ||
} | ||
await walkDir(rootDir, async (filepath) => { | ||
const dest = filepath.replace(rootDir, outDir); | ||
console.log("Copy", filepath, "=>", dest); | ||
await fs.copyFile(filepath, dest); | ||
}); | ||
} | ||
|
||
const rootDir = process.argv[2]; | ||
const outDir = process.argv[3]; | ||
if (!rootDir || !outDir) { | ||
throw new Error("`rootDir` and `outDir` are required"); | ||
} | ||
|
||
await copyfiles(rootDir, outDir); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { spawn } from "node:child_process"; | ||
import path from "node:path"; | ||
import process from "node:process"; | ||
import { fileURLToPath } from "node:url"; | ||
|
||
const __dirname = path.dirname(fileURLToPath(import.meta.url)); | ||
const REMOVE_PATH = path.resolve(__dirname, "remove.mjs"); | ||
const COPYFILES_PATH = path.resolve(__dirname, "copyfiles.mjs"); | ||
const TSC_PATH = path.resolve( | ||
process.cwd(), | ||
"node_modules", | ||
"typescript", | ||
"bin", | ||
"tsc", | ||
); | ||
|
||
export function remove(filepath: string) { | ||
console.log("Remove", filepath); | ||
return spawn("node", [REMOVE_PATH, filepath], { stdio: "inherit" }); | ||
} | ||
|
||
export function tsc(options: { project: string }) { | ||
console.log("Tsc", options); | ||
return spawn("node", [TSC_PATH, "--project", options.project], { | ||
stdio: "inherit", | ||
}); | ||
} | ||
|
||
export function copyfiles(rootDir: string, outDir: string) { | ||
console.log("Copyfiles", rootDir, "=>", outDir); | ||
return spawn("node", [COPYFILES_PATH, rootDir, outDir], { stdio: "inherit" }); | ||
} | ||
|
||
export function exec(filepath: string) { | ||
console.log("Execute", filepath); | ||
return spawn("node", [filepath], { stdio: "inherit" }); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import fs from "node:fs"; | ||
import process from "node:process"; | ||
|
||
/** | ||
* @param filepath absolute filepath | ||
*/ | ||
async function remove(filepath: string) { | ||
await new Promise<void>((resolve, reject) => { | ||
fs.stat(filepath, (err) => { | ||
if (err) { | ||
return err.code === "ENOENT" ? resolve() : reject(err); // do nothing if file not found | ||
} | ||
fs.rm(filepath, { recursive: true }, (e) => (e ? reject(e) : resolve())); | ||
}); | ||
}); | ||
console.log(`Removed ${filepath}`); | ||
} | ||
|
||
const filepath = process.argv[2]; | ||
if (!filepath) { | ||
throw new Error("File path is required"); | ||
} | ||
|
||
await remove(filepath); |
Oops, something went wrong.