Skip to content

Commit 83cc10d

Browse files
authored
Merge pull request #304 from z0ffy/feature/support-worker
feat: support worker
2 parents f8935cb + 04dddb2 commit 83cc10d

6 files changed

Lines changed: 169 additions & 9 deletions

File tree

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,32 @@ export default {
155155
};
156156
```
157157

158+
## 🧵 Web Worker Support
159+
160+
By default, worker bundles generated from `?worker` / `new Worker(new URL(...))` are **not** obfuscated.
161+
162+
Enable worker obfuscation:
163+
164+
```js
165+
import vitePluginBundleObfuscator from 'vite-plugin-bundle-obfuscator';
166+
167+
export default {
168+
plugins: [
169+
vitePluginBundleObfuscator({
170+
obfuscateWorker: true,
171+
}),
172+
],
173+
};
174+
```
175+
176+
Exclude extra files only for workers:
177+
178+
```js
179+
vitePluginBundleObfuscator({
180+
obfuscateWorkerExcludes: [/comlink\\.worker.*\\.js$/],
181+
});
182+
```
183+
158184
## 🚀 Performance Comparison
159185

160186
With **7000+ modules** and **400+ bundles** on a **4C 8G** machine:
@@ -169,6 +195,8 @@ With **7000+ modules** and **400+ bundles** on a **4C 8G** machine:
169195
| threadPool | Configuration for the thread pool. | boolean \| ({ enable: true; size: number } \| { enable: false }) | false | v1.2.0 |
170196
| apply | Apply the plugin only for serve or build, or on certain conditions. | 'serve' \| 'build' \| ((this: void, config: UserConfig, env: ConfigEnv) => boolean) | build | v1.1.0 |
171197
| autoExcludeNodeModules | Enable auto exclude node_modules. | boolean \| ({ enable: true; manualChunks: string[] } \| { enable: false }) | false | v1.0.9 (originally boolean, extended to current type in v1.3.0) |
198+
| obfuscateWorker | Enable or disable obfuscation for Web Worker bundles. | boolean \| { enable: boolean } | false | v1.10.0 |
199+
| obfuscateWorkerExcludes | Additional excludes only for workers (final: `excludes + obfuscateWorkerExcludes`). | (RegExp \| string)[] | [] | v1.10.0 |
172200
| log | Show or hide log output. | boolean | true | v1.0.4 |
173201
| enable | Enable or disable the obfuscator. | boolean | true | v1.0.1 |
174202
| excludes | Bundle names to be excluded. Starting from v1.0.8, RegExp is supported. | (RegExp \| string)[] | [] | v1.0.0 |

README.zh-CN.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,32 @@ export default {
153153
};
154154
```
155155

156+
## 🧵 Web Worker 支持
157+
158+
默认**不会**`?worker` / `new Worker(new URL(...))` 生成的 worker bundle 进行混淆。
159+
160+
启用 worker 混淆:
161+
162+
```js
163+
import vitePluginBundleObfuscator from 'vite-plugin-bundle-obfuscator';
164+
165+
export default {
166+
plugins: [
167+
vitePluginBundleObfuscator({
168+
obfuscateWorker: true,
169+
}),
170+
],
171+
};
172+
```
173+
174+
仅对 worker 额外排除某些文件:
175+
176+
```js
177+
vitePluginBundleObfuscator({
178+
obfuscateWorkerExcludes: [/comlink\\.worker.*\\.js$/],
179+
});
180+
```
181+
156182
## 🚀 性能比较
157183

158184
拥有 **7000+ modules****400+ bundles****4C 8G** 机器上:
@@ -167,6 +193,8 @@ export default {
167193
| threadPool | 线程池的配置。 | boolean \| ({ enable: true; size: number } \| { enable: false }) | false | v1.2.0 |
168194
| apply | 仅将插件应用于服务或构建,或在特定条件下。 | 'serve' \| 'build' \| ((this: void, config: UserConfig, env: ConfigEnv) => boolean) | build | v1.1.0 |
169195
| autoExcludeNodeModules | 启用自动排除node_modules。 | boolean \| ({ enable: true; manualChunks: string[] } \| { enable: false }) | false | v1.0.9(原本为boolean,在v1.3.0版本中扩展到当前类型) |
196+
| obfuscateWorker | 启用或禁用 Web Worker 产物混淆。 | boolean \| { enable: boolean } | false | v1.10.0 |
197+
| obfuscateWorkerExcludes | 仅对 worker 额外排除(最终:`excludes + obfuscateWorkerExcludes`)。 | (RegExp \| string)[] | [] | v1.10.0 |
170198
| log | 显示或隐藏日志输出。 | boolean | true | v1.0.4 |
171199
| enable | 启用或禁用混淆器。 | boolean | true | v1.0.1 |
172200
| excludes | 排除的bundle名。从v1.0.8开始,支持正则。 | (RegExp \| string)[] | [] | v1.0.0 |

src/__tests__/plugin.spec.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ const defaultConfig: Config = {
6767
log: false,
6868
autoExcludeNodeModules: true,
6969
threadPool: true,
70+
obfuscateWorker: true,
71+
obfuscateWorkerExcludes: [],
7072
options: {}
7173
}
7274

@@ -1020,6 +1022,54 @@ describe('viteBundleObfuscator plugin', () => {
10201022
const result = newManualChunks('src/utils.js', {});
10211023
expect(result).toBe('original-chunk');
10221024
});
1025+
1026+
it('should auto inject worker obfuscator plugin', () => {
1027+
const plugin = viteBundleObfuscator({obfuscateWorker: true}) as Plugin;
1028+
const originalWorkerPlugins = vi.fn().mockReturnValue([{name: 'user-worker-plugin'}]);
1029+
const config = {worker: {plugins: originalWorkerPlugins}} as any;
1030+
const env = {command: 'build', mode: 'production', isSsrBuild: false};
1031+
1032+
// @ts-ignore
1033+
plugin.config(config, env);
1034+
1035+
expect(typeof config.worker.plugins).toBe('function');
1036+
const plugins1 = config.worker.plugins();
1037+
const plugins2 = config.worker.plugins();
1038+
1039+
expect(plugins1.some((p: any) => p?.name === 'user-worker-plugin')).toBe(true);
1040+
expect(plugins1.some((p: any) => p?.name === 'vite-plugin-bundle-obfuscator:worker')).toBe(true);
1041+
1042+
const w1 = plugins1.find((p: any) => p?.name === 'vite-plugin-bundle-obfuscator:worker');
1043+
const w2 = plugins2.find((p: any) => p?.name === 'vite-plugin-bundle-obfuscator:worker');
1044+
expect(w1).toBeDefined();
1045+
expect(w2).toBeDefined();
1046+
expect(w1).not.toBe(w2);
1047+
});
1048+
1049+
it('should not auto inject worker plugin when disabled', () => {
1050+
const plugin = viteBundleObfuscator({obfuscateWorker: false}) as Plugin;
1051+
const config = {} as any;
1052+
const env = {command: 'build', mode: 'production', isSsrBuild: false};
1053+
1054+
// @ts-ignore
1055+
plugin.config(config, env);
1056+
1057+
expect(config.worker?.plugins).toBeUndefined();
1058+
});
1059+
1060+
it('should not duplicate worker plugin when already exists', () => {
1061+
const plugin = viteBundleObfuscator() as Plugin;
1062+
const originalWorkerPlugins = vi.fn().mockReturnValue([{name: 'vite-plugin-bundle-obfuscator:worker'}]);
1063+
const config = {worker: {plugins: originalWorkerPlugins}} as any;
1064+
const env = {command: 'build', mode: 'production', isSsrBuild: false};
1065+
1066+
// @ts-ignore
1067+
plugin.config(config, env);
1068+
1069+
const plugins = config.worker.plugins();
1070+
const workerCount = plugins.filter((p: any) => p?.name === 'vite-plugin-bundle-obfuscator:worker').length;
1071+
expect(workerCount).toBe(1);
1072+
});
10231073
});
10241074

10251075
describe('configResolved hook', () => {

src/index.ts

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { PluginOption, Rollup, ResolvedConfig, IndexHtmlTransformHook } from 'vite';
1+
import type { Plugin, PluginOption, Rollup, ResolvedConfig, IndexHtmlTransformHook } from 'vite';
22
import { BundleList, Config, ViteConfigFn } from './type';
33
import {
44
CodeSizeAnalyzer,
@@ -8,6 +8,7 @@ import {
88
getThreadPoolSize,
99
getValidBundleList,
1010
getViteMajorVersion,
11+
isEnabledFeature,
1112
isEnableAutoExcludesNodeModules,
1213
isEnableThreadPool,
1314
Log,
@@ -25,26 +26,31 @@ export default function viteBundleObfuscator(config?: Partial<Config>): PluginOp
2526
let _isNuxtProject = false;
2627
let _isSsrBuild = false;
2728

28-
const obfuscateAllChunks = async (bundle: Rollup.OutputBundle) => {
29-
_log.forceLog(LOG_COLOR.info + '\nstarting obfuscation process...');
30-
const analyzer = new CodeSizeAnalyzer(_log);
31-
const bundleList = getValidBundleList(finalConfig, bundle);
29+
const obfuscateAllChunks = async (
30+
bundle: Rollup.OutputBundle,
31+
opts: { config?: Config; log?: Log } = {},
32+
) => {
33+
const configToUse = opts.config ?? finalConfig;
34+
const log = opts.log ?? _log;
35+
log.forceLog(LOG_COLOR.info + '\nstarting obfuscation process...');
36+
const analyzer = new CodeSizeAnalyzer(log);
37+
const bundleList = getValidBundleList(configToUse, bundle);
3238
analyzer.start(bundleList);
3339

34-
if (isEnableThreadPool(finalConfig)) {
35-
const poolSize = Math.min(getThreadPoolSize(finalConfig), bundleList.length);
40+
if (isEnableThreadPool(configToUse)) {
41+
const poolSize = Math.min(getThreadPoolSize(configToUse), bundleList.length);
3642
const chunkSize = Math.ceil(bundleList.length / poolSize);
3743
const workerPromises = [];
3844

3945
for (let i = 0; i < poolSize; i++) {
4046
const chunk = bundleList.slice(i * chunkSize, (i + 1) * chunkSize);
41-
workerPromises.push(createWorkerTask(finalConfig, chunk));
47+
workerPromises.push(createWorkerTask(configToUse, chunk));
4248
}
4349

4450
await Promise.all(workerPromises);
4551
} else {
4652
bundleList.forEach(([fileName, bundleItem]) => {
47-
const { code, map } = obfuscateBundle(finalConfig, fileName, bundleItem);
53+
const { code, map } = obfuscateBundle(configToUse, fileName, bundleItem);
4854
bundleItem.code = code;
4955
bundleItem.map = map as any;
5056
});
@@ -58,6 +64,44 @@ export default function viteBundleObfuscator(config?: Partial<Config>): PluginOp
5864
_isLibMode = isLibMode(config);
5965
_isNuxtProject = isNuxtProject(config);
6066

67+
if (finalConfig.enable && isEnabledFeature(finalConfig.obfuscateWorker)) {
68+
const original = config.worker?.plugins;
69+
70+
config.worker = config.worker || {};
71+
config.worker.plugins = () => {
72+
const originalPluginsOption = original ?? [];
73+
const resolvedOriginalPlugins = isFunction(originalPluginsOption)
74+
? originalPluginsOption()
75+
: originalPluginsOption;
76+
const originalPlugins = (isArray(resolvedOriginalPlugins)
77+
? resolvedOriginalPlugins
78+
: [resolvedOriginalPlugins]
79+
).filter(Boolean);
80+
81+
const hasWorkerPlugin = originalPlugins.some(
82+
p => isObject(p) && 'name' in p && (p as any).name === 'vite-plugin-bundle-obfuscator:worker',
83+
);
84+
if (hasWorkerPlugin) return originalPlugins;
85+
86+
return [
87+
...originalPlugins,
88+
{
89+
name: 'vite-plugin-bundle-obfuscator:worker',
90+
apply: finalConfig.apply,
91+
enforce: 'post',
92+
async generateBundle(_outputOptions, bundle) {
93+
if (!finalConfig.enable || !bundle || _isSsrBuild) return;
94+
const workerConfig: Config = {
95+
...finalConfig,
96+
excludes: [...finalConfig.excludes, ...finalConfig.obfuscateWorkerExcludes],
97+
};
98+
await obfuscateAllChunks(bundle, { config: workerConfig, log: new Log(workerConfig.log) });
99+
},
100+
} satisfies Plugin,
101+
];
102+
};
103+
}
104+
61105
if (!finalConfig.enable || !isEnableAutoExcludesNodeModules(finalConfig) || _isLibMode || _isNuxtProject) {
62106
return;
63107
}

src/type.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,12 @@ export interface Config {
7272
* JavaScript obfuscator options.
7373
*/
7474
options: ObfuscatorOptions;
75+
/**
76+
* Enable or disable obfuscation for Vite Web Worker bundles (?worker imports).
77+
*/
78+
obfuscateWorker: boolean | { enable: boolean };
79+
/**
80+
* Additional excludes only for worker build (final excludes: excludes + obfuscateWorkerExcludes).
81+
*/
82+
obfuscateWorkerExcludes: (RegExp | string)[];
7583
}

src/utils/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ export const defaultConfig: Readonly<Config> = {
77
apply: 'build',
88
autoExcludeNodeModules: false,
99
threadPool: false,
10+
obfuscateWorker: false,
11+
obfuscateWorkerExcludes: [],
1012
options: {
1113
compact: true,
1214
controlFlowFlattening: true,

0 commit comments

Comments
 (0)