如何在 angular 网络工作者中导入节点模块?

How to import a node module inside an angular web worker?

我尝试在 Angular 8 web worker 中导入节点模块,但出现编译错误 'Cannot find module'。有人知道怎么解决吗?

我用 ng generate web-worker app 在我的 electron 项目中创建了一个新的 worker,就像上面提到的 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 和此消息,例如

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

我该如何解决这个问题?也许我在生成的 tsconfig.worker.json?

中需要一些额外的参数

重现错误,运行:

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

或在 Travis 上查看项目的 build log

注:

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

2) 我用 minimal electron seed 测试了相同的导入,它使用 web worker 并且它有效,但是这个例子使用没有 angular.

的普通 java 脚本

1。打字稿错误

如您所见,第一个错误是 TypeScript 错误。查看 tsconfig.worker.json 我发现它将 types 设置为一个空数组:

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

指定 types 将关闭 automatic inclusion of @types packages。在这种情况下这是一个问题,因为 path@types/node.

中有其类型定义

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

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

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

2。 Webpack 错误

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 is treated by Webpack similarily to node. Especially interesting for us: It adds the NodeTargetPlugin

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

为什么它对工人不起作用

worker 是使用 WorkerPlugin. In their documentation 他们声明的单独编译的:

By default, WorkerPlugin doesn't run any of your configured Webpack plugins when bundling worker code - this avoids running things like html-webpack-plugin twice. For cases where it's necessary to apply a plugin to Worker code, use the plugins option.

查看 @angular-devkitWorkerPlugin 的用法,我们看到以下内容:

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

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

正如我们所见,它使用了 plugins 选项,但仅针对负责 TypeScript 编译的单个插件。这样,由 Webpack 配置的默认插件,包括 NodeTargetPlugin 就会丢失,不会被 worker 使用。

解决方案

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

下一步,打开angular.json并更新projects > angular-electron > architect > 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.