diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/index-html-webpack-plugin.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/index-html-webpack-plugin.ts index 7bdbded397d8..6f91eab8cf21 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/index-html-webpack-plugin.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/index-html-webpack-plugin.ts @@ -20,6 +20,7 @@ export interface IndexHtmlWebpackPluginOptions { deployUrl?: string; sri: boolean; noModuleEntrypoints: string[]; + moduleEntrypoints: string[]; postTransform?: IndexHtmlTransform; } @@ -37,7 +38,6 @@ function readFile(filename: string, compilation: compilation.Compilation): Promi }); } - export class IndexHtmlWebpackPlugin { private _options: IndexHtmlWebpackPluginOptions; @@ -47,6 +47,7 @@ export class IndexHtmlWebpackPlugin { output: 'index.html', entrypoints: ['polyfills', 'main'], noModuleEntrypoints: [], + moduleEntrypoints: [], sri: false, ...options, }; @@ -56,23 +57,28 @@ export class IndexHtmlWebpackPlugin { compiler.hooks.emit.tapPromise('index-html-webpack-plugin', async compilation => { // Get input html file const inputContent = await readFile(this._options.input, compilation); - (compilation as compilation.Compilation & { fileDependencies: Set }) - .fileDependencies.add(this._options.input); + (compilation as compilation.Compilation & { + fileDependencies: Set; + }).fileDependencies.add(this._options.input); // Get all files for selected entrypoints const files: FileInfo[] = []; const noModuleFiles: FileInfo[] = []; + const moduleFiles: FileInfo[] = []; for (const [entryName, entrypoint] of compilation.entrypoints) { - const entryFiles: FileInfo[] = (entrypoint && entrypoint.getFiles() || []) - .map((f: string): FileInfo => ({ + const entryFiles: FileInfo[] = ((entrypoint && entrypoint.getFiles()) || []).map( + (f: string): FileInfo => ({ name: entryName, file: f, extension: path.extname(f), - })); + }), + ); if (this._options.noModuleEntrypoints.includes(entryName)) { noModuleFiles.push(...entryFiles); + } else if (this._options.moduleEntrypoints.includes(entryName)) { + moduleFiles.push(...entryFiles); } else { files.push(...entryFiles); } @@ -88,6 +94,7 @@ export class IndexHtmlWebpackPlugin { files, noModuleFiles, loadOutputFile, + moduleFiles, entrypoints: this._options.entrypoints, }); diff --git a/packages/angular_devkit/build_angular/src/browser/index.ts b/packages/angular_devkit/build_angular/src/browser/index.ts index 4550bb570576..fca271e2fafd 100644 --- a/packages/angular_devkit/build_angular/src/browser/index.ts +++ b/packages/angular_devkit/build_angular/src/browser/index.ts @@ -5,17 +5,13 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { - BuilderContext, - BuilderOutput, - createBuilder, -} from '@angular-devkit/architect'; +import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect'; import { BuildResult, EmittedFiles, WebpackLoggingCallback, runWebpack, - } from '@angular-devkit/build-webpack'; +} from '@angular-devkit/build-webpack'; import { experimental, getSystemPath, @@ -62,9 +58,10 @@ import { assertCompatibleAngularVersion } from '../utils/version'; import { generateBrowserWebpackConfigFromContext } from '../utils/webpack-browser-config'; import { Schema as BrowserBuilderSchema } from './schema'; -export type BrowserBuilderOutput = json.JsonObject & BuilderOutput & { - outputPath: string; -}; +export type BrowserBuilderOutput = json.JsonObject & + BuilderOutput & { + outputPath: string; + }; export function createBrowserLoggingCallback( verbose: boolean, @@ -92,7 +89,7 @@ export async function buildBrowserWebpackConfigFromContext( options: BrowserBuilderSchema, context: BuilderContext, host: virtualFs.Host = new NodeJsSyncHost(), -): Promise<{ workspace: experimental.workspace.Workspace, config: webpack.Configuration[] }> { +): Promise<{ workspace: experimental.workspace.Workspace; config: webpack.Configuration[] }> { return generateBrowserWebpackConfigFromContext( options, context, @@ -125,9 +122,7 @@ function getAnalyticsConfig( // The category is the builder name if it's an angular builder. return { - plugins: [ - new NgBuildAnalyticsPlugin(wco.projectRoot, context.analytics, category), - ], + plugins: [new NgBuildAnalyticsPlugin(wco.projectRoot, context.analytics, category)], }; } @@ -147,7 +142,7 @@ async function initialize( context: BuilderContext, host: virtualFs.Host, webpackConfigurationTransform?: ExecutionTransformer, -): Promise<{ workspace: experimental.workspace.Workspace, config: webpack.Configuration[] }> { +): Promise<{ workspace: experimental.workspace.Workspace; config: webpack.Configuration[] }> { const { config, workspace } = await buildBrowserWebpackConfigFromContext(options, context, host); let transformedConfig; @@ -173,9 +168,9 @@ export function buildWebpackBrowser( options: BrowserBuilderSchema, context: BuilderContext, transforms: { - webpackConfiguration?: ExecutionTransformer, - logging?: WebpackLoggingCallback, - indexHtml?: IndexHtmlTransform, + webpackConfiguration?: ExecutionTransformer; + logging?: WebpackLoggingCallback; + indexHtml?: IndexHtmlTransform; } = {}, ) { const host = new NodeJsSyncHost(); @@ -184,13 +179,14 @@ export function buildWebpackBrowser( // Check Angular version. assertCompatibleAngularVersion(context.workspaceRoot, context.logger); - const loggingFn = transforms.logging - || createBrowserLoggingCallback(!!options.verbose, context.logger); + const loggingFn = + transforms.logging || createBrowserLoggingCallback(!!options.verbose, context.logger); return from(initialize(options, context, host, transforms.webpackConfiguration)).pipe( switchMap(({ workspace, config: configs }) => { const projectName = context.target - ? context.target.project : workspace.getDefaultProjectName(); + ? context.target.project + : workspace.getDefaultProjectName(); if (!projectName) { throw new Error('Must either have a target from the context or a default project.'); @@ -203,12 +199,11 @@ export function buildWebpackBrowser( const tsConfig = readTsconfig(options.tsConfig, context.workspaceRoot); const target = tsConfig.options.target || ScriptTarget.ES5; - const buildBrowserFeatures = new BuildBrowserFeatures( - getSystemPath(projectRoot), - target, - ); + const buildBrowserFeatures = new BuildBrowserFeatures(getSystemPath(projectRoot), target); - if (target > ScriptTarget.ES2015 && buildBrowserFeatures.isDifferentialLoadingNeeded()) { + const isDifferentialLoadingNeeded = buildBrowserFeatures.isDifferentialLoadingNeeded(); + + if (target > ScriptTarget.ES2015 && isDifferentialLoadingNeeded) { context.logger.warn(tags.stripIndent` WARNING: Using differential loading with targets ES5 and ES2016 or higher may cause problems. Browsers with support for ES2015 will load the ES2016+ scripts @@ -219,14 +214,18 @@ export function buildWebpackBrowser( return from(configs).pipe( // the concurrency parameter (3rd parameter of mergeScan) is deliberately // set to 1 to make sure the build steps are executed in sequence. - mergeScan((lastResult, config) => { - // Make sure to only run the 2nd build step, if 1st one succeeded - if (lastResult.success) { - return runWebpack(config, context, { logging: loggingFn }); - } else { - return of(); - } - }, { success: true } as BuildResult, 1), + mergeScan( + (lastResult, config) => { + // Make sure to only run the 2nd build step, if 1st one succeeded + if (lastResult.success) { + return runWebpack(config, context, { logging: loggingFn }); + } else { + return of(); + } + }, + { success: true } as BuildResult, + 1, + ), bufferCount(configs.length), switchMap(buildEvents => { const success = buildEvents.every(r => r.success); @@ -235,14 +234,19 @@ export function buildWebpackBrowser( let moduleFiles: EmittedFiles[] | undefined; let files: EmittedFiles[] | undefined; - const [ES5Result, ES2015Result] = buildEvents; + const [firstBuild, secondBuild] = buildEvents; if (buildEvents.length === 2) { - noModuleFiles = ES5Result.emittedFiles; - moduleFiles = ES2015Result.emittedFiles || []; + noModuleFiles = firstBuild.emittedFiles; + moduleFiles = secondBuild.emittedFiles || []; + files = moduleFiles.filter(x => x.extension === '.css'); + } else if (options.watch && isDifferentialLoadingNeeded) { + // differential loading is not enabled in watch mode + // but we still want to use module type tags + moduleFiles = firstBuild.emittedFiles || []; files = moduleFiles.filter(x => x.extension === '.css'); } else { - const { emittedFiles = [] } = ES5Result; + const { emittedFiles = [] } = firstBuild; files = emittedFiles.filter(x => x.name !== 'polyfills-es5'); noModuleFiles = emittedFiles.filter(x => x.name === 'polyfills-es5'); } @@ -260,8 +264,7 @@ export function buildWebpackBrowser( scripts: options.scripts, styles: options.styles, postTransform: transforms.indexHtml, - }) - .pipe( + }).pipe( map(() => ({ success: true })), catchError(error => of({ success: false, error: mapErrorToMessage(error) })), ); @@ -271,26 +274,31 @@ export function buildWebpackBrowser( }), concatMap(buildEvent => { if (buildEvent.success && !options.watch && options.serviceWorker) { - return from(augmentAppWithServiceWorker( - host, - root, - projectRoot, - resolve(root, normalize(options.outputPath)), - options.baseHref || '/', - options.ngswConfigPath, - ).then( - () => ({ success: true }), - error => ({ success: false, error: mapErrorToMessage(error) }), - )); + return from( + augmentAppWithServiceWorker( + host, + root, + projectRoot, + resolve(root, normalize(options.outputPath)), + options.baseHref || '/', + options.ngswConfigPath, + ).then( + () => ({ success: true }), + error => ({ success: false, error: mapErrorToMessage(error) }), + ), + ); } else { return of(buildEvent); } }), - map(event => ({ - ...event, - // If we use differential loading, both configs have the same outputs - outputPath: path.resolve(context.workspaceRoot, options.outputPath), - } as BrowserBuilderOutput)), + map( + event => + ({ + ...event, + // If we use differential loading, both configs have the same outputs + outputPath: path.resolve(context.workspaceRoot, options.outputPath), + } as BrowserBuilderOutput), + ), ); }), ); diff --git a/packages/angular_devkit/build_angular/src/dev-server/index.ts b/packages/angular_devkit/build_angular/src/dev-server/index.ts index dd46b80e9ae1..b7ac62ee4507 100644 --- a/packages/angular_devkit/build_angular/src/dev-server/index.ts +++ b/packages/angular_devkit/build_angular/src/dev-server/index.ts @@ -6,22 +6,27 @@ * found in the LICENSE file at https://angular.io/license */ -import { - BuilderContext, - createBuilder, - targetFromTargetString, -} from '@angular-devkit/architect'; +import { BuilderContext, createBuilder, targetFromTargetString } from '@angular-devkit/architect'; import { DevServerBuildOutput, WebpackLoggingCallback, runWebpackDevServer, } from '@angular-devkit/build-webpack'; -import { json, logging, tags } from '@angular-devkit/core'; +import { + experimental, + getSystemPath, + json, + logging, + normalize, + resolve, + tags, +} from '@angular-devkit/core'; import { NodeJsSyncHost } from '@angular-devkit/core/node'; import { existsSync, readFileSync } from 'fs'; import * as path from 'path'; import { Observable, from } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; +import * as ts from 'typescript'; import * as url from 'url'; import * as webpack from 'webpack'; import * as WebpackDevServer from 'webpack-dev-server'; @@ -29,13 +34,11 @@ import { IndexHtmlWebpackPlugin } from '../angular-cli-files/plugins/index-html- 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, -} from '../browser'; +import { readTsconfig } from '../angular-cli-files/utilities/read-tsconfig'; +import { buildBrowserWebpackConfigFromContext, createBrowserLoggingCallback } from '../browser'; import { Schema as BrowserBuilderSchema } from '../browser/schema'; import { ExecutionTransformer } from '../transforms'; -import { normalizeOptimization } from '../utils'; +import { BuildBrowserFeatures, normalizeOptimization } from '../utils'; import { assertCompatibleAngularVersion } from '../utils/version'; import { Schema } from './schema'; const open = require('open'); @@ -58,7 +61,6 @@ export const devServerBuildOverriddenKeys: (keyof DevServerBuilderOptions)[] = [ 'deployUrl', ]; - export type DevServerBuilderOutput = DevServerBuildOutput & { baseUrl: string; }; @@ -74,9 +76,9 @@ export function serveWebpackBrowser( options: DevServerBuilderOptions, context: BuilderContext, transforms: { - webpackConfiguration?: ExecutionTransformer, - logging?: WebpackLoggingCallback, - indexHtml?: IndexHtmlTransform, + webpackConfiguration?: ExecutionTransformer; + logging?: WebpackLoggingCallback; + indexHtml?: IndexHtmlTransform; } = {}, ): Observable { // Check Angular version. @@ -87,25 +89,29 @@ export function serveWebpackBrowser( let first = true; const host = new NodeJsSyncHost(); - const loggingFn = transforms.logging - || createBrowserLoggingCallback(!!options.verbose, context.logger); + const loggingFn = + transforms.logging || createBrowserLoggingCallback(!!options.verbose, context.logger); async function setup(): Promise<{ - browserOptions: json.JsonObject & BrowserBuilderSchema, - webpackConfig: webpack.Configuration, - webpackDevServerConfig: WebpackDevServer.Configuration, - port: number, + browserOptions: json.JsonObject & BrowserBuilderSchema; + webpackConfig: webpack.Configuration; + webpackDevServerConfig: WebpackDevServer.Configuration; + port: number; + workspace: experimental.workspace.Workspace; }> { // Get the browser configuration from the target name. const rawBrowserOptions = await context.getTargetOptions(browserTarget); // Override options we need to override, if defined. const overrides = (Object.keys(options) as (keyof DevServerBuilderOptions)[]) - .filter(key => options[key] !== undefined && devServerBuildOverriddenKeys.includes(key)) - .reduce>((previous, key) => ({ - ...previous, - [key]: options[key], - }), {}); + .filter(key => options[key] !== undefined && devServerBuildOverriddenKeys.includes(key)) + .reduce>( + (previous, key) => ({ + ...previous, + [key]: options[key], + }), + {}, + ); // In dev server we should not have budgets because of extra libs such as socks-js overrides.budgets = undefined; @@ -126,22 +132,28 @@ export function serveWebpackBrowser( let webpackConfig = webpackConfigResult.config[0]; const port = await checkPort(options.port || 0, options.host || 'localhost', 4200); - const webpackDevServerConfig = webpackConfig.devServer = buildServerConfig( + const webpackDevServerConfig = (webpackConfig.devServer = buildServerConfig( root, options, browserOptions, context.logger, - ); + )); if (transforms.webpackConfiguration) { webpackConfig = await transforms.webpackConfiguration(webpackConfig); } - return { browserOptions, webpackConfig, webpackDevServerConfig, port }; + return { + browserOptions, + webpackConfig, + webpackDevServerConfig, + port, + workspace: webpackConfigResult.workspace, + }; } return from(setup()).pipe( - switchMap(({ browserOptions, webpackConfig, webpackDevServerConfig, port }) => { + switchMap(({ browserOptions, webpackConfig, webpackDevServerConfig, port, workspace }) => { options.port = port; // Resolve public host and client address. @@ -171,25 +183,48 @@ export function serveWebpackBrowser( // tslint:disable-next-line:no-any apply: (compiler: any) => { compiler.hooks.afterEnvironment.tap('angular-cli', () => { - compiler.watchFileSystem = { watch: () => { } }; + 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 { scripts = [], styles = [], index, baseHref, tsConfig } = browserOptions; + const projectName = context.target + ? context.target.project + : workspace.getDefaultProjectName(); + + if (!projectName) { + throw new Error('Must either have a target from the context or a default project.'); + } + const projectRoot = resolve( + workspace.root, + normalize(workspace.getProject(projectName).root), + ); + + const { options: compilerOptions } = readTsconfig(tsConfig, context.workspaceRoot); + const target = compilerOptions.target || ts.ScriptTarget.ES5; + const buildBrowserFeatures = new BuildBrowserFeatures(getSystemPath(projectRoot), target); + + const entrypoints = generateEntryPoints({ scripts, styles }); + const moduleEntrypoints = buildBrowserFeatures.isDifferentialLoadingNeeded() + ? entrypoints + : []; + + webpackConfig.plugins.push( + new IndexHtmlWebpackPlugin({ + input: path.resolve(root, index), + output: path.basename(index), + baseHref, + moduleEntrypoints, + entrypoints, + deployUrl: browserOptions.deployUrl, + sri: browserOptions.subresourceIntegrity, + noModuleEntrypoints: ['polyfills-es5'], + postTransform: transforms.indexHtml, + }), + ); } const normalizedOptimization = normalizeOptimization(browserOptions.optimization); @@ -235,7 +270,6 @@ export function serveWebpackBrowser( ); } - /** * Create a webpack configuration for the dev server. * @param workspaceRoot The root of the workspace. This comes from the context. @@ -279,17 +313,19 @@ export function buildServerConfig( host: serverOptions.host, port: serverOptions.port, headers: { 'Access-Control-Allow-Origin': '*' }, - historyApiFallback: !!browserOptions.index && { - index: `${servePath}/${path.basename(browserOptions.index)}`, - disableDotRule: true, - htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'], - rewrites: [ - { - from: new RegExp(`^(?!${servePath})/.*`), - to: context => url.format(context.parsedUrl), - }, - ], - } as WebpackDevServer.HistoryApiFallbackConfig, + historyApiFallback: + !!browserOptions.index && + ({ + index: `${servePath}/${path.basename(browserOptions.index)}`, + disableDotRule: true, + htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'], + rewrites: [ + { + from: new RegExp(`^(?!${servePath})/.*`), + to: context => url.format(context.parsedUrl), + }, + ], + } as WebpackDevServer.HistoryApiFallbackConfig), stats: false, compress: styles || scripts, watchOptions: { @@ -388,8 +424,7 @@ function _addLiveReload( if (options.hmr) { const webpackHmrLink = 'https://webpack.js.org/guides/hot-module-replacement'; - logger.warn( - tags.oneLine`NOTICE: Hot Module Replacement (HMR) is enabled for the dev server.`); + logger.warn(tags.oneLine`NOTICE: Hot Module Replacement (HMR) is enabled for the dev server.`); const showWarning = options.hmrWarning; if (showWarning) { @@ -398,8 +433,7 @@ function _addLiveReload( but to take advantage of HMR additional application code is required' (not included in an Angular CLI project by default).' See ${webpackHmrLink} - for information on working with HMR for Webpack.`, - ); + for information on working with HMR for Webpack.`); logger.warn( tags.oneLine`To disable this warning use "hmrWarning: false" under "serve" options in "angular.json".`, @@ -493,9 +527,7 @@ function _findDefaultServePath(baseHref?: string, deployUrl?: string): string | // normalize baseHref // for ng serve the starting base is always `/` so a relative // and root relative value are identical - const baseHrefParts = (baseHref || '') - .split('/') - .filter(part => part !== ''); + const baseHrefParts = (baseHref || '').split('/').filter(part => part !== ''); if (baseHref && !baseHref.endsWith('/')) { baseHrefParts.pop(); } @@ -514,5 +546,4 @@ function _findDefaultServePath(baseHref?: string, deployUrl?: string): string | return `${normalizedBaseHref}${deployUrl || ''}`; } - export default createBuilder(serveWebpackBrowser); diff --git a/packages/angular_devkit/build_angular/test/browser/differential_loading_spec_large.ts b/packages/angular_devkit/build_angular/test/browser/differential_loading_spec_large.ts index 16530d00e934..2141c89a1a12 100644 --- a/packages/angular_devkit/build_angular/test/browser/differential_loading_spec_large.ts +++ b/packages/angular_devkit/build_angular/test/browser/differential_loading_spec_large.ts @@ -18,7 +18,7 @@ describe('Browser Builder with differential loading', () => { await host.initialize().toPromise(); // to trigger differential loading we need an non ever green browser host.writeMultipleFiles({ - 'browserslist': 'IE 10', + browserslist: 'IE 10', }); architect = (await createArchitect(host.root())).architect; @@ -59,8 +59,7 @@ describe('Browser Builder with differential loading', () => { 'vendor-es5.js.map', ] as PathFragment[]; - expect(Object.keys(files)) - .toEqual(jasmine.arrayWithExactContents(expectedOutputs)); + expect(Object.keys(files)).toEqual(jasmine.arrayWithExactContents(expectedOutputs)); }); it('deactivates differential loading for watch mode', async () => { @@ -86,11 +85,9 @@ describe('Browser Builder with differential loading', () => { 'vendor.js.map', ] as PathFragment[]; - expect(Object.keys(files)) - .toEqual(jasmine.arrayWithExactContents(expectedOutputs)); + expect(Object.keys(files)).toEqual(jasmine.arrayWithExactContents(expectedOutputs)); }); - it('emits the right ES formats', async () => { const { files } = await browserBuild(architect, host, target, { optimization: true }); expect(await files['main-es5.js']).not.toContain('class'); @@ -105,4 +102,22 @@ describe('Browser Builder with differential loading', () => { expect(await files['polyfills-es2015.js']).toContain('zone.js/dist/zone-evergreen'); expect(await files['polyfills-es2015.js']).not.toContain('registerElementPatch'); }); + + it('adds `type="module"` when differential loading is needed', async () => { + host.writeMultipleFiles({ + browserslist: ` + last 1 chrome version + IE 9 + `, + }); + + const { files } = await browserBuild(architect, host, target, { watch: true }); + expect(await files['index.html']).toContain( + '' + + '' + + '' + + '' + + '', + ); + }); }); diff --git a/packages/angular_devkit/build_angular/test/dev-server/index_spec_large.ts b/packages/angular_devkit/build_angular/test/dev-server/index_spec_large.ts new file mode 100644 index 000000000000..bf261a5b5987 --- /dev/null +++ b/packages/angular_devkit/build_angular/test/dev-server/index_spec_large.ts @@ -0,0 +1,62 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import fetch from 'node-fetch'; // tslint:disable-line:no-implicit-dependencies +import { DevServerBuilderOutput } from '../../src/dev-server/index'; +import { createArchitect, host } from '../utils'; + +describe('Dev Server Builder index', () => { + const targetSpec = { project: 'app', target: 'serve' }; + + beforeEach(async () => host.initialize().toPromise()); + afterEach(async () => host.restore().toPromise()); + + it(`adds 'type="module"' when differential loading is needed`, async () => { + host.writeMultipleFiles({ + browserslist: ` + last 1 chrome version + IE 10 + `, + }); + + const architect = (await createArchitect(host.root())).architect; + const run = await architect.scheduleTarget(targetSpec); + const output = (await run.result) as DevServerBuilderOutput; + expect(output.success).toBe(true); + const response = await fetch('http://localhost:4200/index.html'); + expect(await response.text()).toContain( + '' + + '' + + '' + + '' + + '', + ); + await run.stop(); + }); + + it(`doesn't 'type="module"' when differential loading is not needed`, async () => { + host.writeMultipleFiles({ + browserslist: ` + last 1 chrome version + `, + }); + + const architect = (await createArchitect(host.root())).architect; + const run = await architect.scheduleTarget(targetSpec); + const output = (await run.result) as DevServerBuilderOutput; + expect(output.success).toBe(true); + const response = await fetch('http://localhost:4200/index.html'); + expect(await response.text()).toContain( + '' + + '' + + '' + + '' + + '', + ); + await run.stop(); + }); +});