@poppinss/ts-exec 1.4.3 → 1.5.0

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 CHANGED
@@ -189,6 +189,22 @@ If you have `rewriteRelativeImportExtensions` enabled in your TypeScript configu
189
189
 
190
190
  <br />
191
191
 
192
+ ### Files loaded using `require`
193
+
194
+ TypeScript files loaded using `require` (for example, via `createRequire`) are also compiled by `ts-exec`, so enums and decorators work inside them. The same file resolution rules apply to them as well. You must enable the `rewriteRelativeImportExtensions` compiler option to require a file using its TypeScript extension.
195
+
196
+ ```ts
197
+ import { createRequire } from 'node:module'
198
+ const require = createRequire(import.meta.url)
199
+
200
+ // Needs "rewriteRelativeImportExtensions"
201
+ const { Role } = require('./role.cts')
202
+ ```
203
+
204
+ The `.jsx` and `.tsx` files are always compiled as ES modules, including when they are loaded using `require`. Therefore, they cannot use `module.exports` or other CommonJS globals.
205
+
206
+ <br />
207
+
192
208
  ## 🤔 Why not tsx?
193
209
 
194
210
  If you're currently using `tsx`, you might wonder why to switch. The answer depends on your priorities.
package/build/index.js CHANGED
@@ -1,4 +1,230 @@
1
1
  // index.ts
2
- import { register } from "module";
2
+ import { registerHooks } from "module";
3
+
4
+ // src/loader.ts
5
+ import { extname } from "path";
6
+ import { fileURLToPath as fileURLToPath2 } from "url";
7
+
8
+ // src/debug.ts
9
+ import { debuglog } from "util";
10
+ var debug_default = debuglog("ts-exec");
11
+
12
+ // src/get_config.ts
13
+ import { fileURLToPath } from "url";
14
+ import { getTsconfig } from "get-tsconfig";
15
+ var DEFAULT_CONFIG = {
16
+ sourceMaps: true,
17
+ module: {
18
+ type: "es6"
19
+ },
20
+ jsc: {
21
+ target: "esnext",
22
+ externalHelpers: false,
23
+ parser: {
24
+ syntax: "typescript",
25
+ tsx: true,
26
+ decorators: true,
27
+ dynamicImport: true
28
+ },
29
+ transform: {
30
+ decoratorMetadata: true,
31
+ legacyDecorator: true,
32
+ verbatimModuleSyntax: true,
33
+ useDefineForClassFields: true,
34
+ react: {
35
+ runtime: "automatic"
36
+ }
37
+ },
38
+ experimental: {
39
+ keepImportAttributes: true
40
+ },
41
+ keepClassNames: true
42
+ }
43
+ };
44
+ function tsConfigToSwcConfig(tsConfig2) {
45
+ const tsTarget = tsConfig2.compilerOptions?.target?.toLowerCase();
46
+ return {
47
+ sourceMaps: tsConfig2.compilerOptions?.sourceMap ?? true,
48
+ module: {
49
+ /**
50
+ * Since, we will run the SWC transformer via Nodejs loader hooks, the
51
+ * entry point will always be ES6
52
+ */
53
+ type: "es6"
54
+ },
55
+ jsc: {
56
+ target: tsTarget ? tsTarget === "es6" ? "esnext" : tsTarget : "esnext",
57
+ baseUrl: tsConfig2.compilerOptions?.baseUrl,
58
+ externalHelpers: false,
59
+ parser: {
60
+ syntax: "typescript",
61
+ tsx: !!tsConfig2.compilerOptions?.jsx,
62
+ decorators: true,
63
+ dynamicImport: true
64
+ },
65
+ transform: {
66
+ ...tsConfig2.compilerOptions?.emitDecoratorMetadata ? {
67
+ decoratorMetadata: true
68
+ } : {},
69
+ ...tsConfig2.compilerOptions?.experimentalDecorators ? {
70
+ legacyDecorator: true
71
+ } : {},
72
+ ...tsConfig2.compilerOptions?.verbatimModuleSyntax ? {
73
+ verbatimModuleSyntax: true
74
+ } : {},
75
+ ...tsConfig2.compilerOptions?.useDefineForClassFields ? {
76
+ useDefineForClassFields: true
77
+ } : {},
78
+ react: {
79
+ runtime: "automatic",
80
+ ...tsConfig2.compilerOptions?.jsxImportSource ? {
81
+ importSource: tsConfig2.compilerOptions?.jsxImportSource
82
+ } : {}
83
+ }
84
+ },
85
+ experimental: {
86
+ keepImportAttributes: true
87
+ },
88
+ keepClassNames: true
89
+ }
90
+ };
91
+ }
92
+ function getConfig(searchPath) {
93
+ const tsConfig2 = getTsconfig(
94
+ typeof searchPath === "string" ? searchPath : fileURLToPath(searchPath)
95
+ );
96
+ if (!tsConfig2) {
97
+ debug_default("Unable to locate tsconfig file");
98
+ return {
99
+ tsConfig: null,
100
+ tsConfigPath: null,
101
+ swcConfig: {
102
+ ...DEFAULT_CONFIG
103
+ }
104
+ };
105
+ }
106
+ debug_default("tsConfig %O", tsConfig2.config);
107
+ const swcConfig2 = tsConfigToSwcConfig(tsConfig2.config);
108
+ debug_default("swcConfig %O", swcConfig2);
109
+ return { tsConfig: tsConfig2.config, tsConfigPath: tsConfig2.path, swcConfig: swcConfig2 };
110
+ }
111
+
112
+ // src/transform.ts
113
+ import { transformSync as swcTransformSync } from "@swc/core";
114
+ function transformSync(source, options) {
115
+ const input = `${source ?? ""}`;
116
+ const { format, ...rest } = options;
117
+ rest.module.type = format === "commonjs-typescript" ? "commonjs" : "es6";
118
+ return swcTransformSync(input, rest);
119
+ }
120
+
121
+ // src/loader.ts
122
+ var swcConfig;
123
+ var tsConfig;
124
+ function isTypeScriptFile(filePath) {
125
+ return !filePath.endsWith(".d.ts") && (filePath.endsWith(".ts") || filePath.endsWith(".tsx") || filePath.endsWith(".mts") || filePath.endsWith(".cts"));
126
+ }
127
+ function isLocalPath(specifier) {
128
+ return specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/") || specifier.startsWith("file:");
129
+ }
130
+ function wrapAndReThrowSwcError(error) {
131
+ const errorHints = `${error.filename}:${error.startLine}
132
+ ${error.snippet}
133
+ `;
134
+ switch (error.code) {
135
+ case "UnsupportedSyntax": {
136
+ const unsupportedSyntaxError = new Error(error.message);
137
+ unsupportedSyntaxError.name = "UnsupportedSyntaxError";
138
+ unsupportedSyntaxError.stack = `${errorHints}${unsupportedSyntaxError.stack}`;
139
+ throw unsupportedSyntaxError;
140
+ }
141
+ case "InvalidSyntax": {
142
+ const syntaxError = new SyntaxError(error.message);
143
+ syntaxError.stack = `${errorHints}${syntaxError.stack}`;
144
+ throw syntaxError;
145
+ }
146
+ default:
147
+ throw new Error(error.message);
148
+ }
149
+ }
150
+ function initialize() {
151
+ const searchPath = process.env.TS_EXEC_PWD ?? process.cwd();
152
+ const config = getConfig(searchPath);
153
+ swcConfig = config.swcConfig;
154
+ tsConfig = config.tsConfig;
155
+ }
156
+ var resolve = (specifier, context, nextResolve) => {
157
+ debug_default("resolving specifier %s", specifier);
158
+ const [pathname] = specifier.split("?");
159
+ if (!tsConfig?.compilerOptions?.rewriteRelativeImportExtensions && context.parentURL && isTypeScriptFile(pathname)) {
160
+ throw new Error(
161
+ `Cannot import "${specifier}" using "${extname(pathname)}" extension. Enable "compilerOptions.rewriteRelativeImportExtensions" to import TypeScript files`
162
+ );
163
+ }
164
+ try {
165
+ return nextResolve(specifier, context);
166
+ } catch (error) {
167
+ if (error.code === "ERR_MODULE_NOT_FOUND" && error.url && isLocalPath(error.url)) {
168
+ let [urlPathname, ...urlQs] = error.url.split("?");
169
+ const fileExtension = extname(urlPathname);
170
+ const testExtensions = [];
171
+ if ([".js", ".mjs", ".cjs", ".jsx"].includes(fileExtension)) {
172
+ testExtensions.push(fileExtension.replace("js", "ts"));
173
+ }
174
+ if (fileExtension === ".js") {
175
+ testExtensions.push(".tsx");
176
+ }
177
+ for (const ext of testExtensions) {
178
+ const tryPath = urlPathname.slice(0, -fileExtension.length) + ext;
179
+ try {
180
+ const url = urlQs.length ? `${tryPath}?${urlQs.join("?")}` : tryPath;
181
+ debug_default("retrying to resolve file with specifier %s", url);
182
+ return nextResolve(url, context);
183
+ } catch {
184
+ }
185
+ }
186
+ }
187
+ throw error;
188
+ }
189
+ };
190
+ var load = function load2(url, context, nextLoad) {
191
+ const { format } = context;
192
+ debug_default("load file %s, %O", url, context);
193
+ if (format && (format === "module-typescript" || format === "commonjs-typescript") || url.endsWith(".tsx") || url.endsWith(".jsx")) {
194
+ try {
195
+ const { source } = nextLoad(url, {
196
+ ...context,
197
+ format: "module"
198
+ });
199
+ const { code, map } = transformSync(source.toString(), {
200
+ format: format ?? "module-tsx",
201
+ filename: fileURLToPath2(url),
202
+ ...swcConfig
203
+ });
204
+ let output = code;
205
+ if (map) {
206
+ const base64SourceMap = Buffer.from(map).toString("base64");
207
+ output = `${code}
208
+
209
+ //# sourceMappingURL=data:application/json;base64,${base64SourceMap}`;
210
+ }
211
+ return {
212
+ format: format ? format.replace("-typescript", "") : "module",
213
+ source: `${output}
214
+
215
+ //# sourceURL=${url}`
216
+ };
217
+ } catch (error) {
218
+ if (error.code !== void 0) {
219
+ wrapAndReThrowSwcError(error);
220
+ }
221
+ throw error;
222
+ }
223
+ }
224
+ return nextLoad(url, context);
225
+ };
226
+
227
+ // index.ts
3
228
  process.setSourceMapsEnabled(true);
4
- register("./src/loader.js", import.meta.url);
229
+ initialize();
230
+ registerHooks({ resolve, load });
@@ -1,2 +1,2 @@
1
- declare const _default: import("util").DebugLogger;
1
+ declare const _default: import("node:util").DebugLogger;
2
2
  export default _default;
@@ -1,8 +1,9 @@
1
- import type { LoadHook, ResolveHook } from 'node:module';
1
+ import type { LoadHookSync, ResolveHookSync } from 'node:module';
2
2
  /**
3
- * Initialize hook to load different config files
3
+ * Loads the tsconfig file and computes the SWC config. Must be called
4
+ * before registering the hooks
4
5
  */
5
- export declare function initialize(): Promise<void>;
6
+ export declare function initialize(): void;
6
7
  /**
7
8
  * Resolve hook to rewrite ".js" imports to ".ts" file only when
8
9
  * "allowArbitraryExtensions" is not enabled. Otherwise, we expect
@@ -13,8 +14,11 @@ export declare function initialize(): Promise<void>;
13
14
  * For example, someone registers a loader to import files from Github and
14
15
  * our extension rewrite checks for same file on disk, hence it will fail.
15
16
  */
16
- export declare const resolve: ResolveHook;
17
+ export declare const resolve: ResolveHookSync;
17
18
  /**
18
- * Load hook to compile TypeScript and JSX files on the fly
19
+ * Load hook to compile TypeScript and JSX files on the fly.
20
+ *
21
+ * Node.js does not report a format for ".jsx" and ".tsx" files, hence
22
+ * they are always compiled as ES modules, even when loaded via "require".
19
23
  */
20
- export declare const load: LoadHook;
24
+ export declare const load: LoadHookSync;
@@ -5,4 +5,4 @@ import { type Config as SwcConfig } from '@swc/core';
5
5
  export declare function transformSync(source: string, options: SwcConfig & {
6
6
  filename: string;
7
7
  format: 'module-typescript' | 'commonjs-typescript' | 'module-tsx';
8
- }): Promise<import("@swc/types").Output>;
8
+ }): import("@swc/types").Output;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@poppinss/ts-exec",
3
3
  "description": "TypeScript JIT compiler built for correctness and supports legacy/experimental decorators",
4
- "version": "1.4.3",
4
+ "version": "1.5.0",
5
5
  "engines": {
6
6
  "node": ">=24.0.0"
7
7
  },
@@ -27,33 +27,33 @@
27
27
  "version": "npm run build",
28
28
  "prepublishOnly": "npm run build",
29
29
  "release": "release-it",
30
- "quick:test": "node --experimental-transform-types --enable-source-maps bin/test.ts"
30
+ "quick:test": "node --enable-source-maps bin/test.ts"
31
31
  },
32
32
  "devDependencies": {
33
- "@adonisjs/eslint-config": "^3.0.0-next.0",
34
- "@adonisjs/prettier-config": "^1.4.5",
35
- "@adonisjs/tsconfig": "^2.0.0-next.0",
36
- "@japa/assert": "^4.1.1",
37
- "@japa/expect": "^3.0.6",
38
- "@japa/expect-type": "^2.0.3",
39
- "@japa/file-system": "^2.3.2",
40
- "@japa/runner": "^4.4.0",
41
- "@japa/snapshot": "^2.0.9",
42
- "@release-it/conventional-changelog": "^10.0.2",
43
- "@types/node": "^24.10.1",
44
- "c8": "^10.1.3",
45
- "eslint": "^9.39.1",
46
- "ipaddr.js": "^2.3.0",
47
- "preact": "^10.28.0",
48
- "preact-render-to-string": "^6.6.3",
49
- "prettier": "^3.7.4",
50
- "release-it": "^19.0.6",
33
+ "@adonisjs/eslint-config": "^3.1.0",
34
+ "@adonisjs/prettier-config": "^1.5.0",
35
+ "@adonisjs/tsconfig": "^2.0.0",
36
+ "@japa/assert": "^4.2.0",
37
+ "@japa/expect": "^3.0.7",
38
+ "@japa/expect-type": "^2.0.4",
39
+ "@japa/file-system": "^3.0.0",
40
+ "@japa/runner": "^5.3.0",
41
+ "@japa/snapshot": "^2.0.10",
42
+ "@release-it/conventional-changelog": "^12.0.2",
43
+ "@types/node": "^26.6.4",
44
+ "c8": "^12.0.0",
45
+ "eslint": "^10.12.0",
46
+ "ipaddr.js": "^2.5.0",
47
+ "preact": "^11.0.0",
48
+ "preact-render-to-string": "^6.8.0",
49
+ "prettier": "^3.9.9",
50
+ "release-it": "^21.1.1",
51
51
  "tsup": "^8.5.1",
52
- "typescript": "^5.9.3"
52
+ "typescript": "^6.0.3"
53
53
  },
54
54
  "dependencies": {
55
- "@swc/core": "1.15.3",
56
- "get-tsconfig": "^4.13.0"
55
+ "@swc/core": "~1.16.13",
56
+ "get-tsconfig": "^4.14.3"
57
57
  },
58
58
  "homepage": "https://github.com/poppinss/ts-exec#readme",
59
59
  "repository": {
@@ -77,8 +77,7 @@
77
77
  },
78
78
  "tsup": {
79
79
  "entry": [
80
- "index.ts",
81
- "src/loader.ts"
80
+ "index.ts"
82
81
  ],
83
82
  "outDir": "./build",
84
83
  "clean": true,
@@ -1,223 +0,0 @@
1
- // src/loader.ts
2
- import { extname } from "path";
3
- import { fileURLToPath as fileURLToPath2 } from "url";
4
-
5
- // src/debug.ts
6
- import { debuglog } from "util";
7
- var debug_default = debuglog("ts-exec");
8
-
9
- // src/get_config.ts
10
- import { fileURLToPath } from "url";
11
- import { getTsconfig } from "get-tsconfig";
12
- var DEFAULT_CONFIG = {
13
- sourceMaps: true,
14
- module: {
15
- type: "es6"
16
- },
17
- jsc: {
18
- target: "esnext",
19
- externalHelpers: false,
20
- parser: {
21
- syntax: "typescript",
22
- tsx: true,
23
- decorators: true,
24
- dynamicImport: true
25
- },
26
- transform: {
27
- decoratorMetadata: true,
28
- legacyDecorator: true,
29
- verbatimModuleSyntax: true,
30
- useDefineForClassFields: true,
31
- react: {
32
- runtime: "automatic"
33
- }
34
- },
35
- experimental: {
36
- keepImportAttributes: true
37
- },
38
- keepClassNames: true
39
- }
40
- };
41
- function tsConfigToSwcConfig(tsConfig2) {
42
- const tsTarget = tsConfig2.compilerOptions?.target?.toLowerCase();
43
- return {
44
- sourceMaps: tsConfig2.compilerOptions?.sourceMap ?? true,
45
- module: {
46
- /**
47
- * Since, we will run the SWC transformer via Nodejs loader hooks, the
48
- * entry point will always be ES6
49
- */
50
- type: "es6"
51
- },
52
- jsc: {
53
- target: tsTarget ? tsTarget === "es6" ? "esnext" : tsTarget : "esnext",
54
- baseUrl: tsConfig2.compilerOptions?.baseUrl,
55
- externalHelpers: false,
56
- parser: {
57
- syntax: "typescript",
58
- tsx: !!tsConfig2.compilerOptions?.jsx,
59
- decorators: true,
60
- dynamicImport: true
61
- },
62
- transform: {
63
- ...tsConfig2.compilerOptions?.emitDecoratorMetadata ? {
64
- decoratorMetadata: true
65
- } : {},
66
- ...tsConfig2.compilerOptions?.experimentalDecorators ? {
67
- legacyDecorator: true
68
- } : {},
69
- ...tsConfig2.compilerOptions?.verbatimModuleSyntax ? {
70
- verbatimModuleSyntax: true
71
- } : {},
72
- ...tsConfig2.compilerOptions?.useDefineForClassFields ? {
73
- useDefineForClassFields: true
74
- } : {},
75
- react: {
76
- runtime: "automatic",
77
- ...tsConfig2.compilerOptions?.jsxImportSource ? {
78
- importSource: tsConfig2.compilerOptions?.jsxImportSource
79
- } : {}
80
- }
81
- },
82
- experimental: {
83
- keepImportAttributes: true
84
- },
85
- keepClassNames: true
86
- }
87
- };
88
- }
89
- function getConfig(searchPath) {
90
- const tsConfig2 = getTsconfig(
91
- typeof searchPath === "string" ? searchPath : fileURLToPath(searchPath)
92
- );
93
- if (!tsConfig2) {
94
- debug_default("Unable to locate tsconfig file");
95
- return {
96
- tsConfig: null,
97
- tsConfigPath: null,
98
- swcConfig: {
99
- ...DEFAULT_CONFIG
100
- }
101
- };
102
- }
103
- debug_default("tsConfig %O", tsConfig2.config);
104
- const swcConfig2 = tsConfigToSwcConfig(tsConfig2.config);
105
- debug_default("swcConfig %O", swcConfig2);
106
- return { tsConfig: tsConfig2.config, tsConfigPath: tsConfig2.path, swcConfig: swcConfig2 };
107
- }
108
-
109
- // src/transform.ts
110
- import { transform as swcTransform } from "@swc/core";
111
- function transformSync(source, options) {
112
- const input = `${source ?? ""}`;
113
- const { format, ...rest } = options;
114
- rest.module.type = format === "commonjs-typescript" ? "commonjs" : "es6";
115
- return swcTransform(input, rest);
116
- }
117
-
118
- // src/loader.ts
119
- var swcConfig;
120
- var tsConfig;
121
- function isTypeScriptFile(filePath) {
122
- return !filePath.endsWith(".d.ts") && (filePath.endsWith(".ts") || filePath.endsWith(".tsx") || filePath.endsWith(".mts") || filePath.endsWith(".cts"));
123
- }
124
- function isLocalPath(specifier) {
125
- return specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/") || specifier.startsWith("file:");
126
- }
127
- function wrapAndReThrowSwcError(error) {
128
- const errorHints = `${error.filename}:${error.startLine}
129
- ${error.snippet}
130
- `;
131
- switch (error.code) {
132
- case "UnsupportedSyntax": {
133
- const unsupportedSyntaxError = new Error(error.message);
134
- unsupportedSyntaxError.name = "UnsupportedSyntaxError";
135
- unsupportedSyntaxError.stack = `${errorHints}${unsupportedSyntaxError.stack}`;
136
- throw unsupportedSyntaxError;
137
- }
138
- case "InvalidSyntax": {
139
- const syntaxError = new SyntaxError(error.message);
140
- syntaxError.stack = `${errorHints}${syntaxError.stack}`;
141
- throw syntaxError;
142
- }
143
- default:
144
- throw new Error(error.message);
145
- }
146
- }
147
- async function initialize() {
148
- const searchPath = process.env.TS_EXEC_PWD ?? process.cwd();
149
- const config = getConfig(searchPath);
150
- swcConfig = config.swcConfig;
151
- tsConfig = config.tsConfig;
152
- }
153
- var resolve = async (specifier, context, nextResolve) => {
154
- debug_default("resolving specifier %s", specifier);
155
- const [pathname] = specifier.split("?");
156
- if (!tsConfig?.compilerOptions?.rewriteRelativeImportExtensions && context.parentURL && isTypeScriptFile(pathname)) {
157
- throw new Error(
158
- `Cannot import "${specifier}" using "${extname(pathname)}" extension. Enable "compilerOptions.rewriteRelativeImportExtensions" to import TypeScript files`
159
- );
160
- }
161
- try {
162
- return await nextResolve(specifier, context);
163
- } catch (error) {
164
- if (error.code === "ERR_MODULE_NOT_FOUND" && error.url && isLocalPath(error.url)) {
165
- let [urlPathname, ...urlQs] = error.url.split("?");
166
- const fileExtension = extname(urlPathname);
167
- if (fileExtension === ".js") {
168
- urlPathname = urlPathname.replace(".js", ".ts");
169
- } else if (fileExtension === ".mjs") {
170
- urlPathname = urlPathname.replace(".mjs", ".mts");
171
- } else if (fileExtension === ".cjs") {
172
- urlPathname = urlPathname.replace(".cjs", ".cts");
173
- } else if (fileExtension === ".jsx") {
174
- urlPathname = urlPathname.replace(".jsx", ".tsx");
175
- }
176
- const url = urlQs.length ? `${urlPathname}?${urlQs.join("?")}` : urlPathname;
177
- debug_default("retrying to resolve file with specifier %s", url);
178
- return nextResolve(url, context);
179
- }
180
- throw error;
181
- }
182
- };
183
- var load = async function load2(url, context, nextLoad) {
184
- const { format } = context;
185
- debug_default("load file %s, %O", url, context);
186
- if (format && (format === "module-typescript" || format === "commonjs-typescript") || url.endsWith(".tsx") || url.endsWith(".jsx")) {
187
- try {
188
- const { source } = await nextLoad(url, {
189
- ...context,
190
- format: "module"
191
- });
192
- const { code, map } = await transformSync(source.toString(), {
193
- format: format ?? "module-tsx",
194
- filename: fileURLToPath2(url),
195
- ...swcConfig
196
- });
197
- let output = code;
198
- if (map) {
199
- const base64SourceMap = Buffer.from(map).toString("base64");
200
- output = `${code}
201
-
202
- //# sourceMappingURL=data:application/json;base64,${base64SourceMap}`;
203
- }
204
- return {
205
- format: format ? format.replace("-typescript", "") : "module",
206
- source: `${output}
207
-
208
- //# sourceURL=${url}`
209
- };
210
- } catch (error) {
211
- if (error.code !== void 0) {
212
- wrapAndReThrowSwcError(error);
213
- }
214
- throw error;
215
- }
216
- }
217
- return nextLoad(url, context);
218
- };
219
- export {
220
- initialize,
221
- load,
222
- resolve
223
- };