fe-stack 0.0.11 → 0.0.13

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