简体   繁体   English

Webpack:Webworker 和 Web 代码之间共享代码的通用块?

[英]Webpack: Common chunks for code shared between Webworker and Web code?

I have lots of code shared between web and web worker parts of my browser app.我在我的浏览器应用程序的 Web 和 Web Worker 部分之间共享了很多代码。

How can I tell webpack to split my code up into common chunks so that the result is garanteed to work 100%?我如何告诉 webpack 将我的代码拆分成公共块,以便保证结果 100% 有效?

The webworker code breaks (fails at runtime) after I tell webpack to generate the common chunks (which it does).在我告诉 webpack 生成公共块(它确实如此)之后,webworker 代码中断(在运行时失败)。 Even after I fix the trivial "window not defined" error the worker just does nothing.即使在我修复了微不足道的“未定义窗口”错误之后,工作人员也什么都不做。

I believe this has to do with the webpack "target" option, which per default is set to "web".我相信这与 webpack 的“target”选项有关,默认设置为“web”。 But I need "web" target because I don't have purely webworker code.但我需要“网络”目标,因为我没有纯粹的网络工作者代码。

I also cannot do multiple webpack configs because I cannot do the common chunks thing with multiple configs...我也不能做多个 webpack 配置,因为我不能用多个配置做公共块的事情......

What should I do?我应该怎么办?

If anybody is interested: I am trying build a minimal sized build for my app which includes the monaco editor (which provides the workers):如果有人感兴趣:我正在尝试为我的应用程序构建一个最小尺寸的构建,其中包括 monaco 编辑器(它提供工作人员):

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

You can see here (at the bottom of the page) that the entry points consist of 1 main entry file + the workers.您可以在此处(页面底部)看到入口点由 1 个主入口文件 + 工作人员组成。

Currently at least 6 MB is wasted because of duplicate code I am using and currently can not be split up because of this problem.目前至少有 6 MB 被浪费,因为我正在使用重复代码,并且由于这个问题目前无法拆分。 That is a lot of wasted traffic.那是很多浪费的流量。

Any ideas?有任何想法吗? :) :)

my webpack 4.1.1 config is basically:我的 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
        })] : [])
    }
};

EDIT: Alright I wrote a webpack plugin based on everyone's knowledge just put together.编辑:好吧,我根据大家的知识编写了一个 webpack 插件。

https://www.npmjs.com/package/worker-injector-generator-plugin https://www.npmjs.com/package/worker-injector-generator-plugin

You can ignore the content below, and use the plugin or if you want to understand how the plugin came to be and do it by hand yourself (so you don't have to depend on my code) you can keep reading.你可以忽略下面的内容,使用插件,或者如果你想了解插件是怎么来的并自己动手做(这样你就不必依赖我的代码)你可以继续阅读。

===================================================== ================================================ ===

Alright after so much researching I figured out this solution, you need to create an injection file, for a simple case you need the https://github.com/webpack-contrib/copy-webpack-plugin as it works pretty well... so let's say your setup is:好吧,经过这么多研究我想出了这个解决方案,你需要创建一个注入文件,对于一个简单的案例你需要https://github.com/webpack-contrib/copy-webpack-plugin因为它工作得很好.. . 假设您的设置是:

entry: {
    "worker": ["./src/worker.ts"],
    "app": ["./src/index.tsx"],
  },

And you have setup your common plugins already let's say this example.你已经设置了你的常用插件让我们说这个例子。

optimization: {
    splitChunks: {
      cacheGroups: {
        commons: {
          name: 'commons',
          chunks: 'initial',
          minChunks: 2
        },
      }
    }
  },

You need to now create an injection "Vanilla JS" which might look like this:您现在需要创建一个注入“Vanilla JS”,它可能看起来像这样:

var base = location.protocol + "//" + location.host;
window = self;

self.importScripts(base + "/resources/commons.js", base + "/resources/worker.js");

Then you can add that alongside your worker, say in src/worker-injector.js然后你可以将它添加到你的工作人员旁边,比如在src/worker-injector.js

And using the copy plugin并使用复制插件

new CopyPlugin([
      {
        from: "./src/worker-injector.js",
        to: path.resolve(__dirname, 'dist/[name].js'),
      },
    ]),

Make sure your output is set to umd.确保您的输出设置为 umd。

output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist'),
    libraryTarget: "umd",
    globalObject: "this",
  }

This is nothing but a hack, but allows you to use everything as it is without having to do something as overblown.这只不过是一个 hack,但允许您按原样使用所有内容,而不必做一些夸张的事情。

If you need hashing (so that copy plugin doesn't work) functionality you would have to generate this file (rather than copying it), refer to this.如果您需要散列(以便复制插件不起作用)功能,则必须生成此文件(而不是复制它),请参阅此文件。

How to inject Webpack build hash to application code 如何将 Webpack 构建哈希注入应用程序代码

For that you would have to create your own plugin which would generate the vanilla js file and consider the hash within itself, you would pass the urls that you want to load together, and it would attach the hash to them, this is more tricky but if you need hashes it should be straightforward to implement with your custom plugin.为此,您必须创建自己的插件来生成 vanilla js 文件并在其内部考虑哈希,您将传递要一起加载的 url,并将哈希附加到它们,这更棘手但是如果您需要哈希,那么使用您的自定义插件应该很容易实现。

Sadly so far there doesn't seem to be other way.可悲的是,到目前为止似乎没有其他方法。

I could probably write the plugin myself that would do the workaround and create the injectors, but I do think this is more of a hack and shouldn't be the accepted solution.我可能可以自己编写插件来解决问题并创建注入器,但我确实认为这更像是一种 hack,不应该成为公认的解决方案。

I might later go around and write the injector plugin, it could be something as:稍后我可能会四处编写注入器插件,它可能是这样的:

something like new WorkerInjectorGeneratorPlugin({name: "worker.[hash].injector.js", importScripts: ["urlToLoad.[hash].js", secondURLToLoad.[hash].js"])new WorkerInjectorGeneratorPlugin({name: "worker.[hash].injector.js", importScripts: ["urlToLoad.[hash].js", secondURLToLoad.[hash].js"])

refer to this issues for reference, and why it should be fixed within webpack and something as a WorkerInjectorGeneratorPlugin would be pretty much a hack plugin.请参阅此问题以供参考,以及为什么它应该在 webpack 中修复,而作为 WorkerInjectorGeneratorPlugin 的东西几乎是一个 hack 插件。

https://github.com/webpack/webpack/issues/6472 https://github.com/webpack/webpack/issues/6472

This is really bad answer, but i've managed to share chunks between workers and main thread.这是一个非常糟糕的答案,但我已经设法在工作人员和主线程之间共享块。

The clue is that线索是

  1. globalObject has to be defined as above to (self || this) : globalObject必须像上面那样定义为(self || this)
output: {
    globalObject: "(self || this)"
}
  1. Webpack loads chunks with document.createElement('script') and document.head.appendChild() sequence, which is not available in worker context, but we have self.importScript . Webpack 使用document.createElement('script')document.head.appendChild()序列加载块,这在 worker 上下文中不可用,但我们有self.importScript So it's just a matter of "polyfiling" it.所以这只是一个“polyfiling”的问题。 Here is working "polyfill" (straight from the hell):这是工作“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. Ensure, that your real code is resolved after polyfill is installed, by using dynamic import, which will.确保在安装 polyfill通过使用动态导入解析您的真实代码,这将。 Assuming, that normal "worker main" is in "./RealWorkerMain", that would be "main worker script":假设,正常的“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();
});
  1. You may need to configure dynamic import in webpack, as documented here is not easy too, this answer was very helpful.您可能需要在 webpack 中配置动态import ,因为 此处记录也不容易,这个答案非常有帮助。

You're looking for universal library target , aka umd .您正在寻找 universal library target ,又名umd

This exposes your library under all the module definitions, allowing it to work with CommonJS, AMD and as global variable.这会在所有模块定义下公开你的库,允许它与 CommonJS、AMD 一起工作并作为全局变量。

To make your Webpack bundle compile to umd you should configure output property like this:要使您的 Webpack 包编译为umd ,您应该像这样配置output属性:

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

There is an issue with Webpack 4, but if you still want to use it, you can workaround the issue by adding globalObject: 'this' to the configuration: Webpack 4 有一个问题,但如果你仍然想使用它,你可以通过在配置中添加globalObject: 'this'来解决这个问题:

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

Native Worker support is introduced in webpack 5. With this feature, you can share chunks between app code and webwokers with simple splitChunk options like webpack 5 中引入了 Native Worker 支持。有了这个特性,你可以使用简单的splitChunk选项在应用程序代码和 webwokers 之间共享块,比如

{
    optimization: {
        splitChunks: {
            chunks: 'all',
            minChunks: 2,
        },
    },
}

When combining new URL for assets with new Worker/new SharedWorker/navigator.serviceWorker.register webpack will automatically create a new entrypoint for a web worker.当将资产的新 URL 与 new Worker/new SharedWorker/navigator.serviceWorker.register 组合时,webpack 将自动为 web worker 创建一个新的入口点。

new Worker(new URL("./worker.js", import.meta.url))

The syntax was chosen to allow running code without bundler too.选择的语法也允许在没有捆绑器的情况下运行代码。 This syntax is also available in native ECMAScript modules in the browser.此语法也可用于浏览器中的原生 ECMAScript 模块。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM