@fulcro/transform-core 0.2.1
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/LICENSE +15 -0
- package/README.md +65 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +30 -0
- package/dist/program/index.d.ts +44 -0
- package/dist/program/index.js +269 -0
- package/dist/shared/index.d.ts +84 -0
- package/dist/shared/index.js +102 -0
- package/dist/transformer/index.d.ts +42 -0
- package/dist/transformer/index.js +43 -0
- package/dist/unplugin/index.d.mts +57 -0
- package/dist/unplugin/index.mjs +61 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 diguu <rodrigogeribola@hotmail.com>
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
10
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
11
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
12
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
13
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
14
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
|
+
PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# @fulcro/transform-core
|
|
2
|
+
|
|
3
|
+
Shared machinery behind the Fulcro compile time transformers.
|
|
4
|
+
|
|
5
|
+
**You do not install this.** `@fulcro/reflect` depends on it, and any other
|
|
6
|
+
package in the library that grows a transformer will too. It is published
|
|
7
|
+
because they depend on it, not because it is meant to be used directly, and its
|
|
8
|
+
shape is theirs to change without notice.
|
|
9
|
+
|
|
10
|
+
If you are looking for the compiler plugin, it lives in the package whose calls
|
|
11
|
+
it rewrites:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{ "plugins": [{ "transform": "@fulcro/reflect/transformer" }] }
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Why it exists
|
|
18
|
+
|
|
19
|
+
The library used to ship one transformer, `@fulcro/transformer`, installed
|
|
20
|
+
beside `@fulcro/reflect` as a peer dependency. Two things were wrong with that.
|
|
21
|
+
|
|
22
|
+
**It could go missing.** npm installs peer dependencies and Yarn does not, so a
|
|
23
|
+
project could end up with the utilities and no transformer. The failure was
|
|
24
|
+
quiet, because the utilities are built to degrade rather than crash: `defaultOf`
|
|
25
|
+
throws, `nameOf` falls back to parsing closures, `typeOf(…).declared` reads
|
|
26
|
+
`null`. Nothing says _your build is missing a plugin_.
|
|
27
|
+
|
|
28
|
+
**It could go out of step.** `@fulcro/reflect@0.3` with
|
|
29
|
+
`@fulcro/transformer@0.2` was an installable, broken combination, because what a
|
|
30
|
+
call means and what it compiles to were versioned separately.
|
|
31
|
+
|
|
32
|
+
Both disappear when the transformer ships inside the package whose calls it
|
|
33
|
+
rewrites. What is left over is the part that belongs to no package in
|
|
34
|
+
particular — and that is this one.
|
|
35
|
+
|
|
36
|
+
## What is in here
|
|
37
|
+
|
|
38
|
+
Everything with nothing to do with any particular utility:
|
|
39
|
+
|
|
40
|
+
| Piece | Does |
|
|
41
|
+
| --------------------------- | --------------------------------------------------------------------------- |
|
|
42
|
+
| `isOwnedCall` | Follows a call back to the declaration that owns it, rather than by name |
|
|
43
|
+
| `createTransformer` | Walks a source file once and hands each call to the rewriter that claims it |
|
|
44
|
+
| `createFileTransformer` | Builds and keeps a program, for the bundlers that have no checker |
|
|
45
|
+
| `createTransformerUnplugin` | The adapter surface Vite, Rollup, Webpack, Rspack, esbuild and Farm expect |
|
|
46
|
+
|
|
47
|
+
What lives in each library instead is the part that knows what to **emit** — how
|
|
48
|
+
`defaultOf` fills a tuple, how a type argument becomes a runtime test. That
|
|
49
|
+
split is why a package can own its compile time behaviour without a second
|
|
50
|
+
install, and why two of them can do so without knowing about each other.
|
|
51
|
+
|
|
52
|
+
## TypeScript 5 and 6 only
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{ "typescript": ">=5.3.3 <7" }
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
7.x is the native port, and its package no longer exposes the compiler API this
|
|
59
|
+
is built on. Declared as an **optional** peer dependency, so that a project only
|
|
60
|
+
using the runtime half of a library never has to install a compiler it is not
|
|
61
|
+
going to run.
|
|
62
|
+
|
|
63
|
+
## Licence
|
|
64
|
+
|
|
65
|
+
ISC.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared machinery behind the Fulcro compile time transformers.
|
|
3
|
+
*
|
|
4
|
+
* **Not a package to depend on directly.** It is installed for you by
|
|
5
|
+
* `@fulcro/reflect` and `@fulcro/collections`, each of which ships its own
|
|
6
|
+
* transformer built on this, and its shape is theirs to change.
|
|
7
|
+
*
|
|
8
|
+
* What lives here is everything that has nothing to do with any particular
|
|
9
|
+
* utility: following a call back to the declaration that owns it, walking a
|
|
10
|
+
* source file once, building and keeping a program for the bundlers that have
|
|
11
|
+
* no checker of their own, and the adapter surface `unplugin` expects.
|
|
12
|
+
*
|
|
13
|
+
* What lives in each library instead is the part that knows what to emit — how
|
|
14
|
+
* `defaultOf` fills a tuple, how `ofType` turns a type argument into a runtime
|
|
15
|
+
* test. That split is why a package can own its compile time behaviour without
|
|
16
|
+
* a second install, and why two of them can do so without knowing about each
|
|
17
|
+
* other.
|
|
18
|
+
*/
|
|
19
|
+
export { createFileTransformer, type FileTransformer, type TransformCoreOptions, } from './program/index.js';
|
|
20
|
+
export { type CallRewriter, IDENTIFIER_PATTERN, isOwnedCall, isTupleType, type RewriteContext, utilityModuleSegment, } from './shared/index.js';
|
|
21
|
+
export { createTransformer, type TransformerFactory, type TransformerOptions, } from './transformer/index.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared machinery behind the Fulcro compile time transformers.
|
|
4
|
+
*
|
|
5
|
+
* **Not a package to depend on directly.** It is installed for you by
|
|
6
|
+
* `@fulcro/reflect` and `@fulcro/collections`, each of which ships its own
|
|
7
|
+
* transformer built on this, and its shape is theirs to change.
|
|
8
|
+
*
|
|
9
|
+
* What lives here is everything that has nothing to do with any particular
|
|
10
|
+
* utility: following a call back to the declaration that owns it, walking a
|
|
11
|
+
* source file once, building and keeping a program for the bundlers that have
|
|
12
|
+
* no checker of their own, and the adapter surface `unplugin` expects.
|
|
13
|
+
*
|
|
14
|
+
* What lives in each library instead is the part that knows what to emit — how
|
|
15
|
+
* `defaultOf` fills a tuple, how `ofType` turns a type argument into a runtime
|
|
16
|
+
* test. That split is why a package can own its compile time behaviour without
|
|
17
|
+
* a second install, and why two of them can do so without knowing about each
|
|
18
|
+
* other.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.createTransformer = exports.utilityModuleSegment = exports.isTupleType = exports.isOwnedCall = exports.IDENTIFIER_PATTERN = exports.createFileTransformer = void 0;
|
|
22
|
+
var program_1 = require("./program/index.js");
|
|
23
|
+
Object.defineProperty(exports, "createFileTransformer", { enumerable: true, get: function () { return program_1.createFileTransformer; } });
|
|
24
|
+
var shared_1 = require("./shared/index.js");
|
|
25
|
+
Object.defineProperty(exports, "IDENTIFIER_PATTERN", { enumerable: true, get: function () { return shared_1.IDENTIFIER_PATTERN; } });
|
|
26
|
+
Object.defineProperty(exports, "isOwnedCall", { enumerable: true, get: function () { return shared_1.isOwnedCall; } });
|
|
27
|
+
Object.defineProperty(exports, "isTupleType", { enumerable: true, get: function () { return shared_1.isTupleType; } });
|
|
28
|
+
Object.defineProperty(exports, "utilityModuleSegment", { enumerable: true, get: function () { return shared_1.utilityModuleSegment; } });
|
|
29
|
+
var transformer_1 = require("./transformer/index.js");
|
|
30
|
+
Object.defineProperty(exports, "createTransformer", { enumerable: true, get: function () { return transformer_1.createTransformer; } });
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { CallRewriter } from '../shared/index.js';
|
|
2
|
+
import { TransformerOptions } from '../transformer/index.js';
|
|
3
|
+
/** Options accepted by the transformer core. */
|
|
4
|
+
export interface TransformCoreOptions extends TransformerOptions {
|
|
5
|
+
/** Path of the tsconfig driving the program. Defaults to the nearest one. */
|
|
6
|
+
readonly tsconfig?: string;
|
|
7
|
+
/** Directory the tsconfig search and the program resolve against. */
|
|
8
|
+
readonly root?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Transforms one file, or declines it. */
|
|
11
|
+
export interface FileTransformer {
|
|
12
|
+
/**
|
|
13
|
+
* Tells whether a file is one this transformer is responsible for.
|
|
14
|
+
*
|
|
15
|
+
* Decided from the path alone, because the bundlers that filter before
|
|
16
|
+
* reading a file have nothing else to offer at that point. Whether the file
|
|
17
|
+
* actually calls anything is settled later, inside
|
|
18
|
+
* {@link FileTransformer.transform}.
|
|
19
|
+
*
|
|
20
|
+
* @param id Identifier of the file, as the bundler spells it.
|
|
21
|
+
* @returns `true` when the file is a TypeScript source of the project.
|
|
22
|
+
*/
|
|
23
|
+
readonly handles: (id: string) => boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Applies the transformer to one file.
|
|
26
|
+
*
|
|
27
|
+
* @param id Identifier of the file, as the bundler spells it.
|
|
28
|
+
* @param code Current content of the file.
|
|
29
|
+
* @returns The rewritten TypeScript, or `null` when nothing was done and the
|
|
30
|
+
* bundler should keep the original.
|
|
31
|
+
*/
|
|
32
|
+
readonly transform: (id: string, code: string) => string | null;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Creates the transformer core.
|
|
36
|
+
*
|
|
37
|
+
* The program is built on the first file that actually needs it, so a project
|
|
38
|
+
* never calling the utilities pays nothing.
|
|
39
|
+
*
|
|
40
|
+
* @param rewriters Rewriters of the package this core is serving.
|
|
41
|
+
* @param options Options of the core.
|
|
42
|
+
* @returns A transformer usable by any bundler adapter.
|
|
43
|
+
*/
|
|
44
|
+
export declare const createFileTransformer: (rewriters: readonly CallRewriter[], options?: TransformCoreOptions) => FileTransformer;
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.createFileTransformer = void 0;
|
|
40
|
+
const fs = __importStar(require("node:fs"));
|
|
41
|
+
const path = __importStar(require("node:path"));
|
|
42
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
43
|
+
const transformer_1 = require("../transformer/index.js");
|
|
44
|
+
/**
|
|
45
|
+
* Type checking core shared by every bundler integration.
|
|
46
|
+
*
|
|
47
|
+
* The transformer needs a type checker, and no bundler has one: esbuild and swc
|
|
48
|
+
* reach TypeScript by erasing its types, never by resolving them, so a plugin
|
|
49
|
+
* cannot simply hand them a transformer and expect `defaultOf<Order>()` to mean
|
|
50
|
+
* anything. This module therefore builds and keeps its own program, and
|
|
51
|
+
* transforms each file itself.
|
|
52
|
+
*
|
|
53
|
+
* Nothing here knows about any bundler: the adapters built on top only have to
|
|
54
|
+
* call {@link createFileTransformer} and hand back what it returns. Kept as
|
|
55
|
+
* CommonJS as well, because the compiler plugin loads it through `require`.
|
|
56
|
+
*/
|
|
57
|
+
/** Extensions carrying TypeScript this core is responsible for. */
|
|
58
|
+
const HANDLED_EXTENSIONS = ['.ts', '.mts', '.cts'];
|
|
59
|
+
/**
|
|
60
|
+
* Builds the cheap pre-filter that skips files with nothing to rewrite.
|
|
61
|
+
*
|
|
62
|
+
* Reading a file is far cheaper than type checking it, so a source mentioning
|
|
63
|
+
* none of the owned function names never reaches the program. Derived from the
|
|
64
|
+
* rewriters rather than written out, because each package brings its own names
|
|
65
|
+
* and this core belongs to none of them.
|
|
66
|
+
*
|
|
67
|
+
* @param rewriters Rewriters the core was built with.
|
|
68
|
+
* @returns A pattern matching any of their function names as a whole word.
|
|
69
|
+
*/
|
|
70
|
+
const buildUtilityPattern = (rewriters) => {
|
|
71
|
+
const names = rewriters
|
|
72
|
+
.map((rewriter) => rewriter.functionName)
|
|
73
|
+
.join('|');
|
|
74
|
+
return new RegExp(`\\b(${names})\\b`);
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Rewrites a path the way the compiler spells it.
|
|
78
|
+
*
|
|
79
|
+
* TypeScript keeps every file name with forward slashes, on every platform,
|
|
80
|
+
* while the bundler and `path.normalize` hand back the native separator. Mixing
|
|
81
|
+
* the two silently defeats every lookup against the files of the program: a
|
|
82
|
+
* file already in it looks new, gets added as another root, and invalidates the
|
|
83
|
+
* whole program — turning one startup cost into one per file.
|
|
84
|
+
*
|
|
85
|
+
* @param fileName Path in whatever spelling it arrived.
|
|
86
|
+
* @returns The path as the compiler spells it.
|
|
87
|
+
*/
|
|
88
|
+
const toCompilerPath = (fileName) => path.resolve(fileName).split(path.sep).join('/');
|
|
89
|
+
/**
|
|
90
|
+
* Keeps a TypeScript program alive across recompilations.
|
|
91
|
+
*
|
|
92
|
+
* A language service rather than a one shot program: a watch run recompiles the
|
|
93
|
+
* same files repeatedly, and rebuilding the whole program on each edit would
|
|
94
|
+
* cost seconds every keystroke. The service reuses everything that did not
|
|
95
|
+
* change, and the version counters below are what tell it what did.
|
|
96
|
+
*/
|
|
97
|
+
class ProgramHost {
|
|
98
|
+
/** Files of the program, as resolved from the tsconfig. */
|
|
99
|
+
rootNames;
|
|
100
|
+
/** Compiler options resolved from the tsconfig. */
|
|
101
|
+
options;
|
|
102
|
+
/** Content handed over by the bundler, which may be ahead of the disk. */
|
|
103
|
+
overlays = new Map();
|
|
104
|
+
/** Bumped whenever a file changes, which is how the service invalidates. */
|
|
105
|
+
versions = new Map();
|
|
106
|
+
/** Directory the relative paths of the program resolve against. */
|
|
107
|
+
currentDirectory;
|
|
108
|
+
/**
|
|
109
|
+
* Initializes the host from a parsed tsconfig.
|
|
110
|
+
*
|
|
111
|
+
* @param parsed Parsed contents of the tsconfig.
|
|
112
|
+
* @param currentDirectory Directory the program resolves against.
|
|
113
|
+
*/
|
|
114
|
+
constructor(parsed, currentDirectory) {
|
|
115
|
+
this.rootNames = parsed.fileNames.map(toCompilerPath);
|
|
116
|
+
this.options = parsed.options;
|
|
117
|
+
this.currentDirectory = currentDirectory;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Records the content of a file as the bundler sees it.
|
|
121
|
+
*
|
|
122
|
+
* @param fileName File being compiled.
|
|
123
|
+
* @param content Current content of the file.
|
|
124
|
+
*/
|
|
125
|
+
update(fileName, content) {
|
|
126
|
+
if (this.overlays.get(fileName) === content)
|
|
127
|
+
return;
|
|
128
|
+
// On a cold run the bundler hands over exactly what is on disk, for
|
|
129
|
+
// every file of the project. Recording those as changes would
|
|
130
|
+
// invalidate and re-check the program once per file, turning a single
|
|
131
|
+
// startup cost into a quadratic one — so an unchanged file is left
|
|
132
|
+
// alone and stays at the version the program was built with.
|
|
133
|
+
if (!this.overlays.has(fileName) &&
|
|
134
|
+
this.rootNames.includes(fileName) &&
|
|
135
|
+
this.matchesDisk(fileName, content)) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
this.overlays.set(fileName, content);
|
|
139
|
+
this.versions.set(fileName, (this.versions.get(fileName) ?? 0) + 1);
|
|
140
|
+
// A file the tsconfig never listed — created after startup, or simply
|
|
141
|
+
// excluded — still has to enter the program to be transformed.
|
|
142
|
+
if (!this.rootNames.includes(fileName))
|
|
143
|
+
this.rootNames.push(fileName);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Tells whether the content handed over matches what is on disk.
|
|
147
|
+
*
|
|
148
|
+
* @param fileName File being compiled.
|
|
149
|
+
* @param content Content handed over by the bundler.
|
|
150
|
+
* @returns `true` when the two are identical.
|
|
151
|
+
*/
|
|
152
|
+
matchesDisk(fileName, content) {
|
|
153
|
+
try {
|
|
154
|
+
return fs.readFileSync(fileName, 'utf8') === content;
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
getScriptFileNames() {
|
|
161
|
+
return this.rootNames;
|
|
162
|
+
}
|
|
163
|
+
getScriptVersion(fileName) {
|
|
164
|
+
return String(this.versions.get(fileName) ?? 0);
|
|
165
|
+
}
|
|
166
|
+
getScriptSnapshot(fileName) {
|
|
167
|
+
const overlay = this.overlays.get(fileName);
|
|
168
|
+
if (overlay !== undefined) {
|
|
169
|
+
return typescript_1.default.ScriptSnapshot.fromString(overlay);
|
|
170
|
+
}
|
|
171
|
+
if (!fs.existsSync(fileName))
|
|
172
|
+
return undefined;
|
|
173
|
+
return typescript_1.default.ScriptSnapshot.fromString(fs.readFileSync(fileName, 'utf8'));
|
|
174
|
+
}
|
|
175
|
+
getCurrentDirectory() {
|
|
176
|
+
return this.currentDirectory;
|
|
177
|
+
}
|
|
178
|
+
getCompilationSettings() {
|
|
179
|
+
return this.options;
|
|
180
|
+
}
|
|
181
|
+
getDefaultLibFileName(options) {
|
|
182
|
+
return typescript_1.default.getDefaultLibFilePath(options);
|
|
183
|
+
}
|
|
184
|
+
readFile(fileName, encoding) {
|
|
185
|
+
return (this.overlays.get(fileName) ?? typescript_1.default.sys.readFile(fileName, encoding));
|
|
186
|
+
}
|
|
187
|
+
fileExists(fileName) {
|
|
188
|
+
return this.overlays.has(fileName) || typescript_1.default.sys.fileExists(fileName);
|
|
189
|
+
}
|
|
190
|
+
readDirectory = typescript_1.default.sys.readDirectory;
|
|
191
|
+
directoryExists = typescript_1.default.sys.directoryExists;
|
|
192
|
+
getDirectories = typescript_1.default.sys.getDirectories;
|
|
193
|
+
realpath = typescript_1.default.sys.realpath;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Locates and parses the tsconfig driving the program.
|
|
197
|
+
*
|
|
198
|
+
* @param root Directory the search starts from.
|
|
199
|
+
* @param explicit Path given in the options, when there is one.
|
|
200
|
+
* @returns The parsed tsconfig.
|
|
201
|
+
* @throws {Error} When no tsconfig can be found or it cannot be read.
|
|
202
|
+
*/
|
|
203
|
+
const parseTsconfig = (root, explicit) => {
|
|
204
|
+
const configPath = explicit !== undefined
|
|
205
|
+
? path.resolve(root, explicit)
|
|
206
|
+
: typescript_1.default.findConfigFile(root, typescript_1.default.sys.fileExists);
|
|
207
|
+
if (configPath === undefined) {
|
|
208
|
+
throw new Error(`No tsconfig.json found from ${root}. The transformer needs one to ` +
|
|
209
|
+
'know which files belong to the program.');
|
|
210
|
+
}
|
|
211
|
+
const read = typescript_1.default.readConfigFile(configPath, typescript_1.default.sys.readFile);
|
|
212
|
+
if (read.error !== undefined) {
|
|
213
|
+
throw new Error(typescript_1.default.flattenDiagnosticMessageText(read.error.messageText, '\n'));
|
|
214
|
+
}
|
|
215
|
+
return typescript_1.default.parseJsonConfigFileContent(read.config, typescript_1.default.sys, path.dirname(configPath));
|
|
216
|
+
};
|
|
217
|
+
/**
|
|
218
|
+
* Creates the transformer core.
|
|
219
|
+
*
|
|
220
|
+
* The program is built on the first file that actually needs it, so a project
|
|
221
|
+
* never calling the utilities pays nothing.
|
|
222
|
+
*
|
|
223
|
+
* @param rewriters Rewriters of the package this core is serving.
|
|
224
|
+
* @param options Options of the core.
|
|
225
|
+
* @returns A transformer usable by any bundler adapter.
|
|
226
|
+
*/
|
|
227
|
+
const createFileTransformer = (rewriters, options = {}) => {
|
|
228
|
+
const root = options.root ?? process.cwd();
|
|
229
|
+
const utilityPattern = buildUtilityPattern(rewriters);
|
|
230
|
+
const transformer = (0, transformer_1.createTransformer)(rewriters);
|
|
231
|
+
let host;
|
|
232
|
+
let service;
|
|
233
|
+
const handles = (id) => {
|
|
234
|
+
const fileName = id.split('?')[0];
|
|
235
|
+
if (!HANDLED_EXTENSIONS.includes(path.extname(fileName)))
|
|
236
|
+
return false;
|
|
237
|
+
return !fileName.includes('node_modules');
|
|
238
|
+
};
|
|
239
|
+
const transform = (id, code) => {
|
|
240
|
+
if (!handles(id))
|
|
241
|
+
return null;
|
|
242
|
+
// Cheap rejection before anything expensive: most files of a project
|
|
243
|
+
// mention none of the utilities, and building the program for them
|
|
244
|
+
// would cost seconds for nothing.
|
|
245
|
+
if (!utilityPattern.test(code))
|
|
246
|
+
return null;
|
|
247
|
+
const fileName = toCompilerPath(id.split('?')[0]);
|
|
248
|
+
if (host === undefined || service === undefined) {
|
|
249
|
+
host = new ProgramHost(parseTsconfig(root, options.tsconfig), root);
|
|
250
|
+
service = typescript_1.default.createLanguageService(host, typescript_1.default.createDocumentRegistry());
|
|
251
|
+
}
|
|
252
|
+
host.update(fileName, code);
|
|
253
|
+
const program = service.getProgram();
|
|
254
|
+
const sourceFile = program?.getSourceFile(fileName);
|
|
255
|
+
if (program === undefined || sourceFile === undefined)
|
|
256
|
+
return null;
|
|
257
|
+
const result = typescript_1.default.transform(sourceFile, [transformer(program, { projectRoot: options.projectRoot ?? root })], program.getCompilerOptions());
|
|
258
|
+
const [transformed] = result.transformed;
|
|
259
|
+
const printed = typescript_1.default
|
|
260
|
+
.createPrinter({ newLine: typescript_1.default.NewLineKind.LineFeed })
|
|
261
|
+
.printFile(transformed);
|
|
262
|
+
result.dispose();
|
|
263
|
+
// Still TypeScript, only with the calls resolved: the bundler goes on to
|
|
264
|
+
// erase the types as it would have anyway.
|
|
265
|
+
return printed;
|
|
266
|
+
};
|
|
267
|
+
return { handles, transform };
|
|
268
|
+
};
|
|
269
|
+
exports.createFileTransformer = createFileTransformer;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import typescript from 'typescript';
|
|
2
|
+
/**
|
|
3
|
+
* Contract shared by the rewriters of the transformer.
|
|
4
|
+
*
|
|
5
|
+
* Each utility owns one module here, so that a change to how `defaultOf` fills
|
|
6
|
+
* a tuple never has to be made inside the same file that decides how `typeOf`
|
|
7
|
+
* reports a declaration site. The composition root walks the tree once and asks
|
|
8
|
+
* each rewriter whether the call in hand belongs to it.
|
|
9
|
+
*/
|
|
10
|
+
/** Everything a rewriter needs from the compilation in progress. */
|
|
11
|
+
export interface RewriteContext {
|
|
12
|
+
/** Checker of the program being compiled. */
|
|
13
|
+
readonly checker: typescript.TypeChecker;
|
|
14
|
+
/** Node factory of the current transformation. */
|
|
15
|
+
readonly factory: typescript.NodeFactory;
|
|
16
|
+
/** Root that reported declaration paths are made relative to. */
|
|
17
|
+
readonly projectRoot: string;
|
|
18
|
+
/**
|
|
19
|
+
* Visits a node with the whole transformer.
|
|
20
|
+
*
|
|
21
|
+
* Handed over so that a rewriter keeping part of the original call — as
|
|
22
|
+
* `typeOf` does with its argument — still has the other utilities applied
|
|
23
|
+
* inside whatever it keeps.
|
|
24
|
+
*/
|
|
25
|
+
readonly visit: (node: typescript.Node) => typescript.Node;
|
|
26
|
+
}
|
|
27
|
+
/** Rewrites the calls of a single utility. */
|
|
28
|
+
export interface CallRewriter {
|
|
29
|
+
/** Name the exported function is called by. */
|
|
30
|
+
readonly functionName: string;
|
|
31
|
+
/** Path segment identifying the module that declares it. */
|
|
32
|
+
readonly moduleSegment: string;
|
|
33
|
+
/**
|
|
34
|
+
* Rewrites one call.
|
|
35
|
+
*
|
|
36
|
+
* @param call Call already known to belong to this rewriter.
|
|
37
|
+
* @param context Compilation in progress.
|
|
38
|
+
* @returns The replacement node, or `null` to leave the call untouched so
|
|
39
|
+
* that the runtime implementation stays in charge.
|
|
40
|
+
*/
|
|
41
|
+
readonly rewrite: (call: typescript.CallExpression, context: RewriteContext) => typescript.Node | null;
|
|
42
|
+
}
|
|
43
|
+
/** Property names that can be emitted unquoted in an object literal. */
|
|
44
|
+
export declare const IDENTIFIER_PATTERN: RegExp;
|
|
45
|
+
/**
|
|
46
|
+
* Builds the path segment identifying a utility module.
|
|
47
|
+
*
|
|
48
|
+
* These segments tie this package to the folder layout `@fulcro/reflect`
|
|
49
|
+
* publishes — a call is recognised by the module that declares it, and after
|
|
50
|
+
* resolution that module is `functions/utils/<name>` inside the built output of
|
|
51
|
+
* that package. Moving those folders there silently stops the rewriting here,
|
|
52
|
+
* because a mismatch does not fail loudly on its own: the transformer simply
|
|
53
|
+
* leaves the calls alone and the runtime fallbacks take over. The compile
|
|
54
|
+
* fixture is what catches it, and it imports `@fulcro/reflect` by name rather
|
|
55
|
+
* than by path precisely so that it resolves the same way a consumer would.
|
|
56
|
+
*
|
|
57
|
+
* Matched exactly, casing included.
|
|
58
|
+
*
|
|
59
|
+
* @param name Folder of the utility inside the output of `@fulcro/reflect`.
|
|
60
|
+
* @returns The segment to look for in a declaration path.
|
|
61
|
+
*/
|
|
62
|
+
export declare const utilityModuleSegment: (name: string) => string;
|
|
63
|
+
/**
|
|
64
|
+
* Tells whether a call resolves to the function a rewriter owns.
|
|
65
|
+
*
|
|
66
|
+
* The symbol is followed back to its declaration rather than matched by name,
|
|
67
|
+
* so an unrelated local `nameOf` is never rewritten.
|
|
68
|
+
*
|
|
69
|
+
* @param call Call being inspected.
|
|
70
|
+
* @param checker Checker of the program being compiled.
|
|
71
|
+
* @param rewriter Rewriter claiming the call.
|
|
72
|
+
* @returns `true` when the call targets the function of that rewriter.
|
|
73
|
+
*/
|
|
74
|
+
export declare const isOwnedCall: (call: typescript.CallExpression, checker: typescript.TypeChecker, rewriter: CallRewriter) => boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Tells whether an object type is a tuple.
|
|
77
|
+
*
|
|
78
|
+
* Shared because both the description built by `typeOf` and the value built by
|
|
79
|
+
* `defaultOf` have to tell a tuple apart from a plain array.
|
|
80
|
+
*
|
|
81
|
+
* @param type Type being inspected.
|
|
82
|
+
* @returns `true` when the type is a tuple.
|
|
83
|
+
*/
|
|
84
|
+
export declare const isTupleType: (type: typescript.ObjectType) => boolean;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.isTupleType = exports.isOwnedCall = exports.utilityModuleSegment = exports.IDENTIFIER_PATTERN = void 0;
|
|
40
|
+
const path = __importStar(require("node:path"));
|
|
41
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
42
|
+
/** Property names that can be emitted unquoted in an object literal. */
|
|
43
|
+
exports.IDENTIFIER_PATTERN = /^[A-Za-z_$][\w$]*$/;
|
|
44
|
+
/**
|
|
45
|
+
* Builds the path segment identifying a utility module.
|
|
46
|
+
*
|
|
47
|
+
* These segments tie this package to the folder layout `@fulcro/reflect`
|
|
48
|
+
* publishes — a call is recognised by the module that declares it, and after
|
|
49
|
+
* resolution that module is `functions/utils/<name>` inside the built output of
|
|
50
|
+
* that package. Moving those folders there silently stops the rewriting here,
|
|
51
|
+
* because a mismatch does not fail loudly on its own: the transformer simply
|
|
52
|
+
* leaves the calls alone and the runtime fallbacks take over. The compile
|
|
53
|
+
* fixture is what catches it, and it imports `@fulcro/reflect` by name rather
|
|
54
|
+
* than by path precisely so that it resolves the same way a consumer would.
|
|
55
|
+
*
|
|
56
|
+
* Matched exactly, casing included.
|
|
57
|
+
*
|
|
58
|
+
* @param name Folder of the utility inside the output of `@fulcro/reflect`.
|
|
59
|
+
* @returns The segment to look for in a declaration path.
|
|
60
|
+
*/
|
|
61
|
+
const utilityModuleSegment = (name) => path.join('functions', 'utils', name);
|
|
62
|
+
exports.utilityModuleSegment = utilityModuleSegment;
|
|
63
|
+
/**
|
|
64
|
+
* Tells whether a call resolves to the function a rewriter owns.
|
|
65
|
+
*
|
|
66
|
+
* The symbol is followed back to its declaration rather than matched by name,
|
|
67
|
+
* so an unrelated local `nameOf` is never rewritten.
|
|
68
|
+
*
|
|
69
|
+
* @param call Call being inspected.
|
|
70
|
+
* @param checker Checker of the program being compiled.
|
|
71
|
+
* @param rewriter Rewriter claiming the call.
|
|
72
|
+
* @returns `true` when the call targets the function of that rewriter.
|
|
73
|
+
*/
|
|
74
|
+
const isOwnedCall = (call, checker, rewriter) => {
|
|
75
|
+
if (!typescript_1.default.isIdentifier(call.expression))
|
|
76
|
+
return false;
|
|
77
|
+
if (call.expression.text !== rewriter.functionName)
|
|
78
|
+
return false;
|
|
79
|
+
const symbol = checker.getSymbolAtLocation(call.expression);
|
|
80
|
+
const resolved = symbol !== undefined && (symbol.flags & typescript_1.default.SymbolFlags.Alias) !== 0
|
|
81
|
+
? checker.getAliasedSymbol(symbol)
|
|
82
|
+
: symbol;
|
|
83
|
+
const declarations = resolved?.declarations ?? [];
|
|
84
|
+
return declarations.some((declaration) => path
|
|
85
|
+
.normalize(declaration.getSourceFile().fileName)
|
|
86
|
+
.includes(rewriter.moduleSegment));
|
|
87
|
+
};
|
|
88
|
+
exports.isOwnedCall = isOwnedCall;
|
|
89
|
+
/**
|
|
90
|
+
* Tells whether an object type is a tuple.
|
|
91
|
+
*
|
|
92
|
+
* Shared because both the description built by `typeOf` and the value built by
|
|
93
|
+
* `defaultOf` have to tell a tuple apart from a plain array.
|
|
94
|
+
*
|
|
95
|
+
* @param type Type being inspected.
|
|
96
|
+
* @returns `true` when the type is a tuple.
|
|
97
|
+
*/
|
|
98
|
+
const isTupleType = (type) => (type.objectFlags & typescript_1.default.ObjectFlags.Reference) !== 0 &&
|
|
99
|
+
(type.target.objectFlags &
|
|
100
|
+
typescript_1.default.ObjectFlags.Tuple) !==
|
|
101
|
+
0;
|
|
102
|
+
exports.isTupleType = isTupleType;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import typescript from 'typescript';
|
|
2
|
+
import { CallRewriter } from '../shared/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Composition root shared by every Fulcro transformer.
|
|
5
|
+
*
|
|
6
|
+
* TypeScript erases its own type system on the way to JavaScript: interfaces,
|
|
7
|
+
* type aliases, generic arguments and the file a type was declared in leave no
|
|
8
|
+
* trace in the emitted code. A runtime function can therefore only report what
|
|
9
|
+
* the value in front of it shows, which is why these libraries need a compiler
|
|
10
|
+
* plugin to answer properly at all.
|
|
11
|
+
*
|
|
12
|
+
* This module holds the checker, walks every source file once, and hands each
|
|
13
|
+
* call to the rewriter that owns it. What any particular utility emits lives in
|
|
14
|
+
* the package that owns that utility — this one knows only how to walk and how
|
|
15
|
+
* to ask.
|
|
16
|
+
*
|
|
17
|
+
* Which is the whole reason it is a package of its own: `@fulcro/reflect` and
|
|
18
|
+
* `@fulcro/collections` each ship their own transformer, so neither has to be
|
|
19
|
+
* installed for the other to work, and neither can drift out of version with
|
|
20
|
+
* the runtime code it rewrites. The walking is the only part they share.
|
|
21
|
+
*
|
|
22
|
+
* Calls no rewriter can resolve are left untouched, so the runtime
|
|
23
|
+
* implementations stay in charge and a project compiling without the
|
|
24
|
+
* transformer keeps working, in whatever reduced form each utility documents.
|
|
25
|
+
*/
|
|
26
|
+
/** Options accepted from the `plugins` entry of the tsconfig. */
|
|
27
|
+
export interface TransformerOptions {
|
|
28
|
+
/** Root the reported declaration paths are made relative to. */
|
|
29
|
+
readonly projectRoot?: string;
|
|
30
|
+
}
|
|
31
|
+
/** The factory shape `ts-patch` expects from a plugin module. */
|
|
32
|
+
export type TransformerFactory = (program: typescript.Program, options?: TransformerOptions) => typescript.TransformerFactory<typescript.SourceFile>;
|
|
33
|
+
/**
|
|
34
|
+
* Builds a transformer from a set of rewriters.
|
|
35
|
+
*
|
|
36
|
+
* @param rewriters Rewriters consulted for every call, in order. Each one
|
|
37
|
+
* claims a single exported function of a single module, so the order carries no
|
|
38
|
+
* meaning beyond the one a reader gives it.
|
|
39
|
+
* @returns The factory a package exports as the default of its transformer
|
|
40
|
+
* entry point.
|
|
41
|
+
*/
|
|
42
|
+
export declare const createTransformer: (rewriters: readonly CallRewriter[]) => TransformerFactory;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createTransformer = void 0;
|
|
7
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
8
|
+
const shared_1 = require("../shared/index.js");
|
|
9
|
+
/**
|
|
10
|
+
* Builds a transformer from a set of rewriters.
|
|
11
|
+
*
|
|
12
|
+
* @param rewriters Rewriters consulted for every call, in order. Each one
|
|
13
|
+
* claims a single exported function of a single module, so the order carries no
|
|
14
|
+
* meaning beyond the one a reader gives it.
|
|
15
|
+
* @returns The factory a package exports as the default of its transformer
|
|
16
|
+
* entry point.
|
|
17
|
+
*/
|
|
18
|
+
const createTransformer = (rewriters) => (program, options = {}) => {
|
|
19
|
+
const checker = program.getTypeChecker();
|
|
20
|
+
const projectRoot = options.projectRoot ?? program.getCurrentDirectory();
|
|
21
|
+
return (context) => {
|
|
22
|
+
const visit = (node) => {
|
|
23
|
+
if (typescript_1.default.isCallExpression(node)) {
|
|
24
|
+
const rewritten = rewriteCall(node);
|
|
25
|
+
if (rewritten !== null)
|
|
26
|
+
return rewritten;
|
|
27
|
+
}
|
|
28
|
+
return typescript_1.default.visitEachChild(node, visit, context);
|
|
29
|
+
};
|
|
30
|
+
const rewriteContext = {
|
|
31
|
+
checker,
|
|
32
|
+
factory: context.factory,
|
|
33
|
+
projectRoot,
|
|
34
|
+
visit,
|
|
35
|
+
};
|
|
36
|
+
const rewriteCall = (call) => {
|
|
37
|
+
const owner = rewriters.find((rewriter) => (0, shared_1.isOwnedCall)(call, checker, rewriter));
|
|
38
|
+
return owner === undefined ? null : owner.rewrite(call, rewriteContext);
|
|
39
|
+
};
|
|
40
|
+
return (sourceFile) => typescript_1.default.visitNode(sourceFile, visit);
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
exports.createTransformer = createTransformer;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type UnpluginFactory, type UnpluginInstance } from 'unplugin';
|
|
2
|
+
import { type TransformCoreOptions } from '../program/index.js';
|
|
3
|
+
import { type CallRewriter } from '../shared/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* Bundler adapters for the Fulcro transformers.
|
|
6
|
+
*
|
|
7
|
+
* Built on `unplugin`, so the same integration serves Vite, Rollup, Webpack,
|
|
8
|
+
* esbuild, Rspack and Farm — and, through them, the frameworks layered on top.
|
|
9
|
+
* Everything specific to type checking lives in the core this wraps; what is
|
|
10
|
+
* left here is the shape each bundler expects.
|
|
11
|
+
*
|
|
12
|
+
* Nothing here knows which utilities are being rewritten. The owning package
|
|
13
|
+
* passes its own rewriters in, which is what lets `@fulcro/reflect` and
|
|
14
|
+
* `@fulcro/collections` each publish a plugin of their own without either
|
|
15
|
+
* having to know the other exists.
|
|
16
|
+
*
|
|
17
|
+
* Emitted as an ES module because `unplugin` ships as one. The core stays
|
|
18
|
+
* CommonJS, since the compiler plugin loads it through `require`, and an ES
|
|
19
|
+
* module importing CommonJS is the direction that works.
|
|
20
|
+
*/
|
|
21
|
+
/** Options accepted by every adapter. */
|
|
22
|
+
export type PluginOptions = TransformCoreOptions;
|
|
23
|
+
/**
|
|
24
|
+
* Every form `unplugin` produces for these options.
|
|
25
|
+
*
|
|
26
|
+
* Named through `UnpluginInstance` rather than `ReturnType<typeof
|
|
27
|
+
* createUnplugin>`, which infers its option type as `unknown` and makes every
|
|
28
|
+
* adapter below reject the options it is actually given.
|
|
29
|
+
*/
|
|
30
|
+
type Adapters = UnpluginInstance<PluginOptions | undefined>;
|
|
31
|
+
/** The adapters a package re-exports from its own `/unplugin` entry point. */
|
|
32
|
+
export interface TransformerUnplugin {
|
|
33
|
+
/** The factory itself, for a bundler not covered below. */
|
|
34
|
+
readonly unpluginFactory: UnpluginFactory<PluginOptions | undefined>;
|
|
35
|
+
/** Adapter for Vite, which is also what vitest runs on. */
|
|
36
|
+
readonly vite: Adapters['vite'];
|
|
37
|
+
/** Adapter for Rollup. */
|
|
38
|
+
readonly rollup: Adapters['rollup'];
|
|
39
|
+
/** Adapter for Webpack. */
|
|
40
|
+
readonly webpack: Adapters['webpack'];
|
|
41
|
+
/** Adapter for Rspack. */
|
|
42
|
+
readonly rspack: Adapters['rspack'];
|
|
43
|
+
/** Adapter for esbuild. */
|
|
44
|
+
readonly esbuild: Adapters['esbuild'];
|
|
45
|
+
/** Adapter for Farm. */
|
|
46
|
+
readonly farm: Adapters['farm'];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Builds the bundler adapters for one set of rewriters.
|
|
50
|
+
*
|
|
51
|
+
* @param rewriters Rewriters of the package publishing the plugin.
|
|
52
|
+
* @param name Name the plugin reports to the bundler, which is what shows up
|
|
53
|
+
* in its logs and timings.
|
|
54
|
+
* @returns Every adapter `unplugin` can produce.
|
|
55
|
+
*/
|
|
56
|
+
export declare const createTransformerUnplugin: (rewriters: readonly CallRewriter[], name: string) => TransformerUnplugin;
|
|
57
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createUnplugin, } from 'unplugin';
|
|
2
|
+
import { createFileTransformer, } from '../program/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Builds the bundler adapters for one set of rewriters.
|
|
5
|
+
*
|
|
6
|
+
* @param rewriters Rewriters of the package publishing the plugin.
|
|
7
|
+
* @param name Name the plugin reports to the bundler, which is what shows up
|
|
8
|
+
* in its logs and timings.
|
|
9
|
+
* @returns Every adapter `unplugin` can produce.
|
|
10
|
+
*/
|
|
11
|
+
export const createTransformerUnplugin = (rewriters, name) => {
|
|
12
|
+
const unpluginFactory = (options = {}) => {
|
|
13
|
+
let transformer;
|
|
14
|
+
/**
|
|
15
|
+
* Resolves the core lazily, so that the root reported by the bundler is
|
|
16
|
+
* already known when the program is configured.
|
|
17
|
+
*
|
|
18
|
+
* @returns The transformer core.
|
|
19
|
+
*/
|
|
20
|
+
const resolve = () => {
|
|
21
|
+
transformer ??= createFileTransformer(rewriters, options);
|
|
22
|
+
return transformer;
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
name,
|
|
26
|
+
// Ahead of the TypeScript handling of the bundler: once esbuild or swc
|
|
27
|
+
// has erased the types, there is nothing left for the transformer to
|
|
28
|
+
// read.
|
|
29
|
+
enforce: 'pre',
|
|
30
|
+
transformInclude(id) {
|
|
31
|
+
return resolve().handles(id);
|
|
32
|
+
},
|
|
33
|
+
transform(code, id) {
|
|
34
|
+
const transformed = resolve().transform(id, code);
|
|
35
|
+
return transformed === null ? null : { code: transformed, map: null };
|
|
36
|
+
},
|
|
37
|
+
vite: {
|
|
38
|
+
configResolved(config) {
|
|
39
|
+
// The root of the bundler wins over the working directory, which
|
|
40
|
+
// is what makes the plugin behave inside a monorepo.
|
|
41
|
+
if (config.root !== undefined && options.root === undefined) {
|
|
42
|
+
transformer = createFileTransformer(rewriters, {
|
|
43
|
+
...options,
|
|
44
|
+
root: config.root,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
const unplugin = createUnplugin(unpluginFactory);
|
|
52
|
+
return {
|
|
53
|
+
unpluginFactory,
|
|
54
|
+
vite: unplugin.vite,
|
|
55
|
+
rollup: unplugin.rollup,
|
|
56
|
+
webpack: unplugin.webpack,
|
|
57
|
+
rspack: unplugin.rspack,
|
|
58
|
+
esbuild: unplugin.esbuild,
|
|
59
|
+
farm: unplugin.farm,
|
|
60
|
+
};
|
|
61
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fulcro/transform-core",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Shared machinery behind the Fulcro compile time transformers. Installed for you; not meant to be depended on directly.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"typescript",
|
|
7
|
+
"transformer",
|
|
8
|
+
"ts-patch",
|
|
9
|
+
"unplugin",
|
|
10
|
+
"compiler"
|
|
11
|
+
],
|
|
12
|
+
"license": "ISC",
|
|
13
|
+
"author": "diguu <rodrigogeribola@hotmail.com>",
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./unplugin": {
|
|
22
|
+
"types": "./dist/unplugin/index.d.mts",
|
|
23
|
+
"default": "./dist/unplugin/index.mjs"
|
|
24
|
+
},
|
|
25
|
+
"./package.json": "./package.json"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
|
|
33
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
34
|
+
"prepublishOnly": "npm run build"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"unplugin": "^3.3.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"typescript": ">=5.3.3 <7"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"typescript": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=22"
|
|
52
|
+
},
|
|
53
|
+
"repository": {
|
|
54
|
+
"type": "git",
|
|
55
|
+
"url": "git+https://github.com/DigUu-RL/fulcro.git",
|
|
56
|
+
"directory": "packages/transform-core"
|
|
57
|
+
},
|
|
58
|
+
"homepage": "https://github.com/DigUu-RL/fulcro/tree/main/packages/transform-core#readme",
|
|
59
|
+
"bugs": {
|
|
60
|
+
"url": "https://github.com/DigUu-RL/fulcro/issues"
|
|
61
|
+
}
|
|
62
|
+
}
|