vite-plugin-ops 0.1.1 → 1.0.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 -21
- package/README.md +409 -409
- package/dist/deps.d.ts +14 -0
- package/dist/deps.d.ts.map +1 -0
- package/dist/index.cjs +158 -62
- package/dist/index.d.cts +15 -1
- package/dist/index.d.ts +9 -34
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +158 -62
- package/dist/matcher.d.ts +43 -0
- package/dist/matcher.d.ts.map +1 -0
- package/dist/presets.d.ts +15 -0
- package/dist/presets.d.ts.map +1 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +69 -56
package/dist/deps.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 读取项目依赖列表
|
|
3
|
+
* @param cwd 项目根目录
|
|
4
|
+
* @returns 依赖名称集合
|
|
5
|
+
*/
|
|
6
|
+
export declare function readProjectDependencies(cwd: string): Set<string>;
|
|
7
|
+
/**
|
|
8
|
+
* 检查模式列表是否匹配项目依赖
|
|
9
|
+
* 改进的匹配逻辑:避免误匹配(如 vue 匹配到 vuex)
|
|
10
|
+
* @param patterns 匹配模式列表
|
|
11
|
+
* @param depsArray 依赖数组
|
|
12
|
+
*/
|
|
13
|
+
export declare function hasDependencyMatch(patterns: string[], depsArray: string[]): boolean;
|
|
14
|
+
//# sourceMappingURL=deps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deps.d.ts","sourceRoot":"","sources":["../src/deps.ts"],"names":[],"mappings":"AAwBA;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CA8BhE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAmBnF"}
|
package/dist/index.cjs
CHANGED
|
@@ -8,7 +8,9 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
8
8
|
var fs__default = /*#__PURE__*/_interopDefault(fs);
|
|
9
9
|
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
10
10
|
|
|
11
|
-
// src/
|
|
11
|
+
// src/deps.ts
|
|
12
|
+
|
|
13
|
+
// src/presets.ts
|
|
12
14
|
var COMMON_LARGE_LIBS = {
|
|
13
15
|
// UI Frameworks
|
|
14
16
|
react: ["react", "react-dom"],
|
|
@@ -51,31 +53,74 @@ var MEDIUM_LIB_GROUPS = {
|
|
|
51
53
|
"form": ["react-hook-form", "formik", "async-validator"],
|
|
52
54
|
"i18n": ["i18next", "react-i18next", "vue-i18n"]
|
|
53
55
|
};
|
|
56
|
+
var VERY_LARGE_LIBS = ["react", "vue", "angular", "antd", "element-plus", "echarts", "three"];
|
|
57
|
+
|
|
58
|
+
// src/matcher.ts
|
|
59
|
+
var PRIORITIES = {
|
|
60
|
+
CUSTOM: 100,
|
|
61
|
+
// 用户自定义分组
|
|
62
|
+
DETECTED: 90,
|
|
63
|
+
// 插件检测到的框架
|
|
64
|
+
LARGE_LIB: 80,
|
|
65
|
+
// 大型库
|
|
66
|
+
MEDIUM_LIB: 70,
|
|
67
|
+
// 中型库
|
|
68
|
+
ALL_DEPS: 50
|
|
69
|
+
// 所有依赖(aggressive 模式)
|
|
70
|
+
};
|
|
71
|
+
var MAX_PATH_LENGTH = 2e3;
|
|
72
|
+
var MAX_CACHE_SIZE = 100;
|
|
73
|
+
var patternCache = /* @__PURE__ */ new Map();
|
|
74
|
+
var cacheKeys = [];
|
|
75
|
+
var logger = {
|
|
76
|
+
warn: (msg) => console.warn(`[vite-plugin-ops] ${msg}`),
|
|
77
|
+
error: (msg) => console.error(`[vite-plugin-ops] ERROR: ${msg}`),
|
|
78
|
+
debug: (msg) => process.env.DEBUG && console.log(`[vite-plugin-ops] DEBUG: ${msg}`)
|
|
79
|
+
};
|
|
54
80
|
function normalizeId(id) {
|
|
55
81
|
return id.replace(/\\/g, "/").replace(/%5C/g, "/");
|
|
56
82
|
}
|
|
83
|
+
function manageCache() {
|
|
84
|
+
while (cacheKeys.length >= MAX_CACHE_SIZE) {
|
|
85
|
+
const oldestKey = cacheKeys.shift();
|
|
86
|
+
if (oldestKey) {
|
|
87
|
+
patternCache.delete(oldestKey);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
57
91
|
function makeNodeModulesPattern(pkg) {
|
|
58
92
|
if (pkg instanceof RegExp) {
|
|
59
|
-
return (id) =>
|
|
93
|
+
return (id) => {
|
|
94
|
+
if (id.length > MAX_PATH_LENGTH) {
|
|
95
|
+
logger.warn(`Path too long (${id.length} chars), skipping: ${id.slice(0, 50)}...`);
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
return pkg.test(id);
|
|
99
|
+
};
|
|
60
100
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return new Set(Object.keys(json.dependencies || {}));
|
|
72
|
-
} catch {
|
|
73
|
-
return /* @__PURE__ */ new Set();
|
|
101
|
+
let re = patternCache.get(pkg);
|
|
102
|
+
if (!re) {
|
|
103
|
+
manageCache();
|
|
104
|
+
const scoped = pkg.startsWith("@");
|
|
105
|
+
const escaped = pkg.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
|
|
106
|
+
const base = scoped ? escaped : `(?:@[^/]+/)?${escaped}`;
|
|
107
|
+
const flags = process.platform === "win32" ? "i" : "";
|
|
108
|
+
re = new RegExp(`/node_modules/(?:[.]pnpm/)?(?:${base})(?:/|@|$)`, flags);
|
|
109
|
+
patternCache.set(pkg, re);
|
|
110
|
+
cacheKeys.push(pkg);
|
|
74
111
|
}
|
|
112
|
+
return (id) => {
|
|
113
|
+
if (id.length > MAX_PATH_LENGTH) {
|
|
114
|
+
logger.warn(`Path too long (${id.length} chars), skipping: ${id.slice(0, 50)}...`);
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
return re.test(id);
|
|
118
|
+
};
|
|
75
119
|
}
|
|
76
120
|
function buildGroupMatchers(options, projectDeps, pluginHints) {
|
|
77
121
|
const matchers = [];
|
|
78
|
-
const strategy = options.strategy
|
|
122
|
+
const strategy = options.strategy;
|
|
123
|
+
const depsArray = Array.from(projectDeps);
|
|
79
124
|
if (options.groups) {
|
|
80
125
|
for (const [name, patterns] of Object.entries(options.groups)) {
|
|
81
126
|
if (patterns && patterns.length) {
|
|
@@ -88,7 +133,7 @@ function buildGroupMatchers(options, projectDeps, pluginHints) {
|
|
|
88
133
|
matchers.push({
|
|
89
134
|
name,
|
|
90
135
|
test: (id) => testers.some((t) => t(id)),
|
|
91
|
-
priority:
|
|
136
|
+
priority: PRIORITIES.CUSTOM
|
|
92
137
|
});
|
|
93
138
|
}
|
|
94
139
|
}
|
|
@@ -105,63 +150,49 @@ function buildGroupMatchers(options, projectDeps, pluginHints) {
|
|
|
105
150
|
matchers.push({
|
|
106
151
|
name,
|
|
107
152
|
test: (id) => testers.some((t) => t(id)),
|
|
108
|
-
priority:
|
|
153
|
+
priority: PRIORITIES.DETECTED
|
|
109
154
|
});
|
|
110
155
|
}
|
|
111
156
|
if (strategy === "aggressive") {
|
|
112
|
-
for (const dep of
|
|
157
|
+
for (const dep of depsArray) {
|
|
113
158
|
if (!dep.startsWith("@types/")) {
|
|
114
159
|
matchers.push({
|
|
115
160
|
name: dep,
|
|
116
161
|
test: makeNodeModulesPattern(dep),
|
|
117
|
-
priority:
|
|
162
|
+
priority: PRIORITIES.ALL_DEPS
|
|
118
163
|
});
|
|
119
164
|
}
|
|
120
165
|
}
|
|
121
166
|
} else if (strategy === "balanced") {
|
|
122
167
|
for (const [groupName, patterns] of Object.entries(COMMON_LARGE_LIBS)) {
|
|
123
|
-
|
|
124
|
-
const pkgName = p.replace(/\//g, "");
|
|
125
|
-
return Array.from(projectDeps).some((dep) => dep.includes(pkgName));
|
|
126
|
-
});
|
|
127
|
-
if (hasAny) {
|
|
168
|
+
if (hasDependencyMatch(patterns, depsArray)) {
|
|
128
169
|
const testers = patterns.map(makeNodeModulesPattern);
|
|
129
170
|
matchers.push({
|
|
130
171
|
name: groupName,
|
|
131
172
|
test: (id) => testers.some((t) => t(id)),
|
|
132
|
-
priority:
|
|
173
|
+
priority: PRIORITIES.LARGE_LIB
|
|
133
174
|
});
|
|
134
175
|
}
|
|
135
176
|
}
|
|
136
177
|
for (const [groupName, patterns] of Object.entries(MEDIUM_LIB_GROUPS)) {
|
|
137
|
-
|
|
138
|
-
return Array.from(projectDeps).some(
|
|
139
|
-
(dep) => dep.includes(p.replace(/\//g, "").replace(/\*/g, ""))
|
|
140
|
-
);
|
|
141
|
-
});
|
|
142
|
-
if (hasAny) {
|
|
178
|
+
if (hasDependencyMatch(patterns, depsArray)) {
|
|
143
179
|
const testers = patterns.map(makeNodeModulesPattern);
|
|
144
180
|
matchers.push({
|
|
145
181
|
name: groupName,
|
|
146
182
|
test: (id) => testers.some((t) => t(id)),
|
|
147
|
-
priority:
|
|
183
|
+
priority: PRIORITIES.MEDIUM_LIB
|
|
148
184
|
});
|
|
149
185
|
}
|
|
150
186
|
}
|
|
151
187
|
} else if (strategy === "conservative") {
|
|
152
|
-
const veryLargeLibs = ["react", "vue", "angular", "antd", "element-plus", "echarts", "three"];
|
|
153
188
|
for (const [groupName, patterns] of Object.entries(COMMON_LARGE_LIBS)) {
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
const pkgName = p.replace(/\//g, "");
|
|
157
|
-
return Array.from(projectDeps).some((dep) => dep.includes(pkgName));
|
|
158
|
-
});
|
|
159
|
-
if (hasAny) {
|
|
189
|
+
if (VERY_LARGE_LIBS.includes(groupName)) {
|
|
190
|
+
if (hasDependencyMatch(patterns, depsArray)) {
|
|
160
191
|
const testers = patterns.map(makeNodeModulesPattern);
|
|
161
192
|
matchers.push({
|
|
162
193
|
name: groupName,
|
|
163
194
|
test: (id) => testers.some((t) => t(id)),
|
|
164
|
-
priority:
|
|
195
|
+
priority: PRIORITIES.LARGE_LIB
|
|
165
196
|
});
|
|
166
197
|
}
|
|
167
198
|
}
|
|
@@ -169,6 +200,61 @@ function buildGroupMatchers(options, projectDeps, pluginHints) {
|
|
|
169
200
|
}
|
|
170
201
|
return matchers.sort((a, b) => b.priority - a.priority);
|
|
171
202
|
}
|
|
203
|
+
|
|
204
|
+
// src/deps.ts
|
|
205
|
+
var MAX_PACKAGE_JSON_SIZE = 1024 * 1024;
|
|
206
|
+
function isValidPackageJson(obj) {
|
|
207
|
+
if (typeof obj !== "object" || obj === null) return false;
|
|
208
|
+
const json = obj;
|
|
209
|
+
if (json.dependencies !== void 0) {
|
|
210
|
+
if (typeof json.dependencies !== "object" || json.dependencies === null) return false;
|
|
211
|
+
for (const val of Object.values(json.dependencies)) {
|
|
212
|
+
if (typeof val !== "string") return false;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
function readProjectDependencies(cwd) {
|
|
218
|
+
try {
|
|
219
|
+
const pkgPath = path__default.default.join(cwd, "package.json");
|
|
220
|
+
const stats = fs__default.default.statSync(pkgPath);
|
|
221
|
+
if (stats.size > MAX_PACKAGE_JSON_SIZE) {
|
|
222
|
+
logger.warn(`package.json too large (${stats.size} bytes), skipping`);
|
|
223
|
+
return /* @__PURE__ */ new Set();
|
|
224
|
+
}
|
|
225
|
+
const content = fs__default.default.readFileSync(pkgPath, "utf8");
|
|
226
|
+
const json = JSON.parse(content);
|
|
227
|
+
if (!isValidPackageJson(json)) {
|
|
228
|
+
logger.warn("Invalid package.json format: dependencies field is malformed");
|
|
229
|
+
return /* @__PURE__ */ new Set();
|
|
230
|
+
}
|
|
231
|
+
return new Set(Object.keys(json.dependencies || {}));
|
|
232
|
+
} catch (error) {
|
|
233
|
+
if (error instanceof SyntaxError) {
|
|
234
|
+
logger.warn("Failed to parse package.json: Invalid JSON format");
|
|
235
|
+
} else if (error.code === "ENOENT") {
|
|
236
|
+
logger.warn(`package.json not found in: ${cwd}`);
|
|
237
|
+
} else if (process.env.DEBUG) {
|
|
238
|
+
logger.debug(`Failed to read package.json: ${error}`);
|
|
239
|
+
}
|
|
240
|
+
return /* @__PURE__ */ new Set();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function hasDependencyMatch(patterns, depsArray) {
|
|
244
|
+
const depsSet = new Set(depsArray);
|
|
245
|
+
return patterns.some((pattern) => {
|
|
246
|
+
if (pattern.includes("/")) {
|
|
247
|
+
return depsArray.some((dep) => dep.startsWith(pattern));
|
|
248
|
+
}
|
|
249
|
+
return depsSet.has(pattern) || depsArray.some((dep) => {
|
|
250
|
+
if (dep === pattern) return true;
|
|
251
|
+
if (dep.startsWith(`@${pattern}/`)) return true;
|
|
252
|
+
return false;
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/index.ts
|
|
172
258
|
function OPS(opts = {}) {
|
|
173
259
|
const options = {
|
|
174
260
|
override: opts.override ?? false,
|
|
@@ -178,22 +264,32 @@ function OPS(opts = {}) {
|
|
|
178
264
|
};
|
|
179
265
|
let groupsRef = [];
|
|
180
266
|
const manualChunks = (id) => {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
267
|
+
try {
|
|
268
|
+
const nid = normalizeId(id);
|
|
269
|
+
if (!/\/node_modules\//.test(nid)) return void 0;
|
|
270
|
+
for (const g of groupsRef) {
|
|
271
|
+
if (g.test(nid)) return g.name;
|
|
272
|
+
}
|
|
273
|
+
return "vendor";
|
|
274
|
+
} catch (error) {
|
|
275
|
+
logger.warn(`Error in manualChunks for ${id}: ${error}`);
|
|
276
|
+
return "vendor";
|
|
185
277
|
}
|
|
186
|
-
return "vendor";
|
|
187
278
|
};
|
|
188
279
|
const assetFileNamesFn = (assetInfo) => {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
280
|
+
try {
|
|
281
|
+
const name = assetInfo.name ?? "";
|
|
282
|
+
const ext = name.split(".").pop()?.toLowerCase();
|
|
283
|
+
if (ext === "css") return "css/[name]-[hash][extname]";
|
|
284
|
+
if (["png", "jpg", "jpeg", "gif", "svg", "webp", "avif"].includes(ext ?? ""))
|
|
285
|
+
return "img/[name]-[hash][extname]";
|
|
286
|
+
if (["woff", "woff2", "eot", "ttf", "otf"].includes(ext ?? ""))
|
|
287
|
+
return "fonts/[name]-[hash][extname]";
|
|
288
|
+
return "assets/[name]-[hash][extname]";
|
|
289
|
+
} catch (error) {
|
|
290
|
+
logger.warn(`Error in assetFileNames: ${error}`);
|
|
291
|
+
return "assets/[name]-[hash][extname]";
|
|
292
|
+
}
|
|
197
293
|
};
|
|
198
294
|
return {
|
|
199
295
|
name: "vite-plugin-ops",
|
|
@@ -209,14 +305,14 @@ function OPS(opts = {}) {
|
|
|
209
305
|
manualChunks
|
|
210
306
|
};
|
|
211
307
|
let output;
|
|
212
|
-
if (shouldMerge) {
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
308
|
+
if (shouldMerge && existingOutput) {
|
|
309
|
+
const existing = existingOutput;
|
|
310
|
+
output = {
|
|
311
|
+
entryFileNames: existing?.entryFileNames ?? injected.entryFileNames,
|
|
312
|
+
chunkFileNames: existing?.chunkFileNames ?? injected.chunkFileNames,
|
|
313
|
+
assetFileNames: existing?.assetFileNames ?? injected.assetFileNames,
|
|
314
|
+
manualChunks: existing?.manualChunks ?? injected.manualChunks
|
|
315
|
+
};
|
|
220
316
|
} else {
|
|
221
317
|
output = injected;
|
|
222
318
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
|
+
export { Plugin as OPSPlugin } from 'vite';
|
|
2
3
|
|
|
3
4
|
type SplitStrategy = 'aggressive' | 'balanced' | 'conservative';
|
|
4
5
|
type OPSOptions = {
|
|
@@ -29,6 +30,19 @@ type OPSOptions = {
|
|
|
29
30
|
*/
|
|
30
31
|
groups?: Record<string, (string | RegExp)[]>;
|
|
31
32
|
};
|
|
33
|
+
type ResolvedOptions = Required<Pick<OPSOptions, 'override' | 'strategy' | 'minSize'>> & {
|
|
34
|
+
groups?: OPSOptions['groups'];
|
|
35
|
+
};
|
|
36
|
+
type GroupMatcher = {
|
|
37
|
+
name: string;
|
|
38
|
+
test: (id: string) => boolean;
|
|
39
|
+
priority: number;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* OPS - Optimized Packaging Strategy
|
|
44
|
+
* 一个智能的 Vite 分包优化插件
|
|
45
|
+
*/
|
|
32
46
|
declare function OPS(opts?: OPSOptions): Plugin;
|
|
33
47
|
|
|
34
|
-
export { type OPSOptions, type SplitStrategy, OPS as default };
|
|
48
|
+
export { type GroupMatcher, type OPSOptions, type ResolvedOptions, type SplitStrategy, OPS as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,34 +1,9 @@
|
|
|
1
|
-
import { Plugin } from 'vite';
|
|
2
|
-
|
|
3
|
-
type SplitStrategy
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
override?: boolean;
|
|
11
|
-
/**
|
|
12
|
-
* Chunking strategy:
|
|
13
|
-
* - 'aggressive': Split almost all dependencies into separate chunks
|
|
14
|
-
* - 'balanced': Split large dependencies and common frameworks (default)
|
|
15
|
-
* - 'conservative': Minimal splitting, only very large dependencies
|
|
16
|
-
* Default: 'balanced'
|
|
17
|
-
*/
|
|
18
|
-
strategy?: SplitStrategy;
|
|
19
|
-
/**
|
|
20
|
-
* Minimum size (in KB) for a dependency to be split into its own chunk.
|
|
21
|
-
* Only applies when strategy is 'balanced' or 'conservative'.
|
|
22
|
-
* Default: 50
|
|
23
|
-
*/
|
|
24
|
-
minSize?: number;
|
|
25
|
-
/**
|
|
26
|
-
* Additional custom chunk groups. Keys are chunk names; values are string or RegExp
|
|
27
|
-
* matchers to detect a module path in node_modules. Example:
|
|
28
|
-
* { three: ['three'], lodash: [/node_modules\\/lodash(?!-)/] }
|
|
29
|
-
*/
|
|
30
|
-
groups?: Record<string, (string | RegExp)[]>;
|
|
31
|
-
};
|
|
32
|
-
declare function OPS(opts?: OPSOptions): Plugin;
|
|
33
|
-
|
|
34
|
-
export { type OPSOptions, type SplitStrategy, OPS as default };
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
import type { OPSOptions } from './types';
|
|
3
|
+
export type { OPSPlugin, OPSOptions, SplitStrategy, ResolvedOptions, GroupMatcher } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* OPS - Optimized Packaging Strategy
|
|
6
|
+
* 一个智能的 Vite 分包优化插件
|
|
7
|
+
*/
|
|
8
|
+
export default function OPS(opts?: OPSOptions): Plugin;
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,MAAM,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAA2E,MAAM,SAAS,CAAA;AAIlH,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAUlG;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,GAAG,CAAC,IAAI,GAAE,UAAe,GAAG,MAAM,CAgHzD"}
|