chore(web): clean up webpack setup (#7411)

This commit is contained in:
Jack Hsu 2021-10-19 11:07:33 -04:00 committed by GitHub
parent 24c9164ec7
commit 9c1ae3a1b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 493 additions and 2398 deletions

View File

@ -1,4 +1,3 @@
import { serializeJson } from '@nrwl/workspace';
import {
checkFilesDoNotExist,
checkFilesExist,
@ -42,20 +41,19 @@ describe('React Applications', () => {
await testGeneratedApp(appName, {
checkStyles: true,
checkProdBuild: true,
checkLinter: true,
checkE2E: true,
});
}, 500000);
it('should support vendor sourcemaps', () => {
it('should support sourcemaps', () => {
const appName = uniq('app');
runCLI(`generate @nrwl/react:app ${appName} --no-interactive`);
runCLI(`build ${appName} --sourceMap`);
runCLI(`build ${appName} --sourceMap --outputHashing none`);
checkFilesExist(`dist/apps/${appName}/vendor.js.map`);
checkFilesExist(`dist/apps/${appName}/main.esm.js.map`);
}, 250000);
it('should be able to generate a publishable react lib', async () => {
@ -114,7 +112,6 @@ describe('React Applications', () => {
await testGeneratedApp(appName, {
checkStyles: true,
checkProdBuild: false,
checkLinter: false,
checkE2E: false,
});
@ -145,7 +142,6 @@ describe('React Applications', () => {
await testGeneratedApp(appName, {
checkStyles: true,
checkProdBuild: false,
checkLinter: false,
checkE2E: false,
});
@ -185,18 +181,10 @@ describe('React Applications', () => {
await testGeneratedApp(styledComponentsApp, {
checkStyles: false,
checkProdBuild: true,
checkLinter: false,
checkE2E: false,
});
expect(readFile(`dist/apps/${styledComponentsApp}/main.js`)).toContain(
'app__StyledApp'
);
expect(
readFile(`dist/apps/${styledComponentsApp}/prod/main.esm.js`)
).not.toContain('app__StyledApp');
const styledJsxApp = uniq('app');
runCLI(
@ -205,7 +193,6 @@ describe('React Applications', () => {
await testGeneratedApp(styledJsxApp, {
checkStyles: false,
checkProdBuild: false,
checkLinter: false,
checkE2E: false,
});
@ -218,7 +205,6 @@ describe('React Applications', () => {
await testGeneratedApp(noStylesApp, {
checkStyles: false,
checkProdBuild: false,
checkLinter: false,
checkE2E: false,
});
@ -236,24 +222,23 @@ describe('React Applications', () => {
runCLI(`generate @nrwl/react:app ${appName} --style=css --no-interactive`);
// changing browser suporrt of this application
// changing browser support of this application
updateFile(`apps/${appName}/.browserslistrc`, `IE 11`);
await testGeneratedApp(appName, {
checkStyles: false,
checkProdBuild: true,
checkLinter: false,
checkE2E: false,
});
const filesToCheck = [
`dist/apps/${appName}/prod/polyfills.es5.js`,
`dist/apps/${appName}/prod/main.es5.js`,
`dist/apps/${appName}/polyfills.es5.js`,
`dist/apps/${appName}/main.es5.js`,
];
checkFilesExist(...filesToCheck);
expect(readFile(`dist/apps/${appName}/prod/index.html`)).toContain(
expect(readFile(`dist/apps/${appName}/index.html`)).toContain(
`<script src="main.esm.js" type="module"></script><script src="main.es5.js" nomodule defer></script>`
);
}, 250000);
@ -312,7 +297,6 @@ describe('React Applications', () => {
await testGeneratedApp(appName, {
checkStyles: true,
checkProdBuild: false,
checkLinter: false,
checkE2E: false,
});
@ -321,7 +305,6 @@ describe('React Applications', () => {
async function testGeneratedApp(
appName,
opts: {
checkProdBuild: boolean;
checkStyles: boolean;
checkLinter: boolean;
checkE2E: boolean;
@ -332,44 +315,22 @@ describe('React Applications', () => {
expect(lintResults).toContain('All files pass linting.');
}
runCLI(`build ${appName}`);
let filesToCheck = [
runCLI(`build ${appName} --outputHashing none`);
const filesToCheck = [
`dist/apps/${appName}/index.html`,
`dist/apps/${appName}/polyfills.js`,
`dist/apps/${appName}/runtime.js`,
`dist/apps/${appName}/vendor.js`,
`dist/apps/${appName}/main.js`,
`dist/apps/${appName}/runtime.esm.js`,
`dist/apps/${appName}/polyfills.esm.js`,
`dist/apps/${appName}/main.esm.js`,
];
if (opts.checkStyles) {
filesToCheck.push(`dist/apps/${appName}/styles.js`);
filesToCheck.push(`dist/apps/${appName}/styles.css`);
}
checkFilesExist(...filesToCheck);
expect(readFile(`dist/apps/${appName}/main.js`)).toContain(
'function App() {'
);
if (opts.checkProdBuild) {
const prodOutputPath = `dist/apps/${appName}/prod`;
runCLI(
`build ${appName} --prod --output-hashing none --outputPath ${prodOutputPath}`
if (opts.checkStyles) {
expect(readFile(`dist/apps/${appName}/index.html`)).toContain(
`<link rel="stylesheet" href="styles.css">`
);
filesToCheck = [
`${prodOutputPath}/index.html`,
`${prodOutputPath}/runtime.esm.js`,
`${prodOutputPath}/polyfills.esm.js`,
`${prodOutputPath}/main.esm.js`,
];
if (opts.checkStyles) {
filesToCheck.push(`${prodOutputPath}/styles.css`);
}
checkFilesExist(...filesToCheck);
if (opts.checkStyles) {
expect(readFile(`${prodOutputPath}/index.html`)).toContain(
`<link rel="stylesheet" href="styles.css">`
);
}
}
const testResults = await runCLIAsync(`test ${appName}`);
@ -385,7 +346,7 @@ describe('React Applications', () => {
}
});
describe('--style option', () => {
fdescribe('--style option', () => {
beforeAll(() => newProject());
it.each`
@ -420,11 +381,7 @@ describe('--style option', () => {
`body { font-family: "Comic Sans MS"; }`
);
runCLI(`build ${appName}`);
expect(readFile(`dist/apps/${appName}/styles.js`)).toMatch(/Comic Sans MS/);
runCLI(`build ${appName} --prod --output-hashing none`);
runCLI(`build ${appName} --outputHashing none`);
expect(readFile(`dist/apps/${appName}/styles.css`)).toMatch(
/Comic Sans MS/

View File

@ -24,20 +24,7 @@ describe('Web Components Applications', () => {
const lintResults = runCLI(`lint ${appName}`);
expect(lintResults).toContain('All files pass linting.');
runCLI(`build ${appName}`);
checkFilesExist(
`dist/apps/${appName}/index.html`,
`dist/apps/${appName}/runtime.js`,
`dist/apps/${appName}/polyfills.js`,
`dist/apps/${appName}/main.js`,
`dist/apps/${appName}/styles.js`
);
expect(readFile(`dist/apps/${appName}/main.js`)).toContain(
'class AppElement'
);
runCLI(`build ${appName} --prod --output-hashing none`);
runCLI(`build ${appName} --outputHashing none`);
checkFilesExist(
`dist/apps/${appName}/index.html`,
`dist/apps/${appName}/runtime.esm.js`,
@ -93,7 +80,7 @@ describe('Web Components Applications', () => {
`dist/apps/${appName}/_should_remove.txt`,
`dist/apps/_should_not_remove.txt`
);
runCLI(`build ${appName}`);
runCLI(`build ${appName} --outputHashing none`);
runCLI(`build ${libName}`);
checkFilesDoNotExist(
`dist/apps/${appName}/_should_remove.txt`,
@ -103,11 +90,11 @@ describe('Web Components Applications', () => {
// `delete-output-path`
createFile(`dist/apps/${appName}/_should_keep.txt`);
runCLI(`build ${appName} --delete-output-path=false`);
runCLI(`build ${appName} --delete-output-path=false --outputHashing none`);
checkFilesExist(`dist/apps/${appName}/_should_keep.txt`);
createFile(`dist/libs/${libName}/_should_keep.txt`);
runCLI(`build ${libName} --delete-output-path=false`);
runCLI(`build ${libName} --delete-output-path=false --outputHashing none`);
checkFilesExist(`dist/libs/${libName}/_should_keep.txt`);
}, 120000);
@ -118,7 +105,7 @@ describe('Web Components Applications', () => {
updateFile(`apps/${appName}/browserslist`, `IE 9-11`);
runCLI(`build ${appName} --prod --outputHashing=none`);
runCLI(`build ${appName} --outputHashing=none`);
checkFilesExist(
`dist/apps/${appName}/main.esm.js`,
@ -159,9 +146,9 @@ describe('Web Components Applications', () => {
`;
return newContent;
});
runCLI(`build ${appName}`);
runCLI(`build ${appName} --outputHashing none`);
expect(readFile(`dist/apps/${appName}/main.js`)).toMatch(
expect(readFile(`dist/apps/${appName}/main.esm.js`)).toMatch(
/Reflect\.metadata/
);
@ -172,9 +159,9 @@ describe('Web Components Applications', () => {
return JSON.stringify(json);
});
runCLI(`build ${appName}`);
runCLI(`build ${appName} --outputHashing none`);
expect(readFile(`dist/apps/${appName}/main.js`)).not.toMatch(
expect(readFile(`dist/apps/${appName}/main.esm.js`)).not.toMatch(
/Reflect\.metadata/
);
}, 120000);
@ -254,7 +241,7 @@ describe('CLI - Environment Variables', () => {
updateFile(main2, `${newCode2}\n${content2}`);
runCLI(`run-many --target=build --all`, {
runCLI(`run-many --target build --all --no-optimization`, {
env: {
...process.env,
NODE_ENV: 'test',
@ -332,13 +319,13 @@ describe('Build Options', () => {
return config;
});
runCLI(`build ${appName}`);
runCLI(`build ${appName} --outputHashing none --optimization false`);
const distPath = `dist/apps/${appName}`;
const scripts = readFile(`${distPath}/scripts.js`);
const styles = readFile(`${distPath}/styles.js`);
const styles = readFile(`${distPath}/styles.css`);
const barScripts = readFile(`${distPath}/${barScriptsBundleName}.js`);
const barStyles = readFile(`${distPath}/${barStylesBundleName}.js`);
const barStyles = readFile(`${distPath}/${barStylesBundleName}.css`);
expect(scripts).toContain(fooJsContent);
expect(scripts).not.toContain(barJsContent);

View File

@ -7,7 +7,6 @@ import {
readFile,
readJson,
readProjectConfig,
readWorkspaceConfig,
removeProject,
rmDist,
runCLI,
@ -669,13 +668,13 @@ describe('print-affected', () => {
);
expect(resWithDeps.tasks[0]).toMatchObject({
id: `${myapp}:build`,
id: `${myapp}:build:production`,
overrides: {},
target: {
project: myapp,
target: 'build',
},
command: `${runNx} build ${myapp}`,
command: `${runNx} build ${myapp} --configuration production`,
outputs: [`dist/apps/${myapp}`],
});

View File

@ -222,7 +222,7 @@
"strip-json-comments": "^3.1.1",
"style-loader": "^3.3.0",
"styled-components": "5.0.0",
"stylus": "0.54.5",
"stylus": "^0.55.0",
"stylus-loader": "^6.2.0",
"tailwindcss": "^2.2.17",
"tcp-port-used": "^1.0.2",

View File

@ -12,6 +12,7 @@ export function addProject(host: Tree, options: NormalizedSchema) {
targets.build = {
builder: '@nrwl/next:build',
outputs: ['{options.outputPath}'],
defaultConfiguration: 'production',
options: {
root: options.appProjectRoot,
outputPath: joinPathFragments('dist', options.appProjectRoot),

View File

@ -58,8 +58,7 @@ function getWebpackConfig(config: Configuration) {
const babelLoader = config.module.rules.find(
(rule) =>
typeof rule !== 'string' &&
rule.loader &&
rule.loader.toString().includes('babel-loader')
rule.loader?.toString().includes('babel-loader')
);
if (babelLoader && typeof babelLoader !== 'string') {
babelLoader.options['plugins'] = [

View File

@ -263,7 +263,6 @@ Object {
});
expect(targetConfig.build.configurations.production).toEqual({
optimization: true,
extractCss: true,
extractLicenses: true,
fileReplacements: [
{

View File

@ -1,7 +1,7 @@
import { NormalizedSchema } from '../schema';
import {
joinPathFragments,
addProjectConfiguration,
joinPathFragments,
ProjectConfiguration,
TargetConfiguration,
} from '@nrwl/devkit';
@ -38,6 +38,7 @@ function createBuildTarget(options: NormalizedSchema): TargetConfiguration {
return {
executor: '@nrwl/web:build',
outputs: ['{options.outputPath}'],
defaultConfiguration: 'production',
options: {
outputPath: joinPathFragments('dist', options.appProjectRoot),
index: joinPathFragments(options.appProjectRoot, 'src/index.html'),
@ -84,7 +85,6 @@ function createBuildTarget(options: NormalizedSchema): TargetConfiguration {
optimization: true,
outputHashing: 'all',
sourceMap: false,
extractCss: true,
namedChunks: false,
extractLicenses: true,
vendorChunk: false,

View File

@ -95,7 +95,7 @@
"source-map": "0.7.3",
"source-map-loader": "^3.0.0",
"style-loader": "^3.3.0",
"stylus": "0.54.5",
"stylus": "^0.55.0",
"stylus-loader": "^6.2.0",
"terser": "4.3.8",
"terser-webpack-plugin": "^5.1.1",

View File

@ -15,24 +15,24 @@ import {
} from '@nrwl/workspace/src/utilities/buildable-libs-utils';
import { readTsConfig } from '@nrwl/workspace/src/utilities/typescript';
import { writeIndexHtml } from '../../utils/third-party/cli-files/utilities/index-file/write-index-html';
import { CrossOriginValue } from '../../utils/third-party/cli-files/utilities/index-file/augment-index-html';
import { BuildBrowserFeatures } from '../../utils/third-party/utils/build-browser-features';
import { normalizeWebBuildOptions } from '../../utils/normalize';
import { getWebConfig } from '../../utils/web.config';
import type { BuildBuilderOptions } from '../../utils/types';
import { deleteOutputDir } from '../../utils/delete-output-dir';
import type { ExtraEntryPoint } from '../../utils/third-party/browser/schema';
import type { BuildBuilderOptions } from '../../utils/shared-models';
import { ExtraEntryPoint } from '../../utils/shared-models';
import { getEmittedFiles, runWebpack } from '../../utils/run-webpack';
import { BuildBrowserFeatures } from '../../utils/webpack/build-browser-features';
import { deleteOutputDir } from '../../utils/fs';
import {
CrossOriginValue,
writeIndexHtml,
} from '../../utils/webpack/write-index-html';
export interface WebBuildBuilderOptions extends BuildBuilderOptions {
export interface WebBuildExecutorOptions extends BuildBuilderOptions {
index: string;
budgets?: any[];
baseHref?: string;
deployUrl?: string;
extractCss?: boolean;
crossOrigin?: CrossOriginValue;
polyfills?: string;
@ -58,7 +58,7 @@ export interface WebBuildBuilderOptions extends BuildBuilderOptions {
}
function getWebpackConfigs(
options: WebBuildBuilderOptions,
options: WebBuildExecutorOptions,
context: ExecutorContext
): Configuration[] {
const metadata = context.workspace.projects[context.projectName];
@ -115,7 +115,7 @@ function getWebpackConfigs(
}
export async function* run(
options: WebBuildBuilderOptions,
options: WebBuildExecutorOptions,
context: ExecutorContext
) {
// Node versions 12.2-12.8 has a bug where prod builds will hang for 2-3 minutes

View File

@ -11,7 +11,7 @@ import { map, tap } from 'rxjs/operators';
import * as WebpackDevServer from 'webpack-dev-server';
import { normalizeWebBuildOptions } from '../../utils/normalize';
import { WebBuildBuilderOptions } from '../build/build.impl';
import { WebBuildExecutorOptions } from '../build/build.impl';
import { getDevServerConfig } from '../../utils/devserver.config';
import {
calculateProjectDependencies,
@ -100,9 +100,9 @@ export default async function* devServerExecutor(
function getBuildOptions(
options: WebDevServerOptions,
context: ExecutorContext
): WebBuildBuilderOptions {
): WebBuildExecutorOptions {
const target = parseTargetString(options.buildTarget);
const overrides: Partial<WebBuildBuilderOptions> = {
const overrides: Partial<WebBuildExecutorOptions> = {
watch: false,
};
if (options.maxWorkers) {

View File

@ -1,6 +1,6 @@
import { dirname } from 'path';
import { AssetGlobPattern } from '../../../utils/types';
import { AssetGlobPattern } from '../../../utils/shared-models';
import { normalizeAssets, normalizePluginPath } from '../../../utils/normalize';
import { WebPackageOptions } from '../schema';

View File

@ -19,8 +19,7 @@ import {
} from '@nrwl/workspace/src/utilities/buildable-libs-utils';
import resolve from '@rollup/plugin-node-resolve';
import { AssetGlobPattern } from '../../utils/types';
import { deleteOutputDir } from '../../utils/delete-output-dir';
import { AssetGlobPattern } from '../../utils/shared-models';
import { WebPackageOptions } from './schema';
import { runRollup } from './lib/run-rollup';
import {
@ -28,6 +27,7 @@ import {
normalizePackageOptions,
} from './lib/normalize';
import { analyze } from './lib/analyze-plugin';
import { deleteOutputDir } from '../../utils/fs';
// These use require because the ES import isn't correct.
const commonjs = require('@rollup/plugin-commonjs');

View File

@ -269,7 +269,6 @@ describe('app', () => {
});
expect(architectConfig.build.configurations.production).toEqual({
optimization: true,
extractCss: true,
extractLicenses: true,
fileReplacements: [
{

View File

@ -23,7 +23,7 @@ import { cypressProjectGenerator } from '@nrwl/cypress';
import { Linter, lintProjectGenerator } from '@nrwl/linter';
import { jestProjectGenerator } from '@nrwl/jest';
import { WebBuildBuilderOptions } from '../../executors/build/build.impl';
import { WebBuildExecutorOptions } from '../../executors/build/build.impl';
import { Schema } from './schema';
interface NormalizedSchema extends Schema {
@ -50,7 +50,7 @@ function addBuildTarget(
project: ProjectConfiguration,
options: NormalizedSchema
): ProjectConfiguration {
const buildOptions: WebBuildBuilderOptions = {
const buildOptions: WebBuildExecutorOptions = {
outputPath: joinPathFragments('dist', options.appProjectRoot),
index: joinPathFragments(options.appProjectRoot, 'src/index.html'),
baseHref: '/',
@ -66,7 +66,7 @@ function addBuildTarget(
],
scripts: [],
};
const productionBuildOptions: Partial<WebBuildBuilderOptions> = {
const productionBuildOptions: Partial<WebBuildExecutorOptions> = {
fileReplacements: [
{
replace: joinPathFragments(
@ -82,7 +82,6 @@ function addBuildTarget(
optimization: true,
outputHashing: 'all',
sourceMap: false,
extractCss: true,
namedChunks: false,
extractLicenses: true,
vendorChunk: false,
@ -95,6 +94,7 @@ function addBuildTarget(
build: {
executor: '@nrwl/web:build',
outputs: ['{options.outputPath}'],
defaultConfiguration: 'production',
options: buildOptions,
configurations: {
production: productionBuildOptions,

View File

@ -4,7 +4,7 @@ import { Configuration, WebpackPluginInstance } from 'webpack';
import { LicenseWebpackPlugin } from 'license-webpack-plugin';
import * as CopyWebpackPlugin from 'copy-webpack-plugin';
import * as TerserWebpackPlugin from 'terser-webpack-plugin';
import { AssetGlobPattern, BuildBuilderOptions } from './types';
import { AssetGlobPattern, BuildBuilderOptions } from './shared-models';
import { getOutputHashFormat } from './hash-format';
// Inlining tsconfig-paths-webpack-plugin with a patch

View File

@ -1,14 +0,0 @@
import { resolve } from 'path';
import { removeSync } from 'fs-extra';
/**
* Delete an output directory, but error out if it's the root of the project.
*/
export function deleteOutputDir(root: string, outputPath: string) {
const resolvedOutputPath = resolve(root, outputPath);
if (resolvedOutputPath === root) {
throw new Error('Output path MUST not be project root directory!');
}
removeSync(resolvedOutputPath);
}

View File

@ -3,17 +3,17 @@ import { Configuration as WebpackDevServerConfiguration } from 'webpack-dev-serv
import * as path from 'path';
import { getWebConfig } from './web.config';
import { WebBuildBuilderOptions } from '../executors/build/build.impl';
import { WebBuildExecutorOptions } from '../executors/build/build.impl';
import { WebDevServerOptions } from '../executors/dev-server/dev-server.impl';
import { buildServePath } from './serve-path';
import { OptimizationOptions } from './types';
import { OptimizationOptions } from './shared-models';
import { readFileSync } from 'fs-extra';
export function getDevServerConfig(
workspaceRoot: string,
projectRoot: string,
sourceRoot: string,
buildOptions: WebBuildBuilderOptions,
buildOptions: WebBuildExecutorOptions,
serveOptions: WebDevServerOptions
): Partial<WebpackDevServerConfiguration> {
const webpackConfig = getWebConfig(
@ -37,7 +37,7 @@ export function getDevServerConfig(
function getDevServerPartial(
root: string,
options: WebDevServerOptions,
buildOptions: WebBuildBuilderOptions
buildOptions: WebBuildExecutorOptions
): WebpackDevServerConfiguration {
const servePath = buildServePath(buildOptions);
@ -49,7 +49,7 @@ function getDevServerPartial(
port: options.port,
headers: { 'Access-Control-Allow-Origin': '*' },
historyApiFallback: {
index: `${servePath}/${path.basename(buildOptions.index)}`,
index: `${servePath}${path.basename(buildOptions.index)}`,
disableDotRule: true,
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
},

View File

@ -1,13 +1,5 @@
/**
* @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 { existsSync } from 'fs';
import * as path from 'path';
import { isDirectory } from './is-directory';
import { existsSync, removeSync, statSync } from 'fs-extra';
export function findUp(
names: string | string[],
@ -60,3 +52,23 @@ export function findAllNodeModules(from: string, root?: string) {
return nodeModules;
}
/**
* Delete an output directory, but error out if it's the root of the project.
*/
export function deleteOutputDir(root: string, outputPath: string) {
const resolvedOutputPath = path.resolve(root, outputPath);
if (resolvedOutputPath === root) {
throw new Error('Output path MUST not be project root directory!');
}
removeSync(resolvedOutputPath);
}
export function isDirectory(path: string) {
try {
return statSync(path).isDirectory();
} catch (_) {
return false;
}
}

View File

@ -1,5 +1,3 @@
// Originally from devkit.
// See: https://github.com/angular/angular-cli/blob/2c8b12f/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/utils.ts
export interface HashFormat {
chunk: string;
extract: string;

View File

@ -1,5 +1,5 @@
import { normalizeBuildOptions } from './normalize';
import { BuildBuilderOptions } from './types';
import { BuildBuilderOptions } from './shared-models';
import * as fs from 'fs';

View File

@ -1,7 +1,12 @@
import { WebBuildBuilderOptions } from '../executors/build/build.impl';
import { WebBuildExecutorOptions } from '../executors/build/build.impl';
import { normalizePath } from '@nrwl/devkit';
import { basename, dirname, relative, resolve } from 'path';
import { AssetGlobPattern, BuildBuilderOptions } from './types';
import {
AssetGlobPattern,
BuildBuilderOptions,
ExtraEntryPoint,
ExtraEntryPointClass,
} from './shared-models';
import { statSync } from 'fs';
export interface FileReplacement {
@ -96,10 +101,10 @@ function normalizeFileReplacements(
}
export function normalizeWebBuildOptions(
options: WebBuildBuilderOptions,
options: WebBuildExecutorOptions,
root: string,
sourceRoot: string
): WebBuildBuilderOptions {
): WebBuildExecutorOptions {
return {
...normalizeBuildOptions(options, root, sourceRoot),
optimization:
@ -116,7 +121,9 @@ export function normalizeWebBuildOptions(
};
}
export function convertBuildOptions(buildOptions: WebBuildBuilderOptions): any {
export function convertBuildOptions(
buildOptions: WebBuildExecutorOptions
): any {
const options = buildOptions as any;
return <any>{
...options,
@ -125,3 +132,42 @@ export function convertBuildOptions(buildOptions: WebBuildBuilderOptions): any {
lazyModules: [] as string[],
};
}
export type NormalizedEntryPoint = Required<Omit<ExtraEntryPointClass, 'lazy'>>;
export function normalizeExtraEntryPoints(
extraEntryPoints: ExtraEntryPoint[],
defaultBundleName: string
): NormalizedEntryPoint[] {
return extraEntryPoints.map((entry) => {
let normalizedEntry;
if (typeof entry === 'string') {
normalizedEntry = {
input: entry,
inject: true,
bundleName: defaultBundleName,
};
} else {
const { lazy, inject = true, ...newEntry } = entry;
const injectNormalized = entry.lazy !== undefined ? !entry.lazy : inject;
let bundleName;
if (entry.bundleName) {
bundleName = entry.bundleName;
} else if (!injectNormalized) {
// Lazy entry points use the file name as bundle name.
bundleName = basename(
normalizePath(
entry.input.replace(/\.(js|css|scss|sass|less|styl)$/i, '')
)
);
} else {
bundleName = defaultBundleName;
}
normalizedEntry = { ...newEntry, inject: injectNormalized, bundleName };
}
return normalizedEntry;
});
}

View File

@ -1,6 +1,6 @@
import { WebBuildBuilderOptions } from '../executors/build/build.impl';
import { WebBuildExecutorOptions } from '../executors/build/build.impl';
export function buildServePath(browserOptions: WebBuildBuilderOptions) {
export function buildServePath(browserOptions: WebBuildExecutorOptions) {
let servePath =
_findDefaultServePath(browserOptions.baseHref, browserOptions.deployUrl) ||
'/';

View File

@ -0,0 +1,120 @@
import { WebBuildExecutorOptions } from '../executors/build/build.impl';
import { FileReplacement } from './normalize';
export interface OptimizationOptions {
scripts: boolean;
styles: boolean;
}
export interface BuildBuilderOptions {
main: string;
outputPath: string;
tsConfig: string;
watch?: boolean;
sourceMap?: boolean | 'hidden';
optimization?: boolean | OptimizationOptions;
memoryLimit?: number;
maxWorkers?: number;
poll?: number;
fileReplacements?: FileReplacement[];
assets?: any[];
progress?: boolean;
statsJson?: boolean;
extractLicenses?: boolean;
verbose?: boolean;
outputHashing?: any;
webpackConfig?: string;
root?: string;
sourceRoot?: string;
}
export interface AssetGlobPattern {
glob: string;
input: string;
output: string;
ignore?: string[];
}
export type AssetPattern = AssetPatternClass | string;
export interface AssetPatternClass {
glob: string;
ignore?: string[];
input: string;
output: string;
}
export enum Type {
All = 'all',
AllScript = 'allScript',
Any = 'any',
AnyComponentStyle = 'anyComponentStyle',
AnyScript = 'anyScript',
Bundle = 'bundle',
Initial = 'initial',
}
export enum CrossOrigin {
Anonymous = 'anonymous',
None = 'none',
UseCredentials = 'use-credentials',
}
export type IndexUnion = IndexObject | string;
export interface IndexObject {
input: string;
output?: string;
}
export type Localize = string[] | boolean;
export type OptimizationUnion = boolean | OptimizationClass;
export interface OptimizationClass {
scripts?: boolean;
styles?: boolean;
}
export enum OutputHashing {
All = 'all',
Bundles = 'bundles',
Media = 'media',
None = 'none',
}
export type ExtraEntryPoint = ExtraEntryPointClass | string;
export interface ExtraEntryPointClass {
bundleName?: string;
inject?: boolean;
input: string;
lazy?: boolean;
}
export type SourceMapUnion = boolean | SourceMapClass;
export interface SourceMapClass {
hidden?: boolean;
scripts?: boolean;
styles?: boolean;
vendor?: boolean;
}
export interface StylePreprocessorOptions {
includePaths?: string[];
}
export interface WebpackConfigOptions<T = WebBuildExecutorOptions> {
root: string;
projectRoot: string;
sourceRoot?: string;
buildOptions: T;
tsConfig: any;
tsConfigPath: string;
supportES2015: boolean;
}

View File

@ -1,425 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
// CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
// tslint:disable:no-global-tslint-disable
// tslint:disable
/**
* Browser target options
*/
export interface Schema {
/**
* Build using Ahead of Time compilation.
*/
aot?: boolean;
/**
* List of static application assets.
*/
assets?: AssetPattern[];
/**
* Base url for the application being built.
*/
baseHref?: string;
/**
* Budget thresholds to ensure parts of your application stay within boundaries which you
* set.
*/
budgets?: Budget[];
/**
* Enables '@angular-devkit/build-optimizer' optimizations when using the 'aot' option.
*/
buildOptimizer?: boolean;
/**
* Use a separate bundle containing code used across multiple bundles.
*/
commonChunk?: boolean;
/**
* Define the crossorigin attribute setting of elements that provide CORS support.
*/
crossOrigin?: CrossOrigin;
/**
* Delete the output path before building.
*/
deleteOutputPath?: boolean;
/**
* URL where files will be deployed.
*/
deployUrl?: string;
/**
* Enables conditionally loaded ES2015 polyfills.
* @deprecated This will be determined from the list of supported browsers specified in the
* 'browserslist' file.
*/
es5BrowserSupport?: boolean;
/**
* Output in-file eval sourcemaps.
* @deprecated
*/
evalSourceMap?: boolean;
/**
* Concatenate modules with Rollup before bundling them with Webpack.
*/
experimentalRollupPass?: boolean;
/**
* Extract css from global styles into css files instead of js ones.
*/
extractCss?: boolean;
/**
* Extract all licenses in a separate file.
*/
extractLicenses?: boolean;
/**
* Replace files with other files in the build.
*/
fileReplacements?: FileReplacement[];
/**
* Run the TypeScript type checker in a forked process.
*/
forkTypeChecker?: boolean;
/**
* Localization file to use for i18n.
* @deprecated Use 'locales' object in the project metadata instead.
*/
i18nFile?: string;
/**
* Format of the localization file specified with --i18n-file.
* @deprecated No longer needed as the format will be determined automatically.
*/
i18nFormat?: string;
/**
* Locale to use for i18n.
* @deprecated Use 'localize' instead.
*/
i18nLocale?: string;
/**
* How to handle missing translations for i18n.
*/
i18nMissingTranslation?: I18NMissingTranslation;
/**
* Configures the generation of the application's HTML index.
*/
index: IndexUnion;
/**
* List of additional NgModule files that will be lazy loaded. Lazy router modules will be
* discovered automatically.
* @deprecated 'SystemJsNgModuleLoader' is deprecated, and this is part of its usage. Use
* 'import()' syntax instead.
*/
lazyModules?: string[];
localize?: Localize;
/**
* The full path for the main entry point to the app, relative to the current workspace.
*/
main: string;
/**
* Use file name for lazy loaded chunks.
*/
namedChunks?: boolean;
/**
* Path to ngsw-config.json.
*/
ngswConfigPath?: string;
/**
* Enables optimization of the build output.
*/
optimization?: OptimizationUnion;
/**
* Define the output filename cache-busting hashing mode.
*/
outputHashing?: OutputHashing;
/**
* The full path for the new output directory, relative to the current workspace.
*
* By default, writes output to a folder named dist/ in the current project.
*/
outputPath: string;
/**
* Enable and define the file watching poll time period in milliseconds.
*/
poll?: number;
/**
* The full path for the polyfills file, relative to the current workspace.
*/
polyfills?: string;
/**
* Do not use the real path when resolving modules.
*/
preserveSymlinks?: boolean;
/**
* Output profile events for Chrome profiler.
* @deprecated Use "NG_BUILD_PROFILING" environment variable instead.
*/
profile?: boolean;
/**
* Log progress to the console while building.
*/
progress?: boolean;
/**
* Change root relative URLs in stylesheets to include base HREF and deploy URL. Use only
* for compatibility and transition. The behavior of this option is non-standard and will be
* removed in the next major release.
* @deprecated
*/
rebaseRootRelativeCssUrls?: boolean;
/**
* The path where style resources will be placed, relative to outputPath.
*/
resourcesOutputPath?: string;
/**
* Global scripts to be included in the build.
*/
scripts?: ExtraEntryPoint[];
/**
* Generates a service worker config for production builds.
*/
serviceWorker?: boolean;
/**
* Flag to prevent building an app shell.
* @deprecated
*/
skipAppShell?: boolean;
/**
* Output sourcemaps.
*/
sourceMap?: SourceMapUnion;
/**
* Generates a 'stats.json' file which can be analyzed using tools such as
* 'webpack-bundle-analyzer'.
*/
statsJson?: boolean;
/**
* Options to pass to style preprocessors.
*/
stylePreprocessorOptions?: StylePreprocessorOptions;
/**
* Global styles to be included in the build.
*/
styles?: ExtraEntryPoint[];
/**
* Enables the use of subresource integrity validation.
*/
subresourceIntegrity?: boolean;
/**
* The full path for the TypeScript configuration file, relative to the current workspace.
*/
tsConfig: string;
/**
* Use a separate bundle containing only vendor libraries.
*/
vendorChunk?: boolean;
/**
* Resolve vendor packages sourcemaps.
* @deprecated
*/
vendorSourceMap?: boolean;
/**
* Adds more details to output logging.
*/
verbose?: boolean;
/**
* Run build when files change.
*/
watch?: boolean;
/**
* TypeScript configuration for Web Worker modules.
*/
webWorkerTsConfig?: string;
}
export type AssetPattern = AssetPatternClass | string;
export interface AssetPatternClass {
/**
* The pattern to match.
*/
glob: string;
/**
* An array of globs to ignore.
*/
ignore?: string[];
/**
* The input directory path in which to apply 'glob'. Defaults to the project root.
*/
input: string;
/**
* Absolute path within the output.
*/
output: string;
}
export interface Budget {
/**
* The baseline size for comparison.
*/
baseline?: string;
/**
* The threshold for error relative to the baseline (min & max).
*/
error?: string;
/**
* The maximum threshold for error relative to the baseline.
*/
maximumError?: string;
/**
* The maximum threshold for warning relative to the baseline.
*/
maximumWarning?: string;
/**
* The minimum threshold for error relative to the baseline.
*/
minimumError?: string;
/**
* The minimum threshold for warning relative to the baseline.
*/
minimumWarning?: string;
/**
* The name of the bundle.
*/
name?: string;
/**
* The type of budget.
*/
type: Type;
/**
* The threshold for warning relative to the baseline (min & max).
*/
warning?: string;
}
/**
* The type of budget.
*/
export enum Type {
All = 'all',
AllScript = 'allScript',
Any = 'any',
AnyComponentStyle = 'anyComponentStyle',
AnyScript = 'anyScript',
Bundle = 'bundle',
Initial = 'initial',
}
/**
* Define the crossorigin attribute setting of elements that provide CORS support.
*/
export enum CrossOrigin {
Anonymous = 'anonymous',
None = 'none',
UseCredentials = 'use-credentials',
}
export interface FileReplacement {
replace?: string;
replaceWith?: string;
src?: string;
with?: string;
}
/**
* How to handle missing translations for i18n.
*/
export enum I18NMissingTranslation {
Error = 'error',
Ignore = 'ignore',
Warning = 'warning',
}
/**
* Configures the generation of the application's HTML index.
*/
export type IndexUnion = IndexObject | string;
export interface IndexObject {
/**
* The path of a file to use for the application's generated HTML index.
*/
input: string;
/**
* The output path of the application's generated HTML index file. The full provided path
* will be used and will be considered relative to the application's configured output path.
*/
output?: string;
}
export type Localize = string[] | boolean;
/**
* Enables optimization of the build output.
*/
export type OptimizationUnion = boolean | OptimizationClass;
export interface OptimizationClass {
/**
* Enables optimization of the scripts output.
*/
scripts?: boolean;
/**
* Enables optimization of the styles output.
*/
styles?: boolean;
}
/**
* Define the output filename cache-busting hashing mode.
*/
export enum OutputHashing {
All = 'all',
Bundles = 'bundles',
Media = 'media',
None = 'none',
}
export type ExtraEntryPoint = ExtraEntryPointClass | string;
export interface ExtraEntryPointClass {
/**
* The bundle name for this extra entry point.
*/
bundleName?: string;
/**
* If the bundle will be referenced in the HTML file.
*/
inject?: boolean;
/**
* The file to include.
*/
input: string;
/**
* If the bundle will be lazy loaded.
*/
lazy?: boolean;
}
/**
* Output sourcemaps.
*/
export type SourceMapUnion = boolean | SourceMapClass;
export interface SourceMapClass {
/**
* Output sourcemaps used for error reporting tools.
*/
hidden?: boolean;
/**
* Output sourcemaps for all scripts.
*/
scripts?: boolean;
/**
* Output sourcemaps for all styles.
*/
styles?: boolean;
/**
* Resolve vendor packages sourcemaps.
*/
vendor?: boolean;
}
/**
* Options to pass to style preprocessors.
*/
export interface StylePreprocessorOptions {
/**
* Paths to include. Paths will be resolved to project root.
*/
includePaths?: string[];
}

View File

@ -1,96 +0,0 @@
/**
* @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
*/
// TODO: cleanup this file, it's copied as is from Angular CLI.
import { ScriptTarget } from 'typescript';
import {
AssetPatternClass,
Budget,
ExtraEntryPoint,
OptimizationClass,
SourceMapClass,
} from '../../browser/schema';
import { NormalizedFileReplacement } from '../../utils/normalize-file-replacements';
export interface BuildOptions {
optimization: OptimizationClass;
environment?: string;
outputPath: string;
resourcesOutputPath?: string;
aot?: boolean;
sourceMap: SourceMapClass;
/** @deprecated use sourceMap instead */
vendorSourceMap?: boolean;
/** @deprecated */
evalSourceMap?: boolean;
vendorChunk?: boolean;
commonChunk?: boolean;
runtimeChunk?: boolean;
baseHref?: string;
deployUrl?: string;
verbose?: boolean;
progress?: boolean;
i18nFile?: string;
i18nFormat?: string;
i18nLocale?: string;
i18nMissingTranslation?: string;
extractCss?: boolean;
bundleDependencies?: 'none' | 'all';
watch?: boolean;
outputHashing?: string;
poll?: number;
app?: string;
deleteOutputPath?: boolean;
preserveSymlinks?: boolean;
extractLicenses?: boolean;
buildOptimizer?: boolean;
namedChunks?: boolean;
subresourceIntegrity?: boolean;
serviceWorker?: boolean;
webWorkerTsConfig?: string;
skipAppShell?: boolean;
statsJson: boolean;
forkTypeChecker: boolean;
profile?: boolean;
es5BrowserSupport?: boolean;
main: string;
polyfills?: string;
budgets: Budget[];
assets: AssetPatternClass[];
scripts: ExtraEntryPoint[];
styles: ExtraEntryPoint[];
stylePreprocessorOptions?: { includePaths: string[] };
lazyModules: string[];
platform?: 'browser' | 'server';
fileReplacements: NormalizedFileReplacement[];
/** @deprecated use only for compatibility in 8.x; will be removed in 9.0 */
rebaseRootRelativeCssUrls?: boolean;
/* Append script target version to filename. */
esVersionInFileName?: boolean;
/* When specified it will be used instead of the script target in the tsconfig.json. */
scriptTargetOverride?: ScriptTarget;
}
export interface WebpackTestOptions extends BuildOptions {
codeCoverage?: boolean;
codeCoverageExclude?: string[];
}
export interface WebpackConfigOptions<T = BuildOptions> {
root: string;
projectRoot: string;
sourceRoot?: string;
buildOptions: T;
tsConfig: any;
tsConfigPath: string;
supportES2015: boolean;
}

View File

@ -1,94 +0,0 @@
/**
* @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
*/
// tslint:disable
// TODO: cleanup this file, it's copied as is from Angular CLI.
import { basename } from 'path';
import { normalizePath } from '@nrwl/devkit';
import { ExtraEntryPoint, ExtraEntryPointClass } from '../../../browser/schema';
import { ScriptTarget } from 'typescript';
export interface HashFormat {
chunk: string;
extract: string;
file: string;
script: string;
}
export function getOutputHashFormat(option: string, length = 20): HashFormat {
const hashFormats: { [option: string]: HashFormat } = {
none: { chunk: '', extract: '', file: '', script: '' },
media: { chunk: '', extract: '', file: `.[hash:${length}]`, script: '' },
bundles: {
chunk: `.[chunkhash:${length}]`,
extract: `.[contenthash:${length}]`,
file: '',
script: `.[hash:${length}]`,
},
all: {
chunk: `.[chunkhash:${length}]`,
extract: `.[contenthash:${length}]`,
file: `.[hash:${length}]`,
script: `.[hash:${length}]`,
},
};
return hashFormats[option] || hashFormats['none'];
}
// todo: replace with Omit when we update to TS 3.5
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export type NormalizedEntryPoint = Required<Omit<ExtraEntryPointClass, 'lazy'>>;
export function normalizeExtraEntryPoints(
extraEntryPoints: ExtraEntryPoint[],
defaultBundleName: string
): NormalizedEntryPoint[] {
return extraEntryPoints.map((entry) => {
let normalizedEntry;
if (typeof entry === 'string') {
normalizedEntry = {
input: entry,
inject: true,
bundleName: defaultBundleName,
};
} else {
const { lazy, inject = true, ...newEntry } = entry;
const injectNormalized = entry.lazy !== undefined ? !entry.lazy : inject;
let bundleName;
if (entry.bundleName) {
bundleName = entry.bundleName;
} else if (!injectNormalized) {
// Lazy entry points use the file name as bundle name.
bundleName = basename(
normalizePath(
entry.input.replace(/\.(js|css|scss|sass|less|styl)$/i, '')
)
);
} else {
bundleName = defaultBundleName;
}
normalizedEntry = { ...newEntry, inject: injectNormalized, bundleName };
}
return normalizedEntry;
});
}
/**
* Returns an ES version file suffix to differentiate between various builds.
*/
export function getEsVersionForFileName(
scriptTargetOverride: ScriptTarget | undefined,
esVersionInFileName = false
): string {
return scriptTargetOverride && esVersionInFileName
? `-${ScriptTarget[scriptTargetOverride].toLowerCase()}`
: '';
}

View File

@ -1,142 +0,0 @@
/**
* @license
* Copyright Google LLC 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 * as webpack from 'webpack';
import { basename, dirname, extname } from 'path';
import { FileInfo } from '../utils/index-file/augment-index-html';
import {
IndexHtmlGenerator,
IndexHtmlGeneratorOptions,
IndexHtmlGeneratorProcessOptions,
} from '../utils/index-file/index-html-generator';
export interface IndexHtmlWebpackPluginOptions
extends IndexHtmlGeneratorOptions,
Omit<
IndexHtmlGeneratorProcessOptions,
'files' | 'noModuleFiles' | 'moduleFiles'
> {
noModuleEntrypoints: string[];
moduleEntrypoints: string[];
}
type Compiler = any;
const PLUGIN_NAME = 'index-html-webpack-plugin';
export class IndexHtmlWebpackPlugin extends IndexHtmlGenerator {
private _compilation: any | undefined;
get compilation(): any {
if (this._compilation) {
return this._compilation;
}
throw new Error('compilation is undefined.');
}
constructor(readonly options: IndexHtmlWebpackPluginOptions) {
super(options);
}
apply(compiler: Compiler) {
compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
this._compilation = compilation;
compilation.hooks.processAssets.tapPromise(
{
name: PLUGIN_NAME,
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE + 1,
},
callback
);
});
function addWarning(compilation: any, message: string): void {
compilation.warnings.push(new this.webpack.WebpackError(message));
}
function addError(compilation: any, message: string): void {
compilation.errors.push(new this.webpack.WebpackError(message));
}
const callback = async (assets: Record<string, unknown>) => {
// Get all files for selected entrypoints
const files: FileInfo[] = [];
const noModuleFiles: FileInfo[] = [];
const moduleFiles: FileInfo[] = [];
try {
for (const [entryName, entrypoint] of this.compilation.entrypoints) {
const entryFiles: FileInfo[] = entrypoint
?.getFiles()
?.filter((f) => !f.endsWith('.hot-update.js'))
?.map(
(f: string): FileInfo => ({
name: entryName,
file: f,
extension: extname(f),
})
);
if (!entryFiles) {
continue;
}
if (this.options.noModuleEntrypoints.includes(entryName)) {
noModuleFiles.push(...entryFiles);
} else if (this.options.moduleEntrypoints.includes(entryName)) {
moduleFiles.push(...entryFiles);
} else {
files.push(...entryFiles);
}
}
const { content, warnings, errors } = await this.process({
files,
noModuleFiles,
moduleFiles,
outputPath: dirname(this.options.outputPath),
baseHref: this.options.baseHref,
lang: this.options.lang,
});
assets[this.options.outputPath] = new webpack.sources.RawSource(
content
);
warnings.forEach((msg) => addWarning(this.compilation, msg));
errors.forEach((msg) => addError(this.compilation, msg));
} catch (error) {
addError(this.compilation, error.message);
}
};
}
async readAsset(path: string): Promise<string> {
const data = this.compilation.assets[basename(path)].source();
return typeof data === 'string' ? data : data.toString();
}
protected async readIndex(path: string): Promise<string> {
return new Promise<string>((resolve, reject) => {
this.compilation.inputFileSystem.readFile(
path,
(err?: Error, data?: string | Buffer) => {
if (err) {
reject(err);
return;
}
this.compilation.fileDependencies.add(path);
resolve(data?.toString() ?? '');
}
);
});
}
}

View File

@ -1,44 +0,0 @@
/**
* @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 { Compiler } from 'webpack';
export class NamedLazyChunksPlugin {
constructor() {}
apply(compiler: Compiler): void {
// Webpack doesn't export these so the deep imports can potentially break.
// There doesn't seem to exist any ergonomic way to alter chunk names for non-context lazy chunks
// (https://github.com/webpack/webpack/issues/9075) so this is the best alternative for now.
const ImportDependency = require('webpack/lib/dependencies/ImportDependency');
const ImportDependenciesBlock = require('webpack/lib/dependencies/ImportDependenciesBlock');
const Template = require('webpack/lib/Template');
compiler.hooks.compilation.tap(
'named-lazy-chunks-plugin',
(compilation) => {
// The dependencyReference hook isn't in the webpack typings so we have to type it as any.
// tslint:disable-next-line: no-any
(compilation.hooks as any).dependencyReference.tap(
'named-lazy-chunks-plugin',
// tslint:disable-next-line: no-any
(_: any, dependency: any) => {
if (
// Check this dependency is from an `import()` statement.
dependency instanceof ImportDependency &&
dependency.block instanceof ImportDependenciesBlock &&
// Don't rename chunks that already have a name.
dependency.block.chunkName === null
) {
// Convert the request to a valid chunk name using the same logic used
// in webpack/lib/ContextModule.js
dependency.block.chunkName = Template.toPath(dependency.request);
}
}
);
}
);
}
}

View File

@ -1,14 +0,0 @@
/**
* @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
*/
export default function (content: string, map: object) {
const stringifiedContent = JSON.stringify(content);
const stringifiedMap = map ? JSON.stringify(map) : `''`;
return `module.exports = [[module.id, ${stringifiedContent}, '', ${stringifiedMap}]]`;
}

View File

@ -1,24 +0,0 @@
/**
* @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
*/
// Exports the webpack plugins we use internally.
export {
ScriptsWebpackPlugin,
ScriptsWebpackPluginOptions,
} from './scripts-webpack-plugin';
export {
RemoveHashPlugin,
RemoveHashPluginOptions,
} from './remove-hash-plugin';
export {
default as PostcssCliResources,
PostcssCliResourcesOptions,
} from './postcss-cli-resources';
import { join } from 'path';
export const RawCssLoader = require.resolve(join(__dirname, 'raw-css-loader'));

View File

@ -1,7 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`augment-index-html can generate index.html 1`] = `"<html><head><base href=\\"/\\"><link rel=\\"stylesheet\\" href=\\"styles.css\\"></head><body><script src=\\"runtime.js\\" defer></script><script src=\\"polyfills.js\\" defer></script><script src=\\"main.js\\" defer></script></body></html>"`;
exports[`augment-index-html should emit correct script tags when having 'module' and 'non-module' js 1`] = `"<html><head><base href=\\"/\\"><link rel=\\"stylesheet\\" href=\\"styles.css\\"></head><body><script src=\\"runtime-es2015.js\\" type=\\"module\\"></script><script src=\\"polyfills-es2015.js\\" type=\\"module\\"></script><script src=\\"runtime-es5.js\\" nomodule defer></script><script src=\\"polyfills-es5.js\\" nomodule defer></script><script src=\\"main-es2015.js\\" type=\\"module\\"></script><script src=\\"main-es5.js\\" nomodule defer></script></body></html>"`;
exports[`augment-index-html should not add 'module' and 'non-module' attr to js files which are in both module formats 1`] = `"<html><head><base href=\\"/\\"><link rel=\\"stylesheet\\" href=\\"styles.css\\"></head><body><script src=\\"scripts.js\\" defer></script><script src=\\"main-es2015.js\\" type=\\"module\\"></script><script src=\\"main-es5.js\\" nomodule defer></script></body></html>"`;

View File

@ -1,94 +0,0 @@
/**
* @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 {
AugmentIndexHtmlOptions,
FileInfo,
augmentIndexHtml,
} from './augment-index-html';
describe('augment-index-html', () => {
const indexGeneratorOptions: AugmentIndexHtmlOptions = {
input: 'index.html',
inputContent: '<html><head></head><body></body></html>',
baseHref: '/',
sri: false,
files: [],
loadOutputFile: (_fileName: string) => '',
entrypoints: ['scripts', 'polyfills', 'main', 'styles'],
};
it('can generate index.html', async () => {
const source = augmentIndexHtml({
...indexGeneratorOptions,
files: [
{ file: 'styles.css', extension: '.css', name: 'styles' },
{ file: 'runtime.js', extension: '.js', name: 'main' },
{ file: 'main.js', extension: '.js', name: 'main' },
{ file: 'runtime.js', extension: '.js', name: 'polyfills' },
{ file: 'polyfills.js', extension: '.js', name: 'polyfills' },
],
});
const html = await source;
expect(html).toMatchSnapshot();
});
it(`should emit correct script tags when having 'module' and 'non-module' js`, async () => {
const es2015JsFiles: FileInfo[] = [
{ file: 'runtime-es2015.js', extension: '.js', name: 'main' },
{ file: 'main-es2015.js', extension: '.js', name: 'main' },
{ file: 'runtime-es2015.js', extension: '.js', name: 'polyfills' },
{ file: 'polyfills-es2015.js', extension: '.js', name: 'polyfills' },
];
const es5JsFiles: FileInfo[] = [
{ file: 'runtime-es5.js', extension: '.js', name: 'main' },
{ file: 'main-es5.js', extension: '.js', name: 'main' },
{ file: 'runtime-es5.js', extension: '.js', name: 'polyfills' },
{ file: 'polyfills-es5.js', extension: '.js', name: 'polyfills' },
];
const source = augmentIndexHtml({
...indexGeneratorOptions,
files: [
{ file: 'styles.css', extension: '.css', name: 'styles' },
{ file: 'styles.css', extension: '.css', name: 'styles' },
],
moduleFiles: es2015JsFiles,
noModuleFiles: es5JsFiles,
});
const html = await source;
expect(html).toMatchSnapshot();
});
it(`should not add 'module' and 'non-module' attr to js files which are in both module formats`, async () => {
const es2015JsFiles: FileInfo[] = [
{ file: 'scripts.js', extension: '.js', name: 'scripts' },
{ file: 'main-es2015.js', extension: '.js', name: 'main' },
];
const es5JsFiles: FileInfo[] = [
{ file: 'scripts.js', extension: '.js', name: 'scripts' },
{ file: 'main-es5.js', extension: '.js', name: 'main' },
];
const source = augmentIndexHtml({
...indexGeneratorOptions,
files: [
{ file: 'styles.css', extension: '.css', name: 'styles' },
{ file: 'styles.css', extension: '.css', name: 'styles' },
],
moduleFiles: es2015JsFiles,
noModuleFiles: es5JsFiles,
});
const html = await source;
expect(html).toMatchSnapshot();
});
});

View File

@ -1,94 +0,0 @@
/**
* @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 { dirname, join } from 'path';
import { ExtraEntryPoint } from '../../../browser/schema';
import { generateEntryPoints } from '../package-chunk-sort';
import { stripBom } from '../strip-bom';
import {
augmentIndexHtml,
CrossOriginValue,
FileInfo,
} from './augment-index-html';
import { readFileSync, writeFileSync } from 'fs';
import { interpolateEnvironmentVariablesToIndex } from '../../../../interpolate-env-variables-to-index';
import { EmittedFile } from '../../../../run-webpack';
type ExtensionFilter = '.js' | '.css';
export interface WriteIndexHtmlOptions {
outputPath: string;
indexPath: string;
files?: EmittedFile[];
noModuleFiles?: EmittedFile[];
moduleFiles?: EmittedFile[];
baseHref?: string;
deployUrl?: string;
sri?: boolean;
scripts?: ExtraEntryPoint[];
styles?: ExtraEntryPoint[];
postTransform?: IndexHtmlTransform;
crossOrigin?: CrossOriginValue;
}
export type IndexHtmlTransform = (content: string) => Promise<string>;
export async function writeIndexHtml({
outputPath,
indexPath,
files = [],
noModuleFiles = [],
moduleFiles = [],
baseHref,
deployUrl,
sri = false,
scripts = [],
styles = [],
postTransform,
crossOrigin,
}: WriteIndexHtmlOptions) {
let content = readFileSync(indexPath).toString();
content = stripBom(content);
content = augmentIndexHtml({
input: outputPath,
inputContent: interpolateEnvironmentVariablesToIndex(content, deployUrl),
baseHref,
deployUrl,
crossOrigin,
sri,
entrypoints: generateEntryPoints({ scripts, styles }),
files: filterAndMapBuildFiles(files, ['.js', '.css']),
noModuleFiles: filterAndMapBuildFiles(noModuleFiles, '.js'),
moduleFiles: filterAndMapBuildFiles(moduleFiles, '.js'),
loadOutputFile: (filePath) =>
readFileSync(join(dirname(outputPath), filePath)).toString(),
});
if (postTransform) {
content = await postTransform(content);
}
writeFileSync(outputPath, content);
}
function filterAndMapBuildFiles(
files: EmittedFile[],
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 && validExtensions.includes(extension)) {
filteredFiles.push({ file, extension, name });
}
}
return filteredFiles;
}

View File

@ -1,19 +0,0 @@
/**
* @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
*/
// tslint:disable
// TODO: cleanup this file, it's copied as is from Angular CLI.
import { statSync } from 'fs';
export function isDirectory(path: string) {
try {
return statSync(path).isDirectory();
} catch (_) {
return false;
}
}

View File

@ -1,15 +0,0 @@
/**
* @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
*/
// tslint:disable
// TODO: cleanup this file, it's copied as is from Angular CLI.
// Strip BOM from file data.
// https://stackoverflow.com/questions/24356713
export function stripBom(data: string) {
return data.replace(/^\uFEFF/, '');
}

View File

@ -1,239 +0,0 @@
/**
* @license
* Copyright Google LLC 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 { createHash } from 'crypto';
import { htmlRewritingStream } from './html-rewriting-stream';
export type LoadOutputFileFunctionType = (file: string) => Promise<string>;
export type CrossOriginValue = 'none' | 'anonymous' | 'use-credentials';
export interface AugmentIndexHtmlOptions {
/* Input contents */
html: string;
baseHref?: string;
deployUrl?: string;
sri: boolean;
/** crossorigin attribute setting of elements that provide CORS support */
crossOrigin?: CrossOriginValue;
/*
* Files emitted by the build.
* Js files will be added without 'nomodule' nor 'module'.
*/
files: FileInfo[];
/** Files that should be added using 'nomodule'. */
noModuleFiles?: FileInfo[];
/** Files that should be added using 'module'. */
moduleFiles?: FileInfo[];
/*
* Function that loads a file used.
* This allows us to use different routines within the IndexHtmlWebpackPlugin and
* when used without this plugin.
*/
loadOutputFile: LoadOutputFileFunctionType;
/** Used to sort the inseration of files in the HTML file */
entrypoints: string[];
/** Used to set the document default locale */
lang?: string;
}
export interface FileInfo {
file: string;
name: string;
extension: string;
}
/*
* Helper function used by the IndexHtmlWebpackPlugin.
* Can also be directly used by builder, e. g. in order to generate an index.html
* after processing several configurations in order to build different sets of
* bundles for differential serving.
*/
export async function augmentIndexHtml(
params: AugmentIndexHtmlOptions
): Promise<string> {
const {
loadOutputFile,
files,
noModuleFiles = [],
moduleFiles = [],
entrypoints,
sri,
deployUrl = '',
lang,
baseHref,
html,
} = params;
let { crossOrigin = 'none' } = params;
if (sri && crossOrigin === 'none') {
crossOrigin = 'anonymous';
}
const stylesheets = new Set<string>();
const scripts = new Set<string>();
// Sort files in the order we want to insert them by entrypoint and dedupes duplicates
const mergedFiles = [...moduleFiles, ...noModuleFiles, ...files];
for (const entrypoint of entrypoints) {
for (const { extension, file, name } of mergedFiles) {
if (name !== entrypoint) {
continue;
}
switch (extension) {
case '.js':
scripts.add(file);
break;
case '.css':
stylesheets.add(file);
break;
}
}
}
let scriptTags: string[] = [];
for (const script of scripts) {
const attrs = [`src="${deployUrl}${script}"`];
if (crossOrigin !== 'none') {
attrs.push(`crossorigin="${crossOrigin}"`);
}
// We want to include nomodule or module when a file is not common amongs all
// such as runtime.js
const scriptPredictor = ({ file }: FileInfo): boolean => file === script;
if (!files.some(scriptPredictor)) {
// in some cases for differential loading file with the same name is available in both
// nomodule and module such as scripts.js
// we shall not add these attributes if that's the case
const isNoModuleType = noModuleFiles.some(scriptPredictor);
const isModuleType = moduleFiles.some(scriptPredictor);
if (isNoModuleType && !isModuleType) {
attrs.push('nomodule', 'defer');
} else if (isModuleType && !isNoModuleType) {
attrs.push('type="module"');
} else {
attrs.push('defer');
}
} else {
attrs.push('defer');
}
if (sri) {
const content = await loadOutputFile(script);
attrs.push(generateSriAttributes(content));
}
scriptTags.push(`<script ${attrs.join(' ')}></script>`);
}
let linkTags: string[] = [];
for (const stylesheet of stylesheets) {
const attrs = [`rel="stylesheet"`, `href="${deployUrl}${stylesheet}"`];
if (crossOrigin !== 'none') {
attrs.push(`crossorigin="${crossOrigin}"`);
}
if (sri) {
const content = await loadOutputFile(stylesheet);
attrs.push(generateSriAttributes(content));
}
linkTags.push(`<link ${attrs.join(' ')}>`);
}
const { rewriter, transformedContent } = await htmlRewritingStream(html);
const baseTagExists = html.includes('<base');
rewriter
.on('startTag', (tag) => {
switch (tag.tagName) {
case 'html':
// Adjust document locale if specified
if (isString(lang)) {
updateAttribute(tag, 'lang', lang);
}
break;
case 'head':
// Base href should be added before any link, meta tags
if (!baseTagExists && isString(baseHref)) {
rewriter.emitStartTag(tag);
rewriter.emitRaw(`<base href="${baseHref}">`);
return;
}
break;
case 'base':
// Adjust base href if specified
if (isString(baseHref)) {
updateAttribute(tag, 'href', baseHref);
}
break;
}
rewriter.emitStartTag(tag);
})
.on('endTag', (tag) => {
switch (tag.tagName) {
case 'head':
for (const linkTag of linkTags) {
rewriter.emitRaw(linkTag);
}
linkTags = [];
break;
case 'body':
// Add script tags
for (const scriptTag of scriptTags) {
rewriter.emitRaw(scriptTag);
}
scriptTags = [];
break;
}
rewriter.emitEndTag(tag);
});
const content = await transformedContent;
if (linkTags.length || scriptTags.length) {
// In case no body/head tags are not present (dotnet partial templates)
return linkTags.join('') + scriptTags.join('') + content;
}
return content;
}
function generateSriAttributes(content: string): string {
const algo = 'sha384';
const hash = createHash(algo).update(content, 'utf8').digest('base64');
return `integrity="${algo}-${hash}"`;
}
function updateAttribute(
tag: { attrs: { name: string; value: string }[] },
name: string,
value: string
): void {
const index = tag.attrs.findIndex((a) => a.name === name);
const newValue = { name, value };
if (index === -1) {
tag.attrs.push(newValue);
} else {
tag.attrs[index] = newValue;
}
}
function isString(value: unknown): value is string {
return typeof value === 'string';
}

View File

@ -1,50 +0,0 @@
/**
* @license
* Copyright Google LLC 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 { Readable, Writable } from 'stream';
export async function htmlRewritingStream(content: string): Promise<{
rewriter: import('parse5-html-rewriting-stream');
transformedContent: Promise<string>;
}> {
const chunks: Buffer[] = [];
const rewriter = new (await import('parse5-html-rewriting-stream'))();
return {
rewriter,
transformedContent: new Promise((resolve) => {
new Readable({
encoding: 'utf8',
read(): void {
this.push(Buffer.from(content));
this.push(null);
},
})
.pipe(rewriter)
.pipe(
new Writable({
write(
chunk: string | Buffer,
encoding: string | undefined,
callback: Function
): void {
chunks.push(
typeof chunk === 'string'
? Buffer.from(chunk, encoding as BufferEncoding)
: chunk
);
callback();
},
final(callback: (error?: Error) => void): void {
callback();
resolve(Buffer.concat(chunks).toString());
},
})
);
}),
};
}

View File

@ -1,147 +0,0 @@
/**
* @license
* Copyright Google LLC 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 * as fs from 'fs';
import { join } from 'path';
import {
CrossOriginValue,
FileInfo,
augmentIndexHtml,
} from './augment-index-html';
function stripBom(data: string) {
return data.replace(/^\uFEFF/, '');
}
type IndexHtmlGeneratorPlugin = (
html: string,
options: IndexHtmlGeneratorProcessOptions
) => Promise<string | IndexHtmlTransformResult>;
export interface IndexHtmlGeneratorProcessOptions {
lang?: string | undefined;
baseHref?: string | undefined;
outputPath: string;
files: FileInfo[];
noModuleFiles: FileInfo[];
moduleFiles: FileInfo[];
}
export interface IndexHtmlGeneratorOptions {
indexPath: string;
deployUrl?: string;
sri?: boolean;
entrypoints: string[];
postTransform?: IndexHtmlTransform;
crossOrigin?: CrossOriginValue;
optimization?: any;
WOFFSupportNeeded?: boolean;
}
export type IndexHtmlTransform = (content: string) => Promise<string>;
export interface IndexHtmlTransformResult {
content: string;
warnings: string[];
errors: string[];
}
export class IndexHtmlGenerator {
private readonly plugins: IndexHtmlGeneratorPlugin[];
constructor(readonly options: IndexHtmlGeneratorOptions) {
const extraPlugins: IndexHtmlGeneratorPlugin[] = [];
this.plugins = [
augmentIndexHtmlPlugin(this),
...extraPlugins,
postTransformPlugin(this),
];
}
async process(
options: IndexHtmlGeneratorProcessOptions
): Promise<IndexHtmlTransformResult> {
let content = stripBom(await this.readIndex(this.options.indexPath));
const warnings: string[] = [];
const errors: string[] = [];
for (const plugin of this.plugins) {
const result = await plugin(content, options);
if (typeof result === 'string') {
content = result;
} else {
content = result.content;
if (result.warnings.length) {
warnings.push(...result.warnings);
}
if (result.errors.length) {
errors.push(...result.errors);
}
}
}
return {
content,
warnings,
errors,
};
}
async readAsset(path: string): Promise<string> {
return fs.promises.readFile(path, 'utf-8');
}
protected async readIndex(path: string): Promise<string> {
return fs.promises.readFile(path, 'utf-8');
}
}
function augmentIndexHtmlPlugin(
generator: IndexHtmlGenerator
): IndexHtmlGeneratorPlugin {
const {
deployUrl,
crossOrigin,
sri = false,
entrypoints,
} = generator.options;
return async (html, options) => {
const {
lang,
baseHref,
outputPath = '',
noModuleFiles,
files,
moduleFiles,
} = options;
return augmentIndexHtml({
html,
baseHref,
deployUrl,
crossOrigin,
sri,
lang,
entrypoints,
loadOutputFile: (filePath) =>
generator.readAsset(join(outputPath, filePath)),
noModuleFiles,
moduleFiles,
files,
});
};
}
function postTransformPlugin({
options,
}: IndexHtmlGenerator): IndexHtmlGeneratorPlugin {
return async (html) =>
options.postTransform ? options.postTransform(html) : html;
}

View File

@ -1,15 +0,0 @@
/**
* @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
*/
export function defaultProgress(progress: boolean | undefined): boolean {
if (progress === undefined) {
return process.stdout.isTTY === true;
}
return progress;
}

View File

@ -1,24 +0,0 @@
/**
* @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 { resolve } from 'path';
import { existsSync, unlinkSync } from 'fs';
/**
* Delete an output directory, but error out if it's the root of the project.
*/
export function deleteOutputDir(root: string, outputPath: string) {
const resolvedOutputPath = resolve(root, outputPath);
if (resolvedOutputPath === root) {
throw new Error('Output path MUST not be project root directory!');
}
const exists = existsSync(resolvedOutputPath);
if (exists) {
unlinkSync(resolvedOutputPath);
}
}

View File

@ -1,16 +0,0 @@
/**
* @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
*/
export * from './build-browser-features';
export * from './default-progress';
export * from './delete-output-dir';
export * from './normalize-file-replacements';
export * from './normalize-asset-patterns';
export * from './normalize-source-maps';
export * from './normalize-optimization';
export * from './normalize-builder-schema';

View File

@ -1,11 +0,0 @@
/**
* @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
*/
const mangleVariable = process.env['NG_BUILD_MANGLE'];
export const manglingDisabled =
!!mangleVariable &&
(mangleVariable === '0' || mangleVariable.toLowerCase() === 'false');

View File

@ -1,73 +0,0 @@
/**
* @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 { normalizePath } from '@nrwl/devkit';
import { basename, dirname, relative, join, resolve } from 'path';
import { AssetPattern, AssetPatternClass } from '../browser/schema';
import { statSync } from 'fs';
export class MissingAssetSourceRootException extends Error {
constructor(path: String) {
super(`The ${path} asset path must start with the project source root.`);
}
}
export function normalizeAssetPatterns(
assetPatterns: AssetPattern[],
root: string,
projectRoot: string,
maybeSourceRoot: string | undefined
): AssetPatternClass[] {
// When sourceRoot is not available, we default to ${projectRoot}/src.
const sourceRoot = maybeSourceRoot || join(projectRoot, 'src');
const resolvedSourceRoot = resolve(root, sourceRoot);
if (assetPatterns.length === 0) {
return [];
}
return assetPatterns.map((assetPattern) => {
// Normalize string asset patterns to objects.
if (typeof assetPattern === 'string') {
const assetPath = normalizePath(assetPattern);
const resolvedAssetPath = resolve(root, assetPath);
// Check if the string asset is within sourceRoot.
if (!resolvedAssetPath.startsWith(resolvedSourceRoot)) {
throw new MissingAssetSourceRootException(assetPattern);
}
let glob: string, input: string, output: string;
let isDirectory = false;
try {
isDirectory = statSync(resolvedAssetPath).isDirectory();
} catch {}
if (isDirectory) {
// Folders get a recursive star glob.
glob = '**/*';
// Input directory is their original path.
input = assetPath;
} else {
// Files are their own glob.
glob = basename(assetPath);
// Input directory is their original dirname.
input = dirname(assetPath);
}
// Output directory for both is the relative path from source root to input.
output = relative(resolvedSourceRoot, resolve(root, input));
// Return the asset pattern in object format.
return { glob, input, output };
} else {
// It's already an AssetPatternObject, no need to convert.
return assetPattern;
}
});
}

View File

@ -1,75 +0,0 @@
/**
* @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 { BuildOptions } from '../cli-files/models/build-options';
import {
AssetPatternClass,
OptimizationClass,
Schema as BrowserBuilderSchema,
SourceMapClass,
} from '../browser/schema';
import { normalizeAssetPatterns } from './normalize-asset-patterns';
import {
NormalizedFileReplacement,
normalizeFileReplacements,
} from './normalize-file-replacements';
import { normalizeOptimization } from './normalize-optimization';
import { normalizeSourceMaps } from './normalize-source-maps';
/**
* A normalized browser builder schema.
*/
export type NormalizedBrowserBuilderSchema = BrowserBuilderSchema &
BuildOptions & {
sourceMap: SourceMapClass;
assets: AssetPatternClass[];
fileReplacements: NormalizedFileReplacement[];
optimization: OptimizationClass;
};
export function normalizeBrowserSchema(
root: string,
projectRoot: string,
sourceRoot: string | undefined,
options: BrowserBuilderSchema
): NormalizedBrowserBuilderSchema {
const normalizedSourceMapOptions = normalizeSourceMaps(
options.sourceMap || false
);
normalizedSourceMapOptions.vendor =
normalizedSourceMapOptions.vendor || options.vendorSourceMap;
return {
...options,
assets: normalizeAssetPatterns(
options.assets || [],
root,
projectRoot,
sourceRoot
),
fileReplacements: normalizeFileReplacements(
options.fileReplacements || [],
root
),
optimization: normalizeOptimization(options.optimization),
sourceMap: normalizedSourceMapOptions,
statsJson: options.statsJson || false,
forkTypeChecker: options.forkTypeChecker || false,
budgets: options.budgets || [],
scripts: options.scripts || [],
styles: options.styles || [],
stylePreprocessorOptions: {
includePaths:
(options.stylePreprocessorOptions &&
options.stylePreprocessorOptions.includePaths) ||
[],
},
lazyModules: options.lazyModules || [],
};
}

View File

@ -1,77 +0,0 @@
/**
* @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 { normalizePath } from '@nrwl/devkit';
import { join } from 'path';
import { existsSync } from 'fs';
import { FileReplacement } from '../browser/schema';
export class MissingFileReplacementException extends Error {
constructor(path: String) {
super(`The ${path} path in file replacements does not exist.`);
}
}
export interface NormalizedFileReplacement {
replace: string;
with: string;
}
export function normalizeFileReplacements(
fileReplacements: FileReplacement[],
root: string
): NormalizedFileReplacement[] {
if (fileReplacements.length === 0) {
return [];
}
const normalizedReplacement = fileReplacements.map((replacement) =>
normalizeFileReplacement(replacement, root)
);
for (const { replace, with: replacementWith } of normalizedReplacement) {
if (!existsSync(replacementWith)) {
throw new MissingFileReplacementException(replacementWith);
}
if (!existsSync(replace)) {
throw new MissingFileReplacementException(replace);
}
}
return normalizedReplacement;
}
function normalizeFileReplacement(
fileReplacement: FileReplacement,
root?: string
): NormalizedFileReplacement {
let replacePath: string;
let withPath: string;
if (fileReplacement.src && fileReplacement.replaceWith) {
replacePath = normalizePath(fileReplacement.src);
withPath = normalizePath(fileReplacement.replaceWith);
} else if (fileReplacement.replace && fileReplacement.with) {
replacePath = normalizePath(fileReplacement.replace);
withPath = normalizePath(fileReplacement.with);
} else {
throw new Error(
`Invalid file replacement: ${JSON.stringify(fileReplacement)}`
);
}
// TODO: For 7.x should this only happen if not absolute?
if (root) {
replacePath = join(root, replacePath);
}
if (root) {
withPath = join(root, withPath);
}
return { replace: replacePath, with: withPath };
}

View File

@ -1,20 +0,0 @@
/**
* @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 { OptimizationClass, OptimizationUnion } from '../browser/schema';
export function normalizeOptimization(
optimization: OptimizationUnion = false
): Required<OptimizationClass> {
return {
scripts:
typeof optimization === 'object' ? !!optimization.scripts : optimization,
styles:
typeof optimization === 'object' ? !!optimization.styles : optimization,
};
}

View File

@ -1,23 +0,0 @@
/**
* @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 { SourceMapClass, SourceMapUnion } from '../browser/schema';
export function normalizeSourceMaps(sourceMap: SourceMapUnion): SourceMapClass {
const scripts = typeof sourceMap === 'object' ? sourceMap.scripts : sourceMap;
const styles = typeof sourceMap === 'object' ? sourceMap.styles : sourceMap;
const hidden = (typeof sourceMap === 'object' && sourceMap.hidden) || false;
const vendor = (typeof sourceMap === 'object' && sourceMap.vendor) || false;
return {
vendor,
hidden,
scripts,
styles,
};
}

View File

@ -1,39 +0,0 @@
import { FileReplacement } from './normalize';
export interface OptimizationOptions {
scripts: boolean;
styles: boolean;
}
export interface BuildBuilderOptions {
main: string;
outputPath: string;
tsConfig: string;
watch?: boolean;
sourceMap?: boolean | 'hidden';
optimization?: boolean | OptimizationOptions;
memoryLimit?: number;
maxWorkers?: number;
poll?: number;
fileReplacements?: FileReplacement[];
assets?: any[];
progress?: boolean;
statsJson?: boolean;
extractLicenses?: boolean;
verbose?: boolean;
outputHashing?: any;
webpackConfig?: string;
root?: string;
sourceRoot?: string;
}
export interface AssetGlobPattern {
glob: string;
input: string;
output: string;
ignore?: string[];
}

View File

@ -1,20 +1,16 @@
import * as path from 'path';
import { basename, posix, resolve } from 'path';
import { posix, resolve } from 'path';
import { readTsConfig } from '@nrwl/workspace/src/utilities/typescript';
import { ScriptTarget } from 'typescript';
import { getHashDigest, interpolateName } from 'loader-utils';
import { Configuration } from 'webpack';
// TODO @FrozenPandaz we should remove the following imports
import { getBrowserConfig } from './third-party/cli-files/models/webpack-configs/browser';
import { getCommonConfig } from './third-party/cli-files/models/webpack-configs/common';
import { getStylesConfig } from './third-party/cli-files/models/webpack-configs/styles';
import { IndexHtmlWebpackPlugin } from './third-party/cli-files/plugins/index-html-webpack-plugin';
import { generateEntryPoints } from './third-party/cli-files/utilities/package-chunk-sort';
import { WebBuildBuilderOptions } from '../executors/build/build.impl';
import { WebBuildExecutorOptions } from '../executors/build/build.impl';
import { convertBuildOptions } from './normalize';
import { getBaseWebpackPartial } from './config';
import { getBrowserConfig } from './webpack/partials/browser';
import { getCommonConfig } from './webpack/partials/common';
import { getStylesConfig } from './webpack/partials/styles';
import MiniCssExtractPlugin = require('mini-css-extract-plugin');
import webpackMerge = require('webpack-merge');
import postcssImports = require('postcss-import');
@ -31,7 +27,7 @@ export function getWebConfig(
workspaceRoot,
projectRoot,
sourceRoot,
options: WebBuildBuilderOptions,
options: WebBuildExecutorOptions,
esm?: boolean,
isScriptOptimizeOn?: boolean,
configuration?: string
@ -69,53 +65,18 @@ export function getWebConfig(
esm,
isScriptOptimizeOn
),
getStylesPartial(
wco.root,
wco.projectRoot,
wco.buildOptions,
options.extractCss
),
getStylesPartial(wco.root, wco.projectRoot, wco.buildOptions, true),
getCommonPartial(wco),
getBrowserPartial(wco, options, isScriptOptimizeOn),
getBrowserPartial(wco, options),
]);
}
function getBrowserPartial(
wco: any,
options: WebBuildBuilderOptions,
isScriptOptimizeOn: boolean
) {
const config = getBrowserConfig(wco);
if (!isScriptOptimizeOn) {
const {
deployUrl,
subresourceIntegrity,
scripts = [],
styles = [],
index,
baseHref,
} = options;
config.plugins.push(
new IndexHtmlWebpackPlugin({
indexPath: resolve(wco.root, index),
outputPath: basename(index),
baseHref,
entrypoints: generateEntryPoints({ scripts, styles }),
deployUrl,
sri: subresourceIntegrity,
moduleEntrypoints: [],
noModuleEntrypoints: ['polyfills-es5'],
})
);
}
return config;
function getBrowserPartial(wco: any, options: WebBuildExecutorOptions) {
return getBrowserConfig(wco);
}
function _getBaseWebpackPartial(
options: WebBuildBuilderOptions,
options: WebBuildExecutorOptions,
esm: boolean,
isScriptOptimizeOn: boolean,
emitDecoratorMetadata: boolean,
@ -300,9 +261,7 @@ export function getPolyfillsPartial(
// Safari 10.1 supports <script type="module"> but not <script nomodule>.
// Need to patch it up so the browser doesn't load both sets.
config.entry.polyfills = [
require.resolve(
'@nrwl/web/src/utils/third-party/cli-files/models/safari-nomodule.js'
),
require.resolve('@nrwl/web/src/utils/webpack/safari-nomodule.js'),
...(polyfills ? [polyfills] : []),
];
} else if (es2015Polyfills && !esm && isScriptOptimizeOn) {

View File

@ -1,11 +1,3 @@
/**
* @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 { fs, vol } from 'memfs';
jest.mock('fs', () => fs);
import { ScriptTarget } from 'typescript';

View File

@ -1,21 +1,7 @@
/**
* @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 * as browserslist from 'browserslist';
import { feature, features } from 'caniuse-lite';
import * as ts from 'typescript';
const fullDifferentialEnv = process.env['NG_BUILD_DIFFERENTIAL_FULL'];
export const fullDifferential =
fullDifferentialEnv !== undefined &&
fullDifferentialEnv !== '0' &&
fullDifferentialEnv.toLowerCase() !== 'false';
export class BuildBrowserFeatures {
private readonly _supportedBrowsers: string[];
private readonly _es6TargetOrLater: boolean;

View File

@ -1,12 +1,5 @@
/**
* @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 { ExtraEntryPoint } from '../../browser/schema';
import { normalizeExtraEntryPoints } from '../models/webpack-configs/utils';
import { normalizeExtraEntryPoints } from '../normalize';
import { ExtraEntryPoint } from '../shared-models';
export function generateEntryPoints(appConfig: {
styles: ExtraEntryPoint[];

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import { LicenseWebpackPlugin } from 'license-webpack-plugin';
import { WebpackConfigOptions } from '../build-options';
import { WebpackConfigOptions } from '../../shared-models';
const SubresourceIntegrityPlugin = require('webpack-subresource-integrity');
@ -50,7 +50,7 @@ export function getBrowserConfig(wco: WebpackConfigOptions) {
: false,
},
optimization: {
runtimeChunk: !!buildOptions.runtimeChunk ? 'single' : false,
runtimeChunk: 'single',
splitChunks: {
maxAsyncRequests: Infinity,
cacheGroups: {

View File

@ -1,56 +1,33 @@
/**
* @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 * as path from 'path';
import { ScriptTarget } from 'typescript';
import * as webpack from 'webpack';
import { Compiler, Configuration } from 'webpack';
import { ExtraEntryPoint } from '../../../browser/schema';
import { BuildBrowserFeatures, fullDifferential } from '../../../utils';
import { manglingDisabled } from '../../../utils/mangle-options';
import { ScriptsWebpackPlugin } from '../../plugins/scripts-webpack-plugin';
import { findAllNodeModules, findUp } from '../../utilities/find-up';
import { WebpackConfigOptions } from '../build-options';
import {
getEsVersionForFileName,
getOutputHashFormat,
normalizeExtraEntryPoints,
} from './utils';
import { ScriptsWebpackPlugin } from '../plugins/scripts-webpack-plugin';
import { ExtraEntryPoint, WebpackConfigOptions } from '../../shared-models';
import { BuildBrowserFeatures } from '../build-browser-features';
import { getOutputHashFormat } from '../../hash-format';
import { normalizeExtraEntryPoints } from '../../normalize';
import CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
export const GLOBAL_DEFS_FOR_TERSER = {
ngDevMode: false,
ngI18nClosureMode: false,
};
export const GLOBAL_DEFS_FOR_TERSER_WITH_AOT = {
...GLOBAL_DEFS_FOR_TERSER,
ngJitMode: false,
};
import { findAllNodeModules, findUp } from '../../fs';
const ProgressPlugin = require('webpack/lib/ProgressPlugin');
const TerserPlugin = require('terser-webpack-plugin');
// tslint:disable-next-line:no-any
const g: any = typeof global !== 'undefined' ? global : {};
// tslint:disable-next-line:no-big-function
export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
const { ContextReplacementPlugin, debug } = webpack;
const { ContextReplacementPlugin } = webpack;
const { root, projectRoot, sourceRoot, buildOptions, tsConfig } = wco;
const { styles: stylesOptimization, scripts: scriptsOptimization } =
buildOptions.optimization;
const {
styles: stylesSourceMap,
scripts: scriptsSourceMap,
vendor: vendorSourceMap,
} = buildOptions.sourceMap;
let stylesOptimization: boolean;
let scriptsOptimization: boolean;
if (typeof buildOptions.optimization === 'object') {
scriptsOptimization = buildOptions.optimization.scripts;
stylesOptimization = buildOptions.optimization.styles;
} else {
scriptsOptimization = stylesOptimization = !!buildOptions.optimization;
}
const nodeModules = findUp('node_modules', projectRoot);
if (!nodeModules) {
@ -61,108 +38,57 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
const extraPlugins: any[] = [];
const entryPoints: { [key: string]: string[] } = {};
const targetInFileName = getEsVersionForFileName(
fullDifferential
? buildOptions.scriptTargetOverride
: tsConfig.options.target,
buildOptions.esVersionInFileName
);
if (buildOptions.main) {
entryPoints['main'] = [path.resolve(root, buildOptions.main)];
}
let differentialLoadingNeeded = false;
if (wco.buildOptions.platform !== 'server') {
const buildBrowserFeatures = new BuildBrowserFeatures(
projectRoot,
tsConfig.options.target || ScriptTarget.ES5
);
const buildBrowserFeatures = new BuildBrowserFeatures(
projectRoot,
tsConfig.options.target || ScriptTarget.ES5
);
differentialLoadingNeeded =
buildBrowserFeatures.isDifferentialLoadingNeeded();
const differentialLoadingNeeded =
buildBrowserFeatures.isDifferentialLoadingNeeded();
if (
(buildOptions.scriptTargetOverride || tsConfig.options.target) ===
ScriptTarget.ES5
) {
if (
buildOptions.es5BrowserSupport ||
(buildOptions.es5BrowserSupport === undefined &&
buildBrowserFeatures.isEs5SupportNeeded())
) {
// The nomodule polyfill needs to be inject prior to any script and be
// outside of webpack compilation because otherwise webpack will cause the
// script to be wrapped in window["webpackJsonp"] which causes this to fail.
if (buildBrowserFeatures.isNoModulePolyfillNeeded()) {
const noModuleScript: ExtraEntryPoint = {
bundleName: 'polyfills-nomodule-es5',
input: path.join(__dirname, '..', 'safari-nomodule.js'),
};
buildOptions.scripts = buildOptions.scripts
? [...buildOptions.scripts, noModuleScript]
: [noModuleScript];
}
// For full build differential loading we don't need to generate a seperate polyfill file
// because they will be loaded exclusivly based on module and nomodule
const polyfillsChunkName =
fullDifferential && differentialLoadingNeeded
? 'polyfills'
: 'polyfills-es5';
entryPoints[polyfillsChunkName] = [
path.join(__dirname, '..', 'es5-polyfills.js'),
];
if (!fullDifferential && differentialLoadingNeeded) {
// Add zone.js legacy support to the es5 polyfills
// This is a noop execution-wise if zone-evergreen is not used.
entryPoints[polyfillsChunkName].push('zone.js/dist/zone-legacy');
}
if (!buildOptions.aot) {
// If not performing a full differential build the JIT polyfills need to be added to ES5
if (!fullDifferential && differentialLoadingNeeded) {
entryPoints[polyfillsChunkName].push(
path.join(__dirname, '..', 'jit-polyfills.js')
);
}
entryPoints[polyfillsChunkName].push(
path.join(__dirname, '..', 'es5-jit-polyfills.js')
);
}
// If not performing a full differential build the polyfills need to be added to ES5 bundle
if (!fullDifferential && buildOptions.polyfills) {
entryPoints[polyfillsChunkName].push(
path.resolve(root, buildOptions.polyfills)
);
}
if (tsConfig.options.target === ScriptTarget.ES5) {
if (buildBrowserFeatures.isEs5SupportNeeded()) {
// The nomodule polyfill needs to be inject prior to any script and be
// outside of webpack compilation because otherwise webpack will cause the
// script to be wrapped in window["webpackJsonp"] which causes this to fail.
if (buildBrowserFeatures.isNoModulePolyfillNeeded()) {
const noModuleScript: ExtraEntryPoint = {
bundleName: 'polyfills-nomodule-es5',
input: path.join(__dirname, '..', 'safari-nomodule.js'),
};
buildOptions.scripts = buildOptions.scripts
? [...buildOptions.scripts, noModuleScript]
: [noModuleScript];
}
}
if (buildOptions.polyfills) {
entryPoints['polyfills'] = [
...(entryPoints['polyfills'] || []),
path.resolve(root, buildOptions.polyfills),
];
}
// For full build differential loading we don't need to generate a seperate polyfill file
// because they will be loaded exclusivly based on module and nomodule
const polyfillsChunkName = differentialLoadingNeeded
? 'polyfills'
: 'polyfills-es5';
if (!buildOptions.aot) {
entryPoints['polyfills'] = [
...(entryPoints['polyfills'] || []),
path.join(__dirname, '..', 'jit-polyfills.js'),
entryPoints[polyfillsChunkName] = [
path.join(__dirname, '..', 'es5-polyfills.js'),
];
// If not performing a full differential build the polyfills need to be added to ES5 bundle
if (buildOptions.polyfills) {
entryPoints[polyfillsChunkName].push(
path.resolve(root, buildOptions.polyfills)
);
}
}
}
if (buildOptions.profile || process.env['NG_BUILD_PROFILING']) {
extraPlugins.push(
new debug.ProfilingPlugin({
outputPath: path.resolve(
root,
`chrome-profiler-events${targetInFileName}.json`
),
})
);
if (buildOptions.polyfills) {
entryPoints['polyfills'] = [
...(entryPoints['polyfills'] || []),
path.resolve(root, buildOptions.polyfills),
];
}
// determine hashing format
@ -212,7 +138,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
extraPlugins.push(
new ScriptsWebpackPlugin({
name: bundleName,
sourceMap: scriptsSourceMap,
sourceMap: !!buildOptions.sourceMap,
filename: `${path.basename(bundleName)}${hash}.js`,
scripts: script.paths,
basePath: sourceRoot,
@ -234,8 +160,9 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
const data = JSON.stringify(
compilation.getStats().toJson('verbose')
);
compilation.assets[`stats${targetInFileName}.json`] =
new webpack.sources.RawSource(data);
compilation.assets[`stats.json`] = new webpack.sources.RawSource(
data
);
});
}
})()
@ -243,7 +170,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
}
let sourceMapUseRule;
if ((scriptsSourceMap || stylesSourceMap) && vendorSourceMap) {
if (!!buildOptions.sourceMap) {
sourceMapUseRule = {
use: [
{
@ -282,25 +209,6 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
}
if (scriptsOptimization) {
let angularGlobalDefinitions = {
ngDevMode: false,
ngI18nClosureMode: false,
};
if (GLOBAL_DEFS_FOR_TERSER) {
angularGlobalDefinitions = GLOBAL_DEFS_FOR_TERSER;
}
if (buildOptions.aot) {
// Also try to load AOT-only global definitions.
if (GLOBAL_DEFS_FOR_TERSER_WITH_AOT) {
angularGlobalDefinitions = {
...angularGlobalDefinitions,
...GLOBAL_DEFS_FOR_TERSER_WITH_AOT,
};
}
}
// TODO: Investigate why this fails for some packages: wco.supportES2015 ? 6 : 5;
const terserEcma = 5;
@ -314,28 +222,15 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
},
// On server, we don't want to compress anything. We still set the ngDevMode = false for it
// to remove dev code, and ngI18nClosureMode to remove Closure compiler i18n code
compress:
buildOptions.platform == 'server'
? {
ecma: terserEcma,
global_defs: angularGlobalDefinitions,
keep_fnames: true,
}
: {
ecma: terserEcma,
pure_getters: buildOptions.buildOptimizer,
// PURE comments work best with 3 passes.
// See https://github.com/webpack/webpack/issues/2899#issuecomment-317425926.
passes: buildOptions.buildOptimizer ? 3 : 1,
global_defs: angularGlobalDefinitions,
},
// We also want to avoid mangling on server.
// Name mangling is handled within the browser builder
mangle:
!manglingDisabled &&
buildOptions.platform !== 'server' &&
(!differentialLoadingNeeded ||
(differentialLoadingNeeded && fullDifferential)),
compress: {
ecma: terserEcma,
// TODO(jack): Investigate options to enable further optimizations
// pure_getters: true,
// PURE comments work best with 3 passes.
// See https://github.com/webpack/webpack/issues/2899#issuecomment-317425926.
// passes: 3,
},
mangle: true,
};
const es5TerserOptions = {
@ -348,7 +243,6 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
...terserOptions.output,
ecma: 5,
},
mangle: !manglingDisabled && buildOptions.platform !== 'server',
};
extraMinimizers.push(
@ -366,7 +260,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
profile: buildOptions.statsJson,
resolve: {
extensions: ['.ts', '.tsx', '.mjs', '.js'],
symlinks: !buildOptions.preserveSymlinks,
symlinks: true,
modules: [wco.tsConfig.options.baseUrl || projectRoot, 'node_modules'],
alias,
},
@ -380,9 +274,6 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
publicPath: buildOptions.deployUrl,
},
watch: buildOptions.watch,
watchOptions: {
poll: buildOptions.poll,
},
performance: {
hints: false,
},

View File

@ -1,51 +1,31 @@
/**
* @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 * as path from 'path';
import { RuleSetRule } from 'webpack';
import {
PostcssCliResources,
RawCssLoader,
RemoveHashPlugin,
} from '../../plugins/webpack';
import { BuildOptions } from '../build-options';
import { getOutputHashFormat, normalizeExtraEntryPoints } from './utils';
import { RemoveEmptyScriptsPlugin } from '../../plugins/remove-empty-scripts-plugin';
import { WebBuildExecutorOptions } from '../../../executors/build/build.impl';
import { RemoveEmptyScriptsPlugin } from '../plugins/remove-empty-scripts-plugin';
import { getOutputHashFormat } from '../../hash-format';
import { normalizeExtraEntryPoints } from '../../normalize';
import { PostcssCliResources } from '../plugins/postcss-cli-resources';
import { RemoveHashPlugin } from '../plugins/remove-hash-plugin';
import MiniCssExtractPlugin = require('mini-css-extract-plugin');
const autoprefixer = require('autoprefixer');
const postcssImports = require('postcss-import');
/**
* Enumerate loaders and their dependencies from this file to let the dependency validator
* know they are used.
*
* require('style-loader')
* require('postcss-loader')
* require('stylus')
* require('stylus-loader')
* require('less')
* require('less-loader')
* require('node-sass')
* require('sass-loader')
*/
// tslint:disable-next-line:no-big-function
const RawCssLoader = require.resolve(
path.join(__dirname, '../plugins/raw-css-loader.js')
);
export function getStylesConfig(
root: string,
buildOptions: BuildOptions,
buildOptions: WebBuildExecutorOptions,
includePaths: string[]
) {
const entryPoints: { [key: string]: string[] } = {};
const globalStylePaths: string[] = [];
const extraPlugins = [];
const cssSourceMap = buildOptions.sourceMap.styles;
const cssSourceMap = !!buildOptions.sourceMap;
// Determine hashing format.
const hashFormat = getOutputHashFormat(buildOptions.outputHashing as string);
@ -78,7 +58,6 @@ export function getStylesConfig(
PostcssCliResources({
baseHref: buildOptions.baseHref,
deployUrl: buildOptions.deployUrl,
resourcesOutputPath: buildOptions.resourcesOutputPath,
loader,
filename: `[name]${hashFormat.file}.[ext]`,
}),
@ -178,24 +157,24 @@ export function getStylesConfig(
];
// load component css as raw strings
const componentsSourceMap = !!(
cssSourceMap &&
// Never use component css sourcemap when style optimizations are on.
// It will just increase bundle size without offering good debug experience.
!buildOptions.optimization.styles &&
// Inline all sourcemap types except hidden ones, which are the same as no sourcemaps
// for component css.
!buildOptions.sourceMap.hidden
);
const componentsSourceMap = !!(cssSourceMap &&
// Never use component css sourcemap when style optimizations are on.
// It will just increase bundle size without offering good debug experience.
typeof buildOptions.optimization === 'undefined'
? true
: typeof buildOptions.optimization === 'boolean'
? !buildOptions.optimization
: buildOptions.optimization.styles &&
// Inline all sourcemap types except hidden ones, which are the same as no sourcemaps
// for component css.
buildOptions.sourceMap !== 'hidden');
const rules: RuleSetRule[] = baseRules.map(({ test, use }) => ({
exclude: globalStylePaths,
test,
use: [
{ loader: require.resolve('raw-loader') },
// Including RawCssLoader here because per v4.x release notes for postcss-loader under breaking changes:
// "loader output only CSS, so you need to use css-loader/file-loader/raw-loader to inject code inside bundle"
RawCssLoader,
{ loader: RawCssLoader },
{
loader: require.resolve('postcss-loader'),
options: {
@ -203,13 +182,14 @@ export function getStylesConfig(
postcssOptions: postcssOptionsCreator(componentsSourceMap),
},
},
...(use as any[]),
...(Array.isArray(use) ? use : []),
],
}));
// load global css as css files
if (globalStylePaths.length > 0) {
const globalSourceMap = !!cssSourceMap && !buildOptions.sourceMap.hidden;
const globalSourceMap =
!!cssSourceMap && buildOptions.sourceMap !== 'hidden';
rules.push(
...baseRules.map(({ test, use }) => {
@ -217,13 +197,11 @@ export function getStylesConfig(
include: globalStylePaths,
test,
use: [
buildOptions.extractCss
? {
loader: MiniCssExtractPlugin.loader,
options: { esModule: true },
}
: require.resolve('style-loader'),
RawCssLoader,
{
loader: MiniCssExtractPlugin.loader,
options: { esModule: true },
},
{ loader: RawCssLoader },
{
loader: require.resolve('postcss-loader'),
options: {
@ -238,16 +216,14 @@ export function getStylesConfig(
);
}
if (buildOptions.extractCss) {
extraPlugins.push(
// extract global css from js files into own css file
new MiniCssExtractPlugin({
filename: `[name]${hashFormat.extract}.css`,
}),
// suppress empty .js files in css only entry points
new RemoveEmptyScriptsPlugin()
);
}
extraPlugins.push(
// extract global css from js files into own css file
new MiniCssExtractPlugin({
filename: `[name]${hashFormat.extract}.css`,
}),
// suppress empty .js files in css only entry points
new RemoveEmptyScriptsPlugin()
);
return {
entry: entryPoints,

View File

@ -44,7 +44,8 @@ async function resolve(
}
module.exports.postcss = true;
export default (options: PostcssCliResourcesOptions) => {
export function PostcssCliResources(options: PostcssCliResourcesOptions) {
const {
deployUrl = '',
baseHref = '',
@ -198,4 +199,4 @@ export default (options: PostcssCliResourcesOptions) => {
);
},
};
};
}

View File

@ -0,0 +1,5 @@
export default function RawCssLoader(content: string, map: object) {
const stringifiedContent = JSON.stringify(content);
const stringifiedMap = map ? JSON.stringify(map) : `''`;
return `module.exports = [[module.id, ${stringifiedContent}, '', ${stringifiedMap}]]`;
}

View File

@ -1,11 +1,4 @@
/**
* @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 { HashFormat } from '../models/webpack-configs/utils';
import { HashFormat } from '../../hash-format';
type Compiler = any;

View File

@ -1,20 +1,3 @@
// tslint:disable
// TODO: cleanup this file, it's copied as is from Angular CLI.
/**
* @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
*/
/**
* @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 { interpolateName } from 'loader-utils';
import * as path from 'path';
import { Compiler } from 'webpack';

View File

@ -1,14 +1,16 @@
/**
* @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 { dirname, join } from 'path';
import { readFileSync, writeFileSync } from 'fs';
import { EmittedFile } from '../run-webpack';
import { ExtraEntryPoint } from '../shared-models';
import { interpolateEnvironmentVariablesToIndex } from '../interpolate-env-variables-to-index';
import { generateEntryPoints } from './package-chunk-sort';
import { createHash } from 'crypto';
import { RawSource, ReplaceSource } from 'webpack-sources';
function stripBom(data: string) {
return data.replace(/^\uFEFF/, '');
}
const parse5 = require('parse5');
export type LoadOutputFileFunctionType = (file: string) => string;
@ -267,3 +269,77 @@ function _generateSriAttributes(content: string) {
return [{ name: 'integrity', value: `${algo}-${hash}` }];
}
type ExtensionFilter = '.js' | '.css';
export interface WriteIndexHtmlOptions {
outputPath: string;
indexPath: string;
files?: EmittedFile[];
noModuleFiles?: EmittedFile[];
moduleFiles?: EmittedFile[];
baseHref?: string;
deployUrl?: string;
sri?: boolean;
scripts?: ExtraEntryPoint[];
styles?: ExtraEntryPoint[];
postTransform?: IndexHtmlTransform;
crossOrigin?: CrossOriginValue;
}
export type IndexHtmlTransform = (content: string) => Promise<string>;
export async function writeIndexHtml({
outputPath,
indexPath,
files = [],
noModuleFiles = [],
moduleFiles = [],
baseHref,
deployUrl,
sri = false,
scripts = [],
styles = [],
postTransform,
crossOrigin,
}: WriteIndexHtmlOptions) {
let content = readFileSync(indexPath).toString();
content = stripBom(content);
content = augmentIndexHtml({
input: outputPath,
inputContent: interpolateEnvironmentVariablesToIndex(content, deployUrl),
baseHref,
deployUrl,
crossOrigin,
sri,
entrypoints: generateEntryPoints({ scripts, styles }),
files: filterAndMapBuildFiles(files, ['.js', '.css']),
noModuleFiles: filterAndMapBuildFiles(noModuleFiles, '.js'),
moduleFiles: filterAndMapBuildFiles(moduleFiles, '.js'),
loadOutputFile: (filePath) =>
readFileSync(join(dirname(outputPath), filePath)).toString(),
});
if (postTransform) {
content = await postTransform(content);
}
writeFileSync(outputPath, content);
}
function filterAndMapBuildFiles(
files: EmittedFile[],
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 && validExtensions.includes(extension)) {
filteredFiles.push({ file, extension, name });
}
}
return filteredFiles;
}

View File

@ -23801,6 +23801,20 @@ stylus@0.54.8, stylus@^0.54.8:
semver "^6.3.0"
source-map "^0.7.3"
stylus@^0.55.0:
version "0.55.0"
resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.55.0.tgz#bd404a36dd93fa87744a9dd2d2b1b8450345e5fc"
integrity sha512-MuzIIVRSbc8XxHH7FjkvWqkIcr1BvoMZoR/oFuAJDlh7VSaNJzrB4uJ38GRQa+mWjLXODAMzeDe0xi9GYbGwnw==
dependencies:
css "^3.0.0"
debug "~3.1.0"
glob "^7.1.6"
mkdirp "~1.0.4"
safer-buffer "^2.1.2"
sax "~1.2.4"
semver "^6.3.0"
source-map "^0.7.3"
supports-color@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"