场景
某次,在开发一个npm组件包时,打算引入web worker来实现大量计算的性能优化
这个组件包的开发环境用的rollup + babel + typescript,用ts进行开发,rollup会自动打包、编译源码至js文件,web worker脚本我也希望是用ts编写,rollup自动转译
希望其他人用这个组件时,无需额外的配置,可直接用上组件内的web worker优化
于是有了最初的实现方式
// 主线程创建worker(ts源码)
const worker = new Worker('./worker.js', import.meta.url);
worker.postMessage('帮我计算文件md5');
// worker.ts
import SparkMD5 from 'spark-md5'
self.addEventListener('message', e => {
// 处理逻辑...
});
// 打包后文件结构
dist/
├── index,js # 主源码
├── worker.js # 转译后的worker脚本
这样组件方看起来没有问题,worker脚本正常暴露出来了
但实际使用方运行时,会找不到worker.js文件
因为使用方的项目,一般会支持tree-shaking,没有显式依赖的文件,不会打包到最终代码;也就丢失了worker.js文件;我们组件中,只是new Worker('./worker.js', import.meta.url);,理想的加载同目录下的worker脚本,这种不确定性很大
问题
const worker = new Worker('./worker.js', import.meta.url);直接这种方式,会找不到worker脚本
可以直接在源码中内联worker脚本字符串
// 主线程创建worker(ts源码)
const workerStr = `
importScript(./spark-md5.min.js);
// 或者从CDN加载
importScript(https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.2/spark-md5.min.js);
self.addEventListener('message', e => {
// 处理逻辑...
});
`;
const worker = new Worker('./worker.js', import.meta.url);
worker.postMessage('帮我计算文件md5');
也会有以下问题:
会增大主包体积,一方面希望主包较小,一方面又希望利用worker脚本优化性能,纠结
源码中字符串形式,不方便开发,还是希望worker脚本用ts开发,rollup帮助完成后续转译操作
第三方包spark-md5加载方式不可靠
考虑另一种方式,worker.js文件还是正常暴露在dist目录下,通过文档的形式,告知使用方,正确处理worker.js文件到最终的正确位置
会增加使用成本,希望开发出来的组件包,是能开箱即用的,避免复杂的配置
使用方显式import + 内联创建worker
经过查阅资料,比较理想的方案:
组件方:ts编写脚本源码,通过rollup自定转译为iife的脚本字符串,暴露这个脚本字符串
使用方:需要时,导入这个脚本字符串,通过参数传入组件即可
组件方:
// 主线程创建worker(ts源码)
// workerCode字符串通过参数传递
const blob = new Blob([workerCode], { type: 'text/javascript' });
const worker = new Worker(URL.createObjectURL(blob));
worker.postMessage('帮我计算文件md5');
// rollup配置,新增一个配置,将worker ts源代码,编译后作为字符串导出
const workerConfig = {
input: 'src/utils/calcMD5Worker.ts',
output: {
file: 'dist/worker.js',
format: 'iife',
name: 'WorkerCalcMD5',
sourcemap: true
},
plugins: [
resolve({
extensions: ['.ts', '.js'],
browser: true
}),
commonjs({
requireReturnsDefault: 'predefined',
dynamicRequireTargets: ['node_modules/spark-md5/*']
}),
typescript(),
babel({
babelHelpers: 'bundled',
configFile: path.resolve(__dirname, './.babelrc'),
}),
{
name: 'string-export',
renderChunk(code) {
return `export default \`${code.replace(/`/g, '\\`')}\`;`;
}
}
]
};
使用方:
import MyPackage from 'my-package';
import workerCode from 'my-package/dist/worker.js';
const useObj = new MyPackage({
// 只需要设置这个参数,即可使用web worker优化
workerCode,
});
这种方式的优点:
- 配置简单可靠
- 使用方可选择,需要时导入即可,不需要可以不传参数,也就不会往最终代码中加入无用代码
- worker脚本的源码支持ts编写,方便开发