无法将 Angular 的测试与 HTML 模板正确捆绑
Cannot properly bundle tests for Angular with HTML templates
在我们当前的项目中,我们构建了一个自定义混合 NGJS/NGX 应用程序作为完全迁移到 Angular 的中间步骤。
混合版本将每个 Angular 版本放在单独的目录中。它们在两个方向上互通——我们在 AngularJS 中包含降级的 Angular 文件,在 Angular 中包含升级的 AngularJS 文件。
构建工作完全符合预期(所有文件都正确输出),但是,我们无法使测试工作(Karma + Webpack)。
它们在一个非常具体的点上失败了——当你包含需要任何类型的 HTML 的 AngularJS 文件时,例如import Tpl from '../templates/my-template.tpl.html
.
错误如下:
TypeError: Cannot read property 'module' of undefined
at eval (webpack-internal:///./src/ngjs/Account/templates/account.password.edit.tpl.html:4:16)
at Module../src/ngjs/Account/templates/account.password.edit.tpl.html (http://localhost:9879/absolute/path-to-repo/karma.bundle.js?b7d10f4cb57d9b36c6b149128e3c9810b2901be0:7594:1)
at __webpack_require__ (http://localhost:9879/absolute/path-to-repo/karma.bundle.js?b7d10f4cb57d9b36c6b149128e3c9810b2901be0:20:30)
at eval (webpack-internal:///./src/ngjs/Core/services/user.service.ts:11:64)
at Object../src/ngjs/Core/services/user.service.ts (http://localhost:9879/absolute/path-to-repo/karma.bundle.js?b7d10f4cb57d9b36c6b149128e3c9810b2901be0:7610:1)
at __webpack_require__ (http://localhost:9879/absolute/path-to-repo/karma.bundle.js?b7d10f4cb57d9b36c6b149128e3c9810b2901be0:20:30)
at eval (webpack-internal:///./src/ngx/core/components/display-groups-dropdown/display-groups-dropdown.component.ts:14:22)
at Object../src/ngx/core/components/display-groups-dropdown/display-groups-dropdown.component.ts (http://localhost:9879/absolute/path-to-repo/karma.bundle.js?b7d10f4cb57d9b36c6b149128e3c9810b2901be0:8092:1)
at __webpack_require__ (http://localhost:9879/absolute/path-to-repo/karma.bundle.js?b7d10f4cb57d9b36c6b149128e3c9810b2901be0:20:30)
at eval (webpack-internal:///./src/ngx/core/components/display-groups-dropdown/display-groups-dropdown.component.spec.ts:4:43)
(ngx
是应用程序的 Angular 部分,ngjs
是 AngularJS)。
我们的 Karma 配置:
export default (config) => {
config.set({
basePath: 'src',
frameworks: ['jasmine'],
plugins: [
karmaJasminePlugin,
karmaChromeLauncherPlugin,
karmaWebpackPlugin,
tsLoaderPlugin,
karmaMochaReporterPlugin,
karmaCoverageIstanbulReporterPlugin,
],
preprocessors: {
'../karma.bundle.ts': ['webpack'],
},
client: { clearContext: false }, // leave Jasmine Spec Runner output visible in browser
files: [
{ pattern: '../karma.bundle.ts', watched: false },
],
mime: {
'text/x-typescript': ['ts', 'tsx'],
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, 'coverage'),
reports: ['html', 'lcovonly', 'text-summary'],
combineBrowserReports: true,
fixWebpackSourcePaths: true
},
reporters: config.codeCoverage ? ['mocha', 'coverage-istanbul'] : ['mocha'],
mochaReporter: { ignoreSkipped: true },
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: false,
singleRun: true,
browsers: ['ChromeHeadless'],
customLaunchers: {
ChromeDebug: {
base: 'Chrome',
flags: ['--remote-debugging-port=9333'],
debug: true
},
},
webpack: config.codeCoverage ? webpackMerge(webpackConf,
{
module: {
rules: [
{
test: /\.ts$/,
exclude: /\.spec\.ts$/,
enforce: 'post',
use: {
loader: 'istanbul-instrumenter-loader',
options: {
esModules: true
},
}
}
]
}
}
) : webpackConf,
webpackMiddleware: {
noInfo: true,
stats: 'errors-only'
},
concurrency: Infinity,
browserDisconnectTolerance: 3,
browserDisconnectTimeout: 210000,
browserNoActivityTimeout: 210000,
});
};
我们用于测试的 tsconfig:
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"include": [
"../karma.bundle.ts",
"**/*.spec.ts",
"**/*.d.ts"
]
}
karma.bundle.ts
(相当标准):
// First, initialize the Angular testing environment.
beforeAll(() => {
testing.TestBed.resetTestEnvironment();
testing.TestBed.initTestEnvironment(browser.BrowserDynamicTestingModule,
browser.platformBrowserDynamicTesting());
});
/**
* Get all the files, for each file, call the context function
* that will require the file and load it up here. Context will
* loop and require those spec files here
*/
function requireAll(requireContext) {
return requireContext.keys().map((key) => {
requireContext(key);
});
}
/**
* Requires and returns all modules that match
*/
const context = (require as any).context('./src', true, /\.spec\.ts$/);
console.log('Keys: ', context.keys());
requireAll(context);
到目前为止,我有以下怀疑:
- Karma 在加载模块时看不到类型,因此 HTML 不是可识别的类型
- Webpack 不处理 HTMLs,尽管有适当的配置,但只在 Karma 上下文中
我很乐意提供任何帮助,因为我们完全卡住了,我们的测试暂时无法使用。
经过进一步调查,我们找到了允许测试通过并忽略 module
错误的部分解决方案。我们修改了我们的代码以在 Karma 中需要上下文:
function requireAll(requireContext) {
return requireContext.keys().map((key) => {
try {
requireContext(key);
} catch (err) {
console.log('Cannot require:', err);
}
}
}
另外值得一提的是:导入顺序在捆绑文件中很重要(这是我们在 karma.bundle.ts
中遇到的另一个问题)。我们切换自:
import * as testing from '@angular/core/testing';
import * as browser from '@angular/platform-browser-dynamic/testing';
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';
import 'zone.js/dist/zone-testing';
至:
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';
import 'zone.js/dist/zone-testing';
import * as testing from '@angular/core/testing';
import * as browser from '@angular/platform-browser-dynamic/testing';
这并没有直接解决我们的问题,但只要在测试中不使用导致错误的导入模板,就可以通过测试(所有导入错误都将被 catch()
子句抑制).
解决方案的另一部分是修改用于测试的 Webpack 配置 - 我们添加了额外的规则,以最简单的方式处理 HTML。 Karma 可能更改了 Webpack 管道中使用的路径,Webpack 无法正确处理 HTMLs。这个:
rules: [
{
test: /\.tpl\.html$/,
use: [
'raw-loader',
'html-loader'
]
},
{ // ts
test: /\.ts$/,
exclude: [/node_modules/],
use: [
{
loader: 'ts-loader',
options: {
configFile: path.join(srcDir, 'tsconfig.spec.json'),
onlyCompileBundledFiles: true,
experimentalWatchApi: true,
transpileOnly: true
},
},
'angular2-template-loader'
],
},
]
解决了问题。
在我们当前的项目中,我们构建了一个自定义混合 NGJS/NGX 应用程序作为完全迁移到 Angular 的中间步骤。
混合版本将每个 Angular 版本放在单独的目录中。它们在两个方向上互通——我们在 AngularJS 中包含降级的 Angular 文件,在 Angular 中包含升级的 AngularJS 文件。
构建工作完全符合预期(所有文件都正确输出),但是,我们无法使测试工作(Karma + Webpack)。
它们在一个非常具体的点上失败了——当你包含需要任何类型的 HTML 的 AngularJS 文件时,例如import Tpl from '../templates/my-template.tpl.html
.
错误如下:
TypeError: Cannot read property 'module' of undefined
at eval (webpack-internal:///./src/ngjs/Account/templates/account.password.edit.tpl.html:4:16)
at Module../src/ngjs/Account/templates/account.password.edit.tpl.html (http://localhost:9879/absolute/path-to-repo/karma.bundle.js?b7d10f4cb57d9b36c6b149128e3c9810b2901be0:7594:1)
at __webpack_require__ (http://localhost:9879/absolute/path-to-repo/karma.bundle.js?b7d10f4cb57d9b36c6b149128e3c9810b2901be0:20:30)
at eval (webpack-internal:///./src/ngjs/Core/services/user.service.ts:11:64)
at Object../src/ngjs/Core/services/user.service.ts (http://localhost:9879/absolute/path-to-repo/karma.bundle.js?b7d10f4cb57d9b36c6b149128e3c9810b2901be0:7610:1)
at __webpack_require__ (http://localhost:9879/absolute/path-to-repo/karma.bundle.js?b7d10f4cb57d9b36c6b149128e3c9810b2901be0:20:30)
at eval (webpack-internal:///./src/ngx/core/components/display-groups-dropdown/display-groups-dropdown.component.ts:14:22)
at Object../src/ngx/core/components/display-groups-dropdown/display-groups-dropdown.component.ts (http://localhost:9879/absolute/path-to-repo/karma.bundle.js?b7d10f4cb57d9b36c6b149128e3c9810b2901be0:8092:1)
at __webpack_require__ (http://localhost:9879/absolute/path-to-repo/karma.bundle.js?b7d10f4cb57d9b36c6b149128e3c9810b2901be0:20:30)
at eval (webpack-internal:///./src/ngx/core/components/display-groups-dropdown/display-groups-dropdown.component.spec.ts:4:43)
(ngx
是应用程序的 Angular 部分,ngjs
是 AngularJS)。
我们的 Karma 配置:
export default (config) => {
config.set({
basePath: 'src',
frameworks: ['jasmine'],
plugins: [
karmaJasminePlugin,
karmaChromeLauncherPlugin,
karmaWebpackPlugin,
tsLoaderPlugin,
karmaMochaReporterPlugin,
karmaCoverageIstanbulReporterPlugin,
],
preprocessors: {
'../karma.bundle.ts': ['webpack'],
},
client: { clearContext: false }, // leave Jasmine Spec Runner output visible in browser
files: [
{ pattern: '../karma.bundle.ts', watched: false },
],
mime: {
'text/x-typescript': ['ts', 'tsx'],
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, 'coverage'),
reports: ['html', 'lcovonly', 'text-summary'],
combineBrowserReports: true,
fixWebpackSourcePaths: true
},
reporters: config.codeCoverage ? ['mocha', 'coverage-istanbul'] : ['mocha'],
mochaReporter: { ignoreSkipped: true },
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: false,
singleRun: true,
browsers: ['ChromeHeadless'],
customLaunchers: {
ChromeDebug: {
base: 'Chrome',
flags: ['--remote-debugging-port=9333'],
debug: true
},
},
webpack: config.codeCoverage ? webpackMerge(webpackConf,
{
module: {
rules: [
{
test: /\.ts$/,
exclude: /\.spec\.ts$/,
enforce: 'post',
use: {
loader: 'istanbul-instrumenter-loader',
options: {
esModules: true
},
}
}
]
}
}
) : webpackConf,
webpackMiddleware: {
noInfo: true,
stats: 'errors-only'
},
concurrency: Infinity,
browserDisconnectTolerance: 3,
browserDisconnectTimeout: 210000,
browserNoActivityTimeout: 210000,
});
};
我们用于测试的 tsconfig:
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"include": [
"../karma.bundle.ts",
"**/*.spec.ts",
"**/*.d.ts"
]
}
karma.bundle.ts
(相当标准):
// First, initialize the Angular testing environment.
beforeAll(() => {
testing.TestBed.resetTestEnvironment();
testing.TestBed.initTestEnvironment(browser.BrowserDynamicTestingModule,
browser.platformBrowserDynamicTesting());
});
/**
* Get all the files, for each file, call the context function
* that will require the file and load it up here. Context will
* loop and require those spec files here
*/
function requireAll(requireContext) {
return requireContext.keys().map((key) => {
requireContext(key);
});
}
/**
* Requires and returns all modules that match
*/
const context = (require as any).context('./src', true, /\.spec\.ts$/);
console.log('Keys: ', context.keys());
requireAll(context);
到目前为止,我有以下怀疑:
- Karma 在加载模块时看不到类型,因此 HTML 不是可识别的类型
- Webpack 不处理 HTMLs,尽管有适当的配置,但只在 Karma 上下文中
我很乐意提供任何帮助,因为我们完全卡住了,我们的测试暂时无法使用。
经过进一步调查,我们找到了允许测试通过并忽略 module
错误的部分解决方案。我们修改了我们的代码以在 Karma 中需要上下文:
function requireAll(requireContext) {
return requireContext.keys().map((key) => {
try {
requireContext(key);
} catch (err) {
console.log('Cannot require:', err);
}
}
}
另外值得一提的是:导入顺序在捆绑文件中很重要(这是我们在 karma.bundle.ts
中遇到的另一个问题)。我们切换自:
import * as testing from '@angular/core/testing';
import * as browser from '@angular/platform-browser-dynamic/testing';
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';
import 'zone.js/dist/zone-testing';
至:
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';
import 'zone.js/dist/zone-testing';
import * as testing from '@angular/core/testing';
import * as browser from '@angular/platform-browser-dynamic/testing';
这并没有直接解决我们的问题,但只要在测试中不使用导致错误的导入模板,就可以通过测试(所有导入错误都将被 catch()
子句抑制).
解决方案的另一部分是修改用于测试的 Webpack 配置 - 我们添加了额外的规则,以最简单的方式处理 HTML。 Karma 可能更改了 Webpack 管道中使用的路径,Webpack 无法正确处理 HTMLs。这个:
rules: [
{
test: /\.tpl\.html$/,
use: [
'raw-loader',
'html-loader'
]
},
{ // ts
test: /\.ts$/,
exclude: [/node_modules/],
use: [
{
loader: 'ts-loader',
options: {
configFile: path.join(srcDir, 'tsconfig.spec.json'),
onlyCompileBundledFiles: true,
experimentalWatchApi: true,
transpileOnly: true
},
},
'angular2-template-loader'
],
},
]
解决了问题。