@angular-devkit/build-angular 0.802.0-next.1 → 0.802.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +13 -13
- package/src/angular-cli-files/models/webpack-configs/common.js +22 -10
- package/src/angular-cli-files/models/webpack-configs/styles.js +7 -1
- package/src/angular-cli-files/models/webpack-configs/utils.js +6 -3
- package/src/angular-cli-files/plugins/bundle-budget.d.ts +1 -0
- package/src/angular-cli-files/plugins/bundle-budget.js +38 -23
- package/src/angular-cli-files/utilities/bundle-calculator.js +14 -0
- package/src/angular-cli-files/utilities/index-file/augment-index-html.js +4 -1
- package/src/browser/index.js +7 -9
- package/src/browser/schema.d.ts +1 -0
- package/src/browser/schema.js +1 -0
- package/src/browser/schema.json +1 -0
- package/src/dev-server/index.d.ts +0 -1
- package/src/dev-server/index.js +15 -2
- package/src/dev-server/schema.json +2 -5
package/package.json
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular-devkit/build-angular",
|
|
3
|
-
"version": "0.802.
|
|
3
|
+
"version": "0.802.2",
|
|
4
4
|
"description": "Angular Webpack Build Facade",
|
|
5
5
|
"experimental": true,
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"typings": "src/index.d.ts",
|
|
8
8
|
"builders": "builders.json",
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@angular-devkit/architect": "0.802.
|
|
11
|
-
"@angular-devkit/build-optimizer": "0.802.
|
|
12
|
-
"@angular-devkit/build-webpack": "0.802.
|
|
13
|
-
"@angular-devkit/core": "8.2.
|
|
14
|
-
"@ngtools/webpack": "8.2.
|
|
10
|
+
"@angular-devkit/architect": "0.802.2",
|
|
11
|
+
"@angular-devkit/build-optimizer": "0.802.2",
|
|
12
|
+
"@angular-devkit/build-webpack": "0.802.2",
|
|
13
|
+
"@angular-devkit/core": "8.2.2",
|
|
14
|
+
"@ngtools/webpack": "8.2.2",
|
|
15
15
|
"ajv": "6.10.2",
|
|
16
16
|
"autoprefixer": "9.6.1",
|
|
17
17
|
"browserslist": "4.6.6",
|
|
18
|
-
"caniuse-lite": "1.0.
|
|
18
|
+
"caniuse-lite": "1.0.30000986",
|
|
19
19
|
"circular-dependency-plugin": "5.0.2",
|
|
20
20
|
"clean-css": "4.2.1",
|
|
21
|
-
"copy-webpack-plugin": "5.0.
|
|
21
|
+
"copy-webpack-plugin": "5.0.4",
|
|
22
22
|
"core-js": "3.1.4",
|
|
23
|
-
"file-loader": "4.
|
|
23
|
+
"file-loader": "4.1.0",
|
|
24
24
|
"glob": "7.1.4",
|
|
25
25
|
"istanbul-instrumenter-loader": "3.0.1",
|
|
26
26
|
"karma-source-map-support": "1.4.0",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"postcss-loader": "3.0.0",
|
|
38
38
|
"raw-loader": "1.0.0",
|
|
39
39
|
"rxjs": "6.4.0",
|
|
40
|
-
"sass": "1.22.
|
|
40
|
+
"sass": "1.22.7",
|
|
41
41
|
"sass-loader": "7.1.0",
|
|
42
|
-
"semver": "6.
|
|
42
|
+
"semver": "6.3.0",
|
|
43
43
|
"source-map-support": "0.5.12",
|
|
44
44
|
"source-map-loader": "0.2.4",
|
|
45
45
|
"speed-measure-webpack-plugin": "1.3.1",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"stylus-loader": "3.0.2",
|
|
49
49
|
"tree-kill": "1.2.1",
|
|
50
50
|
"terser-webpack-plugin": "1.3.0",
|
|
51
|
-
"webpack": "4.
|
|
51
|
+
"webpack": "4.38.0",
|
|
52
52
|
"webpack-dev-middleware": "3.7.0",
|
|
53
53
|
"webpack-dev-server": "3.7.2",
|
|
54
54
|
"webpack-merge": "4.2.1",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"@angular/compiler-cli": "^8.0.0-beta.0 || ^8.1.0-beta.0 || ^8.2.0-beta.0 || ^8.3.0-beta.0 || ^8.4.0-beta.0 || >=9.0.0-beta < 9",
|
|
61
|
-
"typescript": ">=3.1 < 3.
|
|
61
|
+
"typescript": ">=3.1 < 3.6"
|
|
62
62
|
},
|
|
63
63
|
"keywords": [
|
|
64
64
|
"angular",
|
|
@@ -224,16 +224,20 @@ function getCommonConfig(wco) {
|
|
|
224
224
|
ngDevMode: false,
|
|
225
225
|
ngI18nClosureMode: false,
|
|
226
226
|
};
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (GLOBAL_DEFS_FOR_TERSER) {
|
|
232
|
-
angularGlobalDefinitions = GLOBAL_DEFS_FOR_TERSER;
|
|
233
|
-
}
|
|
227
|
+
// Try to load known global definitions from @angular/compiler-cli.
|
|
228
|
+
const GLOBAL_DEFS_FOR_TERSER = require('@angular/compiler-cli').GLOBAL_DEFS_FOR_TERSER;
|
|
229
|
+
if (GLOBAL_DEFS_FOR_TERSER) {
|
|
230
|
+
angularGlobalDefinitions = GLOBAL_DEFS_FOR_TERSER;
|
|
234
231
|
}
|
|
235
|
-
|
|
236
|
-
//
|
|
232
|
+
if (buildOptions.aot) {
|
|
233
|
+
// Also try to load AOT-only global definitions.
|
|
234
|
+
const GLOBAL_DEFS_FOR_TERSER_WITH_AOT = require('@angular/compiler-cli').GLOBAL_DEFS_FOR_TERSER_WITH_AOT;
|
|
235
|
+
if (GLOBAL_DEFS_FOR_TERSER_WITH_AOT) {
|
|
236
|
+
angularGlobalDefinitions = {
|
|
237
|
+
...angularGlobalDefinitions,
|
|
238
|
+
...GLOBAL_DEFS_FOR_TERSER_WITH_AOT,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
237
241
|
}
|
|
238
242
|
const terserOptions = {
|
|
239
243
|
ecma: wco.supportES2015 ? 6 : 5,
|
|
@@ -320,13 +324,21 @@ function getCommonConfig(wco) {
|
|
|
320
324
|
test: /[\/\\]@angular[\/\\]core[\/\\].+\.js$/,
|
|
321
325
|
parser: { system: true },
|
|
322
326
|
},
|
|
327
|
+
{
|
|
328
|
+
test: /[\/\\]hot[\/\\]emitter\.js$/,
|
|
329
|
+
parser: { node: { events: true } },
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
test: /[\/\\]webpack-dev-server[\/\\]client[\/\\]utils[\/\\]createSocketUrl\.js$/,
|
|
333
|
+
parser: { node: { querystring: true } },
|
|
334
|
+
},
|
|
323
335
|
{
|
|
324
336
|
test: /\.js$/,
|
|
325
337
|
...buildOptimizerUseRule,
|
|
326
338
|
},
|
|
327
339
|
{
|
|
328
340
|
test: /\.js$/,
|
|
329
|
-
exclude: /(ngfactory|ngstyle)
|
|
341
|
+
exclude: /(ngfactory|ngstyle)\.js$/,
|
|
330
342
|
enforce: 'pre',
|
|
331
343
|
...sourceMapUseRule,
|
|
332
344
|
},
|
|
@@ -168,7 +168,13 @@ function getStylesConfig(wco) {
|
|
|
168
168
|
options: {
|
|
169
169
|
ident: 'embedded',
|
|
170
170
|
plugins: postcssPluginCreator,
|
|
171
|
-
sourceMap: cssSourceMap
|
|
171
|
+
sourceMap: cssSourceMap
|
|
172
|
+
// Never use component css sourcemap when style optimizations are on.
|
|
173
|
+
// It will just increase bundle size without offering good debug experience.
|
|
174
|
+
&& !buildOptions.optimization.styles
|
|
175
|
+
// Inline all sourcemap types except hidden ones, which are the same as no sourcemaps
|
|
176
|
+
// for component css.
|
|
177
|
+
&& !buildOptions.sourceMap.hidden ? 'inline' : false,
|
|
172
178
|
},
|
|
173
179
|
},
|
|
174
180
|
...use,
|
|
@@ -13,7 +13,6 @@ const core_1 = require("@angular-devkit/core");
|
|
|
13
13
|
const webpack_1 = require("webpack");
|
|
14
14
|
const typescript_1 = require("typescript");
|
|
15
15
|
function getOutputHashFormat(option, length = 20) {
|
|
16
|
-
/* tslint:disable:max-line-length */
|
|
17
16
|
const hashFormats = {
|
|
18
17
|
none: { chunk: '', extract: '', file: '', script: '' },
|
|
19
18
|
media: { chunk: '', extract: '', file: `.[hash:${length}]`, script: '' },
|
|
@@ -30,7 +29,6 @@ function getOutputHashFormat(option, length = 20) {
|
|
|
30
29
|
script: `.[hash:${length}]`,
|
|
31
30
|
},
|
|
32
31
|
};
|
|
33
|
-
/* tslint:enable:max-line-length */
|
|
34
32
|
return hashFormats[option] || hashFormats['none'];
|
|
35
33
|
}
|
|
36
34
|
exports.getOutputHashFormat = getOutputHashFormat;
|
|
@@ -71,7 +69,12 @@ function getSourceMapDevTool(scriptsSourceMap, stylesSourceMap, hiddenSourceMap
|
|
|
71
69
|
return new webpack_1.SourceMapDevToolPlugin({
|
|
72
70
|
filename: inlineSourceMap ? undefined : '[file].map',
|
|
73
71
|
include,
|
|
74
|
-
|
|
72
|
+
// We want to set sourceRoot to `webpack:///` for non
|
|
73
|
+
// inline sourcemaps as otherwise paths to sourcemaps will be broken in browser
|
|
74
|
+
// `webpack:///` is needed for Visual Studio breakpoints to work properly as currently
|
|
75
|
+
// there is no way to set the 'webRoot'
|
|
76
|
+
sourceRoot: inlineSourceMap ? '' : 'webpack:///',
|
|
77
|
+
moduleFilenameTemplate: '[resource-path]',
|
|
75
78
|
append: hiddenSourceMap ? false : undefined,
|
|
76
79
|
});
|
|
77
80
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const schema_1 = require("../../browser/schema");
|
|
3
4
|
const bundle_calculator_1 = require("../utilities/bundle-calculator");
|
|
4
5
|
const stats_1 = require("../utilities/stats");
|
|
5
6
|
class BundleBudgetPlugin {
|
|
@@ -8,31 +9,25 @@ class BundleBudgetPlugin {
|
|
|
8
9
|
}
|
|
9
10
|
apply(compiler) {
|
|
10
11
|
const { budgets } = this.options;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.
|
|
24
|
-
budgetCheck.sizes.forEach(size => {
|
|
25
|
-
this.checkMaximum(budgetCheck.thresholds.maximumWarning, size, compilation.warnings);
|
|
26
|
-
this.checkMaximum(budgetCheck.thresholds.maximumError, size, compilation.errors);
|
|
27
|
-
this.checkMinimum(budgetCheck.thresholds.minimumWarning, size, compilation.warnings);
|
|
28
|
-
this.checkMinimum(budgetCheck.thresholds.minimumError, size, compilation.errors);
|
|
29
|
-
this.checkMinimum(budgetCheck.thresholds.warningLow, size, compilation.warnings);
|
|
30
|
-
this.checkMaximum(budgetCheck.thresholds.warningHigh, size, compilation.warnings);
|
|
31
|
-
this.checkMinimum(budgetCheck.thresholds.errorLow, size, compilation.errors);
|
|
32
|
-
this.checkMaximum(budgetCheck.thresholds.errorHigh, size, compilation.errors);
|
|
33
|
-
});
|
|
12
|
+
if (!budgets || budgets.length === 0) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
compiler.hooks.compilation.tap('BundleBudgetPlugin', (compilation) => {
|
|
16
|
+
compilation.hooks.afterOptimizeChunkAssets.tap('BundleBudgetPlugin', () => {
|
|
17
|
+
// In AOT compilations component styles get processed in child compilations.
|
|
18
|
+
// tslint:disable-next-line: no-any
|
|
19
|
+
const parentCompilation = compilation.compiler.parentCompilation;
|
|
20
|
+
if (!parentCompilation) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const filteredBudgets = budgets.filter(budget => budget.type === schema_1.Type.AnyComponentStyle);
|
|
24
|
+
this.runChecks(filteredBudgets, compilation);
|
|
34
25
|
});
|
|
35
26
|
});
|
|
27
|
+
compiler.hooks.afterEmit.tap('BundleBudgetPlugin', (compilation) => {
|
|
28
|
+
const filteredBudgets = budgets.filter(budget => budget.type !== schema_1.Type.AnyComponentStyle);
|
|
29
|
+
this.runChecks(filteredBudgets, compilation);
|
|
30
|
+
});
|
|
36
31
|
}
|
|
37
32
|
checkMinimum(threshold, size, messages) {
|
|
38
33
|
if (threshold) {
|
|
@@ -80,5 +75,25 @@ class BundleBudgetPlugin {
|
|
|
80
75
|
}
|
|
81
76
|
return thresholds;
|
|
82
77
|
}
|
|
78
|
+
runChecks(budgets, compilation) {
|
|
79
|
+
budgets
|
|
80
|
+
.map(budget => ({
|
|
81
|
+
budget,
|
|
82
|
+
thresholds: this.calculate(budget),
|
|
83
|
+
sizes: bundle_calculator_1.calculateSizes(budget, compilation),
|
|
84
|
+
}))
|
|
85
|
+
.forEach(budgetCheck => {
|
|
86
|
+
budgetCheck.sizes.forEach(size => {
|
|
87
|
+
this.checkMaximum(budgetCheck.thresholds.maximumWarning, size, compilation.warnings);
|
|
88
|
+
this.checkMaximum(budgetCheck.thresholds.maximumError, size, compilation.errors);
|
|
89
|
+
this.checkMinimum(budgetCheck.thresholds.minimumWarning, size, compilation.warnings);
|
|
90
|
+
this.checkMinimum(budgetCheck.thresholds.minimumError, size, compilation.errors);
|
|
91
|
+
this.checkMinimum(budgetCheck.thresholds.warningLow, size, compilation.warnings);
|
|
92
|
+
this.checkMaximum(budgetCheck.thresholds.warningHigh, size, compilation.warnings);
|
|
93
|
+
this.checkMinimum(budgetCheck.thresholds.errorLow, size, compilation.errors);
|
|
94
|
+
this.checkMaximum(budgetCheck.thresholds.errorHigh, size, compilation.errors);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
}
|
|
83
98
|
}
|
|
84
99
|
exports.BundleBudgetPlugin = BundleBudgetPlugin;
|
|
@@ -6,6 +6,7 @@ function calculateSizes(budget, compilation) {
|
|
|
6
6
|
allScript: AllScriptCalculator,
|
|
7
7
|
any: AnyCalculator,
|
|
8
8
|
anyScript: AnyScriptCalculator,
|
|
9
|
+
anyComponentStyle: AnyComponentStyleCalculator,
|
|
9
10
|
bundle: BundleCalculator,
|
|
10
11
|
initial: InitialCalculator,
|
|
11
12
|
};
|
|
@@ -74,6 +75,19 @@ class AllCalculator extends Calculator {
|
|
|
74
75
|
return [{ size, label: 'total' }];
|
|
75
76
|
}
|
|
76
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Any components styles
|
|
80
|
+
*/
|
|
81
|
+
class AnyComponentStyleCalculator extends Calculator {
|
|
82
|
+
calculate() {
|
|
83
|
+
return Object.keys(this.compilation.assets)
|
|
84
|
+
.filter(key => key.endsWith('.css'))
|
|
85
|
+
.map(key => ({
|
|
86
|
+
size: this.compilation.assets[key].size(),
|
|
87
|
+
label: key,
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
77
91
|
/**
|
|
78
92
|
* Any script, individually.
|
|
79
93
|
*/
|
|
@@ -100,7 +100,10 @@ async function augmentIndexHtml(params) {
|
|
|
100
100
|
const isNoModuleType = noModuleFiles.some(scriptPredictor);
|
|
101
101
|
const isModuleType = moduleFiles.some(scriptPredictor);
|
|
102
102
|
if (isNoModuleType && !isModuleType) {
|
|
103
|
-
attrs.push({ name: 'nomodule', value: null }
|
|
103
|
+
attrs.push({ name: 'nomodule', value: null });
|
|
104
|
+
if (!script.startsWith('polyfills-nomodule-es5')) {
|
|
105
|
+
attrs.push({ name: 'defer', value: null });
|
|
106
|
+
}
|
|
104
107
|
}
|
|
105
108
|
else if (isModuleType && !isNoModuleType) {
|
|
106
109
|
attrs.push({ name: 'type', value: 'module' });
|
package/src/browser/index.js
CHANGED
|
@@ -134,16 +134,14 @@ function buildWebpackBrowser(options, context, transforms = {}) {
|
|
|
134
134
|
let moduleFiles;
|
|
135
135
|
let files;
|
|
136
136
|
const [firstBuild, secondBuild] = buildEvents;
|
|
137
|
-
if (
|
|
137
|
+
if (isDifferentialLoadingNeeded) {
|
|
138
|
+
const scriptsEntryPointName = webpack_configs_1.normalizeExtraEntryPoints(options.scripts || [], 'scripts')
|
|
139
|
+
.map(x => x.bundleName);
|
|
138
140
|
moduleFiles = firstBuild.emittedFiles || [];
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
// differential loading is not enabled in watch mode
|
|
144
|
-
// but we still want to use module type tags
|
|
145
|
-
moduleFiles = firstBuild.emittedFiles || [];
|
|
146
|
-
files = moduleFiles.filter(x => x.extension === '.css');
|
|
141
|
+
files = moduleFiles.filter(x => x.extension === '.css' || (x.name && scriptsEntryPointName.includes(x.name)));
|
|
142
|
+
if (buildEvents.length === 2) {
|
|
143
|
+
noModuleFiles = secondBuild.emittedFiles;
|
|
144
|
+
}
|
|
147
145
|
}
|
|
148
146
|
else {
|
|
149
147
|
const { emittedFiles = [] } = firstBuild;
|
package/src/browser/schema.d.ts
CHANGED
package/src/browser/schema.js
CHANGED
package/src/browser/schema.json
CHANGED
|
@@ -16,7 +16,6 @@ import { Schema as BrowserBuilderSchema } from '../browser/schema';
|
|
|
16
16
|
import { ExecutionTransformer } from '../transforms';
|
|
17
17
|
import { Schema } from './schema';
|
|
18
18
|
export declare type DevServerBuilderOptions = Schema & json.JsonObject;
|
|
19
|
-
export declare const devServerBuildOverriddenKeys: (keyof DevServerBuilderOptions)[];
|
|
20
19
|
export declare type DevServerBuilderOutput = DevServerBuildOutput & {
|
|
21
20
|
baseUrl: string;
|
|
22
21
|
};
|
package/src/dev-server/index.js
CHANGED
|
@@ -27,7 +27,7 @@ const utils_1 = require("../utils");
|
|
|
27
27
|
const version_1 = require("../utils/version");
|
|
28
28
|
const webpack_browser_config_1 = require("../utils/webpack-browser-config");
|
|
29
29
|
const open = require('open');
|
|
30
|
-
|
|
30
|
+
const devServerBuildOverriddenKeys = [
|
|
31
31
|
'watch',
|
|
32
32
|
'optimization',
|
|
33
33
|
'aot',
|
|
@@ -62,7 +62,7 @@ function serveWebpackBrowser(options, context, transforms = {}) {
|
|
|
62
62
|
const rawBrowserOptions = await context.getTargetOptions(browserTarget);
|
|
63
63
|
// Override options we need to override, if defined.
|
|
64
64
|
const overrides = Object.keys(options)
|
|
65
|
-
.filter(key => options[key] !== undefined &&
|
|
65
|
+
.filter(key => options[key] !== undefined && devServerBuildOverriddenKeys.includes(key))
|
|
66
66
|
.reduce((previous, key) => ({
|
|
67
67
|
...previous,
|
|
68
68
|
[key]: options[key],
|
|
@@ -296,6 +296,19 @@ function _addLiveReload(options, browserOptions, webpackConfig, clientAddress, l
|
|
|
296
296
|
if (webpackConfig.plugins === undefined) {
|
|
297
297
|
webpackConfig.plugins = [];
|
|
298
298
|
}
|
|
299
|
+
// Enable the internal node plugins but no individual shims
|
|
300
|
+
// This is needed to allow module specific rules to include node shims
|
|
301
|
+
// Only needed in dev server mode to support live reload capabilities in all package managers
|
|
302
|
+
if (webpackConfig.node === false) {
|
|
303
|
+
webpackConfig.node = {
|
|
304
|
+
global: false,
|
|
305
|
+
process: false,
|
|
306
|
+
__filename: false,
|
|
307
|
+
__dirname: false,
|
|
308
|
+
Buffer: false,
|
|
309
|
+
setImmediate: false,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
299
312
|
// This allows for live reload of page when changes are made to repo.
|
|
300
313
|
// https://webpack.js.org/configuration/dev-server/#devserver-inline
|
|
301
314
|
let webpackDevServerPath;
|
|
@@ -44,8 +44,7 @@
|
|
|
44
44
|
},
|
|
45
45
|
"verbose": {
|
|
46
46
|
"type": "boolean",
|
|
47
|
-
"description": "Adds more details to output logging."
|
|
48
|
-
"default": false
|
|
47
|
+
"description": "Adds more details to output logging."
|
|
49
48
|
},
|
|
50
49
|
"liveReload": {
|
|
51
50
|
"type": "boolean",
|
|
@@ -117,7 +116,6 @@
|
|
|
117
116
|
},
|
|
118
117
|
"sourceMap": {
|
|
119
118
|
"description": "Output sourcemaps.",
|
|
120
|
-
"default": true,
|
|
121
119
|
"oneOf": [
|
|
122
120
|
{
|
|
123
121
|
"type": "object",
|
|
@@ -153,8 +151,7 @@
|
|
|
153
151
|
"vendorSourceMap": {
|
|
154
152
|
"type": "boolean",
|
|
155
153
|
"description": "Resolve vendor packages sourcemaps.",
|
|
156
|
-
"x-deprecated": true
|
|
157
|
-
"default": false
|
|
154
|
+
"x-deprecated": true
|
|
158
155
|
},
|
|
159
156
|
"evalSourceMap": {
|
|
160
157
|
"type": "boolean",
|