@ttsc/unplugin 0.12.2 → 0.12.4
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/lib/api.d.ts +7 -0
- package/lib/api.js +1 -0
- package/lib/api.js.map +1 -1
- package/lib/api.mjs +1 -1
- package/lib/bun.d.ts +30 -0
- package/lib/bun.js +11 -2
- package/lib/bun.js.map +1 -1
- package/lib/bun.mjs +11 -2
- package/lib/bun.mjs.map +1 -1
- package/lib/core/index.d.ts +6 -0
- package/lib/core/index.js +28 -0
- package/lib/core/index.js.map +1 -1
- package/lib/core/index.mjs +28 -1
- package/lib/core/index.mjs.map +1 -1
- package/lib/core/options.d.ts +22 -0
- package/lib/core/options.js +7 -0
- package/lib/core/options.js.map +1 -1
- package/lib/core/options.mjs +7 -0
- package/lib/core/options.mjs.map +1 -1
- package/lib/core/transform.d.ts +73 -0
- package/lib/core/transform.js +122 -0
- package/lib/core/transform.js.map +1 -1
- package/lib/core/transform.mjs +122 -0
- package/lib/core/transform.mjs.map +1 -1
- package/lib/index.d.ts +9 -0
- package/lib/index.js +9 -1
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +9 -1
- package/lib/index.mjs.map +1 -1
- package/lib/next.d.ts +29 -0
- package/lib/next.js +14 -0
- package/lib/next.js.map +1 -1
- package/lib/next.mjs +14 -0
- package/lib/next.mjs.map +1 -1
- package/package.json +3 -3
- package/src/api.ts +7 -0
- package/src/bun.ts +32 -2
- package/src/core/index.ts +28 -1
- package/src/core/options.ts +22 -0
- package/src/core/transform.ts +156 -0
- package/src/index.ts +9 -0
- package/src/next.ts +30 -0
package/lib/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../src/index.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAAA;;;;;;;;AAQG;;;;"}
|
package/lib/next.d.ts
CHANGED
|
@@ -1,8 +1,37 @@
|
|
|
1
1
|
import type { TtscUnpluginOptions } from "./core/options";
|
|
2
|
+
/**
|
|
3
|
+
* Minimal structural type for a Next.js configuration object.
|
|
4
|
+
*
|
|
5
|
+
* Only the `webpack` field is used by this adapter; all other Next.js options
|
|
6
|
+
* are forwarded as-is through the spread operator.
|
|
7
|
+
*/
|
|
2
8
|
export type NextLikeConfig = Record<string, unknown> & {
|
|
9
|
+
/**
|
|
10
|
+
* Optional existing webpack customisation hook. When the caller has already
|
|
11
|
+
* defined one, `next()` will chain through to it after injecting the ttsc
|
|
12
|
+
* webpack plugin.
|
|
13
|
+
*/
|
|
3
14
|
webpack?: (config: WebpackLikeConfig, options: unknown) => WebpackLikeConfig;
|
|
4
15
|
};
|
|
16
|
+
/**
|
|
17
|
+
* Minimal structural type for a webpack configuration object as seen by the
|
|
18
|
+
* Next.js `webpack` hook callback.
|
|
19
|
+
*/
|
|
5
20
|
export type WebpackLikeConfig = Record<string, unknown> & {
|
|
21
|
+
/** The webpack plugin array; initialised to `[]` by this adapter if absent. */
|
|
6
22
|
plugins?: unknown[];
|
|
7
23
|
};
|
|
24
|
+
/**
|
|
25
|
+
* Wrap a Next.js config object so that the ttsc webpack plugin is injected into
|
|
26
|
+
* every webpack build Next.js performs.
|
|
27
|
+
*
|
|
28
|
+
* The adapter uses `unshift` to ensure the ttsc plugin runs before any other
|
|
29
|
+
* plugins in the array (unplugin `enforce: "pre"` semantics). An existing
|
|
30
|
+
* `nextConfig.webpack` hook is preserved and called after the plugin is
|
|
31
|
+
* injected.
|
|
32
|
+
*
|
|
33
|
+
* @param nextConfig - The caller's existing Next.js config (spread into the
|
|
34
|
+
* returned object unchanged, except for `webpack`).
|
|
35
|
+
* @param options - Ttsc plugin options forwarded to the webpack adapter.
|
|
36
|
+
*/
|
|
8
37
|
export default function next(nextConfig?: NextLikeConfig, options?: TtscUnpluginOptions): NextLikeConfig;
|
package/lib/next.js
CHANGED
|
@@ -4,11 +4,25 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
4
4
|
|
|
5
5
|
var webpack = require('./webpack.js');
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Wrap a Next.js config object so that the ttsc webpack plugin is injected into
|
|
9
|
+
* every webpack build Next.js performs.
|
|
10
|
+
*
|
|
11
|
+
* The adapter uses `unshift` to ensure the ttsc plugin runs before any other
|
|
12
|
+
* plugins in the array (unplugin `enforce: "pre"` semantics). An existing
|
|
13
|
+
* `nextConfig.webpack` hook is preserved and called after the plugin is
|
|
14
|
+
* injected.
|
|
15
|
+
*
|
|
16
|
+
* @param nextConfig - The caller's existing Next.js config (spread into the
|
|
17
|
+
* returned object unchanged, except for `webpack`).
|
|
18
|
+
* @param options - Ttsc plugin options forwarded to the webpack adapter.
|
|
19
|
+
*/
|
|
7
20
|
function next(nextConfig = {}, options) {
|
|
8
21
|
return {
|
|
9
22
|
...nextConfig,
|
|
10
23
|
webpack(config, webpackOptions) {
|
|
11
24
|
config.plugins = Array.isArray(config.plugins) ? config.plugins : [];
|
|
25
|
+
// Prepend so ttsc runs before any user-added plugins.
|
|
12
26
|
config.plugins.unshift(webpack.default(options));
|
|
13
27
|
if (typeof nextConfig.webpack === "function") {
|
|
14
28
|
return nextConfig.webpack(config, webpackOptions);
|
package/lib/next.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"next.js","sources":["../src/next.ts"],"sourcesContent":[null],"names":["webpack"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"next.js","sources":["../src/next.ts"],"sourcesContent":[null],"names":["webpack"],"mappings":";;;;;;AA2BA;;;;;;;;;;;;AAYG;AACW,SAAU,IAAI,CAC1B,UAAA,GAA6B,EAAE,EAC/B,OAA6B,EAAA;IAE7B,OAAO;AACL,QAAA,GAAG,UAAU;QACb,OAAO,CAAC,MAAyB,EAAE,cAAuB,EAAA;YACxD,MAAM,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,MAAM,CAAC,OAAO,GAAG,EAAE;;YAEpE,MAAM,CAAC,OAAO,CAAC,OAAO,CAACA,eAAO,CAAC,OAAO,CAAC,CAAC;AACxC,YAAA,IAAI,OAAO,UAAU,CAAC,OAAO,KAAK,UAAU,EAAE;gBAC5C,OAAO,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,cAAc,CAAC;YACnD;AACA,YAAA,OAAO,MAAM;QACf,CAAC;KACF;AACH;;;;"}
|
package/lib/next.mjs
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import webpack from './webpack.mjs';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Wrap a Next.js config object so that the ttsc webpack plugin is injected into
|
|
5
|
+
* every webpack build Next.js performs.
|
|
6
|
+
*
|
|
7
|
+
* The adapter uses `unshift` to ensure the ttsc plugin runs before any other
|
|
8
|
+
* plugins in the array (unplugin `enforce: "pre"` semantics). An existing
|
|
9
|
+
* `nextConfig.webpack` hook is preserved and called after the plugin is
|
|
10
|
+
* injected.
|
|
11
|
+
*
|
|
12
|
+
* @param nextConfig - The caller's existing Next.js config (spread into the
|
|
13
|
+
* returned object unchanged, except for `webpack`).
|
|
14
|
+
* @param options - Ttsc plugin options forwarded to the webpack adapter.
|
|
15
|
+
*/
|
|
3
16
|
function next(nextConfig = {}, options) {
|
|
4
17
|
return {
|
|
5
18
|
...nextConfig,
|
|
6
19
|
webpack(config, webpackOptions) {
|
|
7
20
|
config.plugins = Array.isArray(config.plugins) ? config.plugins : [];
|
|
21
|
+
// Prepend so ttsc runs before any user-added plugins.
|
|
8
22
|
config.plugins.unshift(webpack(options));
|
|
9
23
|
if (typeof nextConfig.webpack === "function") {
|
|
10
24
|
return nextConfig.webpack(config, webpackOptions);
|
package/lib/next.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"next.mjs","sources":["../src/next.ts"],"sourcesContent":[null],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"next.mjs","sources":["../src/next.ts"],"sourcesContent":[null],"names":[],"mappings":";;AA2BA;;;;;;;;;;;;AAYG;AACW,SAAU,IAAI,CAC1B,UAAA,GAA6B,EAAE,EAC/B,OAA6B,EAAA;IAE7B,OAAO;AACL,QAAA,GAAG,UAAU;QACb,OAAO,CAAC,MAAyB,EAAE,cAAuB,EAAA;YACxD,MAAM,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,MAAM,CAAC,OAAO,GAAG,EAAE;;YAEpE,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;AACxC,YAAA,IAAI,OAAO,UAAU,CAAC,OAAO,KAAK,UAAU,EAAE;gBAC5C,OAAO,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,cAAc,CAAC;YACnD;AACA,YAAA,OAAO,MAAM;QACf,CAAC;KACF;AACH;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttsc/unplugin",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.4",
|
|
4
4
|
"description": "Bundler adapters for ttsc plugins.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "lib/index.mjs",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
90
90
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
91
91
|
"@types/node": "^25.3.0",
|
|
92
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
92
|
+
"@typescript/native-preview": "7.0.0-dev.20260519.1",
|
|
93
93
|
"esbuild": "^0.25.12",
|
|
94
94
|
"rimraf": "^6.1.2",
|
|
95
95
|
"rollup": "^4.60.3",
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
"tslib": "^2.8.1",
|
|
100
100
|
"typescript": "^6.0.3",
|
|
101
101
|
"vite": "^7.1.12",
|
|
102
|
-
"ttsc": "0.12.
|
|
102
|
+
"ttsc": "0.12.4"
|
|
103
103
|
},
|
|
104
104
|
"repository": {
|
|
105
105
|
"type": "git",
|
package/src/api.ts
CHANGED
|
@@ -1 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public API surface for `@ttsc/unplugin`.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports everything from `core/index` so consumers can access the unified
|
|
5
|
+
* `unplugin` instance, the transform helpers, and the option types from the
|
|
6
|
+
* `@ttsc/unplugin/api` entry point without importing directly from `core/`.
|
|
7
|
+
*/
|
|
1
8
|
export * from "./core/index";
|
package/src/bun.ts
CHANGED
|
@@ -2,22 +2,51 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import type { UnpluginContextMeta } from "unplugin";
|
|
3
3
|
|
|
4
4
|
import { unplugin } from "./api";
|
|
5
|
+
import { sourceFilePattern } from "./core/index";
|
|
5
6
|
import type { TtscUnpluginOptions } from "./core/options";
|
|
6
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Minimal subset of the Bun plugin API consumed by this adapter.
|
|
10
|
+
*
|
|
11
|
+
* Bun does not yet ship TypeScript types for its bundler plugin interface, so
|
|
12
|
+
* we define the subset we need here. This keeps the adapter free of a Bun
|
|
13
|
+
* runtime dependency while remaining type-safe.
|
|
14
|
+
*/
|
|
7
15
|
export interface BunLikePlugin {
|
|
16
|
+
/** Plugin identifier shown in Bun bundler output. */
|
|
8
17
|
name: string;
|
|
18
|
+
/** Called by Bun when the plugin is registered. */
|
|
9
19
|
setup(build: BunLikeBuild): void | Promise<void>;
|
|
10
20
|
}
|
|
11
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Minimal subset of the Bun `BuildConfig` plugin build object.
|
|
24
|
+
*
|
|
25
|
+
* Only the `onLoad` hook is used; other hooks are not needed for a
|
|
26
|
+
* source-to-source transform.
|
|
27
|
+
*/
|
|
12
28
|
export interface BunLikeBuild {
|
|
29
|
+
/**
|
|
30
|
+
* Register a loader callback for files matching `filter`.
|
|
31
|
+
*
|
|
32
|
+
* The callback receives the absolute file path and must return the
|
|
33
|
+
* transformed file contents.
|
|
34
|
+
*/
|
|
13
35
|
onLoad(
|
|
14
36
|
options: { filter: RegExp },
|
|
15
37
|
loader: (args: { path: string }) => Promise<{ contents: string }>,
|
|
16
38
|
): void;
|
|
17
39
|
}
|
|
18
40
|
|
|
19
|
-
|
|
20
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Create a ttsc plugin for Bun's bundler.
|
|
43
|
+
*
|
|
44
|
+
* Bun does not implement the unplugin protocol, so this adapter instantiates
|
|
45
|
+
* the raw unplugin transform and wires it to Bun's `onLoad` hook manually. The
|
|
46
|
+
* adapter reads each matching file from disk and forwards the content to the
|
|
47
|
+
* ttsc transform; if the transform returns no changes the original source is
|
|
48
|
+
* passed through unchanged.
|
|
49
|
+
*/
|
|
21
50
|
export default function bun(options?: TtscUnpluginOptions): BunLikePlugin {
|
|
22
51
|
return {
|
|
23
52
|
name: "ttsc-unplugin",
|
|
@@ -29,6 +58,7 @@ export default function bun(options?: TtscUnpluginOptions): BunLikePlugin {
|
|
|
29
58
|
typeof raw.transform === "function"
|
|
30
59
|
? await raw.transform.call({} as never, source, args.path)
|
|
31
60
|
: undefined;
|
|
61
|
+
// Unpack both shorthand string and object result shapes.
|
|
32
62
|
if (typeof result === "string") {
|
|
33
63
|
return { contents: result };
|
|
34
64
|
}
|
package/src/core/index.ts
CHANGED
|
@@ -11,10 +11,30 @@ import {
|
|
|
11
11
|
} from "./transform";
|
|
12
12
|
|
|
13
13
|
const name = "ttsc-unplugin";
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Matches any TypeScript or JavaScript source extension (.ts, .tsx, .mts, .cts,
|
|
16
|
+
* etc.). Shared with the Bun adapter (`bun.ts`) so the filter is defined once
|
|
17
|
+
* and both adapters stay in sync.
|
|
18
|
+
*/
|
|
19
|
+
export const sourceFilePattern = /\.[cm]?tsx?$/;
|
|
20
|
+
/** Matches any path segment that is a `node_modules` directory (cross-platform). */
|
|
15
21
|
const nodeModulesPattern = /(?:^|[/\\])node_modules(?:[/\\]|$)/;
|
|
22
|
+
/**
|
|
23
|
+
* Matches virtual module ids — Rollup/Vite use a leading NUL byte (`\0`) as
|
|
24
|
+
* convention.
|
|
25
|
+
*/
|
|
16
26
|
const virtualModulePattern = /\0/;
|
|
17
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Unplugin factory that wires the ttsc transform pipeline into any supported
|
|
30
|
+
* bundler (Vite, Rollup, Rolldown, webpack, Rspack, esbuild, Farm).
|
|
31
|
+
*
|
|
32
|
+
* The factory resolves raw options once, creates a per-build transform cache,
|
|
33
|
+
* and captures Vite alias configuration via the `vite.configResolved` hook so
|
|
34
|
+
* that path aliases are forwarded to the generated tsconfig overlay. The cache
|
|
35
|
+
* is cleared on every `buildStart` to avoid stale results across watch-mode
|
|
36
|
+
* rebuilds.
|
|
37
|
+
*/
|
|
18
38
|
const unpluginFactory: UnpluginFactory<
|
|
19
39
|
TtscUnpluginOptions | undefined,
|
|
20
40
|
false
|
|
@@ -63,6 +83,13 @@ export { createTtscTransformCache, resolveOptions, transformTtsc, unplugin };
|
|
|
63
83
|
|
|
64
84
|
export default unplugin;
|
|
65
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Returns `true` when the module id refers to a real TypeScript/JavaScript
|
|
88
|
+
* source file that should be processed by the ttsc transform.
|
|
89
|
+
*
|
|
90
|
+
* Excluded ids: virtual modules (NUL prefix), `.d.ts` declaration files, and
|
|
91
|
+
* anything inside `node_modules`.
|
|
92
|
+
*/
|
|
66
93
|
function isTransformTarget(id: string): boolean {
|
|
67
94
|
return (
|
|
68
95
|
sourceFilePattern.test(id) &&
|
package/src/core/options.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { ITtscProjectPluginConfig } from "ttsc";
|
|
2
2
|
|
|
3
|
+
/** Raw compiler-options overlay supplied by the caller as a plain JSON value. */
|
|
3
4
|
export type TtscUnpluginCompilerOptionsJson = Record<string, unknown>;
|
|
4
5
|
|
|
6
|
+
/** Options accepted by the `@ttsc/unplugin` bundler adapter. */
|
|
5
7
|
export interface TtscUnpluginOptions {
|
|
6
8
|
/**
|
|
7
9
|
* Project config used by the bundler adapter.
|
|
@@ -29,9 +31,22 @@ export interface TtscUnpluginOptions {
|
|
|
29
31
|
plugins?: readonly ITtscProjectPluginConfig[] | false;
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Fully-resolved plugin options with all defaults applied.
|
|
36
|
+
*
|
|
37
|
+
* Produced by {@link resolveOptions}; consumed internally by the transform
|
|
38
|
+
* pipeline. Every field is present and normalised — callers should not
|
|
39
|
+
* construct this type directly.
|
|
40
|
+
*/
|
|
32
41
|
export interface ResolvedTtscUnpluginOptions {
|
|
42
|
+
/** Compiler-options overlay applied on top of the discovered tsconfig. */
|
|
33
43
|
compilerOptions: TtscUnpluginCompilerOptionsJson;
|
|
44
|
+
/**
|
|
45
|
+
* Resolved plugin list; mirrors the semantics of
|
|
46
|
+
* {@link TtscUnpluginOptions.plugins}.
|
|
47
|
+
*/
|
|
34
48
|
plugins?: readonly ITtscProjectPluginConfig[] | false;
|
|
49
|
+
/** Resolved path to the project tsconfig, or `undefined` to auto-discover. */
|
|
35
50
|
project?: string;
|
|
36
51
|
}
|
|
37
52
|
|
|
@@ -41,6 +56,13 @@ const defaultOptions: ResolvedTtscUnpluginOptions = {
|
|
|
41
56
|
project: undefined,
|
|
42
57
|
};
|
|
43
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Normalise raw user-supplied options into {@link ResolvedTtscUnpluginOptions}.
|
|
61
|
+
*
|
|
62
|
+
* Merges provided values with defaults. The `plugins` field uses an explicit
|
|
63
|
+
* `"plugins" in options` presence check rather than a falsy guard so that
|
|
64
|
+
* `plugins: false` (disable all plugins) is preserved as-is.
|
|
65
|
+
*/
|
|
44
66
|
export function resolveOptions(
|
|
45
67
|
options: TtscUnpluginOptions = {},
|
|
46
68
|
): ResolvedTtscUnpluginOptions {
|
package/src/core/transform.ts
CHANGED
|
@@ -11,31 +11,86 @@ import type { TransformResult } from "unplugin";
|
|
|
11
11
|
|
|
12
12
|
import type { ResolvedTtscUnpluginOptions } from "./options";
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* The normalised transform result type that this module produces.
|
|
16
|
+
*
|
|
17
|
+
* Excludes the shorthand `string`, `null`, and `undefined` variants of
|
|
18
|
+
* unplugin's `TransformResult` so callers always receive an object or
|
|
19
|
+
* `undefined`.
|
|
20
|
+
*/
|
|
14
21
|
export type TtscTransformResult = Exclude<
|
|
15
22
|
TransformResult,
|
|
16
23
|
string | null | undefined
|
|
17
24
|
>;
|
|
18
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Normalised alias entry used when building the `paths` overlay for the
|
|
28
|
+
* generated tsconfig. Derived from either a Vite array alias or a webpack/
|
|
29
|
+
* Rspack object alias.
|
|
30
|
+
*/
|
|
19
31
|
export interface TtscTransformAlias {
|
|
32
|
+
/** The alias key (module specifier prefix). */
|
|
20
33
|
find: string;
|
|
34
|
+
/** Absolute or cwd-relative path that the alias points to. */
|
|
21
35
|
replacement: string;
|
|
22
36
|
}
|
|
23
37
|
|
|
38
|
+
/**
|
|
39
|
+
* A single entry in the per-build transform cache.
|
|
40
|
+
*
|
|
41
|
+
* Stores the full compiler result together with SHA-256 hashes of every input
|
|
42
|
+
* file. On subsequent transforms the cached entry is validated by comparing
|
|
43
|
+
* fresh hashes against {@link inputHashes}; a mismatch triggers a full
|
|
44
|
+
* re-transform of the project.
|
|
45
|
+
*/
|
|
24
46
|
export interface TtscCachedProjectTransform {
|
|
47
|
+
/**
|
|
48
|
+
* SHA-256 hash of each project-relative input path at the time of the
|
|
49
|
+
* transform.
|
|
50
|
+
*/
|
|
25
51
|
inputHashes: Record<string, string>;
|
|
52
|
+
/** Absolute path to the directory that owns the tsconfig. */
|
|
26
53
|
projectRoot: string;
|
|
54
|
+
/** Raw compiler output returned by {@link TtscCompiler.transform}. */
|
|
27
55
|
result: ITtscCompilerTransformation;
|
|
28
56
|
}
|
|
29
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Keyed by a stable JSON string that encodes the tsconfig path, compiler
|
|
60
|
+
* options overlay, plugin list, and alias paths. The value is a `Promise` so
|
|
61
|
+
* concurrent transforms for the same project share a single in-flight
|
|
62
|
+
* compilation rather than spawning multiple `TtscCompiler` instances.
|
|
63
|
+
*/
|
|
30
64
|
export type TtscTransformCache = Map<
|
|
31
65
|
string,
|
|
32
66
|
Promise<TtscCachedProjectTransform>
|
|
33
67
|
>;
|
|
34
68
|
|
|
69
|
+
/** Create an empty transform cache for a single build session. */
|
|
35
70
|
export function createTtscTransformCache(): TtscTransformCache {
|
|
36
71
|
return new Map();
|
|
37
72
|
}
|
|
38
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Apply the ttsc plugin transform to a single source file.
|
|
76
|
+
*
|
|
77
|
+
* The function is intentionally project-scoped: it compiles the entire tsconfig
|
|
78
|
+
* project in one shot and extracts the result for `id`. Subsequent calls for
|
|
79
|
+
* sibling files in the same project reuse the cached result as long as none of
|
|
80
|
+
* the project's input files have changed (verified by comparing SHA-256
|
|
81
|
+
* hashes).
|
|
82
|
+
*
|
|
83
|
+
* Returns `undefined` when no transform is needed (declaration files, virtual
|
|
84
|
+
* modules, disabled plugins, or source unchanged after transform).
|
|
85
|
+
*
|
|
86
|
+
* @param id - Bundler module id (may carry a query string or virtual prefix).
|
|
87
|
+
* @param source - Current file content supplied by the bundler.
|
|
88
|
+
* @param options - Resolved plugin options.
|
|
89
|
+
* @param aliases - Raw bundler alias configuration (Vite array or webpack
|
|
90
|
+
* object).
|
|
91
|
+
* @param cache - Optional per-build cache; cleared by the caller on
|
|
92
|
+
* `buildStart`.
|
|
93
|
+
*/
|
|
39
94
|
export async function transformTtsc(
|
|
40
95
|
id: string,
|
|
41
96
|
source: string,
|
|
@@ -100,21 +155,44 @@ export async function transformTtsc(
|
|
|
100
155
|
return createTransformResult(source, code);
|
|
101
156
|
}
|
|
102
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Strip a query string or hash fragment from a bundler module id.
|
|
160
|
+
*
|
|
161
|
+
* Vite appends query parameters (e.g. `?raw`, `?url`, `?inline`) to
|
|
162
|
+
* differentiate import variants of the same file. We must strip them before
|
|
163
|
+
* using the id as a file-system path.
|
|
164
|
+
*/
|
|
103
165
|
export function stripQuery(id: string): string {
|
|
104
166
|
const query = id.search(/[?#]/);
|
|
105
167
|
return query === -1 ? id : id.slice(0, query);
|
|
106
168
|
}
|
|
107
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Returns `true` for TypeScript declaration files (`.d.ts`, `.d.mts`,
|
|
172
|
+
* `.d.cts`).
|
|
173
|
+
*/
|
|
108
174
|
export function isDeclarationFile(id: string): boolean {
|
|
109
175
|
return id.endsWith(".d.ts") || id.endsWith(".d.mts") || id.endsWith(".d.cts");
|
|
110
176
|
}
|
|
111
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Returns `true` when the caller has explicitly opted out of all plugins. An
|
|
180
|
+
* empty array is treated as disabled so we don't invoke the compiler for a
|
|
181
|
+
* no-op transform.
|
|
182
|
+
*/
|
|
112
183
|
function pluginsAreDisabled(
|
|
113
184
|
plugins: ResolvedTtscUnpluginOptions["plugins"],
|
|
114
185
|
): boolean {
|
|
115
186
|
return plugins === false || (Array.isArray(plugins) && plugins.length === 0);
|
|
116
187
|
}
|
|
117
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Build the unplugin transform result, or `undefined` when the transform
|
|
191
|
+
* produced no changes.
|
|
192
|
+
*
|
|
193
|
+
* Returning `undefined` instead of `{ code: source }` lets the bundler skip the
|
|
194
|
+
* unnecessary module update and preserves the original source map.
|
|
195
|
+
*/
|
|
118
196
|
export function createTransformResult(
|
|
119
197
|
source: string,
|
|
120
198
|
code: string,
|
|
@@ -136,6 +214,16 @@ function matchesCachedSource(
|
|
|
136
214
|
return sameHashes(cached.inputHashes, currentHashes);
|
|
137
215
|
}
|
|
138
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Build the complete input-hash snapshot stored alongside a fresh compiler
|
|
219
|
+
* result.
|
|
220
|
+
*
|
|
221
|
+
* Combines filesystem hashes for every file in the project directory with
|
|
222
|
+
* hashes for each emitted TypeScript output key (the compiler may have read
|
|
223
|
+
* files not visible via the directory walk). The in-memory source for the file
|
|
224
|
+
* that triggered the build is overlaid last to capture unsaved editor content
|
|
225
|
+
* correctly.
|
|
226
|
+
*/
|
|
139
227
|
function collectInputHashes(props: {
|
|
140
228
|
currentFile: string;
|
|
141
229
|
currentSource: string;
|
|
@@ -154,6 +242,7 @@ function collectInputHashes(props: {
|
|
|
154
242
|
}
|
|
155
243
|
}
|
|
156
244
|
}
|
|
245
|
+
// Overlay the in-memory source so unsaved edits invalidate the cache.
|
|
157
246
|
hashes[toProjectKey(props.projectRoot, props.currentFile)] = hashText(
|
|
158
247
|
props.currentSource,
|
|
159
248
|
);
|
|
@@ -175,6 +264,14 @@ function collectProjectInputHashes(
|
|
|
175
264
|
return hashes;
|
|
176
265
|
}
|
|
177
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Enumerate every regular file under `root`, skipping well-known output and
|
|
269
|
+
* tooling directories (see {@link isIgnoredProjectDirectory}).
|
|
270
|
+
*
|
|
271
|
+
* Uses an iterative DFS instead of `fs.readdirSync` recursion to avoid
|
|
272
|
+
* unbounded call-stack depth on deep project trees. The result is sorted so
|
|
273
|
+
* that hash comparisons are deterministic across OS-level directory orderings.
|
|
274
|
+
*/
|
|
178
275
|
function listProjectInputFiles(root: string): string[] {
|
|
179
276
|
const out: string[] = [];
|
|
180
277
|
const stack = [root];
|
|
@@ -311,16 +408,30 @@ function createTransformTsconfig(props: {
|
|
|
311
408
|
};
|
|
312
409
|
}
|
|
313
410
|
|
|
411
|
+
/**
|
|
412
|
+
* Resolve all relative paths inside `compilerOptions` against `tsconfigDir`.
|
|
413
|
+
*
|
|
414
|
+
* The generated tsconfig lives in a system temp directory, so any relative path
|
|
415
|
+
* (e.g. `"outDir": "../dist"`) that was meaningful relative to the original
|
|
416
|
+
* tsconfig must be converted to an absolute path before writing the generated
|
|
417
|
+
* file. Otherwise TypeScript-Go resolves it against the temp dir.
|
|
418
|
+
*
|
|
419
|
+
* Also inserts a synthetic `baseUrl` equal to `tsconfigDir` when `paths` is
|
|
420
|
+
* provided but `baseUrl` is absent — TypeScript requires `baseUrl` alongside
|
|
421
|
+
* `paths` when the latter contains non-absolute targets.
|
|
422
|
+
*/
|
|
314
423
|
function normalizeCompilerOptionsForGeneratedTsconfig(
|
|
315
424
|
compilerOptions: Record<string, unknown>,
|
|
316
425
|
tsconfigDir: string,
|
|
317
426
|
): Record<string, unknown> {
|
|
318
427
|
const output = { ...compilerOptions };
|
|
428
|
+
// Scalar path fields: resolve each against the original tsconfig directory.
|
|
319
429
|
for (const key of ["baseUrl", "declarationDir", "outDir", "rootDir"]) {
|
|
320
430
|
if (typeof output[key] === "string") {
|
|
321
431
|
output[key] = path.resolve(tsconfigDir, output[key]);
|
|
322
432
|
}
|
|
323
433
|
}
|
|
434
|
+
// Array path fields: resolve each element individually.
|
|
324
435
|
for (const key of ["rootDirs", "typeRoots"]) {
|
|
325
436
|
if (Array.isArray(output[key])) {
|
|
326
437
|
output[key] = output[key].map((entry) =>
|
|
@@ -473,6 +584,14 @@ function createTransformCacheKey(props: {
|
|
|
473
584
|
});
|
|
474
585
|
}
|
|
475
586
|
|
|
587
|
+
/**
|
|
588
|
+
* JSON-serialise `value` with object keys sorted alphabetically.
|
|
589
|
+
*
|
|
590
|
+
* Standard `JSON.stringify` does not guarantee key ordering, so two
|
|
591
|
+
* semantically identical option objects could produce different strings and
|
|
592
|
+
* cause unnecessary cache misses. Sorting keys makes the cache key stable
|
|
593
|
+
* regardless of the order properties were added to the options object.
|
|
594
|
+
*/
|
|
476
595
|
function stableStringify(value: unknown): string {
|
|
477
596
|
if (Array.isArray(value)) {
|
|
478
597
|
return `[${value.map(stableStringify).join(",")}]`;
|
|
@@ -508,6 +627,15 @@ function isAlias(value: unknown): value is TtscTransformAlias {
|
|
|
508
627
|
);
|
|
509
628
|
}
|
|
510
629
|
|
|
630
|
+
/**
|
|
631
|
+
* Extract the transformed source for a single file from the compiler result.
|
|
632
|
+
*
|
|
633
|
+
* Throws on compiler exception or hard failure so the bundler surfaces the
|
|
634
|
+
* error to the user. On success, tries a fast exact-match lookup by
|
|
635
|
+
* project-relative key first, then falls back to a resolve-based scan for the
|
|
636
|
+
* rare case where the key in `result.typescript` uses an absolute or
|
|
637
|
+
* differently-cased path.
|
|
638
|
+
*/
|
|
511
639
|
function selectTransformedSource(props: {
|
|
512
640
|
file: string;
|
|
513
641
|
projectRoot: string;
|
|
@@ -520,11 +648,13 @@ function selectTransformedSource(props: {
|
|
|
520
648
|
throw new Error(formatDiagnostics(props.result.diagnostics));
|
|
521
649
|
}
|
|
522
650
|
|
|
651
|
+
// Fast path: the compiler key matches the normalised project-relative path.
|
|
523
652
|
const key = toProjectKey(props.projectRoot, props.file);
|
|
524
653
|
const direct = props.result.typescript[key];
|
|
525
654
|
if (direct !== undefined) {
|
|
526
655
|
return direct;
|
|
527
656
|
}
|
|
657
|
+
// Slow path: resolve each candidate to an absolute path for comparison.
|
|
528
658
|
for (const [candidate, source] of Object.entries(props.result.typescript)) {
|
|
529
659
|
if (path.resolve(props.projectRoot, candidate) === props.file) {
|
|
530
660
|
return source;
|
|
@@ -533,6 +663,13 @@ function selectTransformedSource(props: {
|
|
|
533
663
|
throw new Error(`ttsc transform did not return output for ${props.file}`);
|
|
534
664
|
}
|
|
535
665
|
|
|
666
|
+
/**
|
|
667
|
+
* Forward non-fatal plugin diagnostics to stderr.
|
|
668
|
+
*
|
|
669
|
+
* A `success` result may still carry warnings or informational messages from
|
|
670
|
+
* plugins. These are surfaced via stderr rather than throwing so the build
|
|
671
|
+
* continues. Failures and exceptions are handled by the caller.
|
|
672
|
+
*/
|
|
536
673
|
function reportSuccessDiagnostics(result: ITtscCompilerTransformation): void {
|
|
537
674
|
if (result.type !== "success" || result.diagnostics === undefined) {
|
|
538
675
|
return;
|
|
@@ -543,6 +680,14 @@ function reportSuccessDiagnostics(result: ITtscCompilerTransformation): void {
|
|
|
543
680
|
}
|
|
544
681
|
}
|
|
545
682
|
|
|
683
|
+
/**
|
|
684
|
+
* Format a compiler diagnostic list into a human-readable string.
|
|
685
|
+
*
|
|
686
|
+
* Produces `"file: line:col: message"` entries joined by newlines, matching the
|
|
687
|
+
* output style of `tsc`. When the list is empty (e.g. a failure with no
|
|
688
|
+
* attached diagnostics) returns a generic fallback message so the thrown
|
|
689
|
+
* `Error` is never empty.
|
|
690
|
+
*/
|
|
546
691
|
function formatDiagnostics(diagnostics: ITtscCompilerDiagnostic[]): string {
|
|
547
692
|
if (diagnostics.length === 0) {
|
|
548
693
|
return "ttsc transform failed";
|
|
@@ -577,6 +722,16 @@ function formatUnknownError(error: unknown): string {
|
|
|
577
722
|
return String(error);
|
|
578
723
|
}
|
|
579
724
|
|
|
725
|
+
/**
|
|
726
|
+
* Locate the tsconfig that should govern the transform for `file`.
|
|
727
|
+
*
|
|
728
|
+
* If `tsconfig` is supplied it is returned as-is (absolute) or resolved from
|
|
729
|
+
* `process.cwd()` (relative). Otherwise the function walks ancestor directories
|
|
730
|
+
* starting at `file`'s directory, returning the first `tsconfig.json` found.
|
|
731
|
+
* Falls back to `<cwd>/tsconfig.json` when no ancestor contains one — the
|
|
732
|
+
* compiler will error if that file does not exist, which is the correct
|
|
733
|
+
* behavior for a mis-configured project.
|
|
734
|
+
*/
|
|
580
735
|
function resolveTsconfig(file: string, tsconfig?: string): string {
|
|
581
736
|
if (tsconfig !== undefined) {
|
|
582
737
|
return path.isAbsolute(tsconfig)
|
|
@@ -591,6 +746,7 @@ function resolveTsconfig(file: string, tsconfig?: string): string {
|
|
|
591
746
|
return candidate;
|
|
592
747
|
}
|
|
593
748
|
const parent = path.dirname(current);
|
|
749
|
+
// Reached filesystem root — stop walking.
|
|
594
750
|
if (parent === current) {
|
|
595
751
|
break;
|
|
596
752
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ttsc/unplugin — bundler-agnostic ttsc plugin adapter.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the unified `unplugin` instance that carries named bundler
|
|
5
|
+
* adapters (`.vite`, `.rollup`, `.rolldown`, `.webpack`, `.rspack`, `.esbuild`,
|
|
6
|
+
* `.farm`) as well as the raw factory for custom integrations. Per-bundler
|
|
7
|
+
* entry points (`@ttsc/unplugin/vite`, `/webpack`, …) each re-export the
|
|
8
|
+
* matching adapter directly to keep bundler-specific builds lean.
|
|
9
|
+
*/
|
|
1
10
|
import unplugin from "./core/index";
|
|
2
11
|
|
|
3
12
|
export type { TtscUnpluginOptions } from "./core/options";
|
package/src/next.ts
CHANGED
|
@@ -1,14 +1,43 @@
|
|
|
1
1
|
import type { TtscUnpluginOptions } from "./core/options";
|
|
2
2
|
import webpack from "./webpack";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Minimal structural type for a Next.js configuration object.
|
|
6
|
+
*
|
|
7
|
+
* Only the `webpack` field is used by this adapter; all other Next.js options
|
|
8
|
+
* are forwarded as-is through the spread operator.
|
|
9
|
+
*/
|
|
4
10
|
export type NextLikeConfig = Record<string, unknown> & {
|
|
11
|
+
/**
|
|
12
|
+
* Optional existing webpack customisation hook. When the caller has already
|
|
13
|
+
* defined one, `next()` will chain through to it after injecting the ttsc
|
|
14
|
+
* webpack plugin.
|
|
15
|
+
*/
|
|
5
16
|
webpack?: (config: WebpackLikeConfig, options: unknown) => WebpackLikeConfig;
|
|
6
17
|
};
|
|
7
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Minimal structural type for a webpack configuration object as seen by the
|
|
21
|
+
* Next.js `webpack` hook callback.
|
|
22
|
+
*/
|
|
8
23
|
export type WebpackLikeConfig = Record<string, unknown> & {
|
|
24
|
+
/** The webpack plugin array; initialised to `[]` by this adapter if absent. */
|
|
9
25
|
plugins?: unknown[];
|
|
10
26
|
};
|
|
11
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Wrap a Next.js config object so that the ttsc webpack plugin is injected into
|
|
30
|
+
* every webpack build Next.js performs.
|
|
31
|
+
*
|
|
32
|
+
* The adapter uses `unshift` to ensure the ttsc plugin runs before any other
|
|
33
|
+
* plugins in the array (unplugin `enforce: "pre"` semantics). An existing
|
|
34
|
+
* `nextConfig.webpack` hook is preserved and called after the plugin is
|
|
35
|
+
* injected.
|
|
36
|
+
*
|
|
37
|
+
* @param nextConfig - The caller's existing Next.js config (spread into the
|
|
38
|
+
* returned object unchanged, except for `webpack`).
|
|
39
|
+
* @param options - Ttsc plugin options forwarded to the webpack adapter.
|
|
40
|
+
*/
|
|
12
41
|
export default function next(
|
|
13
42
|
nextConfig: NextLikeConfig = {},
|
|
14
43
|
options?: TtscUnpluginOptions,
|
|
@@ -17,6 +46,7 @@ export default function next(
|
|
|
17
46
|
...nextConfig,
|
|
18
47
|
webpack(config: WebpackLikeConfig, webpackOptions: unknown) {
|
|
19
48
|
config.plugins = Array.isArray(config.plugins) ? config.plugins : [];
|
|
49
|
+
// Prepend so ttsc runs before any user-added plugins.
|
|
20
50
|
config.plugins.unshift(webpack(options));
|
|
21
51
|
if (typeof nextConfig.webpack === "function") {
|
|
22
52
|
return nextConfig.webpack(config, webpackOptions);
|