vite-plugin-performance 0.0.1 → 1.0.0-alpha.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/README.md +77 -74
- package/README.zh-CN.md +123 -0
- package/dist/index.cjs +110 -105
- package/dist/index.d.cts +37 -7
- package/dist/index.d.mts +37 -7
- package/dist/index.d.ts +37 -7
- package/dist/index.mjs +108 -106
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,119 +1,122 @@
|
|
|
1
|
-
#
|
|
1
|
+
# vite-plugin-performance
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[中文文档](./README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`vite-plugin-performance` wraps one or more Vite plugins and measures how long each lifecycle hook takes. It keeps the original behaviour intact while giving you actionable performance insights.
|
|
6
6
|
|
|
7
7
|
## ✨ Features
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
---
|
|
9
|
+
- Wrap a single plugin or an array of plugins with one call
|
|
10
|
+
- Ship with a sensible default hook list, or use `hooks: 'all'` to cover every function hook
|
|
11
|
+
- Threshold-based reporting so you only see the hooks that really hurt
|
|
12
|
+
- Pluggable logger, formatter, and lifecycle callback for easy monitoring integration
|
|
13
|
+
- Works with asynchronous hooks and still handles errors gracefully
|
|
16
14
|
|
|
17
15
|
## 📦 Installation
|
|
18
16
|
|
|
19
|
-
```
|
|
20
|
-
|
|
17
|
+
```bash
|
|
18
|
+
pnpm add -D vite-plugin-performance
|
|
19
|
+
# or npm / yarn / bun
|
|
21
20
|
```
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
## 🚀 Usage
|
|
22
|
+
## 🚀 Quick Start
|
|
26
23
|
|
|
27
24
|
```ts
|
|
28
|
-
import someVitePlugin from 'some-vite-plugin'
|
|
29
25
|
import { defineConfig } from 'vite'
|
|
26
|
+
import Inspect from 'vite-plugin-inspect'
|
|
30
27
|
import { wrapPlugin } from 'vite-plugin-performance'
|
|
31
28
|
|
|
32
29
|
export default defineConfig({
|
|
33
30
|
plugins: [
|
|
34
|
-
wrapPlugin(
|
|
35
|
-
|
|
36
|
-
{
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
reportToMonitoringSystem({ pluginName, hookName, duration })
|
|
41
|
-
},
|
|
42
|
-
}
|
|
43
|
-
),
|
|
31
|
+
wrapPlugin(Inspect(), {
|
|
32
|
+
threshold: 50,
|
|
33
|
+
onHookExecution({ pluginName, hookName, duration }) {
|
|
34
|
+
reportToAPM({ pluginName, hookName, duration })
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
44
37
|
],
|
|
45
38
|
})
|
|
46
39
|
```
|
|
47
40
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
## 🧩 API
|
|
41
|
+
Whenever a hook crosses the threshold (default `0` ms) you will see output similar to:
|
|
51
42
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
43
|
+
```
|
|
44
|
+
[inspect] transform ⏱ 78.42 ms
|
|
45
|
+
```
|
|
55
46
|
|
|
56
|
-
|
|
57
|
-
- `options`: Optional configuration object
|
|
47
|
+
## ⚙️ Options
|
|
58
48
|
|
|
59
|
-
|
|
49
|
+
| Option | Type | Default | Description |
|
|
50
|
+
| ----------------- | ---------------------------- | --------------------------------- | ------------------------------------------------------------------------- |
|
|
51
|
+
| `hooks` | `PluginHookName[] \| 'all'` | `DEFAULT_PLUGIN_HOOKS` | Choose which hooks to wrap; pass `'all'` to wrap every function hook |
|
|
52
|
+
| `threshold` | `number` | `0` | Only hooks with a duration greater or equal to the threshold are reported |
|
|
53
|
+
| `silent` | `boolean` | `false` | Disable the built-in logger |
|
|
54
|
+
| `logger` | `(message, context) => void` | `console.log` | Custom log writer |
|
|
55
|
+
| `formatter` | `(context) => string` | `[plugin] transform ⏱ 12.34 ms` | Custom message formatter |
|
|
56
|
+
| `onHookExecution` | `(context) => void` | `undefined` | Callback invoked after a hook finishes |
|
|
57
|
+
| `clock` | `() => number` | `performance.now` or `Date.now` | High-resolution timer, handy for tests |
|
|
60
58
|
|
|
61
|
-
|
|
62
|
-
| ----------------- | ----------------------------------------- | ---------------------------- | -------------------------------------- |
|
|
63
|
-
| `threshold` | `number` | `0` | Minimum duration (ms) to log a hook |
|
|
64
|
-
| `onHookExecution` | `(params: OnHookExecutionParams) => void` | `undefined` | Callback invoked when a hook completes |
|
|
65
|
-
| `hooks` | `PluginHooks[]` | Common hook list (see below) | List of Vite plugin hooks to wrap |
|
|
66
|
-
| `slient` | `boolean` | `false` | Suppress default console output |
|
|
59
|
+
> The legacy misspelled option `slient` is still recognised and mapped to `silent`.
|
|
67
60
|
|
|
68
|
-
|
|
61
|
+
### Default Hook List
|
|
69
62
|
|
|
70
63
|
```ts
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
64
|
+
import { DEFAULT_PLUGIN_HOOKS } from 'vite-plugin-performance'
|
|
65
|
+
// [
|
|
66
|
+
// 'options',
|
|
67
|
+
// 'config',
|
|
68
|
+
// 'configResolved',
|
|
69
|
+
// 'configureServer',
|
|
70
|
+
// 'buildStart',
|
|
71
|
+
// 'resolveId',
|
|
72
|
+
// 'load',
|
|
73
|
+
// 'transform',
|
|
74
|
+
// 'buildEnd',
|
|
75
|
+
// 'generateBundle',
|
|
76
|
+
// 'renderChunk',
|
|
77
|
+
// 'writeBundle',
|
|
78
|
+
// ]
|
|
82
79
|
```
|
|
83
80
|
|
|
84
|
-
|
|
81
|
+
## 🧠 Advanced Usage
|
|
85
82
|
|
|
86
|
-
|
|
83
|
+
### Wrap Multiple Plugins
|
|
87
84
|
|
|
88
|
-
|
|
85
|
+
```ts
|
|
86
|
+
const pluginA = ...
|
|
87
|
+
const pluginB = ...
|
|
89
88
|
|
|
89
|
+
export default defineConfig({
|
|
90
|
+
plugins: wrapPlugin([pluginA, pluginB], { threshold: 20 }),
|
|
91
|
+
})
|
|
90
92
|
```
|
|
91
|
-
[my-plugin] transform ⏱ 132.57 ms
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
---
|
|
95
93
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
### `OnHookExecutionParams`
|
|
94
|
+
### Custom Log Format
|
|
99
95
|
|
|
100
96
|
```ts
|
|
101
|
-
|
|
102
|
-
pluginName
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
97
|
+
wrapPlugin(plugin, {
|
|
98
|
+
formatter({ pluginName, hookName, duration }) {
|
|
99
|
+
return `${pluginName}:${hookName} took ${duration}ms`
|
|
100
|
+
},
|
|
101
|
+
logger(message) {
|
|
102
|
+
myLogger.info(message)
|
|
103
|
+
},
|
|
104
|
+
})
|
|
107
105
|
```
|
|
108
106
|
|
|
109
|
-
|
|
107
|
+
### Limit to Specific Hooks
|
|
110
108
|
|
|
111
|
-
|
|
109
|
+
```ts
|
|
110
|
+
wrapPlugin(plugin, {
|
|
111
|
+
hooks: ['resolveId', 'load', 'transform'],
|
|
112
|
+
})
|
|
113
|
+
```
|
|
112
114
|
|
|
113
|
-
|
|
114
|
-
- Async hooks are automatically awaited to ensure accurate timing.
|
|
115
|
+
## 🧪 Testing
|
|
115
116
|
|
|
116
|
-
|
|
117
|
+
```bash
|
|
118
|
+
pnpm --filter vite-plugin-performance test
|
|
119
|
+
```
|
|
117
120
|
|
|
118
121
|
## 📄 License
|
|
119
122
|
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# vite-plugin-performance
|
|
2
|
+
|
|
3
|
+
[English Documentation](./README.md)
|
|
4
|
+
|
|
5
|
+
一个用于包裹 Vite 插件、统计生命周期钩子耗时的小工具,帮助你快速定位构建链路中的性能瓶颈。插件原有行为保持不变,我们只在钩子执行完成后采集数据并按需输出或上报。
|
|
6
|
+
|
|
7
|
+
## ✨ 特性
|
|
8
|
+
|
|
9
|
+
- 支持同时包裹单个插件或插件数组
|
|
10
|
+
- 内置常用钩子列表,可设置 `hooks: 'all'` 包裹所有函数钩子
|
|
11
|
+
- 阈值过滤机制,只关注真正慢的钩子
|
|
12
|
+
- 自定义日志、格式化与回调,方便接入监控体系
|
|
13
|
+
- 兼容异步钩子,异常也会统计耗时
|
|
14
|
+
|
|
15
|
+
## 📦 安装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pnpm add -D vite-plugin-performance
|
|
19
|
+
# 或 npm / yarn / bun
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 🚀 快速上手
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { defineConfig } from 'vite'
|
|
26
|
+
import Inspect from 'vite-plugin-inspect'
|
|
27
|
+
import { wrapPlugin } from 'vite-plugin-performance'
|
|
28
|
+
|
|
29
|
+
export default defineConfig({
|
|
30
|
+
plugins: [
|
|
31
|
+
wrapPlugin(Inspect(), {
|
|
32
|
+
threshold: 50,
|
|
33
|
+
onHookExecution({ pluginName, hookName, duration }) {
|
|
34
|
+
reportToAPM({ pluginName, hookName, duration })
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
37
|
+
],
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
当某个钩子耗时超过设置的阈值(默认 0 ms)时,控制台会输出:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
[inspect] transform ⏱ 78.42 ms
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## ⚙️ 选项
|
|
48
|
+
|
|
49
|
+
| 选项 | 类型 | 默认值 | 说明 |
|
|
50
|
+
| ----------------- | ---------------------------- | --------------------------------- | --------------------------------------------- |
|
|
51
|
+
| `hooks` | `PluginHookName[] \| 'all'` | `DEFAULT_PLUGIN_HOOKS` | 指定需要包裹的钩子;传 `all` 包裹所有函数钩子 |
|
|
52
|
+
| `threshold` | `number` | `0` | 只有耗时大于等于该值的钩子才会被记录 |
|
|
53
|
+
| `silent` | `boolean` | `false` | 关闭内置日志输出 |
|
|
54
|
+
| `logger` | `(message, context) => void` | `console.log` | 自定义日志输出函数 |
|
|
55
|
+
| `formatter` | `(context) => string` | `[plugin] transform ⏱ 12.34 ms` | 自定义日志内容格式 |
|
|
56
|
+
| `onHookExecution` | `(context) => void` | `undefined` | 钩子执行完毕后触发,可用于上报 |
|
|
57
|
+
| `clock` | `() => number` | `performance.now` 或 `Date.now` | 高精度计时器,便于测试或自定义时间源 |
|
|
58
|
+
|
|
59
|
+
> 兼容早期误拼写的 `slient` 选项,会自动映射为 `silent`。
|
|
60
|
+
|
|
61
|
+
### 默认钩子列表
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { DEFAULT_PLUGIN_HOOKS } from 'vite-plugin-performance'
|
|
65
|
+
// [
|
|
66
|
+
// 'options',
|
|
67
|
+
// 'config',
|
|
68
|
+
// 'configResolved',
|
|
69
|
+
// 'configureServer',
|
|
70
|
+
// 'buildStart',
|
|
71
|
+
// 'resolveId',
|
|
72
|
+
// 'load',
|
|
73
|
+
// 'transform',
|
|
74
|
+
// 'buildEnd',
|
|
75
|
+
// 'generateBundle',
|
|
76
|
+
// 'renderChunk',
|
|
77
|
+
// 'writeBundle',
|
|
78
|
+
// ]
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 🧠 进阶用法
|
|
82
|
+
|
|
83
|
+
### 包裹多个插件
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
const pluginA = ...
|
|
87
|
+
const pluginB = ...
|
|
88
|
+
|
|
89
|
+
export default defineConfig({
|
|
90
|
+
plugins: wrapPlugin([pluginA, pluginB], { threshold: 20 }),
|
|
91
|
+
})
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 自定义日志格式
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
wrapPlugin(plugin, {
|
|
98
|
+
formatter({ pluginName, hookName, duration }) {
|
|
99
|
+
return `${pluginName}:${hookName} took ${duration}ms`
|
|
100
|
+
},
|
|
101
|
+
logger(message) {
|
|
102
|
+
myLogger.info(message)
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 仅关注特定钩子
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
wrapPlugin(plugin, {
|
|
111
|
+
hooks: ['resolveId', 'load', 'transform'],
|
|
112
|
+
})
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## 🧪 测试
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
pnpm --filter vite-plugin-performance test
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## 📄 许可
|
|
122
|
+
|
|
123
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -1,122 +1,127 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const DEFAULT_PLUGIN_HOOKS = [
|
|
4
|
+
"options",
|
|
5
|
+
"config",
|
|
6
|
+
"configResolved",
|
|
7
|
+
"configureServer",
|
|
8
|
+
"buildStart",
|
|
9
|
+
"resolveId",
|
|
10
|
+
"load",
|
|
11
|
+
"transform",
|
|
12
|
+
"buildEnd",
|
|
13
|
+
"generateBundle",
|
|
14
|
+
"renderChunk",
|
|
15
|
+
"writeBundle"
|
|
16
|
+
];
|
|
17
|
+
const DEFAULT_THRESHOLD = 0;
|
|
18
|
+
const ANONYMOUS_PLUGIN_NAME = "anonymous-plugin";
|
|
19
|
+
const DEFAULT_FORMATTER = ({ pluginName, hookName, duration }) => {
|
|
20
|
+
const paddedHook = hookName.padEnd(20);
|
|
21
|
+
const formattedDuration = duration.toFixed(2).padStart(7);
|
|
22
|
+
return `[${pluginName}] ${paddedHook} \u23F1 ${formattedDuration} ms`;
|
|
23
|
+
};
|
|
24
|
+
const DEFAULT_LOGGER = (message) => {
|
|
25
|
+
console.log(message);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const hasPerformanceNow = typeof globalThis !== "undefined" && typeof globalThis.performance?.now === "function";
|
|
29
|
+
const defaultClock = hasPerformanceNow ? () => globalThis.performance.now() : () => Date.now();
|
|
30
|
+
function resolveOptions(options = {}) {
|
|
31
|
+
const {
|
|
32
|
+
hooks = DEFAULT_PLUGIN_HOOKS,
|
|
33
|
+
threshold = DEFAULT_THRESHOLD,
|
|
34
|
+
silent = options.slient ?? false,
|
|
35
|
+
logger = DEFAULT_LOGGER,
|
|
36
|
+
formatter = DEFAULT_FORMATTER,
|
|
37
|
+
onHookExecution,
|
|
38
|
+
clock = defaultClock
|
|
39
|
+
} = options;
|
|
4
40
|
return {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"buildEnd",
|
|
13
|
-
"generateBundle",
|
|
14
|
-
"renderChunk",
|
|
15
|
-
"writeBundle"
|
|
16
|
-
],
|
|
17
|
-
slient: false
|
|
41
|
+
hooks,
|
|
42
|
+
threshold,
|
|
43
|
+
silent,
|
|
44
|
+
logger,
|
|
45
|
+
formatter,
|
|
46
|
+
onHookExecution,
|
|
47
|
+
clock
|
|
18
48
|
};
|
|
19
49
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (value === null || typeof value !== "object") {
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
const prototype = Object.getPrototypeOf(value);
|
|
26
|
-
if (prototype !== null && prototype !== Object.prototype && Object.getPrototypeOf(prototype) !== null) {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
if (Symbol.iterator in value) {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
if (Symbol.toStringTag in value) {
|
|
33
|
-
return Object.prototype.toString.call(value) === "[object Module]";
|
|
34
|
-
}
|
|
35
|
-
return true;
|
|
50
|
+
function resolvePluginName(rawName) {
|
|
51
|
+
return rawName && rawName.length > 0 ? rawName : ANONYMOUS_PLUGIN_NAME;
|
|
36
52
|
}
|
|
37
53
|
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
return _defu(baseObject, {}, namespace, merger);
|
|
41
|
-
}
|
|
42
|
-
const object = Object.assign({}, defaults);
|
|
43
|
-
for (const key in baseObject) {
|
|
44
|
-
if (key === "__proto__" || key === "constructor") {
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
const value = baseObject[key];
|
|
48
|
-
if (value === null || value === void 0) {
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
if (merger && merger(object, key, value, namespace)) {
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
if (Array.isArray(value) && Array.isArray(object[key])) {
|
|
55
|
-
object[key] = [...value, ...object[key]];
|
|
56
|
-
} else if (isPlainObject(value) && isPlainObject(object[key])) {
|
|
57
|
-
object[key] = _defu(
|
|
58
|
-
value,
|
|
59
|
-
object[key],
|
|
60
|
-
(namespace ? `${namespace}.` : "") + key.toString(),
|
|
61
|
-
merger
|
|
62
|
-
);
|
|
63
|
-
} else {
|
|
64
|
-
object[key] = value;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return object;
|
|
54
|
+
function ensureArray(value) {
|
|
55
|
+
return Array.isArray(value) ? value : [value];
|
|
68
56
|
}
|
|
69
|
-
function
|
|
70
|
-
return
|
|
71
|
-
// eslint-disable-next-line unicorn/no-array-reduce
|
|
72
|
-
arguments_.reduce((p, c) => _defu(p, c, "", merger), {})
|
|
73
|
-
);
|
|
57
|
+
function isPromiseLike(value) {
|
|
58
|
+
return typeof value === "object" && value !== null && "then" in value;
|
|
74
59
|
}
|
|
75
60
|
|
|
76
|
-
const defuOverrideArray = createDefu((obj, key, value) => {
|
|
77
|
-
if (Array.isArray(obj[key]) && Array.isArray(value)) {
|
|
78
|
-
obj[key] = value;
|
|
79
|
-
return true;
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
61
|
function wrapPlugin(maybePlugin, options) {
|
|
84
|
-
const
|
|
85
|
-
const plugins =
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
};
|
|
62
|
+
const resolvedOptions = resolveOptions(options);
|
|
63
|
+
const plugins = ensureArray(maybePlugin);
|
|
64
|
+
const wrapped = plugins.map((plugin) => wrapSinglePlugin(plugin, resolvedOptions));
|
|
65
|
+
return Array.isArray(maybePlugin) ? wrapped : wrapped[0];
|
|
66
|
+
}
|
|
67
|
+
function wrapSinglePlugin(plugin, options) {
|
|
68
|
+
const wrapped = { ...plugin };
|
|
69
|
+
const pluginName = resolvePluginName(plugin.name);
|
|
70
|
+
const hooksToWrap = resolveHooks(plugin, options.hooks);
|
|
71
|
+
for (const hookName of hooksToWrap) {
|
|
72
|
+
const original = plugin[hookName];
|
|
73
|
+
if (typeof original !== "function") {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
wrapped[hookName] = function performanceHook(...args) {
|
|
77
|
+
const start = options.clock();
|
|
78
|
+
const invoke = () => original.apply(this, args);
|
|
79
|
+
const finalize = (result) => {
|
|
80
|
+
reportExecution({ pluginName, hookName, args, start, options });
|
|
81
|
+
return result;
|
|
82
|
+
};
|
|
83
|
+
const handleError = (error) => {
|
|
84
|
+
reportExecution({ pluginName, hookName, args, start, options });
|
|
85
|
+
throw error;
|
|
86
|
+
};
|
|
87
|
+
try {
|
|
88
|
+
const result = invoke();
|
|
89
|
+
if (isPromiseLike(result)) {
|
|
90
|
+
return result.then(finalize, handleError);
|
|
114
91
|
}
|
|
92
|
+
return finalize(result);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
return handleError(error);
|
|
115
95
|
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return wrapped;
|
|
99
|
+
}
|
|
100
|
+
function resolveHooks(plugin, hooks) {
|
|
101
|
+
if (hooks === "all") {
|
|
102
|
+
return Object.keys(plugin).filter((key) => typeof plugin[key] === "function");
|
|
103
|
+
}
|
|
104
|
+
return [...hooks];
|
|
105
|
+
}
|
|
106
|
+
function reportExecution(params) {
|
|
107
|
+
const { pluginName, hookName, args, start, options } = params;
|
|
108
|
+
const duration = Math.max(0, options.clock() - start);
|
|
109
|
+
const context = {
|
|
110
|
+
pluginName,
|
|
111
|
+
hookName,
|
|
112
|
+
args,
|
|
113
|
+
duration
|
|
114
|
+
};
|
|
115
|
+
if (duration >= options.threshold) {
|
|
116
|
+
if (!options.silent) {
|
|
117
|
+
const message = options.formatter(context);
|
|
118
|
+
options.logger(message, context);
|
|
116
119
|
}
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
return isArr ? wrappedPlugins : wrappedPlugins[0];
|
|
120
|
+
options.onHookExecution?.(context);
|
|
121
|
+
}
|
|
120
122
|
}
|
|
121
123
|
|
|
124
|
+
exports.DEFAULT_PLUGIN_HOOKS = DEFAULT_PLUGIN_HOOKS;
|
|
125
|
+
exports.DEFAULT_THRESHOLD = DEFAULT_THRESHOLD;
|
|
126
|
+
exports.resolveOptions = resolveOptions;
|
|
122
127
|
exports.wrapPlugin = wrapPlugin;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,20 +1,50 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
2
|
|
|
3
|
-
type
|
|
4
|
-
interface
|
|
3
|
+
type PluginHookName = Extract<keyof Plugin, string>;
|
|
4
|
+
interface HookExecutionContext {
|
|
5
5
|
pluginName: string;
|
|
6
|
-
hookName:
|
|
7
|
-
args:
|
|
6
|
+
hookName: PluginHookName;
|
|
7
|
+
args: unknown[];
|
|
8
8
|
duration: number;
|
|
9
9
|
}
|
|
10
|
+
type HookLogger = (message: string, context: HookExecutionContext) => void;
|
|
11
|
+
type HookFormatter = (context: HookExecutionContext) => string;
|
|
12
|
+
type HookClock = () => number;
|
|
10
13
|
interface WrapPluginOptions {
|
|
14
|
+
/** Wrap only these hooks. Use `all` to wrap every function hook on the plugin. */
|
|
15
|
+
hooks?: PluginHookName[] | 'all';
|
|
16
|
+
/** Minimum duration (ms) required to log or emit notifications. */
|
|
11
17
|
threshold?: number;
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
/** Disable the default logger output. */
|
|
19
|
+
silent?: boolean;
|
|
20
|
+
/** Custom logger called with the formatted message. Defaults to `console.log`. */
|
|
21
|
+
logger?: HookLogger;
|
|
22
|
+
/** Custom formatter for the log message. */
|
|
23
|
+
formatter?: HookFormatter;
|
|
24
|
+
/** Lifecycle callback invoked after a hook has completed. */
|
|
25
|
+
onHookExecution?: (context: HookExecutionContext) => void;
|
|
26
|
+
/** Provide a custom high-resolution clock implementation. */
|
|
27
|
+
clock?: HookClock;
|
|
28
|
+
/** @deprecated backwards compatibility typo */
|
|
14
29
|
slient?: boolean;
|
|
15
30
|
}
|
|
31
|
+
interface ResolvedWrapPluginOptions {
|
|
32
|
+
hooks: PluginHookName[] | 'all';
|
|
33
|
+
threshold: number;
|
|
34
|
+
silent: boolean;
|
|
35
|
+
logger: HookLogger;
|
|
36
|
+
formatter: HookFormatter;
|
|
37
|
+
onHookExecution?: (context: HookExecutionContext) => void;
|
|
38
|
+
clock: HookClock;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
declare const DEFAULT_PLUGIN_HOOKS: PluginHookName[];
|
|
42
|
+
declare const DEFAULT_THRESHOLD = 0;
|
|
43
|
+
|
|
44
|
+
declare function resolveOptions(options?: Partial<WrapPluginOptions>): ResolvedWrapPluginOptions;
|
|
16
45
|
|
|
17
46
|
declare function wrapPlugin(maybePlugin: Plugin, options?: Partial<WrapPluginOptions>): Plugin;
|
|
18
47
|
declare function wrapPlugin(maybePlugin: Plugin[], options?: Partial<WrapPluginOptions>): Plugin[];
|
|
19
48
|
|
|
20
|
-
export {
|
|
49
|
+
export { DEFAULT_PLUGIN_HOOKS, DEFAULT_THRESHOLD, resolveOptions, wrapPlugin };
|
|
50
|
+
export type { HookClock, HookExecutionContext, HookFormatter, HookLogger, PluginHookName, ResolvedWrapPluginOptions, WrapPluginOptions };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,20 +1,50 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
2
|
|
|
3
|
-
type
|
|
4
|
-
interface
|
|
3
|
+
type PluginHookName = Extract<keyof Plugin, string>;
|
|
4
|
+
interface HookExecutionContext {
|
|
5
5
|
pluginName: string;
|
|
6
|
-
hookName:
|
|
7
|
-
args:
|
|
6
|
+
hookName: PluginHookName;
|
|
7
|
+
args: unknown[];
|
|
8
8
|
duration: number;
|
|
9
9
|
}
|
|
10
|
+
type HookLogger = (message: string, context: HookExecutionContext) => void;
|
|
11
|
+
type HookFormatter = (context: HookExecutionContext) => string;
|
|
12
|
+
type HookClock = () => number;
|
|
10
13
|
interface WrapPluginOptions {
|
|
14
|
+
/** Wrap only these hooks. Use `all` to wrap every function hook on the plugin. */
|
|
15
|
+
hooks?: PluginHookName[] | 'all';
|
|
16
|
+
/** Minimum duration (ms) required to log or emit notifications. */
|
|
11
17
|
threshold?: number;
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
/** Disable the default logger output. */
|
|
19
|
+
silent?: boolean;
|
|
20
|
+
/** Custom logger called with the formatted message. Defaults to `console.log`. */
|
|
21
|
+
logger?: HookLogger;
|
|
22
|
+
/** Custom formatter for the log message. */
|
|
23
|
+
formatter?: HookFormatter;
|
|
24
|
+
/** Lifecycle callback invoked after a hook has completed. */
|
|
25
|
+
onHookExecution?: (context: HookExecutionContext) => void;
|
|
26
|
+
/** Provide a custom high-resolution clock implementation. */
|
|
27
|
+
clock?: HookClock;
|
|
28
|
+
/** @deprecated backwards compatibility typo */
|
|
14
29
|
slient?: boolean;
|
|
15
30
|
}
|
|
31
|
+
interface ResolvedWrapPluginOptions {
|
|
32
|
+
hooks: PluginHookName[] | 'all';
|
|
33
|
+
threshold: number;
|
|
34
|
+
silent: boolean;
|
|
35
|
+
logger: HookLogger;
|
|
36
|
+
formatter: HookFormatter;
|
|
37
|
+
onHookExecution?: (context: HookExecutionContext) => void;
|
|
38
|
+
clock: HookClock;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
declare const DEFAULT_PLUGIN_HOOKS: PluginHookName[];
|
|
42
|
+
declare const DEFAULT_THRESHOLD = 0;
|
|
43
|
+
|
|
44
|
+
declare function resolveOptions(options?: Partial<WrapPluginOptions>): ResolvedWrapPluginOptions;
|
|
16
45
|
|
|
17
46
|
declare function wrapPlugin(maybePlugin: Plugin, options?: Partial<WrapPluginOptions>): Plugin;
|
|
18
47
|
declare function wrapPlugin(maybePlugin: Plugin[], options?: Partial<WrapPluginOptions>): Plugin[];
|
|
19
48
|
|
|
20
|
-
export {
|
|
49
|
+
export { DEFAULT_PLUGIN_HOOKS, DEFAULT_THRESHOLD, resolveOptions, wrapPlugin };
|
|
50
|
+
export type { HookClock, HookExecutionContext, HookFormatter, HookLogger, PluginHookName, ResolvedWrapPluginOptions, WrapPluginOptions };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,20 +1,50 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
2
|
|
|
3
|
-
type
|
|
4
|
-
interface
|
|
3
|
+
type PluginHookName = Extract<keyof Plugin, string>;
|
|
4
|
+
interface HookExecutionContext {
|
|
5
5
|
pluginName: string;
|
|
6
|
-
hookName:
|
|
7
|
-
args:
|
|
6
|
+
hookName: PluginHookName;
|
|
7
|
+
args: unknown[];
|
|
8
8
|
duration: number;
|
|
9
9
|
}
|
|
10
|
+
type HookLogger = (message: string, context: HookExecutionContext) => void;
|
|
11
|
+
type HookFormatter = (context: HookExecutionContext) => string;
|
|
12
|
+
type HookClock = () => number;
|
|
10
13
|
interface WrapPluginOptions {
|
|
14
|
+
/** Wrap only these hooks. Use `all` to wrap every function hook on the plugin. */
|
|
15
|
+
hooks?: PluginHookName[] | 'all';
|
|
16
|
+
/** Minimum duration (ms) required to log or emit notifications. */
|
|
11
17
|
threshold?: number;
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
/** Disable the default logger output. */
|
|
19
|
+
silent?: boolean;
|
|
20
|
+
/** Custom logger called with the formatted message. Defaults to `console.log`. */
|
|
21
|
+
logger?: HookLogger;
|
|
22
|
+
/** Custom formatter for the log message. */
|
|
23
|
+
formatter?: HookFormatter;
|
|
24
|
+
/** Lifecycle callback invoked after a hook has completed. */
|
|
25
|
+
onHookExecution?: (context: HookExecutionContext) => void;
|
|
26
|
+
/** Provide a custom high-resolution clock implementation. */
|
|
27
|
+
clock?: HookClock;
|
|
28
|
+
/** @deprecated backwards compatibility typo */
|
|
14
29
|
slient?: boolean;
|
|
15
30
|
}
|
|
31
|
+
interface ResolvedWrapPluginOptions {
|
|
32
|
+
hooks: PluginHookName[] | 'all';
|
|
33
|
+
threshold: number;
|
|
34
|
+
silent: boolean;
|
|
35
|
+
logger: HookLogger;
|
|
36
|
+
formatter: HookFormatter;
|
|
37
|
+
onHookExecution?: (context: HookExecutionContext) => void;
|
|
38
|
+
clock: HookClock;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
declare const DEFAULT_PLUGIN_HOOKS: PluginHookName[];
|
|
42
|
+
declare const DEFAULT_THRESHOLD = 0;
|
|
43
|
+
|
|
44
|
+
declare function resolveOptions(options?: Partial<WrapPluginOptions>): ResolvedWrapPluginOptions;
|
|
16
45
|
|
|
17
46
|
declare function wrapPlugin(maybePlugin: Plugin, options?: Partial<WrapPluginOptions>): Plugin;
|
|
18
47
|
declare function wrapPlugin(maybePlugin: Plugin[], options?: Partial<WrapPluginOptions>): Plugin[];
|
|
19
48
|
|
|
20
|
-
export {
|
|
49
|
+
export { DEFAULT_PLUGIN_HOOKS, DEFAULT_THRESHOLD, resolveOptions, wrapPlugin };
|
|
50
|
+
export type { HookClock, HookExecutionContext, HookFormatter, HookLogger, PluginHookName, ResolvedWrapPluginOptions, WrapPluginOptions };
|
package/dist/index.mjs
CHANGED
|
@@ -1,120 +1,122 @@
|
|
|
1
|
-
|
|
1
|
+
const DEFAULT_PLUGIN_HOOKS = [
|
|
2
|
+
"options",
|
|
3
|
+
"config",
|
|
4
|
+
"configResolved",
|
|
5
|
+
"configureServer",
|
|
6
|
+
"buildStart",
|
|
7
|
+
"resolveId",
|
|
8
|
+
"load",
|
|
9
|
+
"transform",
|
|
10
|
+
"buildEnd",
|
|
11
|
+
"generateBundle",
|
|
12
|
+
"renderChunk",
|
|
13
|
+
"writeBundle"
|
|
14
|
+
];
|
|
15
|
+
const DEFAULT_THRESHOLD = 0;
|
|
16
|
+
const ANONYMOUS_PLUGIN_NAME = "anonymous-plugin";
|
|
17
|
+
const DEFAULT_FORMATTER = ({ pluginName, hookName, duration }) => {
|
|
18
|
+
const paddedHook = hookName.padEnd(20);
|
|
19
|
+
const formattedDuration = duration.toFixed(2).padStart(7);
|
|
20
|
+
return `[${pluginName}] ${paddedHook} \u23F1 ${formattedDuration} ms`;
|
|
21
|
+
};
|
|
22
|
+
const DEFAULT_LOGGER = (message) => {
|
|
23
|
+
console.log(message);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const hasPerformanceNow = typeof globalThis !== "undefined" && typeof globalThis.performance?.now === "function";
|
|
27
|
+
const defaultClock = hasPerformanceNow ? () => globalThis.performance.now() : () => Date.now();
|
|
28
|
+
function resolveOptions(options = {}) {
|
|
29
|
+
const {
|
|
30
|
+
hooks = DEFAULT_PLUGIN_HOOKS,
|
|
31
|
+
threshold = DEFAULT_THRESHOLD,
|
|
32
|
+
silent = options.slient ?? false,
|
|
33
|
+
logger = DEFAULT_LOGGER,
|
|
34
|
+
formatter = DEFAULT_FORMATTER,
|
|
35
|
+
onHookExecution,
|
|
36
|
+
clock = defaultClock
|
|
37
|
+
} = options;
|
|
2
38
|
return {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"buildEnd",
|
|
11
|
-
"generateBundle",
|
|
12
|
-
"renderChunk",
|
|
13
|
-
"writeBundle"
|
|
14
|
-
],
|
|
15
|
-
slient: false
|
|
39
|
+
hooks,
|
|
40
|
+
threshold,
|
|
41
|
+
silent,
|
|
42
|
+
logger,
|
|
43
|
+
formatter,
|
|
44
|
+
onHookExecution,
|
|
45
|
+
clock
|
|
16
46
|
};
|
|
17
47
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (value === null || typeof value !== "object") {
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
const prototype = Object.getPrototypeOf(value);
|
|
24
|
-
if (prototype !== null && prototype !== Object.prototype && Object.getPrototypeOf(prototype) !== null) {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
if (Symbol.iterator in value) {
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
|
-
if (Symbol.toStringTag in value) {
|
|
31
|
-
return Object.prototype.toString.call(value) === "[object Module]";
|
|
32
|
-
}
|
|
33
|
-
return true;
|
|
48
|
+
function resolvePluginName(rawName) {
|
|
49
|
+
return rawName && rawName.length > 0 ? rawName : ANONYMOUS_PLUGIN_NAME;
|
|
34
50
|
}
|
|
35
51
|
|
|
36
|
-
function
|
|
37
|
-
|
|
38
|
-
return _defu(baseObject, {}, namespace, merger);
|
|
39
|
-
}
|
|
40
|
-
const object = Object.assign({}, defaults);
|
|
41
|
-
for (const key in baseObject) {
|
|
42
|
-
if (key === "__proto__" || key === "constructor") {
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
const value = baseObject[key];
|
|
46
|
-
if (value === null || value === void 0) {
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
if (merger && merger(object, key, value, namespace)) {
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
if (Array.isArray(value) && Array.isArray(object[key])) {
|
|
53
|
-
object[key] = [...value, ...object[key]];
|
|
54
|
-
} else if (isPlainObject(value) && isPlainObject(object[key])) {
|
|
55
|
-
object[key] = _defu(
|
|
56
|
-
value,
|
|
57
|
-
object[key],
|
|
58
|
-
(namespace ? `${namespace}.` : "") + key.toString(),
|
|
59
|
-
merger
|
|
60
|
-
);
|
|
61
|
-
} else {
|
|
62
|
-
object[key] = value;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return object;
|
|
52
|
+
function ensureArray(value) {
|
|
53
|
+
return Array.isArray(value) ? value : [value];
|
|
66
54
|
}
|
|
67
|
-
function
|
|
68
|
-
return
|
|
69
|
-
// eslint-disable-next-line unicorn/no-array-reduce
|
|
70
|
-
arguments_.reduce((p, c) => _defu(p, c, "", merger), {})
|
|
71
|
-
);
|
|
55
|
+
function isPromiseLike(value) {
|
|
56
|
+
return typeof value === "object" && value !== null && "then" in value;
|
|
72
57
|
}
|
|
73
58
|
|
|
74
|
-
const defuOverrideArray = createDefu((obj, key, value) => {
|
|
75
|
-
if (Array.isArray(obj[key]) && Array.isArray(value)) {
|
|
76
|
-
obj[key] = value;
|
|
77
|
-
return true;
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
|
|
81
59
|
function wrapPlugin(maybePlugin, options) {
|
|
82
|
-
const
|
|
83
|
-
const plugins =
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
};
|
|
60
|
+
const resolvedOptions = resolveOptions(options);
|
|
61
|
+
const plugins = ensureArray(maybePlugin);
|
|
62
|
+
const wrapped = plugins.map((plugin) => wrapSinglePlugin(plugin, resolvedOptions));
|
|
63
|
+
return Array.isArray(maybePlugin) ? wrapped : wrapped[0];
|
|
64
|
+
}
|
|
65
|
+
function wrapSinglePlugin(plugin, options) {
|
|
66
|
+
const wrapped = { ...plugin };
|
|
67
|
+
const pluginName = resolvePluginName(plugin.name);
|
|
68
|
+
const hooksToWrap = resolveHooks(plugin, options.hooks);
|
|
69
|
+
for (const hookName of hooksToWrap) {
|
|
70
|
+
const original = plugin[hookName];
|
|
71
|
+
if (typeof original !== "function") {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
wrapped[hookName] = function performanceHook(...args) {
|
|
75
|
+
const start = options.clock();
|
|
76
|
+
const invoke = () => original.apply(this, args);
|
|
77
|
+
const finalize = (result) => {
|
|
78
|
+
reportExecution({ pluginName, hookName, args, start, options });
|
|
79
|
+
return result;
|
|
80
|
+
};
|
|
81
|
+
const handleError = (error) => {
|
|
82
|
+
reportExecution({ pluginName, hookName, args, start, options });
|
|
83
|
+
throw error;
|
|
84
|
+
};
|
|
85
|
+
try {
|
|
86
|
+
const result = invoke();
|
|
87
|
+
if (isPromiseLike(result)) {
|
|
88
|
+
return result.then(finalize, handleError);
|
|
112
89
|
}
|
|
90
|
+
return finalize(result);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
return handleError(error);
|
|
113
93
|
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return wrapped;
|
|
97
|
+
}
|
|
98
|
+
function resolveHooks(plugin, hooks) {
|
|
99
|
+
if (hooks === "all") {
|
|
100
|
+
return Object.keys(plugin).filter((key) => typeof plugin[key] === "function");
|
|
101
|
+
}
|
|
102
|
+
return [...hooks];
|
|
103
|
+
}
|
|
104
|
+
function reportExecution(params) {
|
|
105
|
+
const { pluginName, hookName, args, start, options } = params;
|
|
106
|
+
const duration = Math.max(0, options.clock() - start);
|
|
107
|
+
const context = {
|
|
108
|
+
pluginName,
|
|
109
|
+
hookName,
|
|
110
|
+
args,
|
|
111
|
+
duration
|
|
112
|
+
};
|
|
113
|
+
if (duration >= options.threshold) {
|
|
114
|
+
if (!options.silent) {
|
|
115
|
+
const message = options.formatter(context);
|
|
116
|
+
options.logger(message, context);
|
|
114
117
|
}
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
return isArr ? wrappedPlugins : wrappedPlugins[0];
|
|
118
|
+
options.onHookExecution?.(context);
|
|
119
|
+
}
|
|
118
120
|
}
|
|
119
121
|
|
|
120
|
-
export { wrapPlugin };
|
|
122
|
+
export { DEFAULT_PLUGIN_HOOKS, DEFAULT_THRESHOLD, resolveOptions, wrapPlugin };
|