【问题标题】:How to import a node module inside an angular web worker?如何在 Angular Web Worker 中导入节点模块?
【发布时间】:2019-10-31 07:13:32
【问题描述】:

我尝试在 Angular 8 web worker 中导入节点模块,但得到一个编译错误“找不到模块”。有谁知道如何解决这个问题?

我使用ng generate web-worker app 在我的电子项目中创建了一个新工作人员,如上述 ng 文档中所述。

一切正常,直到我添加一些导入,如 pathfs-extra 例如:

/// <reference lib="webworker" />    
import * as path from 'path';

addEventListener('message', ({ data }) => {    
  console.log(path.resolve('/'))
  const response = `worker response to ${data}`;
  postMessage(response);
});

此导入在任何其他 ts 组件中都可以正常工作,但在网络工作者内部,我收到一条带有此消息的编译 错误,例如

Error: app/app.worker.ts:3:23 - error TS2307: Cannot find module 'path'.

我该如何解决这个问题?也许我需要在生成的tsconfig.worker.json 中添加一些额外的参数?

重现错误,运行:

$ git clone https://github.com/hoefling/stackoverflow-57774039
$ cd stackoverflow-57774039
$ yarn build

或者在 Travis 上查看项目的 build log

注意:

1) 我只发现this 是一个类似的问题,但答案只处理自定义模块。

2) 我使用minimal electron seed 测试了相同的导入,它使用了网络工作者并且它工作正常,但是这个示例使用了没有角度的纯 java 脚本。

【问题讨论】:

    标签: node.js angular typescript web-worker angular8


    【解决方案1】:

    1。打字稿错误

    您已经注意到第一个错误是 TypeScript 错误。查看tsconfig.worker.json 我发现它将types 设置为一个空数组:

    {
      "compilerOptions": {
        "types": [],
        // ...
      }
      // ...
    }
    

    指定types 会关闭automatic inclusion of @types packages。在这种情况下这是一个问题,因为path@types/node 中有其类型定义。

    所以让我们通过将node 显式添加到types 数组来解决这个问题:

    {
      "compilerOptions": {
        "types": [
            "node"
        ],
        // ...
      }
      // ...
    }
    

    这修复了 TypeScript 错误,但是尝试再次构建时,我们会遇到非常相似的错误。这次直接来自 Webpack。

    2。网页包错误

    ERROR in ./src/app/app.worker.ts (./node_modules/worker-plugin/dist/loader.js!./src/app/app.worker.ts)
    Module build failed (from ./node_modules/worker-plugin/dist/loader.js):
    ModuleNotFoundError: Module not found: Error: Can't resolve 'path' in './src/app'
    

    要弄清楚这一点,我们需要深入挖掘......

    为什么它在其他任何地方都有效

    首先,重要的是要了解为什么导入 path 在所有其他模块中都有效。 Webpack 有targets(web、node 等)的概念。 Webpack 使用这个目标来决定使用哪些默认选项和插件。

    通常使用@angular-devkit/build-angular:browser 的Angular 应用程序的目标是web。但是,在您的情况下,postinstall:electron 脚本实际上修补了node_modules 以改变它:

    postinstall.js(为简洁起见省略部分内容)

    const f_angular = 'node_modules/@angular-devkit/build-angular/src/angular-cli-files/models/webpack-configs/browser.js';
    
    fs.readFile(f_angular, 'utf8', function (err, data) {
      var result = data.replace(/target: "electron-renderer",/g, '');
      var result = result.replace(/target: "web",/g, '');
      var result = result.replace(/return \{/g, 'return {target: "electron-renderer",');
    
      fs.writeFile(f_angular, result, 'utf8');
    });
    

    目标electron-renderer 被Webpack 以与node 类似的方式处理。对我们来说特别有趣:它默认添加了NodeTargetPlugin

    你想知道那个插件是做什么的?它将所有已知的 Node.js 内置模块添加为 externals。在构建应用程序时,Webpack 不会尝试捆绑外部。相反,它们在运行时使用require 解决。这就是导入 path 的原因,即使它没有作为 Webpack 已知的模块安装。

    为什么它对工人不起作用

    worker 使用WorkerPlugin 单独编译。他们在documentation 中声明:

    默认情况下,WorkerPlugin 在捆绑工作代码时不会运行您配置的任何 Webpack 插件 - 这可以避免运行像 html-webpack-plugin twice 这样的东西。对于需要将插件应用到 Worker 代码的情况,请使用 plugins 选项。

    查看WorkerPlugin@angular-devkit 深处的用法,我们看到以下内容:

    @angular-devkit/src/angular-cli-files/models/webpack-configs/worker.js(简体)

    new WorkerPlugin({
        globalObject: false,
        plugins: [
            getTypescriptWorkerPlugin(wco, workerTsConfigPath)
        ],
    })
    

    正如我们所见,它使用plugins 选项,但仅适用于负责 TypeScript 编译的单个插件。这样,由 Webpack 配置的默认插件,包括 NodeTargetPlugin 会丢失并且不会用于工作人员。

    解决方案

    要解决这个问题,我们必须修改 Webpack 配置。为此,我们将使用@angular-builders/custom-webpack。继续安装该软件包。

    接下来,打开angular.json并更新projects &gt; angular-electron &gt; architect &gt; build

    "build": {
      "builder": "@angular-builders/custom-webpack:browser",
      "options": {
        "customWebpackConfig": {
          "path": "./extra-webpack.config.js"
        }
        // existing options
      }
    }
    

    serve重复同样的操作。

    现在,在与angular.json 相同的目录中创建extra-webpack.config.js

    const WorkerPlugin = require('worker-plugin');
    const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin');
    
    module.exports = (config, options) => {
      let workerPlugin = config.plugins.find(p => p instanceof WorkerPlugin);
      if (workerPlugin) {
        workerPlugin.options.plugins.push(new NodeTargetPlugin());
      }
      return config;
    };
    

    该文件导出一个函数,该函数将由 @angular-builders/custom-webpack 使用现有的 Webpack 配置对象调用。然后我们可以在所有plugins 中搜索WorkerPlugin 的实例并修补其选项添加NodeTargetPlugin

    【讨论】:

    • 感谢您的详细回答。确实,编译错误消失了,应用程序现在开始了,但“路径”导入不起作用,并以Uncaught ReferenceError: require is not defined 失败。我搜索了错误,但找不到任何合适的解决方案。我应该添加任何进一步的插件吗?有什么想法吗?
    • 您需要在worker中启用节点集成。在您创建浏览器窗口的main.ts 中,在webPreferences 下添加nodeIntegrationInWorker: true
    • 太棒了,感谢您的解决方案和对问题原因的深入解释!
    猜你喜欢
    • 2020-01-06
    • 2018-08-02
    • 1970-01-01
    • 2019-07-28
    • 2020-07-20
    • 2018-12-28
    • 2019-10-30
    • 2017-05-19
    • 2012-11-03
    相关资源
    最近更新 更多