Webpack:Webworker和Web代码共享的常见块?

5
我有很多代码在我的浏览器应用程序的Web和Web Worker部分之间共享。
我该如何告诉webpack将我的代码拆分成常见的块,以确保结果能够100%工作?
当我告诉webpack生成常见块时,WebWorker代码会中断(在运行时失败)。即使我修复了微不足道的“未定义窗口”错误,Worker也什么都不做。
我认为这与webpack的“target”选项有关,该选项默认设置为“web”。但是我需要“web”目标,因为我没有纯WebWorker代码。
我也不能使用多个webpack配置,因为我无法使用多个配置进行常见块操作...
我该怎么办?
如果有人感兴趣:我正在尝试构建一个包含Monaco编辑器(提供工作程序)的最小大小的应用程序构建。

https://github.com/Microsoft/monaco-editor/blob/master/docs/integrate-esm.md

你可以在页面底部看到,入口点由1个主要的入口文件和工作者组成。
目前由于我正在使用重复的代码,并且由于这个问题目前无法拆分,至少浪费了6MB。这是很多浪费的流量。
有什么想法吗? :)
我的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
        })] : [])
    }
};

看起来像这个 bug:https://github.com/webpack/webpack/issues/6642 - lukas-reineke
不幸的是,那个帖子中的解决方法(删除HotModuleReplacementPlugin)对我不适用,因为我没有启用该插件。我认为这更适用于我:https://github.com/webpack/webpack/issues/6525。所以这是webpack中缺少的功能,而不是bug? - cdbeelala89
我认为这个链接可以解决你的问题:https://dev59.com/AVUM5IYBdhLWcg3wQObw#49119917 - JeB
我对工作线程不是很了解,但这似乎也相关?https://github.com/webpack/webpack/issues/6472 - lukas-reineke
手动更改全局对象(window=self)并没有帮助。工作线程什么也不做(运行,但不执行其工作,也没有错误)。我真的怀疑这是因为没有“web+webworker”组合目标。目标不仅仅是改变全局对象,我想。 - cdbeelala89
目标:'umd',globalObject:'this'。 - JeB
4个回答

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方法,但可以让你使用现有的所有内容而无需进行繁琐的操作。如果你需要哈希功能(使复制插件失效),则需要生成此文件(而非复制它),请参阅如何将Webpack构建哈希注入应用程序代码。为此,你需要创建自己的插件来生成原始js文件并在其中考虑哈希值,传递要一起加载的URL,并将哈希附加到它们上面。这更加棘手,但如果需要哈希,则应该很容易通过自定义插件实现。可悲的是,目前似乎没有其他方法。我可能会稍后编写注入器插件,例如new WorkerInjectorGeneratorPlugin({name: "worker.[hash].injector.js", importScripts: ["urlToLoad.[hash].js", secondURLToLoad.[hash].js"]),请参考此问题以获得参考,并了解为什么应该在Webpack内部修复此问题,而类似WorkerInjectorGeneratorPlugin的东西实际上只是一个hack插件。https://github.com/webpack/webpack/issues/6472

1
天啊,我花了好几个小时在Monaco编辑器、Webpack和Web Workers上纠结...最终找到了你的答案和npm包...现在一切都正常了!<3 - Olivier Lance
1
@OlivierLance :) 很高兴能帮到你,我为此苦恼了好几天;尽管这似乎不是开发人员经常遇到的问题,但由于它仍然没有解决,webpack应该能够为webworker目标提供一些importScripts和异步功能,以便代码得到嵌入;目前我所做的方式是让你依赖于一个额外的文件(注入器);基本上它所做的事情与普通网站通过获取源码所做的事情相同;这个逻辑需要直接实现在webpack生成的文件中。但我们会等待 :) - Onza
是的,我实际上想尝试另一种方法,使用一个插件来充当 MonacoEditorWebpackPlugin 的功能,该插件利用内部的 WebWorkerTemplatePlugin,在编译时似乎被注入。我对 Webpack 内部并不是很熟悉,所以这可能行不通,但我会尝试 :) - Olivier Lance

2

这并不是一个好的答案,但我成功地在工作线程和主线程之间共享了块。

关键是

  1. globalObject必须像上面那样定义为(self || this):
output: {
    globalObject: "(self || this)"
}
  1. Webpack使用document.createElement('script')document.head.appendChild()序列加载块,但在worker上下文中不可用,但我们有self.importScript。所以这只是一个“polyfill”的问题。 以下是有效的“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'
                })
            }
        }
    }
};
  1. 请使用动态导入确保您的真实代码在polyfill安装之后被解析。假设正常的“工作主体”在“./RealWorkerMain”中,那么它将成为“主要工作者脚本”:
// so, typescript recognizes this as module
export let dummy = 2;

// insert "polyfill from hell" from here

import("./RealWorkerMain").then(({ init }) => {
    init();
});
  1. 您可能需要在webpack中配置动态import,如此处所述,这并不容易,这个答案非常有帮助。

这是唯一有意义的答案,但天哪,它太丑了 :( - Onza
@Zbigniew Zagórski,非常感谢您拯救了我的一天!关于第四个问题,我不需要使用插件 @babel/plugin-syntax-dynamic-import,而是使用 @babel/plugin-transform-runtime 来避免 ReferenceError: regeneratorRuntime is not defined 错误。(使用 babel 7.11.x) - Elvynia

1
你正在寻找通用的库目标,也就是umd

这将在所有模块定义下公开你的库,使其能够与CommonJS、AMD和全局变量一起使用。

为了使你的Webpack捆绑包编译成umd,你应该像这样配置output属性:
output: {
    filename: '[name].bundle.js',
    libraryTarget: 'umd',
    library: 'yourName',
    umdNamedDefine: true,
},

Webpack 4存在一个问题,但如果您仍然想使用它,可以通过将globalObject:'this'添加到配置中来解决该问题:

output: {
    filename: '[name].bundle.js',
    libraryTarget: 'umd',
    library: 'yourName',
    umdNamedDefine: true,
    globalObject: 'this'
},

无法工作 :( 没有控制台错误,因此我不必手动更改全局对象。但是工作线程仍然无法正常工作(如果我不拆分块,则 Monaco 编辑器不会再下划线错误)。在起始帖子中添加了我的 webpack 配置。 - cdbeelala89
很难说为什么它不起作用,特别是如果你没有收到任何错误信息。你需要进行调试:确保它在网络选项卡中获取所有块,然后设置一些断点并查看它失败的位置。 - JeB
它根本不执行,这就是为什么它不起作用。代码永远挂起,等待其他数据,但这些数据永远不会到来,因为它从未请求或执行过。 - Onza

0
webpack 5 引入了 Native Worker 支持。通过这个特性,你可以使用简单的 splitChunk 选项在应用代码和 Web Workers 之间共享 chunks。
{
    optimization: {
        splitChunks: {
            chunks: 'all',
            minChunks: 2,
        },
    },
}

当将新的URL与新的Worker / new SharedWorker / navigator.serviceWorker.register结合使用时,webpack将自动为Web Worker创建一个新的入口点。
选择这种语法是为了允许在没有打包工具的情况下运行代码。这种语法也适用于浏览器中的原生ECMAScript模块。

https://webpack.js.org/blog/2020-10-10-webpack-5-release/#native-worker-support


我不明白这是如何工作的。Webpack 5 可以检测到 webworker 并生成它,但是 Webpack 5 不会在主应用程序和 worker 之间共享模块,而是在捆绑代码中,你需要在主应用程序中复制 N 个模块的副本,在 worker 中也需要复制 N 个模块的副本。这里有一些澄清:https://github.com/webpack/webpack/issues/6472#issuecomment-797078303 - terpimost

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接