我尝试在Angular 8 web worker内导入节点模块,但出现编译错误“找不到模块”。有人知道如何解决吗?
我在电子项目中使用ng generate web-worker app
创建了一个新工作人员,如上述ng文档中所述。
一切正常,直到我添加诸如path
或fs-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脚本。
答案 0 :(得分:9)
您已经注意到,第一个错误是TypeScript错误。看着tsconfig.worker.json
,我发现它会将types
设置为一个空数组:
{
"compilerOptions": {
"types": [],
// ...
}
// ...
}
指定types
将关闭automatic inclusion of @types
packages。在这种情况下,这是一个问题,因为path
在@types/node
中有其类型定义。
因此,我们通过将node
显式添加到types
数组来解决此问题:
{
"compilerOptions": {
"types": [
"node"
],
// ...
}
// ...
}
这修复了TypeScript错误,但是尝试再次构建时,我们遇到了一个非常类似的错误。这次直接从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-renderer
与node
类似。对我们来说特别有趣:默认情况下,它会添加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
。