如何在有角度的Web Worker内导入节点模块?

时间:2019-09-03 14:44:57

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

我尝试在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组件中正常运行,但在Web Worker中,我会收到一条带有以下消息的编译错误,例如

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测试了相同的导入,该导入使用了Web工作程序,并且可以正常工作,但是此示例使用了无角的纯Java脚本。

1 个答案:

答案 0 :(得分:9)

1。 TypeScript错误

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

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

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

因此,我们通过将node显式添加到types数组来解决此问题:

{
  "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(网络,节点等)的概念。 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');
});

Webpack将目标electron-renderernode类似。对我们来说特别有趣:默认情况下,它会添加NodeTargetPlugin

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

为什么对工人不起作用

使用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 > angular-electron > architect > build

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

serve重复相同的操作。

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

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