如何在有角度的 Web worker 中导入节点模块?



我尝试在 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 上查看项目的构建日志。

注意:

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

2(我用使用Web工人的最小电子种子测试了相同的导入并且它有效,但是此示例使用没有角度的纯java脚本。

1. 打字稿错误

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

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

指定types将关闭自动包含@types包。在这种情况下这是一个问题,因为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 有目标的概念(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');
});

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

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

为什么它对工人不起作用

工作线程使用WorkerPlugin单独编译。在他们的文档中,他们指出:

默认情况下,WorkerPlugin在捆绑工作线程代码时不会运行任何已配置的 Webpack 插件 - 这样可以避免运行html-webpack-plugin twice之类的东西。对于需要将插件应用于工作线程代码的情况,请使用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重复相同的操作。

现在,在与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

最新更新