tsnite 0.1.2 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +132 -32
- package/dist/cache.js +5 -5
- package/dist/cli.js +5 -3
- package/dist/loader.js +70 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,63 +5,163 @@
|
|
|
5
5
|
[](https://img.shields.io/badge/Prettier-de9954?logo=prettier&logoColor=ffffff)
|
|
6
6
|
[](https://img.shields.io/github/license/luas10c/tsnite)
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
TypeScript runner for Node.js with watch mode, `tsconfig` path alias support, and extensionless TypeScript import resolution.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install --save-dev tsnite
|
|
10
14
|
```
|
|
11
15
|
|
|
12
|
-
|
|
16
|
+
## Usage
|
|
13
17
|
|
|
14
|
-
|
|
18
|
+
Run a TypeScript entry file:
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
```bash
|
|
21
|
+
npx tsnite src/index.ts
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Run in watch mode:
|
|
17
25
|
|
|
18
26
|
```bash
|
|
19
|
-
|
|
27
|
+
npx tsnite watch src/index.ts
|
|
20
28
|
```
|
|
21
29
|
|
|
22
|
-
|
|
30
|
+
Pass extra Node.js arguments after the entry file:
|
|
23
31
|
|
|
24
32
|
```bash
|
|
25
|
-
|
|
33
|
+
npx tsnite src/index.ts --env-file=.env
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Use in `package.json`:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"scripts": {
|
|
41
|
+
"dev": "tsnite src/index.ts",
|
|
42
|
+
"dev:watch": "tsnite watch src/index.ts"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
26
45
|
```
|
|
27
46
|
|
|
28
|
-
|
|
47
|
+
## Features
|
|
29
48
|
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
49
|
+
- Run `.ts`, `.tsx`, `.mts`, and `.cts` files directly in Node.js
|
|
50
|
+
- Watch mode with automatic restart
|
|
51
|
+
- Reads the current project's `tsconfig.json`
|
|
52
|
+
- Resolves `compilerOptions.paths`
|
|
53
|
+
- Resolves extensionless TypeScript imports
|
|
54
|
+
- ESM-friendly runtime
|
|
55
|
+
- Decorator support in transpilation
|
|
34
56
|
|
|
35
|
-
|
|
57
|
+
## Import Resolution
|
|
36
58
|
|
|
37
|
-
|
|
59
|
+
`tsnite` resolves TypeScript files without requiring explicit extensions.
|
|
38
60
|
|
|
39
|
-
|
|
61
|
+
These imports work:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import './server'
|
|
65
|
+
import './components/App'
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
It will try these TypeScript candidates:
|
|
69
|
+
|
|
70
|
+
- `./server.ts`
|
|
71
|
+
- `./server.tsx`
|
|
72
|
+
- `./server.mts`
|
|
73
|
+
- `./server.cts`
|
|
74
|
+
- `./server/index.ts`
|
|
75
|
+
- `./server/index.tsx`
|
|
76
|
+
- `./server/index.mts`
|
|
77
|
+
- `./server/index.cts`
|
|
78
|
+
|
|
79
|
+
Explicit JavaScript imports such as `.js`, `.mjs`, and `.cjs` are delegated to Node.js.
|
|
80
|
+
|
|
81
|
+
## `tsconfig` Paths
|
|
82
|
+
|
|
83
|
+
`tsnite` supports `compilerOptions.paths` aliases.
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"compilerOptions": {
|
|
88
|
+
"baseUrl": ".",
|
|
89
|
+
"paths": {
|
|
90
|
+
"#/*": ["src/*"]
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Then this works without an extension:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { getMetadata } from '#/common/metadata'
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
If `baseUrl` is not defined, `tsnite` uses the project root by default.
|
|
103
|
+
|
|
104
|
+
## Watch Mode
|
|
105
|
+
|
|
106
|
+
Customize watched paths and extensions:
|
|
40
107
|
|
|
41
108
|
```bash
|
|
42
|
-
npx tsnite
|
|
109
|
+
npx tsnite watch --include src --exclude dist,coverage,uploads --ext ts,tsx,js,jsx,json src/index.ts
|
|
43
110
|
```
|
|
44
111
|
|
|
45
|
-
|
|
112
|
+
Options:
|
|
113
|
+
|
|
114
|
+
- `--include <paths>` comma-separated paths to watch
|
|
115
|
+
- `--exclude <paths>` comma-separated paths to ignore
|
|
116
|
+
- `--ext <extensions>` comma-separated file extensions to watch
|
|
117
|
+
- `--source-root <path>` base path used by the watcher
|
|
118
|
+
|
|
119
|
+
Defaults:
|
|
120
|
+
|
|
121
|
+
- include: `.`
|
|
122
|
+
- exclude: `node_modules,.git,dist,build,coverage`
|
|
123
|
+
- ext: `ts,tsx,js,jsx,json`
|
|
124
|
+
- source root: `.`
|
|
46
125
|
|
|
47
|
-
##
|
|
126
|
+
## Example
|
|
48
127
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
128
|
+
`tsconfig.json`
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"compilerOptions": {
|
|
133
|
+
"target": "es2024",
|
|
134
|
+
"module": "es2022",
|
|
135
|
+
"moduleResolution": "bundler",
|
|
136
|
+
"baseUrl": ".",
|
|
137
|
+
"paths": {
|
|
138
|
+
"#/*": ["src/*"]
|
|
56
139
|
}
|
|
57
140
|
}
|
|
58
|
-
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`src/index.ts`
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { startServer } from '#/server/start'
|
|
148
|
+
|
|
149
|
+
await startServer()
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Run it:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
npx tsnite src/index.ts
|
|
156
|
+
```
|
|
59
157
|
|
|
60
|
-
##
|
|
158
|
+
## Notes
|
|
61
159
|
|
|
62
|
-
|
|
63
|
-
|
|
160
|
+
- `tsnite` is focused on development-time execution
|
|
161
|
+
- the current project's `tsconfig.json` is used
|
|
162
|
+
- watch mode clears cached resolution and transpilation state before restart
|
|
64
163
|
|
|
65
|
-
|
|
164
|
+
## Links
|
|
66
165
|
|
|
67
|
-
|
|
166
|
+
- npm: <https://www.npmjs.com/package/tsnite>
|
|
167
|
+
- repository: <https://github.com/luas10c/tsnite>
|
package/dist/cache.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { mkdir, rm, stat } from 'node:fs/promises';
|
|
3
|
-
import
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
4
|
export const resolveCache = new Map();
|
|
5
5
|
const statCache = new Map();
|
|
6
6
|
function getTranspileCacheDir() {
|
|
7
|
-
return
|
|
7
|
+
return join(import.meta.dirname, '..', 'node_modules', '.cache', 'tsnite');
|
|
8
8
|
}
|
|
9
9
|
function getTSConfigPath() {
|
|
10
|
-
return
|
|
10
|
+
return join(import.meta.dirname, '..', 'tsconfig.json');
|
|
11
11
|
}
|
|
12
12
|
function hash(value) {
|
|
13
13
|
return createHash('sha1').update(value).digest('hex');
|
|
@@ -34,7 +34,7 @@ export function invalidateStatCache(filePath) {
|
|
|
34
34
|
statCache.delete(filePath);
|
|
35
35
|
}
|
|
36
36
|
export function getTranspileCacheFile(filePath) {
|
|
37
|
-
return
|
|
37
|
+
return join(getTranspileCacheDir(), `${hash(filePath)}.json`);
|
|
38
38
|
}
|
|
39
39
|
export async function ensureTranspileCacheDir() {
|
|
40
40
|
await mkdir(getTranspileCacheDir(), { recursive: true });
|
|
@@ -43,7 +43,7 @@ export async function clearTranspileCache() {
|
|
|
43
43
|
await rm(getTranspileCacheDir(), { recursive: true, force: true });
|
|
44
44
|
}
|
|
45
45
|
export function isTSConfigPath(filePath) {
|
|
46
|
-
return
|
|
46
|
+
return resolve(filePath) === getTSConfigPath();
|
|
47
47
|
}
|
|
48
48
|
export async function invalidateFileCaches(filePath) {
|
|
49
49
|
invalidateStatCache(filePath);
|
package/dist/cli.js
CHANGED
|
@@ -38,7 +38,8 @@ process.on('SIGTERM', function () {
|
|
|
38
38
|
cleanup('SIGTERM');
|
|
39
39
|
});
|
|
40
40
|
function spawn(entry, nodeArgs) {
|
|
41
|
-
const
|
|
41
|
+
const entryPath = isAbsolute(entry) ? entry : resolve(process.cwd(), entry);
|
|
42
|
+
const child = fork(entryPath, {
|
|
42
43
|
stdio: 'inherit',
|
|
43
44
|
execArgv: [
|
|
44
45
|
'--enable-source-maps',
|
|
@@ -130,6 +131,7 @@ function createWatchConfig(options) {
|
|
|
130
131
|
};
|
|
131
132
|
}
|
|
132
133
|
async function handler(entry, options, nodeArgs, isWatch) {
|
|
134
|
+
const runtimeEntry = isAbsolute(entry) ? entry : resolve(process.cwd(), entry);
|
|
133
135
|
async function restart(reason) {
|
|
134
136
|
process.stdout.write('\x1Bc');
|
|
135
137
|
if (reason) {
|
|
@@ -154,14 +156,14 @@ async function handler(entry, options, nodeArgs, isWatch) {
|
|
|
154
156
|
}
|
|
155
157
|
}
|
|
156
158
|
clearResolveCache();
|
|
157
|
-
spawn(
|
|
159
|
+
spawn(runtimeEntry, nodeArgs);
|
|
158
160
|
}
|
|
159
161
|
const restartDebounced = debounce(restart, WATCH_DEBOUNCE_MS);
|
|
160
162
|
process.stdout.write('\x1Bc');
|
|
161
163
|
if (isWatch) {
|
|
162
164
|
console.log(yellow('Watching for changes...'));
|
|
163
165
|
}
|
|
164
|
-
spawn(
|
|
166
|
+
spawn(runtimeEntry, nodeArgs);
|
|
165
167
|
if (!isWatch)
|
|
166
168
|
return;
|
|
167
169
|
const { ignored, paths } = createWatchConfig(options);
|
package/dist/loader.js
CHANGED
|
@@ -4,7 +4,7 @@ import { readFile, stat, writeFile } from 'node:fs/promises';
|
|
|
4
4
|
import { extname, join, dirname } from 'node:path';
|
|
5
5
|
import { createHash } from 'node:crypto';
|
|
6
6
|
import { parse } from './parse.js';
|
|
7
|
-
import {
|
|
7
|
+
import { ensureTranspileCacheDir, existsWithCache, getTranspileCacheFile, resolveCache } from './cache.js';
|
|
8
8
|
const tsconfigCache = { paths: null, baseUrl: null };
|
|
9
9
|
const transpileCache = new Map();
|
|
10
10
|
const MAX_TRANSPILE_CACHE_ENTRIES = 256;
|
|
@@ -16,6 +16,57 @@ function isTypeScriptSpecifier(specifier) {
|
|
|
16
16
|
const extension = extname(specifier);
|
|
17
17
|
return extension === '' || hasTypeScriptExtension(extension);
|
|
18
18
|
}
|
|
19
|
+
function getTypeScriptTryFiles(basePath) {
|
|
20
|
+
return [
|
|
21
|
+
basePath,
|
|
22
|
+
...TS_EXTENSIONS.map((extension) => basePath + extension),
|
|
23
|
+
...TS_EXTENSIONS.map((extension) => join(basePath, 'index' + extension))
|
|
24
|
+
];
|
|
25
|
+
}
|
|
26
|
+
async function resolveTypeScriptFile(basePath) {
|
|
27
|
+
for (const file of getTypeScriptTryFiles(basePath)) {
|
|
28
|
+
if (await existsWithCache(file)) {
|
|
29
|
+
return file;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
function matchPathPattern(specifier, pattern) {
|
|
35
|
+
const starIndex = pattern.indexOf('*');
|
|
36
|
+
if (starIndex === -1) {
|
|
37
|
+
return specifier === pattern ? [] : null;
|
|
38
|
+
}
|
|
39
|
+
const prefix = pattern.slice(0, starIndex);
|
|
40
|
+
const suffix = pattern.slice(starIndex + 1);
|
|
41
|
+
if (!specifier.startsWith(prefix) || !specifier.endsWith(suffix)) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
return [specifier.slice(prefix.length, specifier.length - suffix.length)];
|
|
45
|
+
}
|
|
46
|
+
function applyPathMapping(target, matches) {
|
|
47
|
+
let mapped = target;
|
|
48
|
+
for (const match of matches) {
|
|
49
|
+
mapped = mapped.replace('*', match);
|
|
50
|
+
}
|
|
51
|
+
return mapped;
|
|
52
|
+
}
|
|
53
|
+
async function resolveTsConfigPath(specifier, paths, baseUrl) {
|
|
54
|
+
if (!paths || !baseUrl || !isTypeScriptSpecifier(specifier)) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
for (const [pattern, targets] of Object.entries(paths)) {
|
|
58
|
+
const matches = matchPathPattern(specifier, pattern);
|
|
59
|
+
if (!matches)
|
|
60
|
+
continue;
|
|
61
|
+
for (const target of targets) {
|
|
62
|
+
const mappedTarget = applyPathMapping(target, matches);
|
|
63
|
+
const resolved = await resolveTypeScriptFile(join(baseUrl, mappedTarget));
|
|
64
|
+
if (resolved)
|
|
65
|
+
return resolved;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
19
70
|
function hash(value) {
|
|
20
71
|
return createHash('sha1').update(value).digest('hex');
|
|
21
72
|
}
|
|
@@ -76,24 +127,33 @@ async function loadTSConfig() {
|
|
|
76
127
|
return tsconfigCache;
|
|
77
128
|
}
|
|
78
129
|
try {
|
|
79
|
-
const data = await readFile(join(
|
|
130
|
+
const data = await readFile(join(process.cwd(), 'tsconfig.json'), 'utf-8');
|
|
80
131
|
const { compilerOptions } = parse(data);
|
|
81
132
|
const paths = compilerOptions?.paths ?? null;
|
|
82
133
|
const baseUrl = compilerOptions?.baseUrl;
|
|
83
134
|
tsconfigCache.paths = paths || null;
|
|
84
135
|
tsconfigCache.baseUrl =
|
|
85
|
-
baseUrl ?
|
|
86
|
-
join(import.meta.dirname, '..', baseUrl)
|
|
87
|
-
: join(import.meta.dirname, '..');
|
|
136
|
+
baseUrl ? join(process.cwd(), baseUrl) : process.cwd();
|
|
88
137
|
return tsconfigCache;
|
|
89
138
|
}
|
|
90
139
|
catch {
|
|
91
140
|
tsconfigCache.paths = null;
|
|
92
|
-
tsconfigCache.baseUrl =
|
|
141
|
+
tsconfigCache.baseUrl = process.cwd();
|
|
93
142
|
return tsconfigCache;
|
|
94
143
|
}
|
|
95
144
|
}
|
|
96
145
|
export async function resolve(specifier, ctx, next) {
|
|
146
|
+
const { paths, baseUrl } = await loadTSConfig();
|
|
147
|
+
const resolvedTsConfigPath = await resolveTsConfigPath(specifier, paths, baseUrl);
|
|
148
|
+
if (resolvedTsConfigPath) {
|
|
149
|
+
const url = pathToFileURL(resolvedTsConfigPath).href;
|
|
150
|
+
resolveCache.set(`${ctx.parentURL}::${specifier}`, url);
|
|
151
|
+
return {
|
|
152
|
+
url,
|
|
153
|
+
format: 'module',
|
|
154
|
+
shortCircuit: true
|
|
155
|
+
};
|
|
156
|
+
}
|
|
97
157
|
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
98
158
|
return next(specifier, ctx);
|
|
99
159
|
}
|
|
@@ -153,7 +213,10 @@ export async function load(url, ctx, next) {
|
|
|
153
213
|
const { paths, baseUrl } = await loadTSConfig();
|
|
154
214
|
const filename = fileURLToPath(url);
|
|
155
215
|
const fileStats = await stat(filename);
|
|
156
|
-
const configHash = hash(JSON.stringify({
|
|
216
|
+
const configHash = hash(JSON.stringify({
|
|
217
|
+
baseUrl: baseUrl || process.cwd(),
|
|
218
|
+
paths: paths ?? {}
|
|
219
|
+
}));
|
|
157
220
|
const cachedCode = await readCachedTranspile(filename, fileStats.mtimeMs, fileStats.size, configHash);
|
|
158
221
|
if (cachedCode !== null) {
|
|
159
222
|
return { format: 'module', source: cachedCode, shortCircuit: true };
|
|
@@ -198,12 +261,3 @@ export async function load(url, ctx, next) {
|
|
|
198
261
|
});
|
|
199
262
|
return { format: 'module', source: code, shortCircuit: true };
|
|
200
263
|
}
|
|
201
|
-
export async function resetLoaderState(options) {
|
|
202
|
-
tsconfigCache.paths = null;
|
|
203
|
-
tsconfigCache.baseUrl = null;
|
|
204
|
-
transpileCache.clear();
|
|
205
|
-
resolveCache.clear();
|
|
206
|
-
if (!options?.preserveTranspileCache) {
|
|
207
|
-
await clearTranspileCache();
|
|
208
|
-
}
|
|
209
|
-
}
|