@zerodep-dev/vue-tsx-pro-tsx 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +44 -2
- package/dist/compile-worker.d.ts +1 -0
- package/dist/compile-worker.js +23 -0
- package/dist/compiler-BDi92tYb.js +1647 -0
- package/dist/compiler.d.ts +104 -0
- package/dist/compiler.js +1 -0
- package/dist/detect-s3UM7xuN.js +17 -0
- package/dist/index.d.ts +140 -0
- package/dist/index.js +283 -0
- package/dist/module--rQ2-rvm.js +194 -0
- package/dist/ssr-register-CZ_lIN2f.js +17 -0
- package/dist/state-YxL7IqUO.js +108 -0
- package/dist/styles.d.ts +15402 -0
- package/dist/styles.js +1282 -0
- package/dist/vite.d.ts +31 -0
- package/dist/vite.js +509 -0
- package/package.json +98 -3
- package/types/jsx-runtime.d.ts +174 -0
- package/types/runes.d.ts +100 -0
package/dist/vite.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Plugin } from "vite";
|
|
2
|
+
import { VueJSXPluginOptions } from "@vue/babel-plugin-jsx";
|
|
3
|
+
//#region src/vite/workspace.d.ts
|
|
4
|
+
/** 解析 @zerodep-dev/vue-tsx-pro-* 用源码;开发与测试时桶文件的具名导入改成直接导入(见文件开头) */
|
|
5
|
+
export declare function workspaceSource(): Plugin;
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/vite/index.d.ts
|
|
8
|
+
/** vueTsxPro() 的选项;都不写就是应用的默认配置 */
|
|
9
|
+
export interface VueTsxProOptions {
|
|
10
|
+
/**
|
|
11
|
+
* 渲染优化,默认 true:组件子节点的插槽标 $stable、JSX 属性里的函数与常量每个实例只建一次,
|
|
12
|
+
* 父组件因为别的状态重新渲染时子组件不再跟着重做(见 RUNES.md)。界面要读响应式数据;
|
|
13
|
+
* 怀疑哪里因此没更新时,传 false 对比
|
|
14
|
+
*/
|
|
15
|
+
optimize?: boolean;
|
|
16
|
+
/** Vue JSX 插件(@vue/babel-plugin-jsx)的选项 */
|
|
17
|
+
jsx?: VueJSXPluginOptions;
|
|
18
|
+
/**
|
|
19
|
+
* 开发服务器把编译结果存在磁盘上(cacheDir/vue-tsx-pro),重启后没改过的文件不再编译,默认 true。
|
|
20
|
+
* 构建与 Vitest 不用它(Vitest 有自己的模块缓存)
|
|
21
|
+
*/
|
|
22
|
+
cache?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* 在工作线程里并行编译(构建与开发服务器;Vitest 本来就是多进程,不用),默认 true。
|
|
25
|
+
* 选项里有函数时不能复制到线程,自动在主线程编译
|
|
26
|
+
*/
|
|
27
|
+
parallel?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/** Vite 预设:`plugins: [vueTsxPro()]` */
|
|
30
|
+
export declare function vueTsxPro(options?: VueTsxProOptions): Plugin[];
|
|
31
|
+
//#endregion
|
package/dist/vite.js
ADDED
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
import { n as languageOf } from "./detect-s3UM7xuN.js";
|
|
2
|
+
import { t as SSR_REGISTER_CODE } from "./ssr-register-CZ_lIN2f.js";
|
|
3
|
+
import { normalizePath, parseSync } from "vite";
|
|
4
|
+
import { availableParallelism } from "node:os";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { createRequire } from "node:module";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { Worker } from "node:worker_threads";
|
|
11
|
+
//#region src/vite/cache-key.ts
|
|
12
|
+
/**
|
|
13
|
+
* Vitest 的文件模块缓存(fsModuleCache)按源文件内容命中,不知道编译器变了:
|
|
14
|
+
* 升级本包、或在本仓库改了编译器后,会继续用旧的编译结果。这里算出编译器代码的哈希,
|
|
15
|
+
* 由预设通过 configureVitest 加进缓存键(见 index.ts)。
|
|
16
|
+
*/
|
|
17
|
+
let cached;
|
|
18
|
+
/** 编译器代码的哈希(进程内只算一次) */
|
|
19
|
+
function compilerCacheKey() {
|
|
20
|
+
return cached ??= hashFiles(compilerFiles());
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 产物:dist 下的全部 js(编译器打进了共享 chunk);仓库源码:编译器与 Vite 预设的源文件
|
|
24
|
+
*/
|
|
25
|
+
function compilerFiles() {
|
|
26
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
return (here.replaceAll("\\", "/").endsWith("/src/vite") ? [here, join(here, "../compiler")] : [here]).flatMap((directory) => readdirSync(directory).filter((file) => /\.(js|ts)$/.test(file) && !file.endsWith(".d.ts")).map((file) => join(directory, file)));
|
|
28
|
+
}
|
|
29
|
+
function hashFiles(files) {
|
|
30
|
+
const hash = createHash("sha1");
|
|
31
|
+
for (const file of files.sort()) hash.update(readFileSync(file));
|
|
32
|
+
return hash.digest("hex").slice(0, 16);
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/vite/cache.ts
|
|
36
|
+
/**
|
|
37
|
+
* 开发服务器的编译缓存:编译结果存在磁盘上(Vite 的 cacheDir 下),重启后没改过的文件直接取用、不再编译。
|
|
38
|
+
* Vite 自己只缓存依赖预构建,每次重启都要把首页用到的几百个模块重新编译一遍。
|
|
39
|
+
*
|
|
40
|
+
* 键包含一切会影响结果的东西:编译器代码(compilerCacheKey)、Babel 与官方插件的版本、选项、模块 id 与代码。
|
|
41
|
+
* 每个模块一个文件,改了就覆盖,不会越积越多。读写失败都当作没有缓存。
|
|
42
|
+
*/
|
|
43
|
+
/** 影响编译结果的依赖包:版本变了缓存全部作废 */
|
|
44
|
+
const COMPILER_PACKAGES = [
|
|
45
|
+
"@babel/core",
|
|
46
|
+
"@babel/plugin-transform-typescript",
|
|
47
|
+
"@vue/babel-plugin-jsx"
|
|
48
|
+
];
|
|
49
|
+
/** 磁盘上的编译缓存;salt 是选项等对所有模块都一样的部分 */
|
|
50
|
+
var CompileCache = class {
|
|
51
|
+
dir;
|
|
52
|
+
salt;
|
|
53
|
+
constructor(cacheDir, salt) {
|
|
54
|
+
this.dir = join(cacheDir, "vue-tsx-pro");
|
|
55
|
+
const require = createRequire(import.meta.url);
|
|
56
|
+
const versions = COMPILER_PACKAGES.map((name) => require(`${name}/package.json`).version);
|
|
57
|
+
this.salt = [
|
|
58
|
+
compilerCacheKey(),
|
|
59
|
+
...versions,
|
|
60
|
+
salt
|
|
61
|
+
].join("\0");
|
|
62
|
+
mkdirSync(this.dir, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
/** 模块的缓存键 */
|
|
65
|
+
key(id, code) {
|
|
66
|
+
return createHash("sha1").update(this.salt).update("\0").update(id).update("\0").update(code).digest("hex");
|
|
67
|
+
}
|
|
68
|
+
/** 键对得上时返回缓存的结果 */
|
|
69
|
+
read(id, key) {
|
|
70
|
+
try {
|
|
71
|
+
const entry = JSON.parse(readFileSync(this.file(id), "utf8"));
|
|
72
|
+
return entry.key === key ? entry : void 0;
|
|
73
|
+
} catch {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** 写入(覆盖这个模块原来的缓存) */
|
|
78
|
+
write(id, key, result) {
|
|
79
|
+
try {
|
|
80
|
+
writeFileSync(this.file(id), JSON.stringify({
|
|
81
|
+
key,
|
|
82
|
+
...result
|
|
83
|
+
}));
|
|
84
|
+
} catch {}
|
|
85
|
+
}
|
|
86
|
+
file(id) {
|
|
87
|
+
return join(this.dir, `${createHash("sha1").update(id).digest("hex")}.json`);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/vite/pool.ts
|
|
92
|
+
/**
|
|
93
|
+
* 编译线程池:冷启动、构建时几百个模块同时等编译,Babel 在主线程上只能一个个来。
|
|
94
|
+
* 每个线程加载一份编译器(compile-worker.ts),请求按空闲分派;线程用到时才启动,最多 size 个。
|
|
95
|
+
*/
|
|
96
|
+
/** 编译线程的入口:仓库内按源码运行时是 .ts(Node 去掉类型),发布包里是构建出的 .js */
|
|
97
|
+
const WORKER = new URL(import.meta.url.endsWith(".ts") ? "./compile-worker.ts" : "./compile-worker.js", import.meta.url);
|
|
98
|
+
/** 编译线程池;设置要能在线程之间复制(见 module.ts) */
|
|
99
|
+
var CompilePool = class {
|
|
100
|
+
setup;
|
|
101
|
+
size;
|
|
102
|
+
workers = [];
|
|
103
|
+
idle = [];
|
|
104
|
+
queue = [];
|
|
105
|
+
running = /* @__PURE__ */ new Map();
|
|
106
|
+
nextId = 0;
|
|
107
|
+
constructor(setup, size) {
|
|
108
|
+
this.setup = setup;
|
|
109
|
+
this.size = size;
|
|
110
|
+
}
|
|
111
|
+
/** 在某个线程里编译一个模块 */
|
|
112
|
+
run(request) {
|
|
113
|
+
return new Promise((resolve, reject) => {
|
|
114
|
+
this.queue.push({
|
|
115
|
+
request,
|
|
116
|
+
resolve,
|
|
117
|
+
reject
|
|
118
|
+
});
|
|
119
|
+
this.dispatch();
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* 先起 count 个线程(不超过上限):线程启动时载入编译器(Babel 与各插件,约 1–2 秒),
|
|
124
|
+
* 开发服务器就绪后就在后台载入,浏览器第一次请求时不用再等
|
|
125
|
+
*/
|
|
126
|
+
warm(count) {
|
|
127
|
+
while (this.workers.length < Math.min(count, this.size)) this.idle.push(this.spawn());
|
|
128
|
+
}
|
|
129
|
+
/** 停掉全部线程(构建结束、服务器关闭) */
|
|
130
|
+
close() {
|
|
131
|
+
for (const worker of this.workers) worker.terminate();
|
|
132
|
+
this.workers = [];
|
|
133
|
+
this.idle = [];
|
|
134
|
+
}
|
|
135
|
+
dispatch() {
|
|
136
|
+
while (this.queue.length) {
|
|
137
|
+
const worker = this.idle.pop() ?? (this.workers.length < this.size ? this.spawn() : void 0);
|
|
138
|
+
if (!worker) return;
|
|
139
|
+
const job = this.queue.shift();
|
|
140
|
+
const id = this.nextId++;
|
|
141
|
+
this.running.set(id, {
|
|
142
|
+
...job,
|
|
143
|
+
worker
|
|
144
|
+
});
|
|
145
|
+
worker.postMessage({
|
|
146
|
+
id,
|
|
147
|
+
request: job.request
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
spawn() {
|
|
152
|
+
const worker = new Worker(WORKER, { workerData: this.setup });
|
|
153
|
+
worker.unref();
|
|
154
|
+
worker.on("message", ({ id, result, error }) => {
|
|
155
|
+
const job = this.running.get(id);
|
|
156
|
+
this.running.delete(id);
|
|
157
|
+
this.idle.push(worker);
|
|
158
|
+
if (error) job.reject(Object.assign(new Error(error.message), error));
|
|
159
|
+
else job.resolve(result ?? null);
|
|
160
|
+
this.dispatch();
|
|
161
|
+
});
|
|
162
|
+
worker.on("error", (error) => {
|
|
163
|
+
for (const [id, job] of this.running) {
|
|
164
|
+
if (job.worker !== worker) continue;
|
|
165
|
+
this.running.delete(id);
|
|
166
|
+
job.reject(error);
|
|
167
|
+
}
|
|
168
|
+
this.workers = this.workers.filter((item) => item !== worker);
|
|
169
|
+
this.idle = this.idle.filter((item) => item !== worker);
|
|
170
|
+
this.dispatch();
|
|
171
|
+
});
|
|
172
|
+
this.workers.push(worker);
|
|
173
|
+
return worker;
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region src/vite/workspace.ts
|
|
178
|
+
/**
|
|
179
|
+
* 仅供本仓库使用,发布包的使用方不需要:
|
|
180
|
+
* - 解析 @zerodep-dev/vue-tsx-pro-* 时直接读源码(exports 里的 @vue-tsx-pro/source 条件),改了组件库不用先构建
|
|
181
|
+
* - 开发服务器与测试里,包入口(桶文件)的具名导入改成直接导入所在模块:
|
|
182
|
+
* `import { Button } from '@zerodep-dev/vue-tsx-pro-ui'` → `import { Button } from '<ui>/src/base/Button.tsx'`。
|
|
183
|
+
* 经过桶文件导入时,组件库里不是组件的模块(样式、状态、工具函数、主题、工厂函数做的组件)改了,
|
|
184
|
+
* 热更新沿着桶文件 → main.ts 一路走到没人接受更新的地方,整页刷新;直接导入时停在用到它的组件上。
|
|
185
|
+
* 构建时不改:lazyBarrel 本来就只编译用到的再导出。
|
|
186
|
+
* - 按源码读的包,它自己导入的第三方依赖一开始就预构建(包在 package.json 的 vueTsxPro.optimizeDeps 里声明)
|
|
187
|
+
*/
|
|
188
|
+
/** 解析出顶层语句;有语法错误时为 undefined */
|
|
189
|
+
function statements(filename, code) {
|
|
190
|
+
const result = parseSync(filename, code);
|
|
191
|
+
return result.errors.length ? void 0 : result.program.body;
|
|
192
|
+
}
|
|
193
|
+
/** 解析 @zerodep-dev/vue-tsx-pro-* 用源码;开发与测试时桶文件的具名导入改成直接导入(见文件开头) */
|
|
194
|
+
function workspaceSource() {
|
|
195
|
+
const condition = "@vue-tsx-pro/source";
|
|
196
|
+
let serve = false;
|
|
197
|
+
/** 解析后的包入口 → 桶文件(不是纯粹的再导出时为 null);入口文件改了重新读 */
|
|
198
|
+
const barrels = /* @__PURE__ */ new Map();
|
|
199
|
+
return {
|
|
200
|
+
name: "vue-tsx-pro:workspace-source",
|
|
201
|
+
config(user) {
|
|
202
|
+
return {
|
|
203
|
+
resolve: { conditions: [
|
|
204
|
+
condition,
|
|
205
|
+
"module",
|
|
206
|
+
"browser",
|
|
207
|
+
"development|production"
|
|
208
|
+
] },
|
|
209
|
+
optimizeDeps: sourceDependencies(user.root ?? process.cwd()),
|
|
210
|
+
ssr: { resolve: { conditions: [
|
|
211
|
+
condition,
|
|
212
|
+
"module",
|
|
213
|
+
"node",
|
|
214
|
+
"development|production"
|
|
215
|
+
] } }
|
|
216
|
+
};
|
|
217
|
+
},
|
|
218
|
+
configResolved(config) {
|
|
219
|
+
serve = config.command === "serve";
|
|
220
|
+
},
|
|
221
|
+
watchChange(id, change) {
|
|
222
|
+
if (change.event === "update") barrels.delete(normalizePath(id));
|
|
223
|
+
else barrels.clear();
|
|
224
|
+
},
|
|
225
|
+
transform: {
|
|
226
|
+
filter: {
|
|
227
|
+
id: {
|
|
228
|
+
include: /\.(?:[cm]?[jt]sx?|vue)(?:\?|$)/,
|
|
229
|
+
exclude: /\/node_modules\//
|
|
230
|
+
},
|
|
231
|
+
code: "@zerodep-dev/vue-tsx-pro-"
|
|
232
|
+
},
|
|
233
|
+
handler(code, id) {
|
|
234
|
+
if (!serve || id.startsWith("\0")) return;
|
|
235
|
+
return directImports(this, code, id, barrels);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
/** 把模块里从桶文件的具名导入改成直接导入;没有可改的时返回 undefined */
|
|
241
|
+
async function directImports(context, code, id, barrels) {
|
|
242
|
+
const body = statements("module.ts", code);
|
|
243
|
+
if (!body) return void 0;
|
|
244
|
+
const edits = [];
|
|
245
|
+
for (const node of body) {
|
|
246
|
+
if (node.type !== "ImportDeclaration" || node.importKind === "type") continue;
|
|
247
|
+
const source = node.source.value;
|
|
248
|
+
if (!source.startsWith("@zerodep-dev/vue-tsx-pro-") || !node.specifiers.length) continue;
|
|
249
|
+
if (!node.specifiers.every((specifier) => specifier.type === "ImportSpecifier")) continue;
|
|
250
|
+
const resolved = await context.resolve(source, id);
|
|
251
|
+
if (!resolved || resolved.external) continue;
|
|
252
|
+
const barrel = await barrelOf(context, resolved.id, barrels);
|
|
253
|
+
if (!barrel) continue;
|
|
254
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
255
|
+
const rest = [];
|
|
256
|
+
for (const specifier of node.specifiers) {
|
|
257
|
+
if (specifier.type !== "ImportSpecifier" || specifier.importKind === "type") continue;
|
|
258
|
+
const local = specifier.local.name;
|
|
259
|
+
const imported = specifier.imported.type === "Identifier" ? specifier.imported.name : void 0;
|
|
260
|
+
const target = imported === void 0 ? void 0 : barrel.exports.get(imported);
|
|
261
|
+
if (!target) {
|
|
262
|
+
rest.push(code.slice(specifier.start, specifier.end));
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const names = byFile.get(target.file) ?? [];
|
|
266
|
+
names.push(target.name === local ? local : `${target.name} as ${local}`);
|
|
267
|
+
byFile.set(target.file, names);
|
|
268
|
+
}
|
|
269
|
+
const statements = [
|
|
270
|
+
...barrel.sideEffects.map((file) => `import ${JSON.stringify(file)};`),
|
|
271
|
+
...[...byFile].map(([file, names]) => `import { ${names.join(", ")} } from ${JSON.stringify(file)};`),
|
|
272
|
+
...rest.length ? [`import { ${rest.join(", ")} } from ${JSON.stringify(source)};`] : []
|
|
273
|
+
];
|
|
274
|
+
const lines = code.slice(node.start, node.end).split("\n").length - 1;
|
|
275
|
+
edits.push({
|
|
276
|
+
start: node.start,
|
|
277
|
+
end: node.end,
|
|
278
|
+
text: statements.join(" ") + "\n".repeat(lines)
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
if (!edits.length) return void 0;
|
|
282
|
+
let next = code;
|
|
283
|
+
for (const edit of edits.reverse()) next = next.slice(0, edit.start) + edit.text + next.slice(edit.end);
|
|
284
|
+
return {
|
|
285
|
+
code: next,
|
|
286
|
+
map: null
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function barrelOf(context, id, barrels) {
|
|
290
|
+
let barrel = barrels.get(id);
|
|
291
|
+
if (!barrel) {
|
|
292
|
+
barrel = readBarrel(context, id);
|
|
293
|
+
barrels.set(id, barrel);
|
|
294
|
+
}
|
|
295
|
+
return barrel;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* 读入口文件:只有从本包相对路径的再导出(`export { … } from './x'`)与副作用导入时才算桶文件;
|
|
299
|
+
* 有自己的声明、`export *`、从别的包再导出时返回 null,不改写
|
|
300
|
+
*/
|
|
301
|
+
async function readBarrel(context, id) {
|
|
302
|
+
const file = id.split("?")[0];
|
|
303
|
+
let body;
|
|
304
|
+
try {
|
|
305
|
+
body = statements(file, readFileSync(file, "utf8"));
|
|
306
|
+
} catch {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
if (!body) return null;
|
|
310
|
+
const barrel = {
|
|
311
|
+
exports: /* @__PURE__ */ new Map(),
|
|
312
|
+
sideEffects: []
|
|
313
|
+
};
|
|
314
|
+
const resolve = async (source) => {
|
|
315
|
+
if (!source.startsWith(".")) return void 0;
|
|
316
|
+
const resolved = await context.resolve(source, id);
|
|
317
|
+
return resolved && !resolved.external ? resolved.id : void 0;
|
|
318
|
+
};
|
|
319
|
+
for (const node of body) {
|
|
320
|
+
if (node.type === "ImportDeclaration" && !node.specifiers.length) {
|
|
321
|
+
const file = await resolve(node.source.value);
|
|
322
|
+
if (!file) return null;
|
|
323
|
+
barrel.sideEffects.push(file);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
if (node.type !== "ExportNamedDeclaration" || !node.source || node.declaration) return null;
|
|
327
|
+
if (node.exportKind === "type") continue;
|
|
328
|
+
const file = await resolve(node.source.value);
|
|
329
|
+
if (!file) return null;
|
|
330
|
+
for (const specifier of node.specifiers) {
|
|
331
|
+
if (specifier.exportKind === "type") continue;
|
|
332
|
+
if (specifier.local.type !== "Identifier" || specifier.exported.type !== "Identifier") return null;
|
|
333
|
+
barrel.exports.set(specifier.exported.name, {
|
|
334
|
+
file,
|
|
335
|
+
name: specifier.local.name
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return barrel;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* 按源码读的 @zerodep-dev/vue-tsx-pro-* 包自己导入的第三方依赖:不声明的话 Vite 要等页面(或浏览器测试)用到才发现,
|
|
343
|
+
* 发现时重新预构建、刷新页面,正在初始化的测试页会卡住。写成“包 > 依赖”,从包的目录解析,应用不必自己安装;
|
|
344
|
+
* 没装的可选依赖(three、@unovis/ts 等)跳过,免得 Vite 报解析不到
|
|
345
|
+
*/
|
|
346
|
+
function sourceDependencies(root) {
|
|
347
|
+
const include = [];
|
|
348
|
+
const exclude = [];
|
|
349
|
+
const own = readManifest(join(root, "package.json"));
|
|
350
|
+
if (!own) return {
|
|
351
|
+
include,
|
|
352
|
+
exclude
|
|
353
|
+
};
|
|
354
|
+
const require = createRequire(join(root, "package.json"));
|
|
355
|
+
for (const name of Object.keys({
|
|
356
|
+
...own.dependencies,
|
|
357
|
+
...own.devDependencies
|
|
358
|
+
})) {
|
|
359
|
+
if (!name.startsWith("@zerodep-dev/vue-tsx-pro-")) continue;
|
|
360
|
+
let manifest;
|
|
361
|
+
try {
|
|
362
|
+
manifest = require.resolve(`${name}/package.json`);
|
|
363
|
+
} catch {
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
const declared = readManifest(manifest)?.vueTsxPro?.optimizeDeps;
|
|
367
|
+
for (const dependency of declared?.include ?? []) if (installed(packageName(dependency), dirname(manifest))) include.push(`${name} > ${dependency}`);
|
|
368
|
+
exclude.push(...declared?.exclude ?? []);
|
|
369
|
+
}
|
|
370
|
+
return {
|
|
371
|
+
include,
|
|
372
|
+
exclude
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
/** 导入说法里的包名:'zod/v4/core' → 'zod','@lucide/icons/x' → '@lucide/icons' */
|
|
376
|
+
function packageName(specifier) {
|
|
377
|
+
const parts = specifier.split("/");
|
|
378
|
+
return specifier.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
|
|
379
|
+
}
|
|
380
|
+
/** 从某个目录往上找得到这个包(Node 的解析方式;不经 exports,只看装没装) */
|
|
381
|
+
function installed(name, from) {
|
|
382
|
+
for (let directory = from;; directory = dirname(directory)) {
|
|
383
|
+
if (existsSync(join(directory, "node_modules", name, "package.json"))) return true;
|
|
384
|
+
if (dirname(directory) === directory) return false;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
function readManifest(path) {
|
|
388
|
+
try {
|
|
389
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
390
|
+
} catch {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
//#endregion
|
|
395
|
+
//#region src/vite/index.ts
|
|
396
|
+
/** Vite 预设:`plugins: [vueTsxPro()]` */
|
|
397
|
+
function vueTsxPro(options = {}) {
|
|
398
|
+
return [compiler(options)];
|
|
399
|
+
}
|
|
400
|
+
/** 脚本模块:JS、TS 文件,或 Vue 单文件组件里的脚本(…/X.vue?vue&type=script&lang.tsx) */
|
|
401
|
+
const SCRIPT = /\.[cm]?[jt]sx?(?:\?|$)/;
|
|
402
|
+
const SCRIPT_FILE = /\.[cm]?[jt]sx?$/;
|
|
403
|
+
/** 编译插件:每个文件一次 compile,加上组件的热更新、SSR 登记 */
|
|
404
|
+
function compiler(options) {
|
|
405
|
+
const optimize = options.optimize ?? true;
|
|
406
|
+
let setup;
|
|
407
|
+
let cache;
|
|
408
|
+
let pool;
|
|
409
|
+
let cacheDir = "";
|
|
410
|
+
const run = async (request) => pool ? pool.run(request) : (await import("./module--rQ2-rvm.js").then((n) => n.n)).compileModule(request, setup);
|
|
411
|
+
return {
|
|
412
|
+
name: "vue-tsx-pro",
|
|
413
|
+
enforce: "pre",
|
|
414
|
+
configureVitest({ defineCacheKeyGenerator }) {
|
|
415
|
+
defineCacheKeyGenerator?.(({ sourceCode }) => sourceCode.includes("import.meta.glob") ? false : `${compilerCacheKey()}${optimize ? "" : ":plain"}`);
|
|
416
|
+
},
|
|
417
|
+
config(user) {
|
|
418
|
+
const flag = (name, fallback) => {
|
|
419
|
+
const value = user.define?.[name];
|
|
420
|
+
if (value === void 0) return fallback;
|
|
421
|
+
try {
|
|
422
|
+
return typeof value === "string" ? JSON.parse(value) : value;
|
|
423
|
+
} catch {
|
|
424
|
+
return value;
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
return {
|
|
428
|
+
oxc: { include: /\.[cm]?ts$/ },
|
|
429
|
+
define: {
|
|
430
|
+
__VUE_OPTIONS_API__: flag("__VUE_OPTIONS_API__", true),
|
|
431
|
+
__VUE_PROD_DEVTOOLS__: flag("__VUE_PROD_DEVTOOLS__", false),
|
|
432
|
+
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: flag("__VUE_PROD_HYDRATION_MISMATCH_DETAILS__", false)
|
|
433
|
+
},
|
|
434
|
+
optimizeDeps: { rolldownOptions: { transform: { jsx: "preserve" } } }
|
|
435
|
+
};
|
|
436
|
+
},
|
|
437
|
+
configResolved(config) {
|
|
438
|
+
cacheDir = `${normalizePath(config.cacheDir)}/`;
|
|
439
|
+
const hmr = config.command === "serve" && !config.isProduction;
|
|
440
|
+
setup = {
|
|
441
|
+
optimize,
|
|
442
|
+
root: config.root,
|
|
443
|
+
jsx: options.jsx,
|
|
444
|
+
hmr
|
|
445
|
+
};
|
|
446
|
+
if ((options.cache ?? true) && config.command === "serve" && !process.env.VITEST) cache = new CompileCache(config.cacheDir, JSON.stringify([
|
|
447
|
+
options,
|
|
448
|
+
hmr,
|
|
449
|
+
process.env.NODE_ENV
|
|
450
|
+
]));
|
|
451
|
+
if ((options.parallel ?? true) && !process.env.VITEST && cloneable(setup)) pool = new CompilePool(setup, Math.max(1, Math.min(availableParallelism() - 1, 6)));
|
|
452
|
+
},
|
|
453
|
+
configureServer() {
|
|
454
|
+
pool?.warm(2);
|
|
455
|
+
},
|
|
456
|
+
closeBundle() {
|
|
457
|
+
pool?.close();
|
|
458
|
+
},
|
|
459
|
+
resolveId: (id) => id === "/__vue-jsx-ssr-register-helper" ? id : void 0,
|
|
460
|
+
load: (id) => id === "/__vue-jsx-ssr-register-helper" ? SSR_REGISTER_CODE : void 0,
|
|
461
|
+
transform: {
|
|
462
|
+
filter: { id: SCRIPT },
|
|
463
|
+
async handler(code, id, transformOptions) {
|
|
464
|
+
if (id.startsWith("\0")) return;
|
|
465
|
+
const [file = id, query = ""] = id.split("?");
|
|
466
|
+
if (/(?:^|&)(?:raw|url)(?:&|$)/.test(query)) return;
|
|
467
|
+
const own = SCRIPT_FILE.test(file) && !file.includes("/node_modules/") && !file.startsWith(cacheDir);
|
|
468
|
+
const lang = languageOf(id);
|
|
469
|
+
if (!lang) return;
|
|
470
|
+
const ssr = transformOptions?.ssr === true;
|
|
471
|
+
const key = cache?.key(ssr ? `${id}\0ssr` : id, code);
|
|
472
|
+
const cached = key ? cache.read(ssr ? `${id}\0ssr` : id, key) : void 0;
|
|
473
|
+
if (cached) {
|
|
474
|
+
for (const warning of cached.warnings) this.warn(warning);
|
|
475
|
+
return {
|
|
476
|
+
code: cached.code,
|
|
477
|
+
map: cached.map
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
const result = await run({
|
|
481
|
+
code,
|
|
482
|
+
id,
|
|
483
|
+
file,
|
|
484
|
+
lang,
|
|
485
|
+
own,
|
|
486
|
+
ssr
|
|
487
|
+
});
|
|
488
|
+
if (!result) return;
|
|
489
|
+
for (const warning of result.warnings) this.warn(warning);
|
|
490
|
+
if (key) cache.write(ssr ? `${id}\0ssr` : id, key, result);
|
|
491
|
+
return {
|
|
492
|
+
code: result.code,
|
|
493
|
+
map: result.map
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
/** 设置能不能复制到编译线程(含函数的选项不能) */
|
|
500
|
+
function cloneable(value) {
|
|
501
|
+
try {
|
|
502
|
+
structuredClone(value);
|
|
503
|
+
return true;
|
|
504
|
+
} catch {
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
//#endregion
|
|
509
|
+
export { vueTsxPro, workspaceSource };
|
package/package.json
CHANGED
|
@@ -1,6 +1,101 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zerodep-dev/vue-tsx-pro-tsx",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "更自然的 Vue TSX:组件边界、符文编译与 Vite 预设",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "kenconnet666",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/kenconnet666/vue-tsx-pro.git",
|
|
11
|
+
"directory": "packages/tsx"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/kenconnet666/vue-tsx-pro#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/kenconnet666/vue-tsx-pro/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"vue",
|
|
19
|
+
"tsx",
|
|
20
|
+
"jsx",
|
|
21
|
+
"runes",
|
|
22
|
+
"css-in-js",
|
|
23
|
+
"stylex",
|
|
24
|
+
"vite-plugin"
|
|
25
|
+
],
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": "^20.19.0 || >=22.12.0"
|
|
29
|
+
},
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"@vue-tsx-pro/source": "./src/index.ts",
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"default": "./dist/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./vite": {
|
|
37
|
+
"@vue-tsx-pro/source": "./src/vite/index.ts",
|
|
38
|
+
"types": "./dist/vite.d.ts",
|
|
39
|
+
"default": "./dist/vite.js"
|
|
40
|
+
},
|
|
41
|
+
"./styles": {
|
|
42
|
+
"@vue-tsx-pro/source": "./src/styles/index.ts",
|
|
43
|
+
"types": "./dist/styles.d.ts",
|
|
44
|
+
"default": "./dist/styles.js"
|
|
45
|
+
},
|
|
46
|
+
"./compiler": {
|
|
47
|
+
"@vue-tsx-pro/source": "./src/compiler/index.ts",
|
|
48
|
+
"types": "./dist/compiler.d.ts",
|
|
49
|
+
"default": "./dist/compiler.js"
|
|
50
|
+
},
|
|
51
|
+
"./jsx-runtime": {
|
|
52
|
+
"types": "./types/jsx-runtime.d.ts"
|
|
53
|
+
},
|
|
54
|
+
"./runes": {
|
|
55
|
+
"types": "./types/runes.d.ts"
|
|
56
|
+
},
|
|
57
|
+
"./package.json": "./package.json"
|
|
58
|
+
},
|
|
59
|
+
"files": [
|
|
60
|
+
"dist",
|
|
61
|
+
"types"
|
|
62
|
+
],
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"@babel/core": "8.0.7",
|
|
65
|
+
"@babel/plugin-transform-typescript": "8.0.6",
|
|
66
|
+
"@vue/babel-plugin-jsx": "3.0.0",
|
|
67
|
+
"@vue/devtools-api": "8.2.1"
|
|
68
|
+
},
|
|
69
|
+
"peerDependencies": {
|
|
70
|
+
"vite": "^8.0.0",
|
|
71
|
+
"vue": "^3.5.0"
|
|
72
|
+
},
|
|
73
|
+
"peerDependenciesMeta": {
|
|
74
|
+
"vite": {
|
|
75
|
+
"optional": true
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"devDependencies": {
|
|
79
|
+
"happy-dom": "21.0.3",
|
|
80
|
+
"tsdown": "0.23.1",
|
|
81
|
+
"typescript": "7.1.0-beta",
|
|
82
|
+
"vite": "8.3.4",
|
|
83
|
+
"vitest": "5.0.5",
|
|
84
|
+
"vue": "3.5.44"
|
|
85
|
+
},
|
|
86
|
+
"vueTsxPro": {
|
|
87
|
+
"optimizeDeps": {
|
|
88
|
+
"include": [
|
|
89
|
+
"@vue/devtools-api"
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
"publishConfig": {
|
|
94
|
+
"access": "public"
|
|
95
|
+
},
|
|
96
|
+
"scripts": {
|
|
97
|
+
"build": "tsdown",
|
|
98
|
+
"typecheck": "tsc -p . --incremental --tsBuildInfoFile node_modules/.cache/typecheck.tsbuildinfo",
|
|
99
|
+
"test": "vitest run --configLoader native"
|
|
100
|
+
}
|
|
6
101
|
}
|