Webpack:Webworker 和 Web 代码之间共享代码的通用块?
Posted
技术标签:
【中文标题】Webpack:Webworker 和 Web 代码之间共享代码的通用块?【英文标题】:Webpack: Common chunks for code shared between Webworker and Web code? 【发布时间】:2018-08-27 11:52:36 【问题描述】:我的浏览器应用程序的网络和网络工作者部分之间共享了大量代码。
我如何告诉 webpack 将我的代码分成公共块,以保证结果 100% 工作?
在我告诉 webpack 生成公共块(它确实如此)之后,webworker 代码中断(在运行时失败)。即使我修复了微不足道的“未定义窗口”错误,工作人员也什么都不做。
我相信这与 webpack 的“target”选项有关,默认设置为“web”。但我需要“网络”目标,因为我没有纯粹的网络工作者代码。
我也不能做多个 webpack 配置,因为我不能用多个配置做公共块的事情......
我该怎么办?
如果有人感兴趣:我正在尝试为我的应用构建一个最小尺寸的构建,其中包括 monaco 编辑器(提供工作人员):
https://github.com/Microsoft/monaco-editor/blob/master/docs/integrate-esm.md
您可以在此处(页面底部)看到入口点由 1 个主入口文件 + 工人组成。
由于我正在使用重复的代码,目前至少浪费了 6 MB,并且由于这个问题目前无法拆分。浪费了很多流量。
有什么想法吗? :)
我的 webpack 4.1.1 配置基本上是:
module.exports = (env, options) =>
const mode = options.mode;
const isProduction = mode === 'production';
const outDir = isProduction ? 'build/release' : 'build/debug';
return
entry:
"app": "./src/main.tsx",
"editor.worker": 'monaco-editor/esm/vs/editor/editor.worker.js',
"ts.worker": 'monaco-editor/esm/vs/language/typescript/ts.worker.js'
,
output:
filename: "[name].bundle.js",
path: `$__dirname/$outDir`,
libraryTarget: 'umd',
globalObject: 'this',
library: 'app',
umdNamedDefine: true
,
node:
fs: 'empty'
,
devtool: isProduction ? undefined : "source-map",
resolve:
extensions: [".ts", ".tsx", ".js", ".json"],
alias:
"@components": path.resolve(__dirname, "src/components"),
"@lib": path.resolve(__dirname, "src/lib"),
"@common": path.resolve(__dirname, "src/common"),
"@redux": path.resolve(__dirname, "src/redux"),
"@services": path.resolve(__dirname, "src/services"),
"@translations": path.resolve(__dirname, "src/translations"),
"@serverApi": path.resolve(__dirname, "src/server-api")
,
optimization: isProduction ? undefined :
splitChunks:
minSize: 30000,
minChunks: 1,
name: true,
maxAsyncRequests: 100,
maxInitialRequests: 100,
cacheGroups:
default:
chunks: "all",
priority: -100,
test: (module) =>
const req = module.userRequest;
if (!req) return false;
return (!/node_modules[\\/]/.test(req));
,
,
vendor:
chunks: "all",
test: (module) =>
const req = module.userRequest;
if (!req) return false;
if (!/[\\/]node_modules[\\/]/.test(req)) return false;
return true;
,
priority: 100,
,
,
module:
rules: [...(isProduction ? [] : [
enforce: "pre", test: /\.js$/, loader: "source-map-loader",
exclude: [
/node_modules[\\/]monaco-editor/
]
]),
test: require.resolve('jquery.hotkeys'),
use: 'imports-loader?jQuery=jquery'
,
test: /\.tsx?$/,
loader: "awesome-typescript-loader",
options:
configFileName: 'src/tsconfig.json',
getCustomTransformers: () =>
return
before: [p => keysTransformer(p)]
;
,
test: /\.(css|sass|scss)$/,
use: extractSass.extract(
use: [
loader: 'css-loader',
options:
minimize: isProduction
,
loader: "postcss-loader",
options:
plugins: () => [autoprefixer(
browsers: [
'last 3 version',
'ie >= 10'
]
)]
,
loader: "sass-loader"
],
fallback: "style-loader"
)
,
test: /node_modules[\/\\]font-awesome/,
loader: 'file-loader',
options:
emitFile: false
,
test: not: [ test: /node_modules[\/\\]font-awesome/ ] ,
rules: [
test: or: [/icomoon\.svg$/, /fonts[\/\\]seti\.svg$/] ,
rules: [
loader: 'file-loader?mimetype=image/svg+xml' ,
]
,
test: not: [/icomoon\.svg$/, /fonts[\/\\]seti\.svg$/] ,
rules: [
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
use:
loader: 'svg-url-loader',
options:
,
]
,
test: /\.(png|jpg|gif)$/,
loader: 'url-loader'
,
test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?mimetype=application/font-woff" ,
test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?mimetype=application/font-woff" ,
test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?mimetype=application/octet-stream" ,
test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader" ,
]
,
]
,
plugins: [
new HardSourceWebpackPlugin(
cacheDirectory: '../node_modules/.cache/hard-source/[confighash]', configHash: function (webpackConfig)
return require('node-object-hash')( sort: false ).hash(Object.assign(, webpackConfig, devServer: false ));
,
environmentHash:
root: process.cwd(),
directories: [],
files: ['../package-lock.json'],
),
new webpack.ProvidePlugin(
"window.$": "jquery"
),
new CleanWebpackPlugin(outDir),
extractSass,
new htmlWebpackPlugin(
title: 'my title',
filename: 'index.html',
minify: isProduction ?
collapseWhitespace: true,
collapseInlineTagWhitespace: true,
removeComments: true,
removeRedundantAttributes: true
: false,
template: 'index_template.html',
excludeChunks: ['ts.worker', "editor.worker"]
),
new webpack.IgnorePlugin(/^((fs)|(path)|(os)|(crypto)|(source-map-support))$/, /vs[\\\/]language[\\\/]typescript[\\\/]lib/)
].concat(isProduction ? [new webpack.optimize.LimitChunkCountPlugin(
maxChunks: 1
)] : [])
;
【问题讨论】:
看起来像这个错误github.com/webpack/webpack/issues/6642 不幸的是,该线程中的修复(删除 HotModuleReplacementPlugin)不适用于我,没有启用该插件。我认为这更适用于我:github.com/webpack/webpack/issues/6525。那么 webpack 中缺少功能并且没有错误? 我想这会解决你的问题:***.com/a/49119917/1544364 我对工人不太了解,但这似乎也相关? github.com/webpack/webpack/issues/6472 手动更改全局对象 (window=self) 没有帮助。工人然后什么都不做(运行,但不做它的工作,没有错误)。我真的怀疑这是因为他们不是“网络+网络工作者”的组合目标。我想目标不仅仅是改变全局对象 【参考方案1】:您正在寻找通用library target,又名umd。
这会在所有模块定义下公开您的库,从而允许 它可以与 CommonJS、AMD 和作为全局变量一起使用。
要让你的 Webpack 包编译成 umd,你应该像这样配置 output
属性:
output:
filename: '[name].bundle.js',
libraryTarget: 'umd',
library: 'yourName',
umdNamedDefine: true,
,
Webpack 4 中有一个 issue,但如果您仍想使用它,可以通过在配置中添加 globalObject: 'this'
来解决此问题:
output:
filename: '[name].bundle.js',
libraryTarget: 'umd',
library: 'yourName',
umdNamedDefine: true,
globalObject: 'this'
,
【讨论】:
将不起作用 :( 没有控制台错误,因此我不必手动更改全局对象。但工作人员仍然无法工作(摩纳哥编辑器不再强调错误,如果我不这样做,它会拆分块)。在开始帖子中添加了我的 webpack 配置 很难说为什么它不起作用,尤其是如果你没有得到任何错误。你需要调试它:确保它把所有的块都带入了网络选项卡,然后放置一些断点,看看它失败的地方。 它根本不执行,这就是它不工作的原因。代码永远挂起,等待其他永远不会到来的数据,因为它从未请求或执行过。【参考方案2】:这是一个非常糟糕的答案,但我已经设法在工作线程和主线程之间共享块。
线索是这样的
globalObject
必须如上定义为(self || this)
:
output:
globalObject: "(self || this)"
-
Webpack 加载带有
document.createElement('script')
和document.head.appendChild()
序列的块,这在worker 上下文中不可用,但我们有self.importScript
。所以这只是一个“polyfiling”它的问题。
这是工作“polyfill”(直接来自地狱):
console.log("#faking document.createElement()");
(self as any).document =
createElement(elementName: string): any
console.log("#fake document.createElement", elementName);
return ;
,
head:
appendChild(element: any)
console.log("#fake document.head.appendChild", element);
try
console.log("loading", element.src);
importScripts(element.src);
element.onload(
target: element,
type: 'load'
)
catch(error)
element.onerror(
target: element,
type: 'error'
)
;
-
确保您的真实代码在安装polyfill 后被解析,通过使用动态导入,这将。假设正常的“worker main”在“./RealWorkerMain”中,那将是“main worker script”:
// so, typescript recognizes this as module
export let dummy = 2;
// insert "polyfill from hell" from here
import("./RealWorkerMain").then(( init ) =>
init();
);
-
您可能需要在 webpack 中配置动态
import
,因为文档中的 here 也不容易,this answer 非常有帮助。
【讨论】:
这是唯一有意义的答案,但天哪,这太丑了:( @Zbigniew Zagórski 非常感谢你拯救了我的一天!关于 4. 我不需要插件@babel/plugin-syntax-dynamic-import
但这个 @babel/plugin-transform-runtime
以避免 ReferenceError: regeneratorRuntime is not defined
错误。 (使用 babel 7.11.x)【参考方案3】:
编辑:好吧,我根据大家的知识编写了一个 webpack 插件。
https://www.npmjs.com/package/worker-injector-generator-plugin
您可以忽略下面的内容,使用插件,或者如果您想了解插件是如何产生的并自己动手(这样您就不必依赖我的代码),您可以继续阅读。
================================================ ======
好吧,经过大量研究,我找到了这个解决方案,您需要创建一个注入文件,对于一个简单的案例,您需要 https://github.com/webpack-contrib/copy-webpack-plugin,因为它运行良好......所以假设您的设置是:
entry:
"worker": ["./src/worker.ts"],
"app": ["./src/index.tsx"],
,
你已经设置了你的常用插件,让我们说这个例子。
optimization:
splitChunks:
cacheGroups:
commons:
name: 'commons',
chunks: 'initial',
minChunks: 2
,
,
您现在需要创建一个注入“Vanilla JS”,它可能看起来像这样:
var base = location.protocol + "//" + location.host;
window = self;
self.importScripts(base + "/resources/commons.js", base + "/resources/worker.js");
然后你可以在你的工人旁边添加它,比如src/worker-injector.js
并使用复制插件
new CopyPlugin([
from: "./src/worker-injector.js",
to: path.resolve(__dirname, 'dist/[name].js'),
,
]),
确保您的输出设置为 umd。
output:
filename: '[name].js',
path: path.resolve(__dirname, 'dist'),
libraryTarget: "umd",
globalObject: "this",
这不过是一种 hack,但允许您按原样使用所有东西,而不必做一些夸张的事情。
如果您需要散列(因此复制插件不起作用)功能,您必须生成此文件(而不是复制它),请参阅此。
How to inject Webpack build hash to application code
为此,您必须创建自己的插件,该插件将生成 vanilla js 文件并考虑其本身的哈希值,您将传递要一起加载的 url,并将哈希附加到它们,这是更棘手,但如果你需要哈希,用你的自定义插件实现应该很简单。
遗憾的是,目前似乎没有其他方法。
我可能自己编写插件来解决问题并创建注入器,但我确实认为这更像是一种 hack,不应该成为公认的解决方案。
我以后可能会去写注入器插件,它可能是这样的:
类似new WorkerInjectorGeneratorPlugin(name: "worker.[hash].injector.js", importScripts: ["urlToLoad.[hash].js", secondURLToLoad.[hash].js"])
参考这个问题以供参考,以及为什么它应该在 webpack 中修复,而 WorkerInjectorGeneratorPlugin 之类的东西几乎是一个 hack 插件。
https://github.com/webpack/webpack/issues/6472
【讨论】:
我的天啊,用 Monaco Editor 和 Webpack 和 Web Workers 数小时拉扯头发……终于找到你的答案和 npm 包……现在一切正常! @OlivierLance :) 很高兴它有帮助,我拉了几天头发;尽管如此,这似乎并不是开发人员遇到的常见问题,因为它似乎仍未解决; webpack 应该能够为 webworker 目标提供一些 importScripts 和异步功能,以便嵌入代码;目前我制作它的方式让你依赖一个额外的文件(注入器);这基本上与普通网站通过获取源所做的事情相同;这个逻辑需要直接在 webpack 生成的文件中实现。但我们会等待 :) 是的,我实际上想尝试另一种方法,使用一个类似于 MonacoEditorWebpackPlugin 的插件,通过使用似乎在编译时注入的内部 WebWorkerTemplatePlugin。我对 Webpack 内部不是很流利,所以这可能不可行,但我会尝试 :)【参考方案4】:在 webpack 5 中引入了 Native Worker 支持。使用此功能,您可以使用简单的 splitChunk
选项在应用程序代码和 webwoker 之间共享块
optimization:
splitChunks:
chunks: 'all',
minChunks: 2,
,
,
当将资产的新 URL 与新 Worker/new SharedWorker/navigator.serviceWorker.register 组合时,webpack 将自动为 web worker 创建一个新入口点。
new Worker(new URL("./worker.js", import.meta.url))
选择语法是为了允许在没有捆绑程序的情况下运行代码。此语法也可用于浏览器的原生 ECMAScript 模块。
https://webpack.js.org/blog/2020-10-10-webpack-5-release/#native-worker-support
【讨论】:
我不明白这是如何工作的。 Webpack 5 确实会检测到 webworker 并生成它。但是 Webpack 5 不会在主应用程序和工作程序之间共享模块,而是在捆绑代码中,您在主应用程序中有 N 个模块的副本,在工作程序中有 N 个模块的副本。以下是澄清github.com/webpack/webpack/issues/6472#issuecomment-797078303以上是关于Webpack:Webworker 和 Web 代码之间共享代码的通用块?的主要内容,如果未能解决你的问题,请参考以下文章
WebPack 5 与使用 React/Typescript 的 Web 工作者
Web Worker - Jest - 无法在模块外使用“import.meta”