fe-stack 0.0.11 → 0.0.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fe-stack",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "description": "共享的配置文件集合,用于 Vite、Tailwind、Biome、Prettier 和 TypeScript",
5
5
  "type": "module",
6
6
  "exports": {
@@ -21,6 +21,7 @@
21
21
  "tsconfig.node.json",
22
22
  "vite.config.base.js",
23
23
  "vite.config.base.d.ts",
24
+ "vite-plugin-rollup-dts.ts",
24
25
  "README.md"
25
26
  ],
26
27
  "keywords": [
@@ -43,6 +44,7 @@
43
44
  "prepublishOnly": "pnpm pack --dry-run"
44
45
  },
45
46
  "dependencies": {
47
+ "@microsoft/api-extractor": "7.55.2",
46
48
  "@tailwindcss/vite": "^4.1.18",
47
49
  "@types/node": "25.0.3",
48
50
  "@vitejs/plugin-vue": "^6.0.3",
@@ -0,0 +1,246 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { Extractor, ExtractorConfig } from '@microsoft/api-extractor';
4
+ import type { Plugin, ResolvedConfig } from 'vite';
5
+
6
+ interface Entry {
7
+ name: string;
8
+ mainEntryPoint: string;
9
+ output: string;
10
+ }
11
+
12
+ interface RollupDtsPluginOptions {
13
+ /** 手动指定入口,如果不指定则从 vite config 的 build.lib.entry 自动获取 */
14
+ entries?: Entry[];
15
+ tsconfigPath?: string;
16
+ }
17
+
18
+ const baseConfig = {
19
+ $schema:
20
+ 'https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json',
21
+ projectFolder: '.',
22
+ apiReport: { enabled: false },
23
+ docModel: { enabled: false },
24
+ tsdocMetadata: { enabled: false },
25
+ dtsRollup: { enabled: true },
26
+ messages: {
27
+ compilerMessageReporting: { default: { logLevel: 'warning' as const } },
28
+ extractorMessageReporting: {
29
+ default: { logLevel: 'warning' as const },
30
+ 'ae-missing-release-tag': { logLevel: 'none' as const },
31
+ 'ae-unresolved-link': { logLevel: 'none' as const },
32
+ },
33
+ tsdocMessageReporting: { default: { logLevel: 'none' as const } },
34
+ },
35
+ };
36
+
37
+ function cleanupIntermediateFiles(dir: string, keepFiles: string[]) {
38
+ if (!fs.existsSync(dir)) return;
39
+
40
+ const items = fs.readdirSync(dir, { withFileTypes: true });
41
+
42
+ for (const item of items) {
43
+ const fullPath = path.join(dir, item.name);
44
+
45
+ if (item.isDirectory()) {
46
+ const hasKeepFile = keepFiles.some(
47
+ (f) => f.startsWith(fullPath + path.sep) || f === fullPath,
48
+ );
49
+ if (hasKeepFile) {
50
+ cleanupIntermediateFiles(fullPath, keepFiles);
51
+ const remaining = fs.readdirSync(fullPath);
52
+ if (remaining.length === 0) {
53
+ fs.rmdirSync(fullPath);
54
+ }
55
+ } else {
56
+ const allDts = fs
57
+ .readdirSync(fullPath, { recursive: true })
58
+ .every((f) => {
59
+ if (typeof f !== 'string') return true;
60
+ const fp = path.join(fullPath, f);
61
+ return fs.statSync(fp).isDirectory() || f.endsWith('.d.ts');
62
+ });
63
+ if (allDts) {
64
+ fs.rmSync(fullPath, { recursive: true });
65
+ }
66
+ }
67
+ } else if (item.name.endsWith('.d.ts') && !keepFiles.includes(fullPath)) {
68
+ fs.unlinkSync(fullPath);
69
+ }
70
+ }
71
+ }
72
+
73
+ /**
74
+ * 从 vite config 的 build.lib.entry 自动生成 entries
75
+ */
76
+ function getEntriesFromConfig(config: ResolvedConfig): Entry[] {
77
+ const lib = config.build?.lib;
78
+ if (!lib) return [];
79
+
80
+ const outDir = config.build?.outDir || 'dist';
81
+ const entry = lib.entry;
82
+
83
+ // entry 可以是 string | string[] | Record<string, string>
84
+ if (typeof entry === 'string') {
85
+ return [
86
+ {
87
+ name: 'index',
88
+ mainEntryPoint: `./${outDir}/index.d.ts`,
89
+ output: `./${outDir}/index.d.ts`,
90
+ },
91
+ ];
92
+ }
93
+
94
+ if (Array.isArray(entry)) {
95
+ return entry.map((_, i) => ({
96
+ name: i === 0 ? 'index' : `entry${i}`,
97
+ mainEntryPoint: `./${outDir}/${i === 0 ? 'index' : `entry${i}`}.d.ts`,
98
+ output: `./${outDir}/${i === 0 ? 'index' : `entry${i}`}.d.ts`,
99
+ }));
100
+ }
101
+
102
+ // Record<string, string>
103
+ return Object.keys(entry).map((name) => ({
104
+ name,
105
+ mainEntryPoint: `./${outDir}/${name}.d.ts`,
106
+ output: `./${outDir}/${name}.d.ts`,
107
+ }));
108
+ }
109
+
110
+ export function rollupDtsPlugin(options: RollupDtsPluginOptions = {}): Plugin {
111
+ const { tsconfigPath = './tsconfig.json' } = options;
112
+ let resolvedConfig: ResolvedConfig;
113
+ let entries: Entry[] = [];
114
+
115
+ return {
116
+ name: 'vite-plugin-rollup-dts',
117
+ apply: 'build',
118
+ enforce: 'post',
119
+
120
+ configResolved(config) {
121
+ resolvedConfig = config;
122
+ // 如果手动指定了 entries,使用手动配置;否则从 config 自动生成
123
+ entries = options.entries || getEntriesFromConfig(config);
124
+ },
125
+
126
+ async closeBundle() {
127
+ if (entries.length === 0) {
128
+ console.log('[rollup-dts] No entries found, skipping...');
129
+ return;
130
+ }
131
+
132
+ const projectFolder = process.cwd();
133
+ const results: { entry: Entry; success: boolean }[] = [];
134
+
135
+ console.log('\n[rollup-dts] Starting to bundle declaration files...');
136
+
137
+ for (const entry of entries) {
138
+ const mainEntryPointFilePath = path.resolve(
139
+ projectFolder,
140
+ entry.mainEntryPoint,
141
+ );
142
+
143
+ if (!fs.existsSync(mainEntryPointFilePath)) {
144
+ console.warn(
145
+ `[rollup-dts] ⚠️ Skipping ${entry.name}: ${entry.mainEntryPoint} not found`,
146
+ );
147
+ continue;
148
+ }
149
+
150
+ console.log(`[rollup-dts] 📦 Rolling up types for: ${entry.name}`);
151
+
152
+ const config = {
153
+ ...baseConfig,
154
+ compiler: {
155
+ tsconfigFilePath: `<projectFolder>/${tsconfigPath}`,
156
+ },
157
+ mainEntryPointFilePath: `<projectFolder>/${entry.mainEntryPoint}`,
158
+ dtsRollup: {
159
+ enabled: true,
160
+ untrimmedFilePath: `<projectFolder>/${entry.output}`,
161
+ },
162
+ };
163
+
164
+ // 用安全的文件名(将 / 替换为 -)
165
+ const safeName = entry.name.replace(/\//g, '-');
166
+ const tempConfigPath = path.resolve(
167
+ projectFolder,
168
+ `api-extractor.${safeName}.tmp.json`,
169
+ );
170
+ fs.writeFileSync(tempConfigPath, JSON.stringify(config, null, 2));
171
+
172
+ let success = false;
173
+ try {
174
+ const extractorConfig =
175
+ ExtractorConfig.loadFileAndPrepare(tempConfigPath);
176
+ const extractorResult = Extractor.invoke(extractorConfig, {
177
+ localBuild: true,
178
+ showVerboseMessages: false,
179
+ });
180
+
181
+ if (extractorResult.succeeded) {
182
+ console.log(
183
+ `[rollup-dts] ✅ ${entry.name}: types rolled up successfully`,
184
+ );
185
+ success = true;
186
+ } else {
187
+ console.warn(
188
+ `[rollup-dts] ⚠️ ${entry.name}: API Extractor completed with warnings/errors`,
189
+ );
190
+ if (fs.existsSync(path.resolve(projectFolder, entry.output))) {
191
+ success = true;
192
+ }
193
+ }
194
+ } catch (error) {
195
+ console.warn(
196
+ `[rollup-dts] ⚠️ ${entry.name}: Error during rollup, keeping original .d.ts files`,
197
+ );
198
+ console.warn(` ${error instanceof Error ? error.message : error}`);
199
+ } finally {
200
+ if (fs.existsSync(tempConfigPath)) {
201
+ fs.unlinkSync(tempConfigPath);
202
+ }
203
+ }
204
+
205
+ results.push({ entry, success });
206
+ }
207
+
208
+ // Determine which directories to keep
209
+ const dirsToKeep = new Set<string>();
210
+
211
+ for (const { entry, success } of results) {
212
+ if (!success) {
213
+ const entryDir = path.dirname(
214
+ path.resolve(projectFolder, entry.mainEntryPoint),
215
+ );
216
+ dirsToKeep.add(entryDir);
217
+ console.log(
218
+ `[rollup-dts] 📁 Keeping directory structure for: ${entry.name}`,
219
+ );
220
+ }
221
+ }
222
+
223
+ // Clean up only if all entries succeeded
224
+ if (dirsToKeep.size === 0) {
225
+ console.log('[rollup-dts] 🧹 Cleaning up intermediate files...');
226
+ const outDir = resolvedConfig?.build?.outDir || 'dist';
227
+ const distDir = path.resolve(projectFolder, outDir);
228
+ cleanupIntermediateFiles(
229
+ distDir,
230
+ entries.map((e) => path.resolve(projectFolder, e.output)),
231
+ );
232
+ } else {
233
+ console.log(
234
+ '[rollup-dts] ⚠️ Some entries failed, keeping intermediate files',
235
+ );
236
+ }
237
+
238
+ console.log('[rollup-dts] 📊 Summary:');
239
+ for (const { entry, success } of results) {
240
+ console.log(` ${success ? '✅' : '❌'} ${entry.name}`);
241
+ }
242
+ },
243
+ };
244
+ }
245
+
246
+ export default rollupDtsPlugin;
@@ -6,6 +6,7 @@ import Components from 'unplugin-vue-components/vite';
6
6
  import VueRouter from 'unplugin-vue-router/vite';
7
7
  import { defineConfig, mergeConfig } from 'vite';
8
8
  import dts from 'vite-plugin-dts';
9
+ import { rollupDtsPlugin } from './scripts/vite-plugin-rollup-dts';
9
10
 
10
11
  /**
11
12
  * @typedef {import('vite').UserConfig} UserConfig
@@ -80,10 +81,13 @@ export function createBaseConfig(dirname, options = {}) {
80
81
  dts({
81
82
  include: ['src/**/*.ts'],
82
83
  root: dirname,
83
- rollupTypes: false,
84
84
  ...dtsConfig,
85
+ rollupTypes: false,
85
86
  }),
86
87
  );
88
+ if (dts.rollupTypes) {
89
+ baseConfig.plugins.push(rollupDtsPlugin());
90
+ }
87
91
  }
88
92
 
89
93
  // 使用 mergeConfig 合并用户自定义配置