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”

Web Worker 和 Server-Sent Events

浏览器中的 Web Worker 支持

Web Worker 案例

web worker原理