Web Worker
不想太多介绍Web Worker,这里主要是想处理qiankun中的跨域问题,Web Worker的介绍可以看使用教程
Web Worker是为JavaScript创建多线程环境,将一些任务分配给后者运行,等到Worker线程完成计算任务,再把结果返回给主线程
注意点
- 同源限制:分配给Worker线程运行的脚本文件,必须与主线程的脚本文件同源
- DOM限制:无法读取DOM对象;无法使用document、window、parent。可以访问navigato和location对象
- 通信联系:必须通过消息完成
- 脚本限制:不能执行alert、confirm
- 文件限制:无法读取本地文件
JS
webpack5 - Web Workers
我们先从单个项目,不存在跨域问题项目入手。通过create-react-app创建一个简单的项目.对应的webpack是5.x版本
webpack5开始可以使用Web Workers代替worker-loader
1.app.jsx
// App.jsx
useEffect(() => {
const worker = new Worker(new URL('./file.worker.js', import.meta.url));
worker.postMessage({ type: 'start' });
worker.onmessage = ({ data: { answer } }) => {
console.log('web worker 接受到的数据', answer);
};
return () => worker.terminate();
}, [])
2.file.worker.js
// file.worker.js
self.onmessage = ({ data: { type } }) => {
if (type === 'start') {
self.postMessage({
answer: 42,
});
}
};
3.运行截图
webpack4 - worker-loader
方式一:修改webpack配置
第二种方式是通过worker-loader
1.config-overrides.js
// config-overrides.js
const { override, addWebpackModuleRule } = require('customize-cra');
module.exports = override(
addWebpackModuleRule({
test: /\.worker\.js$/,
use: { loader: 'worker-loader' },
})
);
2.file.worker.js
// file.worker.js
self.onmessage = ({ data: { type } }) => {
if (type === 'start') {
self.postMessage({
answer: 42,
});
}
};
3.app.jsx
// App.jsx
import Worker from './file.worker.js';
useEffect(() => {
const worker = new Worker();
worker.postMessage({ type: 'start' });
worker.onmessage = function (event) {
console.log("Received message", event.data.answer);
};
return () => worker.terminate();
}, [])
4.运行截图
方式二:显示指定 worker-loader 作为加载器处理
1.不需要上面的 config-overrides.js 配置
2.file.worker.js
// file.worker.js
self.onmessage = ({ data: { type } }) => {
if(type === 'start') {
self.postMessage({
answer: 42,
});
}
};
3.app.jsx
// App.jsx
import Worker from 'worker-loader!./file.worker.js';
useEffect(() => {
const worker = new Worker();
worker.postMessage({ type: 'start' });
worker.onmessage = function (event) {
console.log("Received message worker-loader", event.data.answer);
};
return () => worker.terminate();
}, [])
4.运行截图
注:还有一些其他配置项,可以通过文档查看
TS
js实现起来还是比较简单的,ts使用的时候可能需要加点配置(也不难)
webpack4 - worker-loader
1.config-overrides.js
// config-overrides.js
const { override, addWebpackModuleRule } = require('customize-cra');
module.exports = override(
addWebpackModuleRule({
test: /\.worker\.ts$/,
use: ['worker-loader', 'ts-loader'], // 这里加上ts-loader
}),
);
2.App.tsx
// App.tsx
import MyWorker from './worker.worker.ts'; // 使用静态加载
useEffect(() => {
const worker = new MyWorker();
worker.postMessage({ type: 'start' });
worker.onmessage = function (event) {
console.log("Received message - ts", event.data.answer);
};
return () => worker.terminate();
}, [])
3.worker.worker.ts
self.onmessage = ({ data: { type } }) => {
if (type === 'start') {
self.postMessage({
answer: 42,
});
}
};
export {};
4.运行截图
Webpack5 - Web Worker
ts直接使用web worker会有点特别,webpack将ts转换为js文件,所以需要将worker.ts文件转换为worker.js文件。所以就需要手动将worker.ts文件转换为worker.js文件(具体配置看config-overrides.js)
上面的worker-loader为什么不需要呢?因为这个loader文件进行自动转换并导出一个Worker的构造函数,从而实现直接在主文件中引入ts worker文件的功能
1.config-overrides.js
// config-overrides.js
const { override } = require('customize-cra');
const addMultipleModuleRules = () => config => {
config.entry = {
main: './src/index.tsx',
worker: './src/worker.ts', // 手动将worker ts文件转换为js文件
};
config.output = {
...config.output, // 保留原有输出配置
filename: '[name].bundle.js',
}
return config;
};
module.exports = override(
addMultipleModuleRules()
);
2.worker.ts
// worker.ts
self.addEventListener('message', async (e: MessageEvent) => {
if (e.data.type === 'start') {
self.postMessage({
answer: 44,
});
}
});
export {};
3.App.tsx
useEffect(() => {
const worker = new Worker('./worker.bundle.js'); // 这里引入的是转换后的js文件
worker.postMessage({
type: 'start',
});
worker.onmessage = function (event) {
console.log("Received message - ts - webpack5 - webWorker", event.data.answer);
};
return () => worker.terminate();
}, [])
4.打包结果
打包结果可以看到worker.bundle.js
5.运行截图
qiankun
文章最开始的时候有提到Web Worker必须遵循同源限制。但是我们知道qiankun往往是不同源的,当然也存在同源的情况,但是这个不是我们今天需要讨论的。
创建DEMO
先创建官网提供的demo
React15使用web worker
我们先以React15这个例子进行修改配置。因为demo提供的webpack是4.x版本,所以需要使用到worker-loader
1.examples/react15/App.jsx
// examples/React/App.jsx
import Worker from './worker.worker.js';
componentDidMount() {
const worker = new Worker();
worker.postMessage('Hello from main thread');
worker.postMessage({
type: 'start'
});
}
2.examples/react15/worker.worker.js
self.addEventListener('message', async (e) => {
const { type } = e.data;
if (type === 'start') {
console.info('react15 - 获取到start消息');
}
});
export {};
3.examples/react15/webpack.config.js
module: {
rules: [
{
test: /\.worker\.js$/, // 增加这个rule
loader: 'worker-loader',
},
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-transform-react-jsx'],
},
},
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
4.启动react15
cd examples/react15
npm start
5.运行截图
可以看到单独运行的时候是不存在跨域,worker也可以正常使用
qiankun跨域
启动qiankun项目后访问react15,在devtool可以看到报错
上面的错误提示表明Web Worker模块无法正确实例化为一个构建函数
方式一:worker-loader - inline配置
1.examples/react15/webpack.config.js
module: {
rules: [
{
test: /\.worker.js$/,
use: {
loader: 'worker-loader',
options: {
inline:"fallback" // 修改inline配置
}
}
},
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-transform-react-jsx'],
},
},
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
react15中的jsx和worker.js都不需要修改
方式二:Blob URL
1.App.jsx
componentDidMount() {
const workerCode = `
self.onmessage = function(event) {
console.info(event.type)
};
`;
const blob = new Blob([workerCode], { type: 'application/javascript' });
const blobUrl = URL.createObjectURL(blob);
const worker = new Worker(blobUrl);
worker.postMessage({ type: 'start' });
}
方式三:workerPublicPath
webpack5新出的workerPublicPath可以将worker输出到指定位置(主应用目录下)
方式四:自定义Web Worker类解决跨域访问
自己写一个处理跨域访问的问题
1.cors-worker.js
export class CorsWorker extends window.Worker {
constructor(url, options) {
console.info('url', url)
const parsedUrl = new URL(url);
const webpackWorkerOrigin = `__webpack_worker_origin__ = ${JSON.stringify(
parsedUrl.origin
)};`;
const importScripts = `importScripts(${JSON.stringify(url.toString())});`;
const objectURL = URL.createObjectURL(
new Blob([`${webpackWorkerOrigin};\n${importScripts}`], {
type: "application/javascript",
})
);
super(objectURL, options);
URL.revokeObjectURL(objectURL);
}
}
代码解释:
-
代码功能:
- 这段代码的主要功能是创建一个特定的Web Worker(Worker)实例,通过处理跨域请求的方式来解决跨域问题。在这里,通过创建一个继承自
window.Worker的CorsWorker类,来自定义处理跨域请求的逻辑。
- 这段代码的主要功能是创建一个特定的Web Worker(Worker)实例,通过处理跨域请求的方式来解决跨域问题。在这里,通过创建一个继承自
-
构造函数:
- 构造函数接受两个参数
url和options,url是要加载的脚本地址,options是Worker的配置选项。 - 首先打印出目标url。
- 构造函数接受两个参数
-
解析URL:
- 使用
new URL(url)方法从传入的url字符串中解析出一个URL对象parsedUrl,含有协议、域名、路径等信息。
- 使用
-
生成Webpack Worker Origin 和 importScripts代码:
webpackWorkerOrigin定义了一个全局变量__webpack_worker_origin__,用于记录 Web Worker 的origin(域名),解决跨域问题。importScripts是一个函数,用于动态加载Web Worker的脚本。脚本地址从url参数中获取。
-
创建Blob URL:
- 使用
Blob对象创建一个包含webpackWorkerOrigin和importScripts的JavaScript脚本的 Blob。 - 使用
URL.createObjectURL(blob)方法将Blob对象转换为一个唯一的Blob URL,作为创建Worker实例时的脚本地址。
- 使用
-
调用父类构造函数:
- 使用
super(objectURL, options)调用父类Worker的构造函数,传入Blob URL和配置选项,创建新的Worker实例。
- 使用
-
释放Object URL:
- 使用
URL.revokeObjectURL(objectURL)方法释放之前创建的 Blob URL,以避免内存泄漏。
- 使用
2.App.jsx
useEffect(() => {
const workerUrl = new URL('./worker.worker.js', import.meta.url); // 使用动态加载
const worker = new CorsWorker(workerUrl);
worker.postMessage({
type: 'start',
});
}, [])
上面需要使用动态加载,也就是需要支持webpack5。webpack4的实现方式需要自行查找
文档
- 阮一峰 Web Worker使用教程:www.ruanyifeng.com/blog/2018/0…
- Web Worker文档:developer.mozilla.org/en-US/docs/…
- create-react-app:create-react-app.dev/docs/gettin…
- webpack5 - Web Workers:www.webpackjs.com/guides/web-…
- webpack4 - worker-loader:v4.webpack.docschina.org/loaders/wor…
- qiankun同源:qiankun.umijs.org/zh/cookbook…
- qiankun官网:qiankun.umijs.org/zh
- qiankun-Examples:www.npmjs.com/package/qia…
- workerPublicPath:webpack.js.org/configurati…
- URL:developer.mozilla.org/en-US/docs/…
其他
上面简单写了几个解决web worker跨域的问题,其实解决方式当然不止一个,只要解决同源规则的情况下也就解决了跨域问题呗(简直是废话)
如果还有其他解决方式,欢迎一起讨论