Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,15 @@
* found in the LICENSE file at https://angular.io/license
*/
import { LicenseWebpackPlugin } from 'license-webpack-plugin';
import * as path from 'path';
import * as webpack from 'webpack';
import { IndexHtmlWebpackPlugin } from '../../plugins/index-html-webpack-plugin';
import { generateEntryPoints } from '../../utilities/package-chunk-sort';
import { WebpackConfigOptions } from '../build-options';
import { getSourceMapDevTool, isPolyfillsEntry, normalizeExtraEntryPoints } from './utils';

const SubresourceIntegrityPlugin = require('webpack-subresource-integrity');


export function getBrowserConfig(wco: WebpackConfigOptions): webpack.Configuration {
const { root, buildOptions } = wco;
const { buildOptions } = wco;
const extraPlugins = [];

let isEval = false;
Expand All @@ -37,18 +34,6 @@ export function getBrowserConfig(wco: WebpackConfigOptions): webpack.Configurati
isEval = true;
}

if (buildOptions.index) {
extraPlugins.push(new IndexHtmlWebpackPlugin({
input: path.resolve(root, buildOptions.index),
output: path.basename(buildOptions.index),
baseHref: buildOptions.baseHref,
entrypoints: generateEntryPoints(buildOptions),
deployUrl: buildOptions.deployUrl,
sri: buildOptions.subresourceIntegrity,
noModuleEntrypoints: ['polyfills-es5'],
}));
}

if (buildOptions.subresourceIntegrity) {
extraPlugins.push(new SubresourceIntegrityPlugin({
hashFuncNames: ['sha384'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
*/
import * as path from 'path';
import { Compiler, compilation } from 'webpack';
import { RawSource } from 'webpack-sources';
import { FileInfo, augmentIndexHtml } from '../utilities/index-file/augment-index-html';
import { IndexHtmlTransform } from '../utilities/index-file/write-index-html';
import { stripBom } from '../utilities/strip-bom';

export interface IndexHtmlWebpackPluginOptions {
input: string;
Expand All @@ -17,6 +20,7 @@ export interface IndexHtmlWebpackPluginOptions {
deployUrl?: string;
sri: boolean;
noModuleEntrypoints: string[];
postTransform?: IndexHtmlTransform;
}

function readFile(filename: string, compilation: compilation.Compilation): Promise<string> {
Expand All @@ -28,18 +32,7 @@ function readFile(filename: string, compilation: compilation.Compilation): Promi
return;
}

let content;
if (data.length >= 3 && data[0] === 0xEF && data[1] === 0xBB && data[2] === 0xBF) {
// Strip UTF-8 BOM
content = data.toString('utf8', 3);
} else if (data.length >= 2 && data[0] === 0xFF && data[1] === 0xFE) {
// Strip UTF-16 LE BOM
content = data.toString('utf16le', 2);
} else {
content = data.toString();
}

resolve(content);
resolve(stripBom(data.toString()));
});
});
}
Expand Down Expand Up @@ -86,7 +79,7 @@ export class IndexHtmlWebpackPlugin {
}

const loadOutputFile = (name: string) => compilation.assets[name].source();
const indexSource = await augmentIndexHtml({
let indexSource = await augmentIndexHtml({
input: this._options.input,
inputContent,
baseHref: this._options.baseHref,
Expand All @@ -98,8 +91,12 @@ export class IndexHtmlWebpackPlugin {
entrypoints: this._options.entrypoints,
});

if (this._options.postTransform) {
indexSource = await this._options.postTransform(indexSource);
}

// Add to compilation assets
compilation.assets[this._options.output] = indexSource;
compilation.assets[this._options.output] = new RawSource(indexSource);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@
*/

import { createHash } from 'crypto';
import {
RawSource,
ReplaceSource,
Source,
} from 'webpack-sources';
import { RawSource, ReplaceSource } from 'webpack-sources';

const parse5 = require('parse5');

Expand Down Expand Up @@ -57,7 +53,7 @@ export interface FileInfo {
* after processing several configurations in order to build different sets of
* bundles for differential serving.
*/
export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise<Source> {
export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise<string> {
const {
loadOutputFile,
files,
Expand Down Expand Up @@ -236,7 +232,7 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise
parse5.serialize(styleElements, { treeAdapter }),
);

return indexSource;
return indexSource.source();
}

function _generateSriAttributes(content: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('augment-index-html', () => {
],
});

const html = (await source).source();
const html = await source;
expect(html).toEqual(oneLineHtml`
<html>
<head><base href="/">
Expand Down Expand Up @@ -74,7 +74,7 @@ describe('augment-index-html', () => {
noModuleFiles: es5JsFiles,
});

const html = (await source).source();
const html = await source;
expect(html).toEqual(oneLineHtml`
<html>
<head>
Expand Down Expand Up @@ -116,7 +116,7 @@ describe('augment-index-html', () => {
noModuleFiles: es5JsFiles,
});

const html = (await source).source();
const html = await source;
expect(html).toEqual(oneLineHtml`
<html>
<head>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,45 @@

import { EmittedFiles } from '@angular-devkit/build-webpack';
import { Path, basename, getSystemPath, join, virtualFs } from '@angular-devkit/core';
import { Observable } from 'rxjs';
import { Observable, of } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { ExtraEntryPoint } from '../../../browser/schema';
import { generateEntryPoints } from '../package-chunk-sort';
import { stripBom } from '../strip-bom';
import { FileInfo, augmentIndexHtml } from './augment-index-html';

type ExtensionFilter = '.js' | '.css';

export interface WriteIndexHtmlOptions {
host: virtualFs.Host;
outputPath: Path;
indexPath: Path;
ES5BuildFiles: EmittedFiles[];
ES2015BuildFiles: EmittedFiles[];
files?: EmittedFiles[];
noModuleFiles?: EmittedFiles[];
moduleFiles?: EmittedFiles[];
baseHref?: string;
deployUrl?: string;
sri?: boolean;
scripts?: ExtraEntryPoint[];
styles?: ExtraEntryPoint[];
postTransform?: IndexHtmlTransform;
}

export type IndexHtmlTransform = (content: string) => Promise<string>;

export function writeIndexHtml({
host,
outputPath,
indexPath,
ES5BuildFiles,
ES2015BuildFiles,
files = [],
noModuleFiles = [],
moduleFiles = [],
baseHref,
deployUrl,
sri = false,
scripts = [],
styles = [],
postTransform,
}: WriteIndexHtmlOptions): Observable<void> {

return host.read(indexPath)
Expand All @@ -51,9 +59,9 @@ export function writeIndexHtml({
deployUrl,
sri,
entrypoints: generateEntryPoints({ scripts, styles }),
files: filterAndMapBuildFiles(ES5BuildFiles, '.css'),
noModuleFiles: filterAndMapBuildFiles(ES5BuildFiles, '.js'),
moduleFiles: filterAndMapBuildFiles(ES2015BuildFiles, '.js'),
files: filterAndMapBuildFiles(files, ['.js', '.css']),
noModuleFiles: filterAndMapBuildFiles(noModuleFiles, '.js'),
moduleFiles: filterAndMapBuildFiles(moduleFiles, '.js'),
loadOutputFile: async filePath => {
return host.read(join(outputPath, filePath))
.pipe(
Expand All @@ -63,18 +71,23 @@ export function writeIndexHtml({
},
}),
),
map(content => virtualFs.stringToFileBuffer(content.source())),
switchMap(content => postTransform ? postTransform(content) : of(content)),
map(content => virtualFs.stringToFileBuffer(content)),
switchMap(content => host.write(join(outputPath, basename(indexPath)), content)),
);
}

function filterAndMapBuildFiles(
files: EmittedFiles[],
extensionFilter: '.js' | '.css',
extensionFilter: ExtensionFilter | ExtensionFilter[],
): FileInfo[] {
const filteredFiles: FileInfo[] = [];
const validExtensions: string[] = Array.isArray(extensionFilter)
? extensionFilter
: [extensionFilter];

for (const { file, name, extension, initial } of files) {
if (name && initial && extension === extensionFilter) {
if (name && initial && validExtensions.includes(extension)) {
filteredFiles.push({ file, extension, name });
}
}
Expand Down
38 changes: 31 additions & 7 deletions packages/angular_devkit/build_angular/src/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ import {
BuilderOutput,
createBuilder,
} from '@angular-devkit/architect';
import { BuildResult, WebpackLoggingCallback, runWebpack } from '@angular-devkit/build-webpack';
import {
BuildResult,
EmittedFiles,
WebpackLoggingCallback,
runWebpack,
} from '@angular-devkit/build-webpack';
import {
experimental,
getSystemPath,
Expand Down Expand Up @@ -40,7 +45,10 @@ import {
getStylesConfig,
getWorkerConfig,
} from '../angular-cli-files/models/webpack-configs';
import { writeIndexHtml } from '../angular-cli-files/utilities/index-file/write-index-html';
import {
IndexHtmlTransform,
writeIndexHtml,
} from '../angular-cli-files/utilities/index-file/write-index-html';
import { readTsconfig } from '../angular-cli-files/utilities/read-tsconfig';
import { augmentAppWithServiceWorker } from '../angular-cli-files/utilities/service-worker';
import {
Expand Down Expand Up @@ -165,6 +173,7 @@ export function buildWebpackBrowser(
transforms: {
webpackConfiguration?: ExecutionTransformer<webpack.Configuration>,
logging?: WebpackLoggingCallback,
indexHtml?: IndexHtmlTransform,
} = {},
) {
const host = new NodeJsSyncHost();
Expand Down Expand Up @@ -217,21 +226,36 @@ export function buildWebpackBrowser(
bufferCount(configs.length),
switchMap(buildEvents => {
const success = buildEvents.every(r => r.success);
if (success && buildEvents.length === 2 && options.index) {
const { emittedFiles: ES5BuildFiles = [] } = buildEvents[0];
const { emittedFiles: ES2015BuildFiles = [] } = buildEvents[1];
if (success && options.index) {
let noModuleFiles: EmittedFiles[] | undefined;
let moduleFiles: EmittedFiles[] | undefined;
let files: EmittedFiles[] | undefined;

const [ES5Result, ES2015Result] = buildEvents;

if (buildEvents.length === 2) {
noModuleFiles = ES5Result.emittedFiles;
moduleFiles = ES2015Result.emittedFiles || [];
files = moduleFiles.filter(x => x.extension === '.css');
} else {
const { emittedFiles = [] } = ES5Result;
files = emittedFiles.filter(x => x.name !== 'polyfills-es5');
noModuleFiles = emittedFiles.filter(x => x.name === 'polyfills-es5');
}

return writeIndexHtml({
host,
outputPath: join(root, options.outputPath),
indexPath: join(root, options.index),
ES5BuildFiles,
ES2015BuildFiles,
files,
noModuleFiles,
moduleFiles,
baseHref: options.baseHref,
deployUrl: options.deployUrl,
sri: options.subresourceIntegrity,
scripts: options.scripts,
styles: options.styles,
postTransform: transforms.indexHtml,
})
.pipe(
map(() => ({ success: true })),
Expand Down
27 changes: 24 additions & 3 deletions packages/angular_devkit/build_angular/src/dev-server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
WebpackLoggingCallback,
runWebpackDevServer,
} from '@angular-devkit/build-webpack';
import { experimental, json, logging, tags } from '@angular-devkit/core';
import { json, logging, tags } from '@angular-devkit/core';
import { NodeJsSyncHost } from '@angular-devkit/core/node';
import { existsSync, readFileSync } from 'fs';
import * as path from 'path';
Expand All @@ -25,7 +25,10 @@ import { map, switchMap } from 'rxjs/operators';
import * as url from 'url';
import * as webpack from 'webpack';
import * as WebpackDevServer from 'webpack-dev-server';
import { IndexHtmlWebpackPlugin } from '../angular-cli-files/plugins/index-html-webpack-plugin';
import { checkPort } from '../angular-cli-files/utilities/check-port';
import { IndexHtmlTransform } from '../angular-cli-files/utilities/index-file/write-index-html';
import { generateEntryPoints } from '../angular-cli-files/utilities/package-chunk-sort';
import {
buildBrowserWebpackConfigFromContext,
createBrowserLoggingCallback,
Expand Down Expand Up @@ -73,6 +76,7 @@ export function serveWebpackBrowser(
transforms: {
webpackConfiguration?: ExecutionTransformer<webpack.Configuration>,
logging?: WebpackLoggingCallback,
indexHtml?: IndexHtmlTransform,
} = {},
): Observable<DevServerBuilderOutput> {
// Check Angular version.
Expand Down Expand Up @@ -158,17 +162,34 @@ export function serveWebpackBrowser(
context.logger.warn('Live reload is disabled. HMR option ignored.');
}

webpackConfig.plugins = [...(webpackConfig.plugins || [])];

if (!options.watch) {
// There's no option to turn off file watching in webpack-dev-server, but
// we can override the file watcher instead.
webpackConfig.plugins = [...(webpackConfig.plugins || []), {
webpackConfig.plugins.push({
// tslint:disable-next-line:no-any
apply: (compiler: any) => {
compiler.hooks.afterEnvironment.tap('angular-cli', () => {
compiler.watchFileSystem = { watch: () => { } };
});
},
}];
});
}

if (browserOptions.index) {
const { scripts = [], styles = [], index, baseHref } = browserOptions;

webpackConfig.plugins.push(new IndexHtmlWebpackPlugin({
input: path.resolve(root, index),
output: path.basename(index),
baseHref,
entrypoints: generateEntryPoints({ scripts, styles }),
deployUrl: browserOptions.deployUrl,
sri: browserOptions.subresourceIntegrity,
noModuleEntrypoints: ['polyfills-es5'],
postTransform: transforms.indexHtml,
}));
}

const normalizedOptimization = normalizeOptimization(browserOptions.optimization);
Expand Down
1 change: 0 additions & 1 deletion packages/angular_devkit/build_angular/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ async function buildServerWebpackConfig(
const { config } = await generateBrowserWebpackConfigFromContext(
{
...options,
index: '',
buildOptimizer: false,
aot: true,
platform: 'server',
Expand Down
Loading