@angular-devkit/build-angular 0.801.0-beta.3 → 0.801.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.
Files changed (31) hide show
  1. package/package.json +12 -13
  2. package/plugins/webpack/analytics.d.ts +2 -0
  3. package/plugins/webpack/analytics.js +25 -3
  4. package/src/angular-cli-files/models/es5-polyfills.js +1 -0
  5. package/src/angular-cli-files/models/webpack-configs/common.js +24 -29
  6. package/src/angular-cli-files/models/webpack-configs/styles.js +22 -16
  7. package/src/angular-cli-files/models/webpack-configs/utils.d.ts +3 -3
  8. package/src/angular-cli-files/models/webpack-configs/utils.js +21 -7
  9. package/src/angular-cli-files/plugins/index-html-webpack-plugin.d.ts +3 -0
  10. package/src/angular-cli-files/plugins/index-html-webpack-plugin.js +9 -4
  11. package/src/angular-cli-files/plugins/karma.js +0 -1
  12. package/src/angular-cli-files/plugins/single-test-transform.d.ts +28 -0
  13. package/src/angular-cli-files/plugins/single-test-transform.js +40 -0
  14. package/src/angular-cli-files/utilities/find-tests.d.ts +1 -0
  15. package/src/angular-cli-files/utilities/find-tests.js +55 -0
  16. package/src/angular-cli-files/utilities/index-file/augment-index-html.d.ts +3 -0
  17. package/src/angular-cli-files/utilities/index-file/augment-index-html.js +13 -6
  18. package/src/angular-cli-files/utilities/index-file/write-index-html.d.ts +3 -1
  19. package/src/angular-cli-files/utilities/index-file/write-index-html.js +6 -5
  20. package/src/angular-cli-files/utilities/package-chunk-sort.js +4 -2
  21. package/src/browser/index.js +20 -14
  22. package/src/browser/schema.d.ts +16 -0
  23. package/src/browser/schema.js +9 -0
  24. package/src/browser/schema.json +17 -1
  25. package/src/dev-server/index.js +46 -22
  26. package/src/karma/index.js +30 -2
  27. package/src/karma/schema.d.ts +13 -0
  28. package/src/karma/schema.json +14 -1
  29. package/src/utils/webpack-browser-config.js +1 -1
  30. package/test/utils.d.ts +2 -1
  31. package/test/utils.js +7 -2
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * @license
5
+ * Copyright Google Inc. All Rights Reserved.
6
+ *
7
+ * Use of this source code is governed by an MIT-style license that can be
8
+ * found in the LICENSE file at https://angular.io/license
9
+ */
10
+ const fs_1 = require("fs");
11
+ const glob = require("glob");
12
+ const path_1 = require("path");
13
+ const is_directory_1 = require("./is-directory");
14
+ // go through all patterns and find unique list of files
15
+ function findTests(patterns, cwd, workspaceRoot) {
16
+ return patterns.reduce((files, pattern) => {
17
+ const relativePathToMain = cwd.replace(workspaceRoot, '').substr(1); // remove leading slash
18
+ const tests = findMatchingTests(pattern, cwd, relativePathToMain);
19
+ tests.forEach(file => {
20
+ if (!files.includes(file)) {
21
+ files.push(file);
22
+ }
23
+ });
24
+ return files;
25
+ }, []);
26
+ }
27
+ exports.findTests = findTests;
28
+ function findMatchingTests(pattern, cwd, relativePathToMain) {
29
+ // normalize pattern, glob lib only accepts forward slashes
30
+ pattern = pattern.replace(/\\/g, '/');
31
+ relativePathToMain = relativePathToMain.replace(/\\/g, '/');
32
+ // remove relativePathToMain to support relative paths from root
33
+ // such paths are easy to get when running scripts via IDEs
34
+ if (pattern.startsWith(relativePathToMain + '/')) {
35
+ pattern = pattern.substr(relativePathToMain.length + 1); // +1 to include slash
36
+ }
37
+ // special logic when pattern does not look like a glob
38
+ if (!glob.hasMagic(pattern)) {
39
+ if (is_directory_1.isDirectory(path_1.join(cwd, pattern))) {
40
+ pattern = `${pattern}/**/*.spec.@(ts|tsx)`;
41
+ }
42
+ else {
43
+ // see if matching spec file exists
44
+ const extension = path_1.extname(pattern);
45
+ const matchingSpec = `${path_1.basename(pattern, extension)}.spec${extension}`;
46
+ if (fs_1.existsSync(path_1.join(cwd, path_1.dirname(pattern), matchingSpec))) {
47
+ pattern = path_1.join(path_1.dirname(pattern), matchingSpec).replace(/\\/g, '/');
48
+ }
49
+ }
50
+ }
51
+ const files = glob.sync(pattern, {
52
+ cwd,
53
+ });
54
+ return files;
55
+ }
@@ -6,12 +6,15 @@
6
6
  * found in the LICENSE file at https://angular.io/license
7
7
  */
8
8
  export declare type LoadOutputFileFunctionType = (file: string) => Promise<string>;
9
+ export declare type CrossOriginValue = 'none' | 'anonymous' | 'use-credentials';
9
10
  export interface AugmentIndexHtmlOptions {
10
11
  input: string;
11
12
  inputContent: string;
12
13
  baseHref?: string;
13
14
  deployUrl?: string;
14
15
  sri: boolean;
16
+ /** crossorigin attribute setting of elements that provide CORS support */
17
+ crossOrigin?: CrossOriginValue;
15
18
  files: FileInfo[];
16
19
  /** Files that should be added using 'nomodule'. */
17
20
  noModuleFiles?: FileInfo[];
@@ -17,7 +17,11 @@ const parse5 = require('parse5');
17
17
  * bundles for differential serving.
18
18
  */
19
19
  async function augmentIndexHtml(params) {
20
- const { loadOutputFile, files, noModuleFiles = [], moduleFiles = [], entrypoints, } = params;
20
+ const { loadOutputFile, files, noModuleFiles = [], moduleFiles = [], entrypoints } = params;
21
+ let { crossOrigin = 'none' } = params;
22
+ if (params.sri && crossOrigin === 'none') {
23
+ crossOrigin = 'anonymous';
24
+ }
21
25
  const stylesheets = new Set();
22
26
  const scripts = new Set();
23
27
  // Sort files in the order we want to insert them by entrypoint and dedupes duplicates
@@ -83,6 +87,9 @@ async function augmentIndexHtml(params) {
83
87
  const attrs = [
84
88
  { name: 'src', value: (params.deployUrl || '') + script },
85
89
  ];
90
+ if (crossOrigin !== 'none') {
91
+ attrs.push({ name: 'crossorigin', value: crossOrigin });
92
+ }
86
93
  // We want to include nomodule or module when a file is not common amongs all
87
94
  // such as runtime.js
88
95
  const scriptPredictor = ({ file }) => file === script;
@@ -104,7 +111,7 @@ async function augmentIndexHtml(params) {
104
111
  attrs.push(..._generateSriAttributes(content));
105
112
  }
106
113
  const attributes = attrs
107
- .map(attr => attr.value === null ? attr.name : `${attr.name}="${attr.value}"`)
114
+ .map(attr => (attr.value === null ? attr.name : `${attr.name}="${attr.value}"`))
108
115
  .join(' ');
109
116
  scriptElements += `<script ${attributes}></script>`;
110
117
  }
@@ -148,6 +155,9 @@ async function augmentIndexHtml(params) {
148
155
  { name: 'rel', value: 'stylesheet' },
149
156
  { name: 'href', value: (params.deployUrl || '') + stylesheet },
150
157
  ];
158
+ if (crossOrigin !== 'none') {
159
+ attrs.push({ name: 'crossorigin', value: crossOrigin });
160
+ }
151
161
  if (params.sri) {
152
162
  const content = await loadOutputFile(stylesheet);
153
163
  attrs.push(..._generateSriAttributes(content));
@@ -164,8 +174,5 @@ function _generateSriAttributes(content) {
164
174
  const hash = crypto_1.createHash(algo)
165
175
  .update(content, 'utf8')
166
176
  .digest('base64');
167
- return [
168
- { name: 'integrity', value: `${algo}-${hash}` },
169
- { name: 'crossorigin', value: 'anonymous' },
170
- ];
177
+ return [{ name: 'integrity', value: `${algo}-${hash}` }];
171
178
  }
@@ -9,6 +9,7 @@ import { EmittedFiles } from '@angular-devkit/build-webpack';
9
9
  import { Path, virtualFs } from '@angular-devkit/core';
10
10
  import { Observable } from 'rxjs';
11
11
  import { ExtraEntryPoint } from '../../../browser/schema';
12
+ import { CrossOriginValue } from './augment-index-html';
12
13
  export interface WriteIndexHtmlOptions {
13
14
  host: virtualFs.Host;
14
15
  outputPath: Path;
@@ -22,6 +23,7 @@ export interface WriteIndexHtmlOptions {
22
23
  scripts?: ExtraEntryPoint[];
23
24
  styles?: ExtraEntryPoint[];
24
25
  postTransform?: IndexHtmlTransform;
26
+ crossOrigin?: CrossOriginValue;
25
27
  }
26
28
  export declare type IndexHtmlTransform = (content: string) => Promise<string>;
27
- export declare function writeIndexHtml({ host, outputPath, indexPath, files, noModuleFiles, moduleFiles, baseHref, deployUrl, sri, scripts, styles, postTransform, }: WriteIndexHtmlOptions): Observable<void>;
29
+ export declare function writeIndexHtml({ host, outputPath, indexPath, files, noModuleFiles, moduleFiles, baseHref, deployUrl, sri, scripts, styles, postTransform, crossOrigin, }: WriteIndexHtmlOptions): Observable<void>;
@@ -13,24 +13,25 @@ const operators_1 = require("rxjs/operators");
13
13
  const package_chunk_sort_1 = require("../package-chunk-sort");
14
14
  const strip_bom_1 = require("../strip-bom");
15
15
  const augment_index_html_1 = require("./augment-index-html");
16
- function writeIndexHtml({ host, outputPath, indexPath, files = [], noModuleFiles = [], moduleFiles = [], baseHref, deployUrl, sri = false, scripts = [], styles = [], postTransform, }) {
17
- return host.read(indexPath)
18
- .pipe(operators_1.map(content => strip_bom_1.stripBom(core_1.virtualFs.fileBufferToString(content))), operators_1.switchMap(content => augment_index_html_1.augmentIndexHtml({
16
+ function writeIndexHtml({ host, outputPath, indexPath, files = [], noModuleFiles = [], moduleFiles = [], baseHref, deployUrl, sri = false, scripts = [], styles = [], postTransform, crossOrigin, }) {
17
+ return host.read(indexPath).pipe(operators_1.map(content => strip_bom_1.stripBom(core_1.virtualFs.fileBufferToString(content))), operators_1.switchMap(content => augment_index_html_1.augmentIndexHtml({
19
18
  input: core_1.getSystemPath(outputPath),
20
19
  inputContent: content,
21
20
  baseHref,
22
21
  deployUrl,
22
+ crossOrigin,
23
23
  sri,
24
24
  entrypoints: package_chunk_sort_1.generateEntryPoints({ scripts, styles }),
25
25
  files: filterAndMapBuildFiles(files, ['.js', '.css']),
26
26
  noModuleFiles: filterAndMapBuildFiles(noModuleFiles, '.js'),
27
27
  moduleFiles: filterAndMapBuildFiles(moduleFiles, '.js'),
28
28
  loadOutputFile: async (filePath) => {
29
- return host.read(core_1.join(outputPath, filePath))
29
+ return host
30
+ .read(core_1.join(outputPath, filePath))
30
31
  .pipe(operators_1.map(data => core_1.virtualFs.fileBufferToString(data)))
31
32
  .toPromise();
32
33
  },
33
- })), operators_1.switchMap(content => postTransform ? postTransform(content) : rxjs_1.of(content)), operators_1.map(content => core_1.virtualFs.stringToFileBuffer(content)), operators_1.switchMap(content => host.write(core_1.join(outputPath, core_1.basename(indexPath)), content)));
34
+ })), operators_1.switchMap(content => (postTransform ? postTransform(content) : rxjs_1.of(content))), operators_1.map(content => core_1.virtualFs.stringToFileBuffer(content)), operators_1.switchMap(content => host.write(core_1.join(outputPath, core_1.basename(indexPath)), content)));
34
35
  }
35
36
  exports.writeIndexHtml = writeIndexHtml;
36
37
  function filterAndMapBuildFiles(files, extensionFilter) {
@@ -5,7 +5,7 @@ function generateEntryPoints(appConfig) {
5
5
  // Add all styles/scripts, except lazy-loaded ones.
6
6
  const extraEntryPoints = (extraEntryPoints, defaultBundleName) => {
7
7
  const entryPoints = utils_1.normalizeExtraEntryPoints(extraEntryPoints, defaultBundleName)
8
- .filter(entry => !entry.lazy)
8
+ .filter(entry => entry.inject)
9
9
  .map(entry => entry.bundleName);
10
10
  // remove duplicates
11
11
  return [...new Set(entryPoints)];
@@ -19,7 +19,9 @@ function generateEntryPoints(appConfig) {
19
19
  ...extraEntryPoints(appConfig.scripts, 'scripts'),
20
20
  'main',
21
21
  ];
22
- const duplicates = [...new Set(entryPoints.filter(x => entryPoints.indexOf(x) !== entryPoints.lastIndexOf(x)))];
22
+ const duplicates = [
23
+ ...new Set(entryPoints.filter(x => entryPoints.indexOf(x) !== entryPoints.lastIndexOf(x))),
24
+ ];
23
25
  if (duplicates.length > 0) {
24
26
  throw new Error(`Multiple bundles have been named the same: '${duplicates.join(`', '`)}'.`);
25
27
  }
@@ -61,13 +61,12 @@ function getAnalyticsConfig(wco, context) {
61
61
  let category = 'build';
62
62
  if (context.builder) {
63
63
  // We already vetted that this is a "safe" package, otherwise the analytics would be noop.
64
- category = context.builder.builderName.split(':')[1];
64
+ category =
65
+ context.builder.builderName.split(':')[1] || context.builder.builderName || 'build';
65
66
  }
66
67
  // The category is the builder name if it's an angular builder.
67
68
  return {
68
- plugins: [
69
- new analytics_1.NgBuildAnalyticsPlugin(wco.projectRoot, context.analytics, category),
70
- ],
69
+ plugins: [new analytics_1.NgBuildAnalyticsPlugin(wco.projectRoot, context.analytics, category)],
71
70
  };
72
71
  }
73
72
  return {};
@@ -97,11 +96,11 @@ function buildWebpackBrowser(options, context, transforms = {}) {
97
96
  const root = core_1.normalize(context.workspaceRoot);
98
97
  // Check Angular version.
99
98
  version_1.assertCompatibleAngularVersion(context.workspaceRoot, context.logger);
100
- const loggingFn = transforms.logging
101
- || createBrowserLoggingCallback(!!options.verbose, context.logger);
99
+ const loggingFn = transforms.logging || createBrowserLoggingCallback(!!options.verbose, context.logger);
102
100
  return rxjs_1.from(initialize(options, context, host, transforms.webpackConfiguration)).pipe(operators_1.switchMap(({ workspace, config: configs }) => {
103
101
  const projectName = context.target
104
- ? context.target.project : workspace.getDefaultProjectName();
102
+ ? context.target.project
103
+ : workspace.getDefaultProjectName();
105
104
  if (!projectName) {
106
105
  throw new Error('Must either have a target from the context or a default project.');
107
106
  }
@@ -109,7 +108,8 @@ function buildWebpackBrowser(options, context, transforms = {}) {
109
108
  const tsConfig = read_tsconfig_1.readTsconfig(options.tsConfig, context.workspaceRoot);
110
109
  const target = tsConfig.options.target || typescript_1.ScriptTarget.ES5;
111
110
  const buildBrowserFeatures = new utils_1.BuildBrowserFeatures(core_1.getSystemPath(projectRoot), target);
112
- if (target > typescript_1.ScriptTarget.ES2015 && buildBrowserFeatures.isDifferentialLoadingNeeded()) {
111
+ const isDifferentialLoadingNeeded = buildBrowserFeatures.isDifferentialLoadingNeeded();
112
+ if (target > typescript_1.ScriptTarget.ES2015 && isDifferentialLoadingNeeded) {
113
113
  context.logger.warn(core_1.tags.stripIndent `
114
114
  WARNING: Using differential loading with targets ES5 and ES2016 or higher may
115
115
  cause problems. Browsers with support for ES2015 will load the ES2016+ scripts
@@ -133,14 +133,20 @@ function buildWebpackBrowser(options, context, transforms = {}) {
133
133
  let noModuleFiles;
134
134
  let moduleFiles;
135
135
  let files;
136
- const [ES5Result, ES2015Result] = buildEvents;
136
+ const [firstBuild, secondBuild] = buildEvents;
137
137
  if (buildEvents.length === 2) {
138
- noModuleFiles = ES5Result.emittedFiles;
139
- moduleFiles = ES2015Result.emittedFiles || [];
138
+ moduleFiles = firstBuild.emittedFiles || [];
139
+ noModuleFiles = secondBuild.emittedFiles;
140
+ files = moduleFiles.filter(x => x.extension === '.css');
141
+ }
142
+ else if (options.watch && isDifferentialLoadingNeeded) {
143
+ // differential loading is not enabled in watch mode
144
+ // but we still want to use module type tags
145
+ moduleFiles = firstBuild.emittedFiles || [];
140
146
  files = moduleFiles.filter(x => x.extension === '.css');
141
147
  }
142
148
  else {
143
- const { emittedFiles = [] } = ES5Result;
149
+ const { emittedFiles = [] } = firstBuild;
144
150
  files = emittedFiles.filter(x => x.name !== 'polyfills-es5');
145
151
  noModuleFiles = emittedFiles.filter(x => x.name === 'polyfills-es5');
146
152
  }
@@ -157,8 +163,8 @@ function buildWebpackBrowser(options, context, transforms = {}) {
157
163
  scripts: options.scripts,
158
164
  styles: options.styles,
159
165
  postTransform: transforms.indexHtml,
160
- })
161
- .pipe(operators_1.map(() => ({ success: true })), operators_1.catchError(error => rxjs_1.of({ success: false, error: mapErrorToMessage(error) })));
166
+ crossOrigin: options.crossOrigin,
167
+ }).pipe(operators_1.map(() => ({ success: true })), operators_1.catchError(error => rxjs_1.of({ success: false, error: mapErrorToMessage(error) })));
162
168
  }
163
169
  else {
164
170
  return rxjs_1.of({ success });
@@ -27,6 +27,10 @@ export interface Schema {
27
27
  * Use a separate bundle containing code used across multiple bundles.
28
28
  */
29
29
  commonChunk?: boolean;
30
+ /**
31
+ * Define the crossorigin attribute setting of elements that provide CORS support.
32
+ */
33
+ crossOrigin?: CrossOrigin;
30
34
  /**
31
35
  * Delete the output path before building.
32
36
  */
@@ -277,6 +281,14 @@ export declare enum Type {
277
281
  Bundle = "bundle",
278
282
  Initial = "initial"
279
283
  }
284
+ /**
285
+ * Define the crossorigin attribute setting of elements that provide CORS support.
286
+ */
287
+ export declare enum CrossOrigin {
288
+ Anonymous = "anonymous",
289
+ None = "none",
290
+ UseCredentials = "use-credentials"
291
+ }
280
292
  export interface FileReplacement {
281
293
  replace?: string;
282
294
  replaceWith?: string;
@@ -312,6 +324,10 @@ export interface ExtraEntryPointClass {
312
324
  * The bundle name for this extra entry point.
313
325
  */
314
326
  bundleName?: string;
327
+ /**
328
+ * If the bundle will be referenced in the HTML file.
329
+ */
330
+ inject?: boolean;
315
331
  /**
316
332
  * The file to include.
317
333
  */
@@ -14,6 +14,15 @@ var Type;
14
14
  Type["Bundle"] = "bundle";
15
15
  Type["Initial"] = "initial";
16
16
  })(Type = exports.Type || (exports.Type = {}));
17
+ /**
18
+ * Define the crossorigin attribute setting of elements that provide CORS support.
19
+ */
20
+ var CrossOrigin;
21
+ (function (CrossOrigin) {
22
+ CrossOrigin["Anonymous"] = "anonymous";
23
+ CrossOrigin["None"] = "none";
24
+ CrossOrigin["UseCredentials"] = "use-credentials";
25
+ })(CrossOrigin = exports.CrossOrigin || (exports.CrossOrigin = {}));
17
26
  /**
18
27
  * Define the output filename cache-busting hashing mode.
19
28
  */
@@ -319,6 +319,16 @@
319
319
  "webWorkerTsConfig": {
320
320
  "type": "string",
321
321
  "description": "TypeScript configuration for Web Worker modules."
322
+ },
323
+ "crossOrigin": {
324
+ "type": "string",
325
+ "description": "Define the crossorigin attribute setting of elements that provide CORS support.",
326
+ "default": "none",
327
+ "enum": [
328
+ "none",
329
+ "anonymous",
330
+ "use-credentials"
331
+ ]
322
332
  }
323
333
  },
324
334
  "additionalProperties": false,
@@ -418,7 +428,13 @@
418
428
  "lazy": {
419
429
  "type": "boolean",
420
430
  "description": "If the bundle will be lazy loaded.",
421
- "default": false
431
+ "default": false,
432
+ "x-deprecated": "Use 'inject' option with 'false' value instead."
433
+ },
434
+ "inject": {
435
+ "type": "boolean",
436
+ "description": "If the bundle will be referenced in the HTML file.",
437
+ "default": true
422
438
  }
423
439
  },
424
440
  "additionalProperties": false,
@@ -15,11 +15,13 @@ const fs_1 = require("fs");
15
15
  const path = require("path");
16
16
  const rxjs_1 = require("rxjs");
17
17
  const operators_1 = require("rxjs/operators");
18
+ const ts = require("typescript");
18
19
  const url = require("url");
19
20
  const webpack = require("webpack");
20
21
  const index_html_webpack_plugin_1 = require("../angular-cli-files/plugins/index-html-webpack-plugin");
21
22
  const check_port_1 = require("../angular-cli-files/utilities/check-port");
22
23
  const package_chunk_sort_1 = require("../angular-cli-files/utilities/package-chunk-sort");
24
+ const read_tsconfig_1 = require("../angular-cli-files/utilities/read-tsconfig");
23
25
  const browser_1 = require("../browser");
24
26
  const utils_1 = require("../utils");
25
27
  const version_1 = require("../utils/version");
@@ -53,8 +55,7 @@ function serveWebpackBrowser(options, context, transforms = {}) {
53
55
  const root = context.workspaceRoot;
54
56
  let first = true;
55
57
  const host = new node_1.NodeJsSyncHost();
56
- const loggingFn = transforms.logging
57
- || browser_1.createBrowserLoggingCallback(!!options.verbose, context.logger);
58
+ const loggingFn = transforms.logging || browser_1.createBrowserLoggingCallback(!!options.verbose, context.logger);
58
59
  async function setup() {
59
60
  // Get the browser configuration from the target name.
60
61
  const rawBrowserOptions = await context.getTargetOptions(browserTarget);
@@ -73,13 +74,19 @@ function serveWebpackBrowser(options, context, transforms = {}) {
73
74
  // No differential loading for dev-server, hence there is just one config
74
75
  let webpackConfig = webpackConfigResult.config[0];
75
76
  const port = await check_port_1.checkPort(options.port || 0, options.host || 'localhost', 4200);
76
- const webpackDevServerConfig = webpackConfig.devServer = buildServerConfig(root, options, browserOptions, context.logger);
77
+ const webpackDevServerConfig = (webpackConfig.devServer = buildServerConfig(root, options, browserOptions, context.logger));
77
78
  if (transforms.webpackConfiguration) {
78
79
  webpackConfig = await transforms.webpackConfiguration(webpackConfig);
79
80
  }
80
- return { browserOptions, webpackConfig, webpackDevServerConfig, port };
81
+ return {
82
+ browserOptions,
83
+ webpackConfig,
84
+ webpackDevServerConfig,
85
+ port,
86
+ workspace: webpackConfigResult.workspace,
87
+ };
81
88
  }
82
- return rxjs_1.from(setup()).pipe(operators_1.switchMap(({ browserOptions, webpackConfig, webpackDevServerConfig, port }) => {
89
+ return rxjs_1.from(setup()).pipe(operators_1.switchMap(({ browserOptions, webpackConfig, webpackDevServerConfig, port, workspace }) => {
83
90
  options.port = port;
84
91
  // Resolve public host and client address.
85
92
  let clientAddress = url.parse(`${options.ssl ? 'https' : 'http'}://0.0.0.0:0`);
@@ -112,16 +119,32 @@ function serveWebpackBrowser(options, context, transforms = {}) {
112
119
  });
113
120
  }
114
121
  if (browserOptions.index) {
115
- const { scripts = [], styles = [], index, baseHref } = browserOptions;
122
+ const { scripts = [], styles = [], index, baseHref, tsConfig } = browserOptions;
123
+ const projectName = context.target
124
+ ? context.target.project
125
+ : workspace.getDefaultProjectName();
126
+ if (!projectName) {
127
+ throw new Error('Must either have a target from the context or a default project.');
128
+ }
129
+ const projectRoot = core_1.resolve(workspace.root, core_1.normalize(workspace.getProject(projectName).root));
130
+ const { options: compilerOptions } = read_tsconfig_1.readTsconfig(tsConfig, context.workspaceRoot);
131
+ const target = compilerOptions.target || ts.ScriptTarget.ES5;
132
+ const buildBrowserFeatures = new utils_1.BuildBrowserFeatures(core_1.getSystemPath(projectRoot), target);
133
+ const entrypoints = package_chunk_sort_1.generateEntryPoints({ scripts, styles });
134
+ const moduleEntrypoints = buildBrowserFeatures.isDifferentialLoadingNeeded()
135
+ ? package_chunk_sort_1.generateEntryPoints({ scripts: [], styles })
136
+ : [];
116
137
  webpackConfig.plugins.push(new index_html_webpack_plugin_1.IndexHtmlWebpackPlugin({
117
138
  input: path.resolve(root, index),
118
139
  output: path.basename(index),
119
140
  baseHref,
120
- entrypoints: package_chunk_sort_1.generateEntryPoints({ scripts, styles }),
141
+ moduleEntrypoints,
142
+ entrypoints,
121
143
  deployUrl: browserOptions.deployUrl,
122
144
  sri: browserOptions.subresourceIntegrity,
123
145
  noModuleEntrypoints: ['polyfills-es5'],
124
146
  postTransform: transforms.indexHtml,
147
+ crossOrigin: browserOptions.crossOrigin,
125
148
  }));
126
149
  }
127
150
  const normalizedOptimization = utils_1.normalizeOptimization(browserOptions.optimization);
@@ -196,17 +219,18 @@ function buildServerConfig(workspaceRoot, serverOptions, browserOptions, logger)
196
219
  host: serverOptions.host,
197
220
  port: serverOptions.port,
198
221
  headers: { 'Access-Control-Allow-Origin': '*' },
199
- historyApiFallback: !!browserOptions.index && {
200
- index: `${servePath}/${path.basename(browserOptions.index)}`,
201
- disableDotRule: true,
202
- htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
203
- rewrites: [
204
- {
205
- from: new RegExp(`^(?!${servePath})/.*`),
206
- to: context => url.format(context.parsedUrl),
207
- },
208
- ],
209
- },
222
+ historyApiFallback: !!browserOptions.index &&
223
+ {
224
+ index: `${servePath}/${path.basename(browserOptions.index)}`,
225
+ disableDotRule: true,
226
+ htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
227
+ rewrites: [
228
+ {
229
+ from: new RegExp(`^(?!${servePath})/.*`),
230
+ to: context => url.format(context.parsedUrl),
231
+ },
232
+ ],
233
+ },
210
234
  stats: false,
211
235
  compress: styles || scripts,
212
236
  watchOptions: {
@@ -282,10 +306,12 @@ function _addLiveReload(options, browserOptions, webpackConfig, clientAddress, l
282
306
  }
283
307
  // If a custom path is provided the webpack dev server client drops the sockjs-node segment.
284
308
  // This adds it back so that behavior is consistent when using a custom URL path
309
+ let sockjsPath = '';
285
310
  if (clientAddress.pathname) {
286
311
  clientAddress.pathname = path.posix.join(clientAddress.pathname, 'sockjs-node');
312
+ sockjsPath = '&sockPath=' + clientAddress.pathname;
287
313
  }
288
- const entryPoints = [`${webpackDevServerPath}?${url.format(clientAddress)}`];
314
+ const entryPoints = [`${webpackDevServerPath}?${url.format(clientAddress)}${sockjsPath}`];
289
315
  if (options.hmr) {
290
316
  const webpackHmrLink = 'https://webpack.js.org/guides/hot-module-replacement';
291
317
  logger.warn(core_1.tags.oneLine `NOTICE: Hot Module Replacement (HMR) is enabled for the dev server.`);
@@ -375,9 +401,7 @@ function _findDefaultServePath(baseHref, deployUrl) {
375
401
  // normalize baseHref
376
402
  // for ng serve the starting base is always `/` so a relative
377
403
  // and root relative value are identical
378
- const baseHrefParts = (baseHref || '')
379
- .split('/')
380
- .filter(part => part !== '');
404
+ const baseHrefParts = (baseHref || '').split('/').filter(part => part !== '');
381
405
  if (baseHref && !baseHref.endsWith('/')) {
382
406
  baseHrefParts.pop();
383
407
  }
@@ -8,14 +8,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
8
8
  * found in the LICENSE file at https://angular.io/license
9
9
  */
10
10
  const architect_1 = require("@angular-devkit/architect");
11
+ const core_1 = require("@angular-devkit/core");
11
12
  const path_1 = require("path");
12
13
  const rxjs_1 = require("rxjs");
13
14
  const operators_1 = require("rxjs/operators");
14
15
  const webpack_configs_1 = require("../angular-cli-files/models/webpack-configs");
16
+ const single_test_transform_1 = require("../angular-cli-files/plugins/single-test-transform");
17
+ const find_tests_1 = require("../angular-cli-files/utilities/find-tests");
15
18
  const version_1 = require("../utils/version");
16
19
  const webpack_browser_config_1 = require("../utils/webpack-browser-config");
17
20
  async function initialize(options, context, webpackConfigurationTransformer) {
18
- const { config } = await webpack_browser_config_1.generateBrowserWebpackConfigFromContext(
21
+ const { config, workspace } = await webpack_browser_config_1.generateBrowserWebpackConfigFromContext(
19
22
  // only two properties are missing:
20
23
  // * `outputPath` which is fixed for tests
21
24
  // * `budgets` which might be incorrect due to extra dev libs
@@ -29,6 +32,7 @@ async function initialize(options, context, webpackConfigurationTransformer) {
29
32
  // tslint:disable-next-line:no-implicit-dependencies
30
33
  const karma = await Promise.resolve().then(() => require('karma'));
31
34
  return [
35
+ workspace,
32
36
  karma,
33
37
  webpackConfigurationTransformer ? await webpackConfigurationTransformer(config[0]) : config[0],
34
38
  ];
@@ -36,7 +40,7 @@ async function initialize(options, context, webpackConfigurationTransformer) {
36
40
  function execute(options, context, transforms = {}) {
37
41
  // Check Angular version.
38
42
  version_1.assertCompatibleAngularVersion(context.workspaceRoot, context.logger);
39
- return rxjs_1.from(initialize(options, context, transforms.webpackConfiguration)).pipe(operators_1.switchMap(([karma, webpackConfig]) => new rxjs_1.Observable(subscriber => {
43
+ return rxjs_1.from(initialize(options, context, transforms.webpackConfiguration)).pipe(operators_1.switchMap(([workspace, karma, webpackConfig]) => new rxjs_1.Observable(subscriber => {
40
44
  const karmaOptions = {};
41
45
  if (options.watch !== undefined) {
42
46
  karmaOptions.singleRun = !options.watch;
@@ -54,6 +58,30 @@ function execute(options, context, transforms = {}) {
54
58
  karmaOptions.reporters = reporters;
55
59
  }
56
60
  }
61
+ // prepend special webpack loader that will transform test.ts
62
+ if (webpackConfig &&
63
+ webpackConfig.module &&
64
+ options.include &&
65
+ options.include.length > 0) {
66
+ const mainFilePath = core_1.getSystemPath(core_1.join(workspace.root, options.main));
67
+ const files = find_tests_1.findTests(options.include, path_1.dirname(mainFilePath), core_1.getSystemPath(workspace.root));
68
+ // early exit, no reason to start karma
69
+ if (!files.length) {
70
+ subscriber.error(`Specified patterns: "${options.include.join(', ')}" did not match any spec files`);
71
+ return;
72
+ }
73
+ webpackConfig.module.rules.unshift({
74
+ test: path => path === mainFilePath,
75
+ use: {
76
+ // cannot be a simple path as it differs between environments
77
+ loader: single_test_transform_1.SingleTestTransformLoader,
78
+ options: {
79
+ files,
80
+ logger: context.logger,
81
+ },
82
+ },
83
+ });
84
+ }
57
85
  // Assign additional karmaConfig options to the local ngapp config
58
86
  karmaOptions.configFile = path_1.resolve(context.workspaceRoot, options.karmaConfig);
59
87
  karmaOptions.buildWebpack = {
@@ -31,6 +31,15 @@ export interface Schema {
31
31
  * Replace files with other files in the build.
32
32
  */
33
33
  fileReplacements?: FileReplacement[];
34
+ /**
35
+ * Globs of files to include, relative to workspace or project root.
36
+ * There are 2 special cases:
37
+ * - when a path to directory is provided, all spec files ending ".spec.@(ts|tsx)" will be
38
+ * included
39
+ * - when a path to a file is provided, and a matching spec file exists it will be included
40
+ * instead
41
+ */
42
+ include?: string[];
34
43
  /**
35
44
  * The name of the Karma configuration file.
36
45
  */
@@ -124,6 +133,10 @@ export interface ExtraEntryPointClass {
124
133
  * The bundle name for this extra entry point.
125
134
  */
126
135
  bundleName?: string;
136
+ /**
137
+ * If the bundle will be referenced in the HTML file.
138
+ */
139
+ inject?: boolean;
127
140
  /**
128
141
  * The file to include.
129
142
  */
@@ -63,6 +63,13 @@
63
63
  "type": "string",
64
64
  "description": "Defines the build environment."
65
65
  },
66
+ "include": {
67
+ "type": "array",
68
+ "items": {
69
+ "type": "string"
70
+ },
71
+ "description": "Globs of files to include, relative to workspace or project root. \nThere are 2 special cases:\n - when a path to directory is provided, all spec files ending \".spec.@(ts|tsx)\" will be included\n - when a path to a file is provided, and a matching spec file exists it will be included instead"
72
+ },
66
73
  "sourceMap": {
67
74
  "description": "Output sourcemaps.",
68
75
  "default": true,
@@ -251,7 +258,13 @@
251
258
  "lazy": {
252
259
  "type": "boolean",
253
260
  "description": "If the bundle will be lazy loaded.",
254
- "default": false
261
+ "default": false,
262
+ "x-deprecated": "Use 'inject' option with 'false' value instead."
263
+ },
264
+ "inject": {
265
+ "type": "boolean",
266
+ "description": "If the bundle will be referenced in the HTML file.",
267
+ "default": true
255
268
  }
256
269
  },
257
270
  "additionalProperties": false,
@@ -27,7 +27,7 @@ async function generateWebpackConfig(context, workspaceRoot, projectRoot, source
27
27
  && buildBrowserFeatures.isDifferentialLoadingNeeded();
28
28
  const scriptTargets = [scriptTarget];
29
29
  if (differentialLoading) {
30
- scriptTargets.unshift(ts.ScriptTarget.ES5);
30
+ scriptTargets.push(ts.ScriptTarget.ES5);
31
31
  }
32
32
  // For differential loading, we can have several targets
33
33
  return scriptTargets.map(scriptTarget => {
package/test/utils.d.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  import { Architect, BuilderOutput, ScheduleOptions, Target } from '@angular-devkit/architect';
9
9
  import { TestProjectHost, TestingArchitectHost } from '@angular-devkit/architect/testing';
10
10
  import { Path, experimental, json, virtualFs } from '@angular-devkit/core';
11
+ export declare const ivyEnabled: boolean;
11
12
  export declare const workspaceRoot: Path;
12
13
  export declare const host: TestProjectHost;
13
14
  export declare const outputPath: Path;
@@ -43,7 +44,7 @@ export declare function createArchitect(workspaceRoot: Path): Promise<{
43
44
  export declare function browserBuild(architect: Architect, host: virtualFs.Host, target: Target, overrides?: json.JsonObject, scheduleOptions?: ScheduleOptions): Promise<{
44
45
  output: BuilderOutput;
45
46
  files: {
46
- [file: string]: string;
47
+ [file: string]: Promise<string>;
47
48
  };
48
49
  }>;
49
50
  export declare const lazyModuleFiles: {