protobuf-fastdsl 0.1.2 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -20
- package/dist/index.d.ts +31 -3
- package/dist/index.js +152 -24
- package/dist/runtime.d.ts +10 -0
- package/dist/runtime.js +15 -0
- package/package.json +11 -7
- package/protobuf.d.ts +25 -25
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# protobuf-fastdsl
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
将 TypeScript 接口在构建时编译为**完全内联**、零依赖的 protobuf 编解码函数。
|
|
4
4
|
|
|
5
5
|
无 `.proto` 文件。无运行时库。无函数调用开销。只需 TypeScript 接口 → 确定化的二进制编解码代码。
|
|
6
6
|
|
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
- **零运行时** — 所有 wire-format 逻辑在编译时内联
|
|
10
10
|
- **TypeScript 原生** — 用 `pb<N, Type>` 标记接口字段即可定义 schema
|
|
11
|
+
- **跨文件类型** — 支持 `import type` / `import` 引用其他文件的接口定义
|
|
11
12
|
- **泛型单态化** — `Wrapper<Wrapper<string>>` → 自动生成具体编解码函数
|
|
12
13
|
- **重复字段** — `pb_repeated<N, Type>` 编译为 `Type[]`
|
|
13
14
|
- **编译期预计算 Tag** — 字段标签在编译时折叠为字面量字节
|
|
15
|
+
- **安全的 Fallback** — 未经插件转换时运行会直接抛错,不会静默产生错误结果
|
|
14
16
|
|
|
15
17
|
## 安装
|
|
16
18
|
|
|
@@ -20,30 +22,24 @@ npm install protobuf-fastdsl
|
|
|
20
22
|
|
|
21
23
|
## 快速开始
|
|
22
24
|
|
|
23
|
-
**1.
|
|
25
|
+
**1. 配置构建工具插件:**
|
|
24
26
|
|
|
25
27
|
```ts
|
|
26
|
-
|
|
28
|
+
// vite.config.ts
|
|
29
|
+
import protobufPlugin from 'protobuf-fastdsl/vite';
|
|
27
30
|
|
|
28
31
|
export default defineConfig({
|
|
29
|
-
plugins: [
|
|
32
|
+
plugins: [protobufPlugin()],
|
|
30
33
|
});
|
|
31
34
|
```
|
|
32
35
|
|
|
33
|
-
**2.
|
|
34
|
-
|
|
35
|
-
```json
|
|
36
|
-
{
|
|
37
|
-
"compilerOptions": {
|
|
38
|
-
"types": ["protobuf-fastdsl/types"]
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
**3. 用 TypeScript 接口定义 schema:**
|
|
36
|
+
**2. 定义 protobuf schema(TypeScript 接口):**
|
|
44
37
|
|
|
45
38
|
```ts
|
|
46
|
-
|
|
39
|
+
// schema/user.ts
|
|
40
|
+
import type { pb, pb_repeated, uint_32, bool } from 'protobuf-fastdsl';
|
|
41
|
+
|
|
42
|
+
export interface UserProfile {
|
|
47
43
|
id: pb<1, uint_32>;
|
|
48
44
|
username: pb<2, string>;
|
|
49
45
|
active: pb<3, bool>;
|
|
@@ -51,9 +47,12 @@ interface UserProfile {
|
|
|
51
47
|
}
|
|
52
48
|
```
|
|
53
49
|
|
|
54
|
-
**
|
|
50
|
+
**3. 编解码:**
|
|
55
51
|
|
|
56
52
|
```ts
|
|
53
|
+
import { protobuf_encode, protobuf_decode } from 'protobuf-fastdsl';
|
|
54
|
+
import type { UserProfile } from './schema/user';
|
|
55
|
+
|
|
57
56
|
const bytes = protobuf_encode<UserProfile>({
|
|
58
57
|
id: 42,
|
|
59
58
|
username: 'alice',
|
|
@@ -65,7 +64,7 @@ const user = protobuf_decode<UserProfile>(bytes);
|
|
|
65
64
|
// user.id === 42, user.tags === ['admin', 'dev']
|
|
66
65
|
```
|
|
67
66
|
|
|
68
|
-
|
|
67
|
+
构建时,插件将上述代码转换为:
|
|
69
68
|
|
|
70
69
|
```js
|
|
71
70
|
// 预计算的 tag 字面量,内联 varint 循环,零函数调用
|
|
@@ -73,6 +72,43 @@ const bytes = protobuf_encode_UserProfile({ id: 42, ... });
|
|
|
73
72
|
const user = protobuf_decode_UserProfile(bytes);
|
|
74
73
|
```
|
|
75
74
|
|
|
75
|
+
如果忘记配置插件,`protobuf_encode` / `protobuf_decode` 会在运行时抛出错误提示。
|
|
76
|
+
|
|
77
|
+
## 跨文件类型引用
|
|
78
|
+
|
|
79
|
+
接口定义和编解码调用可以在不同文件中,插件会自动跟踪 import 链:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
// inner.ts
|
|
83
|
+
import type { pb, uint_32 } from 'protobuf-fastdsl';
|
|
84
|
+
|
|
85
|
+
export interface Inner {
|
|
86
|
+
value: pb<1, uint_32>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// outer.ts
|
|
90
|
+
import { protobuf_encode } from 'protobuf-fastdsl';
|
|
91
|
+
import type { Inner } from './inner';
|
|
92
|
+
import type { pb } from 'protobuf-fastdsl';
|
|
93
|
+
|
|
94
|
+
interface Outer {
|
|
95
|
+
inner: pb<1, Inner>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const bytes = protobuf_encode<Outer>({ inner: { value: 42 } });
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
支持传递性导入(A → B → C),插件会递归解析所有依赖。
|
|
102
|
+
|
|
103
|
+
## 别名导入
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { protobuf_encode as encode, protobuf_decode as decode } from 'protobuf-fastdsl';
|
|
107
|
+
|
|
108
|
+
const bytes = encode<UserProfile>(data); // → protobuf_encode_UserProfile(data)
|
|
109
|
+
const user = decode<UserProfile>(bytes); // → protobuf_decode_UserProfile(bytes)
|
|
110
|
+
```
|
|
111
|
+
|
|
76
112
|
## 泛型单态化
|
|
77
113
|
|
|
78
114
|
定义泛型 protobuf 模板,使用具体类型实例化:
|
|
@@ -95,8 +131,8 @@ const data = protobuf_encode<Wrapper<Wrapper<string>>>({
|
|
|
95
131
|
|---------------|-----------------|-----------|
|
|
96
132
|
| `uint_32`, `int_32` | `number` | Varint |
|
|
97
133
|
| `uint_64`, `int_64` | `bigint` | Varint |
|
|
98
|
-
| `sint_32` | `number` | Varint |
|
|
99
|
-
| `sint_64` | `bigint` | Varint |
|
|
134
|
+
| `sint_32` | `number` | Varint (ZigZag) |
|
|
135
|
+
| `sint_64` | `bigint` | Varint (ZigZag) |
|
|
100
136
|
| `bool` | `boolean` | Varint |
|
|
101
137
|
| `string` | `string` | LengthDelimited |
|
|
102
138
|
| `bytes` | `Uint8Array` | LengthDelimited |
|
|
@@ -111,6 +147,14 @@ const data = protobuf_encode<Wrapper<Wrapper<string>>>({
|
|
|
111
147
|
|
|
112
148
|
说明:
|
|
113
149
|
- 所有 64 位整数类型在 TypeScript 中统一映射为 `bigint`
|
|
150
|
+
- `sint_32` / `sint_64` 使用 ZigZag 编码,适用于频繁出现负数的场景
|
|
151
|
+
|
|
152
|
+
## 包入口
|
|
153
|
+
|
|
154
|
+
| 路径 | 用途 |
|
|
155
|
+
|------|------|
|
|
156
|
+
| `protobuf-fastdsl` | 用户代码 — `protobuf_encode`、`protobuf_decode`、所有类型 |
|
|
157
|
+
| `protobuf-fastdsl/vite` | Vite 插件 |
|
|
114
158
|
|
|
115
159
|
## ⚡ 性能测试
|
|
116
160
|
|
package/dist/index.d.ts
CHANGED
|
@@ -20,8 +20,36 @@ interface ProtobufMessage {
|
|
|
20
20
|
name: string;
|
|
21
21
|
fields: ProtobufField[];
|
|
22
22
|
}
|
|
23
|
+
/** Generic interface template, e.g. `interface Wrapper<T> { val: pb<1, T>; }` */
|
|
24
|
+
interface GenericProtobufTemplate {
|
|
25
|
+
name: string;
|
|
26
|
+
typeParams: string[];
|
|
27
|
+
fields: GenericFieldTemplate[];
|
|
28
|
+
}
|
|
29
|
+
interface GenericFieldTemplate {
|
|
30
|
+
name: string;
|
|
31
|
+
fieldNumber: number;
|
|
32
|
+
rawTypeName: string;
|
|
33
|
+
isTypeParam: boolean;
|
|
34
|
+
isOptional: boolean;
|
|
35
|
+
isRepeated: boolean;
|
|
36
|
+
}
|
|
23
37
|
type MessageRegistry = Map<string, ProtobufMessage>;
|
|
24
38
|
|
|
39
|
+
interface ParsedFileEntry {
|
|
40
|
+
concrete: ProtobufMessage[];
|
|
41
|
+
templates: Map<string, GenericProtobufTemplate>;
|
|
42
|
+
}
|
|
43
|
+
interface ImportedDefinitions {
|
|
44
|
+
concrete: ProtobufMessage[];
|
|
45
|
+
templates: Map<string, GenericProtobufTemplate>;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Recursively resolve import-type declarations from a source file.
|
|
49
|
+
* Returns all protobuf interfaces and templates reachable through imports.
|
|
50
|
+
*/
|
|
51
|
+
declare function resolveImports(code: string, importerPath: string, cache: Map<string, ParsedFileEntry>): ImportedDefinitions;
|
|
52
|
+
|
|
25
53
|
/**
|
|
26
54
|
* Deterministic mangled name for a type node.
|
|
27
55
|
* `Foo` → `Foo`
|
|
@@ -51,12 +79,12 @@ interface AnalysisResult {
|
|
|
51
79
|
*
|
|
52
80
|
* Then post-processes: monomorphize → resolve wire types → topo sort.
|
|
53
81
|
*/
|
|
54
|
-
declare function analyze(code: string, filePath: string): AnalysisResult;
|
|
82
|
+
declare function analyze(code: string, filePath: string, imported?: ImportedDefinitions): AnalysisResult;
|
|
55
83
|
/**
|
|
56
84
|
* Backward-compatible wrapper: returns only the MessageRegistry.
|
|
57
85
|
* Uses the same single-walk analysis internally.
|
|
58
86
|
*/
|
|
59
|
-
declare function analyzeSource(code: string, filePath: string): MessageRegistry;
|
|
87
|
+
declare function analyzeSource(code: string, filePath: string, imported?: ImportedDefinitions): MessageRegistry;
|
|
60
88
|
|
|
61
89
|
/**
|
|
62
90
|
* Generate fully self-contained encode/decode source code.
|
|
@@ -83,4 +111,4 @@ declare function replaceCallSites(code: string, registry: MessageRegistry): {
|
|
|
83
111
|
|
|
84
112
|
declare function protobufVitePlugin(): Plugin;
|
|
85
113
|
|
|
86
|
-
export { analyze, analyzeSource, applyReplacements, protobufVitePlugin as default, generateCode, replaceCallSites, typeNodeToMangledName };
|
|
114
|
+
export { analyze, analyzeSource, applyReplacements, protobufVitePlugin as default, generateCode, replaceCallSites, resolveImports, typeNodeToMangledName };
|
package/dist/index.js
CHANGED
|
@@ -166,13 +166,30 @@ function resolveTypeArg(node, sf, templates, out) {
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
// src/ast/analyzer.ts
|
|
169
|
-
function analyze(code, filePath) {
|
|
169
|
+
function analyze(code, filePath, imported) {
|
|
170
170
|
const sf = ts4.createSourceFile(filePath, code, ts4.ScriptTarget.Latest, true);
|
|
171
171
|
const concrete = [];
|
|
172
172
|
const templates = /* @__PURE__ */ new Map();
|
|
173
173
|
const mono = /* @__PURE__ */ new Map();
|
|
174
174
|
const callSites = [];
|
|
175
175
|
const deferredTypeArgs = [];
|
|
176
|
+
if (imported) {
|
|
177
|
+
concrete.push(...imported.concrete);
|
|
178
|
+
for (const [k, v] of imported.templates) templates.set(k, v);
|
|
179
|
+
}
|
|
180
|
+
const CANONICAL = /* @__PURE__ */ new Set(["protobuf_encode", "protobuf_decode"]);
|
|
181
|
+
const aliasToCanonical = /* @__PURE__ */ new Map();
|
|
182
|
+
for (const stmt of sf.statements) {
|
|
183
|
+
if (!ts4.isImportDeclaration(stmt) || !stmt.importClause) continue;
|
|
184
|
+
const bindings = stmt.importClause.namedBindings;
|
|
185
|
+
if (!bindings || !ts4.isNamedImports(bindings)) continue;
|
|
186
|
+
for (const el of bindings.elements) {
|
|
187
|
+
const originalName = (el.propertyName ?? el.name).text;
|
|
188
|
+
if (CANONICAL.has(originalName)) {
|
|
189
|
+
aliasToCanonical.set(el.name.text, originalName);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
176
193
|
ts4.forEachChild(sf, function visit(node) {
|
|
177
194
|
if (ts4.isInterfaceDeclaration(node)) {
|
|
178
195
|
if (node.typeParameters?.length) {
|
|
@@ -185,16 +202,19 @@ function analyze(code, filePath) {
|
|
|
185
202
|
}
|
|
186
203
|
if (ts4.isCallExpression(node)) {
|
|
187
204
|
const e = node.expression;
|
|
188
|
-
if (ts4.isIdentifier(e)
|
|
189
|
-
const
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
205
|
+
if (ts4.isIdentifier(e)) {
|
|
206
|
+
const canonical = CANONICAL.has(e.text) ? e.text : aliasToCanonical.get(e.text);
|
|
207
|
+
if (canonical) {
|
|
208
|
+
const ta = node.typeArguments;
|
|
209
|
+
if (ta?.length) {
|
|
210
|
+
deferredTypeArgs.push(ta[0]);
|
|
211
|
+
callSites.push({
|
|
212
|
+
fnName: canonical,
|
|
213
|
+
exprStart: e.getStart(sf),
|
|
214
|
+
typeArgsEnd: ta.end + 1,
|
|
215
|
+
firstTypeArg: ta[0]
|
|
216
|
+
});
|
|
217
|
+
}
|
|
198
218
|
}
|
|
199
219
|
}
|
|
200
220
|
}
|
|
@@ -219,8 +239,8 @@ function analyze(code, filePath) {
|
|
|
219
239
|
}
|
|
220
240
|
return { registry: topoSort(concrete), callSites, sourceFile: sf };
|
|
221
241
|
}
|
|
222
|
-
function analyzeSource(code, filePath) {
|
|
223
|
-
return analyze(code, filePath).registry;
|
|
242
|
+
function analyzeSource(code, filePath, imported) {
|
|
243
|
+
return analyze(code, filePath, imported).registry;
|
|
224
244
|
}
|
|
225
245
|
function topoSort(messages) {
|
|
226
246
|
const map = new Map(messages.map((m) => [m.name, m]));
|
|
@@ -1107,18 +1127,34 @@ function applyReplacements(code, sf, callSites, registry) {
|
|
|
1107
1127
|
function replaceCallSites(code, registry) {
|
|
1108
1128
|
const sf = ts5.createSourceFile("input.ts", code, ts5.ScriptTarget.Latest, true);
|
|
1109
1129
|
const callSites = [];
|
|
1130
|
+
const CANONICAL = /* @__PURE__ */ new Set(["protobuf_encode", "protobuf_decode"]);
|
|
1131
|
+
const aliasToCanonical = /* @__PURE__ */ new Map();
|
|
1132
|
+
for (const stmt of sf.statements) {
|
|
1133
|
+
if (!ts5.isImportDeclaration(stmt) || !stmt.importClause) continue;
|
|
1134
|
+
const bindings = stmt.importClause.namedBindings;
|
|
1135
|
+
if (!bindings || !ts5.isNamedImports(bindings)) continue;
|
|
1136
|
+
for (const el of bindings.elements) {
|
|
1137
|
+
const originalName = (el.propertyName ?? el.name).text;
|
|
1138
|
+
if (CANONICAL.has(originalName)) {
|
|
1139
|
+
aliasToCanonical.set(el.name.text, originalName);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1110
1143
|
ts5.forEachChild(sf, function visit(node) {
|
|
1111
1144
|
if (ts5.isCallExpression(node)) {
|
|
1112
1145
|
const e = node.expression;
|
|
1113
|
-
if (ts5.isIdentifier(e)
|
|
1114
|
-
const
|
|
1115
|
-
if (
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1146
|
+
if (ts5.isIdentifier(e)) {
|
|
1147
|
+
const canonical = CANONICAL.has(e.text) ? e.text : aliasToCanonical.get(e.text);
|
|
1148
|
+
if (canonical) {
|
|
1149
|
+
const ta = node.typeArguments;
|
|
1150
|
+
if (ta?.length) {
|
|
1151
|
+
callSites.push({
|
|
1152
|
+
fnName: canonical,
|
|
1153
|
+
exprStart: e.getStart(sf),
|
|
1154
|
+
typeArgsEnd: ta.end + 1,
|
|
1155
|
+
firstTypeArg: ta[0]
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1122
1158
|
}
|
|
1123
1159
|
}
|
|
1124
1160
|
}
|
|
@@ -1127,19 +1163,110 @@ function replaceCallSites(code, registry) {
|
|
|
1127
1163
|
return applyReplacements(code, sf, callSites, registry);
|
|
1128
1164
|
}
|
|
1129
1165
|
|
|
1166
|
+
// src/ast/import-resolver.ts
|
|
1167
|
+
import ts6 from "typescript";
|
|
1168
|
+
import { readFileSync, existsSync } from "fs";
|
|
1169
|
+
import { dirname, resolve } from "path";
|
|
1170
|
+
function extractImports(sf) {
|
|
1171
|
+
const result = [];
|
|
1172
|
+
for (const stmt of sf.statements) {
|
|
1173
|
+
if (!ts6.isImportDeclaration(stmt) || !stmt.importClause) continue;
|
|
1174
|
+
const spec = stmt.moduleSpecifier;
|
|
1175
|
+
if (!ts6.isStringLiteral(spec)) continue;
|
|
1176
|
+
const specifier = spec.text;
|
|
1177
|
+
const clause = stmt.importClause;
|
|
1178
|
+
const names = [];
|
|
1179
|
+
if (clause.namedBindings && ts6.isNamedImports(clause.namedBindings)) {
|
|
1180
|
+
for (const el of clause.namedBindings.elements) {
|
|
1181
|
+
names.push(el.name.text);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
if (names.length > 0) result.push({ names, specifier });
|
|
1185
|
+
}
|
|
1186
|
+
return result;
|
|
1187
|
+
}
|
|
1188
|
+
function resolveModulePath(specifier, importerPath) {
|
|
1189
|
+
if (!specifier.startsWith(".")) return null;
|
|
1190
|
+
const base = resolve(dirname(importerPath), specifier);
|
|
1191
|
+
if (existsSync(base) && !base.endsWith(".ts") === false) return base;
|
|
1192
|
+
if (base.endsWith(".ts") && existsSync(base)) return base;
|
|
1193
|
+
const withTs = base + ".ts";
|
|
1194
|
+
if (existsSync(withTs)) return withTs;
|
|
1195
|
+
const indexTs = resolve(base, "index.ts");
|
|
1196
|
+
if (existsSync(indexTs)) return indexTs;
|
|
1197
|
+
return null;
|
|
1198
|
+
}
|
|
1199
|
+
function parseFileForDefinitions(absolutePath) {
|
|
1200
|
+
const code = readFileSync(absolutePath, "utf-8");
|
|
1201
|
+
const sf = ts6.createSourceFile(absolutePath, code, ts6.ScriptTarget.Latest, true);
|
|
1202
|
+
const concrete = [];
|
|
1203
|
+
const templates = /* @__PURE__ */ new Map();
|
|
1204
|
+
for (const stmt of sf.statements) {
|
|
1205
|
+
if (!ts6.isInterfaceDeclaration(stmt)) continue;
|
|
1206
|
+
if (stmt.typeParameters?.length) {
|
|
1207
|
+
const tpl = collectGenericInterface(stmt, sf);
|
|
1208
|
+
if (tpl) templates.set(tpl.name, tpl);
|
|
1209
|
+
} else {
|
|
1210
|
+
const msg = collectInterface(stmt, sf);
|
|
1211
|
+
if (msg) concrete.push(msg);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
return { concrete, templates };
|
|
1215
|
+
}
|
|
1216
|
+
function resolveImports(code, importerPath, cache) {
|
|
1217
|
+
const concrete = [];
|
|
1218
|
+
const templates = /* @__PURE__ */ new Map();
|
|
1219
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
1220
|
+
function walk(filePath, fileCode) {
|
|
1221
|
+
const abs = resolve(filePath);
|
|
1222
|
+
if (visiting.has(abs)) return;
|
|
1223
|
+
visiting.add(abs);
|
|
1224
|
+
const src = fileCode ?? readFileSync(abs, "utf-8");
|
|
1225
|
+
const sf = ts6.createSourceFile(abs, src, ts6.ScriptTarget.Latest, true);
|
|
1226
|
+
const imports = extractImports(sf);
|
|
1227
|
+
for (const imp of imports) {
|
|
1228
|
+
const resolved = resolveModulePath(imp.specifier, abs);
|
|
1229
|
+
if (!resolved) continue;
|
|
1230
|
+
let entry = cache.get(resolved);
|
|
1231
|
+
if (!entry) {
|
|
1232
|
+
entry = parseFileForDefinitions(resolved);
|
|
1233
|
+
cache.set(resolved, entry);
|
|
1234
|
+
}
|
|
1235
|
+
for (const msg of entry.concrete) {
|
|
1236
|
+
if (!concrete.some((m) => m.name === msg.name)) {
|
|
1237
|
+
concrete.push(msg);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
for (const [name, tpl] of entry.templates) {
|
|
1241
|
+
if (!templates.has(name)) templates.set(name, tpl);
|
|
1242
|
+
}
|
|
1243
|
+
walk(resolved);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
walk(importerPath, code);
|
|
1247
|
+
return { concrete, templates };
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1130
1250
|
// src/index.ts
|
|
1131
1251
|
function protobufVitePlugin() {
|
|
1252
|
+
const fileCache = /* @__PURE__ */ new Map();
|
|
1132
1253
|
return {
|
|
1133
1254
|
name: "vite-plugin-protobuf",
|
|
1134
1255
|
enforce: "pre",
|
|
1135
1256
|
transform(code, id) {
|
|
1136
1257
|
if (!id.endsWith(".ts") || id.endsWith(".d.ts")) return null;
|
|
1137
|
-
const
|
|
1138
|
-
|
|
1258
|
+
const imported = resolveImports(code, id, fileCache);
|
|
1259
|
+
const { registry, callSites, sourceFile } = analyze(code, id, imported);
|
|
1260
|
+
if (registry.size === 0 && callSites.length === 0) return null;
|
|
1139
1261
|
const generatedCode = generateCode(registry);
|
|
1140
1262
|
const { transformedCode, hasReplacements } = applyReplacements(code, sourceFile, callSites, registry);
|
|
1141
1263
|
if (!hasReplacements && generatedCode === "") return null;
|
|
1142
1264
|
return { code: generatedCode + "\n" + transformedCode, map: null };
|
|
1265
|
+
},
|
|
1266
|
+
handleHotUpdate({ file }) {
|
|
1267
|
+
if (file.endsWith(".ts")) {
|
|
1268
|
+
fileCache.delete(file);
|
|
1269
|
+
}
|
|
1143
1270
|
}
|
|
1144
1271
|
};
|
|
1145
1272
|
}
|
|
@@ -1150,5 +1277,6 @@ export {
|
|
|
1150
1277
|
protobufVitePlugin as default,
|
|
1151
1278
|
generateCode,
|
|
1152
1279
|
replaceCallSites,
|
|
1280
|
+
resolveImports,
|
|
1153
1281
|
typeNodeToMangledName
|
|
1154
1282
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime fallback functions.
|
|
3
|
+
* If the Vite plugin is active, calls to protobuf_encode/decode are replaced
|
|
4
|
+
* at compile-time with generated type-specific functions.
|
|
5
|
+
* If NOT transformed, these fallbacks throw to alert the developer.
|
|
6
|
+
*/
|
|
7
|
+
declare function protobuf_encode<T>(_params: T): Uint8Array;
|
|
8
|
+
declare function protobuf_decode<T>(_data: Uint8Array): T;
|
|
9
|
+
|
|
10
|
+
export { protobuf_decode, protobuf_encode };
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/runtime.ts
|
|
2
|
+
function protobuf_encode(_params) {
|
|
3
|
+
throw new Error(
|
|
4
|
+
"protobuf_encode<T>() was not transformed by the protobuf-fastdsl Vite plugin. Make sure protobufVitePlugin() is added to your vite.config.ts plugins array."
|
|
5
|
+
);
|
|
6
|
+
}
|
|
7
|
+
function protobuf_decode(_data) {
|
|
8
|
+
throw new Error(
|
|
9
|
+
"protobuf_decode<T>() was not transformed by the protobuf-fastdsl Vite plugin. Make sure protobufVitePlugin() is added to your vite.config.ts plugins array."
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
protobuf_decode,
|
|
14
|
+
protobuf_encode
|
|
15
|
+
};
|
package/package.json
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "protobuf-fastdsl",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"main": "dist/
|
|
6
|
-
"types": "
|
|
5
|
+
"main": "dist/runtime.js",
|
|
6
|
+
"types": "protobuf.d.ts",
|
|
7
7
|
"files": [
|
|
8
8
|
"dist",
|
|
9
9
|
"protobuf.d.ts"
|
|
10
10
|
],
|
|
11
11
|
"exports": {
|
|
12
12
|
".": {
|
|
13
|
+
"types": "./protobuf.d.ts",
|
|
14
|
+
"import": "./dist/runtime.js"
|
|
15
|
+
},
|
|
16
|
+
"./vite": {
|
|
13
17
|
"types": "./dist/index.d.ts",
|
|
14
18
|
"import": "./dist/index.js"
|
|
15
|
-
},
|
|
16
|
-
"./types": {
|
|
17
|
-
"types": "./protobuf.d.ts"
|
|
18
19
|
}
|
|
19
20
|
},
|
|
20
21
|
"scripts": {
|
|
21
|
-
"build": "tsup src/index.ts --format esm --dts",
|
|
22
|
+
"build": "tsup src/index.ts src/runtime.ts --format esm --dts",
|
|
22
23
|
"test": "vitest run",
|
|
23
24
|
"test:watch": "vitest",
|
|
24
25
|
"bench": "npx tsx bench/index.ts"
|
|
@@ -29,6 +30,9 @@
|
|
|
29
30
|
"peerDependencies": {
|
|
30
31
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
|
|
31
32
|
},
|
|
33
|
+
"peerDependenciesMeta": {
|
|
34
|
+
"vite": { "optional": true }
|
|
35
|
+
},
|
|
32
36
|
"devDependencies": {
|
|
33
37
|
"@types/node": "^25.4.0",
|
|
34
38
|
"tsup": "^8.0.0",
|
package/protobuf.d.ts
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
// ── Protobuf field markers ────────────────────────────────────────────
|
|
2
|
-
/** Marks a singular protobuf field: `name: pb<fieldNumber, Type>` */
|
|
3
|
-
type pb<_ProtoNumber extends number, Type> = Type;
|
|
4
|
-
/** Marks a repeated protobuf field: `ids: pb_repeated<fieldNumber, Type>` → Type[] */
|
|
5
|
-
type pb_repeated<_ProtoNumber extends number, Type> = Type[];
|
|
6
|
-
|
|
7
|
-
// ── Protobuf primitive types ──────────────────────────────────────────
|
|
8
|
-
type uint_32 = number;
|
|
9
|
-
type int_32 = number;
|
|
10
|
-
type uint_64 = bigint;
|
|
11
|
-
type int_64 = bigint;
|
|
12
|
-
type sint_32 = number;
|
|
13
|
-
type sint_64 = bigint;
|
|
14
|
-
type bool = boolean;
|
|
15
|
-
type float = number;
|
|
16
|
-
type double = number;
|
|
17
|
-
type fixed_32 = number;
|
|
18
|
-
type fixed_64 = bigint;
|
|
19
|
-
type sfixed_32 = number;
|
|
20
|
-
type sfixed_64 = bigint;
|
|
21
|
-
type bytes = Uint8Array;
|
|
22
|
-
|
|
23
|
-
// ── Encode / decode
|
|
24
|
-
|
|
25
|
-
|
|
1
|
+
// ── Protobuf field markers ────────────────────────────────────────────
|
|
2
|
+
/** Marks a singular protobuf field: `name: pb<fieldNumber, Type>` */
|
|
3
|
+
export type pb<_ProtoNumber extends number, Type> = Type;
|
|
4
|
+
/** Marks a repeated protobuf field: `ids: pb_repeated<fieldNumber, Type>` → Type[] */
|
|
5
|
+
export type pb_repeated<_ProtoNumber extends number, Type> = Type[];
|
|
6
|
+
|
|
7
|
+
// ── Protobuf primitive types ──────────────────────────────────────────
|
|
8
|
+
export type uint_32 = number;
|
|
9
|
+
export type int_32 = number;
|
|
10
|
+
export type uint_64 = bigint;
|
|
11
|
+
export type int_64 = bigint;
|
|
12
|
+
export type sint_32 = number;
|
|
13
|
+
export type sint_64 = bigint;
|
|
14
|
+
export type bool = boolean;
|
|
15
|
+
export type float = number;
|
|
16
|
+
export type double = number;
|
|
17
|
+
export type fixed_32 = number;
|
|
18
|
+
export type fixed_64 = bigint;
|
|
19
|
+
export type sfixed_32 = number;
|
|
20
|
+
export type sfixed_64 = bigint;
|
|
21
|
+
export type bytes = Uint8Array;
|
|
22
|
+
|
|
23
|
+
// ── Encode / decode (replaced at compile-time by the vite plugin) ────
|
|
24
|
+
export function protobuf_encode<T>(params: T): Uint8Array;
|
|
25
|
+
export function protobuf_decode<T>(data: Uint8Array): T;
|