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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,24 +15,24 @@ import {
} from '@nrwl/workspace/src/utilities/buildable-libs-utils'; } from '@nrwl/workspace/src/utilities/buildable-libs-utils';
import { readTsConfig } from '@nrwl/workspace/src/utilities/typescript'; 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 { normalizeWebBuildOptions } from '../../utils/normalize';
import { getWebConfig } from '../../utils/web.config'; import { getWebConfig } from '../../utils/web.config';
import type { BuildBuilderOptions } from '../../utils/types'; import type { BuildBuilderOptions } from '../../utils/shared-models';
import { deleteOutputDir } from '../../utils/delete-output-dir'; import { ExtraEntryPoint } from '../../utils/shared-models';
import type { ExtraEntryPoint } from '../../utils/third-party/browser/schema';
import { getEmittedFiles, runWebpack } from '../../utils/run-webpack'; 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; index: string;
budgets?: any[]; budgets?: any[];
baseHref?: string; baseHref?: string;
deployUrl?: string; deployUrl?: string;
extractCss?: boolean;
crossOrigin?: CrossOriginValue; crossOrigin?: CrossOriginValue;
polyfills?: string; polyfills?: string;
@ -58,7 +58,7 @@ export interface WebBuildBuilderOptions extends BuildBuilderOptions {
} }
function getWebpackConfigs( function getWebpackConfigs(
options: WebBuildBuilderOptions, options: WebBuildExecutorOptions,
context: ExecutorContext context: ExecutorContext
): Configuration[] { ): Configuration[] {
const metadata = context.workspace.projects[context.projectName]; const metadata = context.workspace.projects[context.projectName];
@ -115,7 +115,7 @@ function getWebpackConfigs(
} }
export async function* run( export async function* run(
options: WebBuildBuilderOptions, options: WebBuildExecutorOptions,
context: ExecutorContext context: ExecutorContext
) { ) {
// Node versions 12.2-12.8 has a bug where prod builds will hang for 2-3 minutes // 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 * as WebpackDevServer from 'webpack-dev-server';
import { normalizeWebBuildOptions } from '../../utils/normalize'; import { normalizeWebBuildOptions } from '../../utils/normalize';
import { WebBuildBuilderOptions } from '../build/build.impl'; import { WebBuildExecutorOptions } from '../build/build.impl';
import { getDevServerConfig } from '../../utils/devserver.config'; import { getDevServerConfig } from '../../utils/devserver.config';
import { import {
calculateProjectDependencies, calculateProjectDependencies,
@ -100,9 +100,9 @@ export default async function* devServerExecutor(
function getBuildOptions( function getBuildOptions(
options: WebDevServerOptions, options: WebDevServerOptions,
context: ExecutorContext context: ExecutorContext
): WebBuildBuilderOptions { ): WebBuildExecutorOptions {
const target = parseTargetString(options.buildTarget); const target = parseTargetString(options.buildTarget);
const overrides: Partial<WebBuildBuilderOptions> = { const overrides: Partial<WebBuildExecutorOptions> = {
watch: false, watch: false,
}; };
if (options.maxWorkers) { if (options.maxWorkers) {

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ import { Configuration, WebpackPluginInstance } from 'webpack';
import { LicenseWebpackPlugin } from 'license-webpack-plugin'; import { LicenseWebpackPlugin } from 'license-webpack-plugin';
import * as CopyWebpackPlugin from 'copy-webpack-plugin'; import * as CopyWebpackPlugin from 'copy-webpack-plugin';
import * as TerserWebpackPlugin from 'terser-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'; import { getOutputHashFormat } from './hash-format';
// Inlining tsconfig-paths-webpack-plugin with a patch // 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 * as path from 'path';
import { getWebConfig } from './web.config'; 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 { WebDevServerOptions } from '../executors/dev-server/dev-server.impl';
import { buildServePath } from './serve-path'; import { buildServePath } from './serve-path';
import { OptimizationOptions } from './types'; import { OptimizationOptions } from './shared-models';
import { readFileSync } from 'fs-extra'; import { readFileSync } from 'fs-extra';
export function getDevServerConfig( export function getDevServerConfig(
workspaceRoot: string, workspaceRoot: string,
projectRoot: string, projectRoot: string,
sourceRoot: string, sourceRoot: string,
buildOptions: WebBuildBuilderOptions, buildOptions: WebBuildExecutorOptions,
serveOptions: WebDevServerOptions serveOptions: WebDevServerOptions
): Partial<WebpackDevServerConfiguration> { ): Partial<WebpackDevServerConfiguration> {
const webpackConfig = getWebConfig( const webpackConfig = getWebConfig(
@ -37,7 +37,7 @@ export function getDevServerConfig(
function getDevServerPartial( function getDevServerPartial(
root: string, root: string,
options: WebDevServerOptions, options: WebDevServerOptions,
buildOptions: WebBuildBuilderOptions buildOptions: WebBuildExecutorOptions
): WebpackDevServerConfiguration { ): WebpackDevServerConfiguration {
const servePath = buildServePath(buildOptions); const servePath = buildServePath(buildOptions);
@ -49,7 +49,7 @@ function getDevServerPartial(
port: options.port, port: options.port,
headers: { 'Access-Control-Allow-Origin': '*' }, headers: { 'Access-Control-Allow-Origin': '*' },
historyApiFallback: { historyApiFallback: {
index: `${servePath}/${path.basename(buildOptions.index)}`, index: `${servePath}${path.basename(buildOptions.index)}`,
disableDotRule: true, disableDotRule: true,
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'], 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 * as path from 'path';
import { isDirectory } from './is-directory'; import { existsSync, removeSync, statSync } from 'fs-extra';
export function findUp( export function findUp(
names: string | string[], names: string | string[],
@ -60,3 +52,23 @@ export function findAllNodeModules(from: string, root?: string) {
return nodeModules; 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 { export interface HashFormat {
chunk: string; chunk: string;
extract: string; extract: string;

View File

@ -1,5 +1,5 @@
import { normalizeBuildOptions } from './normalize'; import { normalizeBuildOptions } from './normalize';
import { BuildBuilderOptions } from './types'; import { BuildBuilderOptions } from './shared-models';
import * as fs from 'fs'; 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 { normalizePath } from '@nrwl/devkit';
import { basename, dirname, relative, resolve } from 'path'; import { basename, dirname, relative, resolve } from 'path';
import { AssetGlobPattern, BuildBuilderOptions } from './types'; import {
AssetGlobPattern,
BuildBuilderOptions,
ExtraEntryPoint,
ExtraEntryPointClass,
} from './shared-models';
import { statSync } from 'fs'; import { statSync } from 'fs';
export interface FileReplacement { export interface FileReplacement {
@ -96,10 +101,10 @@ function normalizeFileReplacements(
} }
export function normalizeWebBuildOptions( export function normalizeWebBuildOptions(
options: WebBuildBuilderOptions, options: WebBuildExecutorOptions,
root: string, root: string,
sourceRoot: string sourceRoot: string
): WebBuildBuilderOptions { ): WebBuildExecutorOptions {
return { return {
...normalizeBuildOptions(options, root, sourceRoot), ...normalizeBuildOptions(options, root, sourceRoot),
optimization: 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; const options = buildOptions as any;
return <any>{ return <any>{
...options, ...options,
@ -125,3 +132,42 @@ export function convertBuildOptions(buildOptions: WebBuildBuilderOptions): any {
lazyModules: [] as string[], 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 = let servePath =
_findDefaultServePath(browserOptions.baseHref, browserOptions.deployUrl) || _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 * as path from 'path';
import { basename, posix, resolve } from 'path'; import { posix, resolve } from 'path';
import { readTsConfig } from '@nrwl/workspace/src/utilities/typescript'; import { readTsConfig } from '@nrwl/workspace/src/utilities/typescript';
import { ScriptTarget } from 'typescript'; import { ScriptTarget } from 'typescript';
import { getHashDigest, interpolateName } from 'loader-utils'; import { getHashDigest, interpolateName } from 'loader-utils';
import { Configuration } from 'webpack'; import { Configuration } from 'webpack';
// TODO @FrozenPandaz we should remove the following imports import { WebBuildExecutorOptions } from '../executors/build/build.impl';
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 { convertBuildOptions } from './normalize'; import { convertBuildOptions } from './normalize';
import { getBaseWebpackPartial } from './config'; 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 MiniCssExtractPlugin = require('mini-css-extract-plugin');
import webpackMerge = require('webpack-merge'); import webpackMerge = require('webpack-merge');
import postcssImports = require('postcss-import'); import postcssImports = require('postcss-import');
@ -31,7 +27,7 @@ export function getWebConfig(
workspaceRoot, workspaceRoot,
projectRoot, projectRoot,
sourceRoot, sourceRoot,
options: WebBuildBuilderOptions, options: WebBuildExecutorOptions,
esm?: boolean, esm?: boolean,
isScriptOptimizeOn?: boolean, isScriptOptimizeOn?: boolean,
configuration?: string configuration?: string
@ -69,53 +65,18 @@ export function getWebConfig(
esm, esm,
isScriptOptimizeOn isScriptOptimizeOn
), ),
getStylesPartial( getStylesPartial(wco.root, wco.projectRoot, wco.buildOptions, true),
wco.root,
wco.projectRoot,
wco.buildOptions,
options.extractCss
),
getCommonPartial(wco), getCommonPartial(wco),
getBrowserPartial(wco, options, isScriptOptimizeOn), getBrowserPartial(wco, options),
]); ]);
} }
function getBrowserPartial( function getBrowserPartial(wco: any, options: WebBuildExecutorOptions) {
wco: any, return getBrowserConfig(wco);
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 _getBaseWebpackPartial( function _getBaseWebpackPartial(
options: WebBuildBuilderOptions, options: WebBuildExecutorOptions,
esm: boolean, esm: boolean,
isScriptOptimizeOn: boolean, isScriptOptimizeOn: boolean,
emitDecoratorMetadata: boolean, emitDecoratorMetadata: boolean,
@ -300,9 +261,7 @@ export function getPolyfillsPartial(
// Safari 10.1 supports <script type="module"> but not <script nomodule>. // Safari 10.1 supports <script type="module"> but not <script nomodule>.
// Need to patch it up so the browser doesn't load both sets. // Need to patch it up so the browser doesn't load both sets.
config.entry.polyfills = [ config.entry.polyfills = [
require.resolve( require.resolve('@nrwl/web/src/utils/webpack/safari-nomodule.js'),
'@nrwl/web/src/utils/third-party/cli-files/models/safari-nomodule.js'
),
...(polyfills ? [polyfills] : []), ...(polyfills ? [polyfills] : []),
]; ];
} else if (es2015Polyfills && !esm && isScriptOptimizeOn) { } 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'; import { fs, vol } from 'memfs';
jest.mock('fs', () => fs); jest.mock('fs', () => fs);
import { ScriptTarget } from 'typescript'; 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 * as browserslist from 'browserslist';
import { feature, features } from 'caniuse-lite'; import { feature, features } from 'caniuse-lite';
import * as ts from 'typescript'; 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 { export class BuildBrowserFeatures {
private readonly _supportedBrowsers: string[]; private readonly _supportedBrowsers: string[];
private readonly _es6TargetOrLater: boolean; private readonly _es6TargetOrLater: boolean;

View File

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

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import { LicenseWebpackPlugin } from 'license-webpack-plugin'; import { LicenseWebpackPlugin } from 'license-webpack-plugin';
import { WebpackConfigOptions } from '../build-options'; import { WebpackConfigOptions } from '../../shared-models';
const SubresourceIntegrityPlugin = require('webpack-subresource-integrity'); const SubresourceIntegrityPlugin = require('webpack-subresource-integrity');
@ -50,7 +50,7 @@ export function getBrowserConfig(wco: WebpackConfigOptions) {
: false, : false,
}, },
optimization: { optimization: {
runtimeChunk: !!buildOptions.runtimeChunk ? 'single' : false, runtimeChunk: 'single',
splitChunks: { splitChunks: {
maxAsyncRequests: Infinity, maxAsyncRequests: Infinity,
cacheGroups: { 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 * as path from 'path';
import { ScriptTarget } from 'typescript'; import { ScriptTarget } from 'typescript';
import * as webpack from 'webpack'; import * as webpack from 'webpack';
import { Compiler, Configuration } from 'webpack'; import { Compiler, Configuration } from 'webpack';
import { ExtraEntryPoint } from '../../../browser/schema'; import { ScriptsWebpackPlugin } from '../plugins/scripts-webpack-plugin';
import { BuildBrowserFeatures, fullDifferential } from '../../../utils'; import { ExtraEntryPoint, WebpackConfigOptions } from '../../shared-models';
import { manglingDisabled } from '../../../utils/mangle-options'; import { BuildBrowserFeatures } from '../build-browser-features';
import { ScriptsWebpackPlugin } from '../../plugins/scripts-webpack-plugin'; import { getOutputHashFormat } from '../../hash-format';
import { findAllNodeModules, findUp } from '../../utilities/find-up'; import { normalizeExtraEntryPoints } from '../../normalize';
import { WebpackConfigOptions } from '../build-options';
import {
getEsVersionForFileName,
getOutputHashFormat,
normalizeExtraEntryPoints,
} from './utils';
import CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); import CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
import { findAllNodeModules, findUp } from '../../fs';
export const GLOBAL_DEFS_FOR_TERSER = {
ngDevMode: false,
ngI18nClosureMode: false,
};
export const GLOBAL_DEFS_FOR_TERSER_WITH_AOT = {
...GLOBAL_DEFS_FOR_TERSER,
ngJitMode: false,
};
const ProgressPlugin = require('webpack/lib/ProgressPlugin'); const ProgressPlugin = require('webpack/lib/ProgressPlugin');
const TerserPlugin = require('terser-webpack-plugin'); 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 // tslint:disable-next-line:no-big-function
export function getCommonConfig(wco: WebpackConfigOptions): Configuration { export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
const { ContextReplacementPlugin, debug } = webpack; const { ContextReplacementPlugin } = webpack;
const { root, projectRoot, sourceRoot, buildOptions, tsConfig } = wco; const { root, projectRoot, sourceRoot, buildOptions, tsConfig } = wco;
const { styles: stylesOptimization, scripts: scriptsOptimization } =
buildOptions.optimization; let stylesOptimization: boolean;
const { let scriptsOptimization: boolean;
styles: stylesSourceMap, if (typeof buildOptions.optimization === 'object') {
scripts: scriptsSourceMap, scriptsOptimization = buildOptions.optimization.scripts;
vendor: vendorSourceMap, stylesOptimization = buildOptions.optimization.styles;
} = buildOptions.sourceMap; } else {
scriptsOptimization = stylesOptimization = !!buildOptions.optimization;
}
const nodeModules = findUp('node_modules', projectRoot); const nodeModules = findUp('node_modules', projectRoot);
if (!nodeModules) { if (!nodeModules) {
@ -61,108 +38,57 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
const extraPlugins: any[] = []; const extraPlugins: any[] = [];
const entryPoints: { [key: string]: string[] } = {}; const entryPoints: { [key: string]: string[] } = {};
const targetInFileName = getEsVersionForFileName(
fullDifferential
? buildOptions.scriptTargetOverride
: tsConfig.options.target,
buildOptions.esVersionInFileName
);
if (buildOptions.main) { if (buildOptions.main) {
entryPoints['main'] = [path.resolve(root, buildOptions.main)]; entryPoints['main'] = [path.resolve(root, buildOptions.main)];
} }
let differentialLoadingNeeded = false; const buildBrowserFeatures = new BuildBrowserFeatures(
if (wco.buildOptions.platform !== 'server') { projectRoot,
const buildBrowserFeatures = new BuildBrowserFeatures( tsConfig.options.target || ScriptTarget.ES5
projectRoot, );
tsConfig.options.target || ScriptTarget.ES5
);
differentialLoadingNeeded = const differentialLoadingNeeded =
buildBrowserFeatures.isDifferentialLoadingNeeded(); buildBrowserFeatures.isDifferentialLoadingNeeded();
if ( if (tsConfig.options.target === ScriptTarget.ES5) {
(buildOptions.scriptTargetOverride || tsConfig.options.target) === if (buildBrowserFeatures.isEs5SupportNeeded()) {
ScriptTarget.ES5 // The nomodule polyfill needs to be inject prior to any script and be
) { // outside of webpack compilation because otherwise webpack will cause the
if ( // script to be wrapped in window["webpackJsonp"] which causes this to fail.
buildOptions.es5BrowserSupport || if (buildBrowserFeatures.isNoModulePolyfillNeeded()) {
(buildOptions.es5BrowserSupport === undefined && const noModuleScript: ExtraEntryPoint = {
buildBrowserFeatures.isEs5SupportNeeded()) bundleName: 'polyfills-nomodule-es5',
) { input: path.join(__dirname, '..', 'safari-nomodule.js'),
// The nomodule polyfill needs to be inject prior to any script and be };
// outside of webpack compilation because otherwise webpack will cause the buildOptions.scripts = buildOptions.scripts
// script to be wrapped in window["webpackJsonp"] which causes this to fail. ? [...buildOptions.scripts, noModuleScript]
if (buildBrowserFeatures.isNoModulePolyfillNeeded()) { : [noModuleScript];
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 (buildOptions.polyfills) { // For full build differential loading we don't need to generate a seperate polyfill file
entryPoints['polyfills'] = [ // because they will be loaded exclusivly based on module and nomodule
...(entryPoints['polyfills'] || []), const polyfillsChunkName = differentialLoadingNeeded
path.resolve(root, buildOptions.polyfills), ? 'polyfills'
]; : 'polyfills-es5';
}
if (!buildOptions.aot) { entryPoints[polyfillsChunkName] = [
entryPoints['polyfills'] = [ path.join(__dirname, '..', 'es5-polyfills.js'),
...(entryPoints['polyfills'] || []),
path.join(__dirname, '..', 'jit-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']) { if (buildOptions.polyfills) {
extraPlugins.push( entryPoints['polyfills'] = [
new debug.ProfilingPlugin({ ...(entryPoints['polyfills'] || []),
outputPath: path.resolve( path.resolve(root, buildOptions.polyfills),
root, ];
`chrome-profiler-events${targetInFileName}.json`
),
})
);
} }
// determine hashing format // determine hashing format
@ -212,7 +138,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
extraPlugins.push( extraPlugins.push(
new ScriptsWebpackPlugin({ new ScriptsWebpackPlugin({
name: bundleName, name: bundleName,
sourceMap: scriptsSourceMap, sourceMap: !!buildOptions.sourceMap,
filename: `${path.basename(bundleName)}${hash}.js`, filename: `${path.basename(bundleName)}${hash}.js`,
scripts: script.paths, scripts: script.paths,
basePath: sourceRoot, basePath: sourceRoot,
@ -234,8 +160,9 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
const data = JSON.stringify( const data = JSON.stringify(
compilation.getStats().toJson('verbose') compilation.getStats().toJson('verbose')
); );
compilation.assets[`stats${targetInFileName}.json`] = compilation.assets[`stats.json`] = new webpack.sources.RawSource(
new webpack.sources.RawSource(data); data
);
}); });
} }
})() })()
@ -243,7 +170,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
} }
let sourceMapUseRule; let sourceMapUseRule;
if ((scriptsSourceMap || stylesSourceMap) && vendorSourceMap) { if (!!buildOptions.sourceMap) {
sourceMapUseRule = { sourceMapUseRule = {
use: [ use: [
{ {
@ -282,25 +209,6 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
} }
if (scriptsOptimization) { 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; // TODO: Investigate why this fails for some packages: wco.supportES2015 ? 6 : 5;
const terserEcma = 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 // 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 // to remove dev code, and ngI18nClosureMode to remove Closure compiler i18n code
compress: compress: {
buildOptions.platform == 'server' ecma: terserEcma,
? { // TODO(jack): Investigate options to enable further optimizations
ecma: terserEcma, // pure_getters: true,
global_defs: angularGlobalDefinitions, // PURE comments work best with 3 passes.
keep_fnames: true, // See https://github.com/webpack/webpack/issues/2899#issuecomment-317425926.
} // passes: 3,
: { },
ecma: terserEcma, mangle: true,
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)),
}; };
const es5TerserOptions = { const es5TerserOptions = {
@ -348,7 +243,6 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
...terserOptions.output, ...terserOptions.output,
ecma: 5, ecma: 5,
}, },
mangle: !manglingDisabled && buildOptions.platform !== 'server',
}; };
extraMinimizers.push( extraMinimizers.push(
@ -366,7 +260,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
profile: buildOptions.statsJson, profile: buildOptions.statsJson,
resolve: { resolve: {
extensions: ['.ts', '.tsx', '.mjs', '.js'], extensions: ['.ts', '.tsx', '.mjs', '.js'],
symlinks: !buildOptions.preserveSymlinks, symlinks: true,
modules: [wco.tsConfig.options.baseUrl || projectRoot, 'node_modules'], modules: [wco.tsConfig.options.baseUrl || projectRoot, 'node_modules'],
alias, alias,
}, },
@ -380,9 +274,6 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
publicPath: buildOptions.deployUrl, publicPath: buildOptions.deployUrl,
}, },
watch: buildOptions.watch, watch: buildOptions.watch,
watchOptions: {
poll: buildOptions.poll,
},
performance: { performance: {
hints: false, 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 * as path from 'path';
import { RuleSetRule } from 'webpack'; import { RuleSetRule } from 'webpack';
import { import { WebBuildExecutorOptions } from '../../../executors/build/build.impl';
PostcssCliResources, import { RemoveEmptyScriptsPlugin } from '../plugins/remove-empty-scripts-plugin';
RawCssLoader, import { getOutputHashFormat } from '../../hash-format';
RemoveHashPlugin, import { normalizeExtraEntryPoints } from '../../normalize';
} from '../../plugins/webpack'; import { PostcssCliResources } from '../plugins/postcss-cli-resources';
import { BuildOptions } from '../build-options'; import { RemoveHashPlugin } from '../plugins/remove-hash-plugin';
import { getOutputHashFormat, normalizeExtraEntryPoints } from './utils';
import { RemoveEmptyScriptsPlugin } from '../../plugins/remove-empty-scripts-plugin';
import MiniCssExtractPlugin = require('mini-css-extract-plugin'); import MiniCssExtractPlugin = require('mini-css-extract-plugin');
const autoprefixer = require('autoprefixer'); const autoprefixer = require('autoprefixer');
const postcssImports = require('postcss-import'); const postcssImports = require('postcss-import');
/** const RawCssLoader = require.resolve(
* Enumerate loaders and their dependencies from this file to let the dependency validator path.join(__dirname, '../plugins/raw-css-loader.js')
* 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
export function getStylesConfig( export function getStylesConfig(
root: string, root: string,
buildOptions: BuildOptions, buildOptions: WebBuildExecutorOptions,
includePaths: string[] includePaths: string[]
) { ) {
const entryPoints: { [key: string]: string[] } = {}; const entryPoints: { [key: string]: string[] } = {};
const globalStylePaths: string[] = []; const globalStylePaths: string[] = [];
const extraPlugins = []; const extraPlugins = [];
const cssSourceMap = buildOptions.sourceMap.styles; const cssSourceMap = !!buildOptions.sourceMap;
// Determine hashing format. // Determine hashing format.
const hashFormat = getOutputHashFormat(buildOptions.outputHashing as string); const hashFormat = getOutputHashFormat(buildOptions.outputHashing as string);
@ -78,7 +58,6 @@ export function getStylesConfig(
PostcssCliResources({ PostcssCliResources({
baseHref: buildOptions.baseHref, baseHref: buildOptions.baseHref,
deployUrl: buildOptions.deployUrl, deployUrl: buildOptions.deployUrl,
resourcesOutputPath: buildOptions.resourcesOutputPath,
loader, loader,
filename: `[name]${hashFormat.file}.[ext]`, filename: `[name]${hashFormat.file}.[ext]`,
}), }),
@ -178,24 +157,24 @@ export function getStylesConfig(
]; ];
// load component css as raw strings // load component css as raw strings
const componentsSourceMap = !!( const componentsSourceMap = !!(cssSourceMap &&
cssSourceMap && // Never use component css sourcemap when style optimizations are on.
// Never use component css sourcemap when style optimizations are on. // It will just increase bundle size without offering good debug experience.
// It will just increase bundle size without offering good debug experience. typeof buildOptions.optimization === 'undefined'
!buildOptions.optimization.styles && ? true
// Inline all sourcemap types except hidden ones, which are the same as no sourcemaps : typeof buildOptions.optimization === 'boolean'
// for component css. ? !buildOptions.optimization
!buildOptions.sourceMap.hidden : 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 }) => ({ const rules: RuleSetRule[] = baseRules.map(({ test, use }) => ({
exclude: globalStylePaths, exclude: globalStylePaths,
test, test,
use: [ use: [
{ loader: require.resolve('raw-loader') }, { loader: require.resolve('raw-loader') },
// Including RawCssLoader here because per v4.x release notes for postcss-loader under breaking changes: { loader: RawCssLoader },
// "loader output only CSS, so you need to use css-loader/file-loader/raw-loader to inject code inside bundle"
RawCssLoader,
{ {
loader: require.resolve('postcss-loader'), loader: require.resolve('postcss-loader'),
options: { options: {
@ -203,13 +182,14 @@ export function getStylesConfig(
postcssOptions: postcssOptionsCreator(componentsSourceMap), postcssOptions: postcssOptionsCreator(componentsSourceMap),
}, },
}, },
...(use as any[]), ...(Array.isArray(use) ? use : []),
], ],
})); }));
// load global css as css files // load global css as css files
if (globalStylePaths.length > 0) { if (globalStylePaths.length > 0) {
const globalSourceMap = !!cssSourceMap && !buildOptions.sourceMap.hidden; const globalSourceMap =
!!cssSourceMap && buildOptions.sourceMap !== 'hidden';
rules.push( rules.push(
...baseRules.map(({ test, use }) => { ...baseRules.map(({ test, use }) => {
@ -217,13 +197,11 @@ export function getStylesConfig(
include: globalStylePaths, include: globalStylePaths,
test, test,
use: [ use: [
buildOptions.extractCss {
? { loader: MiniCssExtractPlugin.loader,
loader: MiniCssExtractPlugin.loader, options: { esModule: true },
options: { esModule: true }, },
} { loader: RawCssLoader },
: require.resolve('style-loader'),
RawCssLoader,
{ {
loader: require.resolve('postcss-loader'), loader: require.resolve('postcss-loader'),
options: { options: {
@ -238,16 +216,14 @@ export function getStylesConfig(
); );
} }
if (buildOptions.extractCss) { extraPlugins.push(
extraPlugins.push( // extract global css from js files into own css file
// extract global css from js files into own css file new MiniCssExtractPlugin({
new MiniCssExtractPlugin({ filename: `[name]${hashFormat.extract}.css`,
filename: `[name]${hashFormat.extract}.css`, }),
}), // suppress empty .js files in css only entry points
// suppress empty .js files in css only entry points new RemoveEmptyScriptsPlugin()
new RemoveEmptyScriptsPlugin() );
);
}
return { return {
entry: entryPoints, entry: entryPoints,

View File

@ -44,7 +44,8 @@ async function resolve(
} }
module.exports.postcss = true; module.exports.postcss = true;
export default (options: PostcssCliResourcesOptions) => {
export function PostcssCliResources(options: PostcssCliResourcesOptions) {
const { const {
deployUrl = '', deployUrl = '',
baseHref = '', 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 @@
/** import { HashFormat } from '../../hash-format';
* @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';
type Compiler = any; 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 { interpolateName } from 'loader-utils';
import * as path from 'path'; import * as path from 'path';
import { Compiler } from 'webpack'; import { Compiler } from 'webpack';

View File

@ -1,14 +1,16 @@
/** import { dirname, join } from 'path';
* @license import { readFileSync, writeFileSync } from 'fs';
* Copyright Google Inc. All Rights Reserved. import { EmittedFile } from '../run-webpack';
* import { ExtraEntryPoint } from '../shared-models';
* Use of this source code is governed by an MIT-style license that can be import { interpolateEnvironmentVariablesToIndex } from '../interpolate-env-variables-to-index';
* found in the LICENSE file at https://angular.io/license import { generateEntryPoints } from './package-chunk-sort';
*/
import { createHash } from 'crypto'; import { createHash } from 'crypto';
import { RawSource, ReplaceSource } from 'webpack-sources'; import { RawSource, ReplaceSource } from 'webpack-sources';
function stripBom(data: string) {
return data.replace(/^\uFEFF/, '');
}
const parse5 = require('parse5'); const parse5 = require('parse5');
export type LoadOutputFileFunctionType = (file: string) => string; export type LoadOutputFileFunctionType = (file: string) => string;
@ -267,3 +269,77 @@ function _generateSriAttributes(content: string) {
return [{ name: 'integrity', value: `${algo}-${hash}` }]; 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" semver "^6.3.0"
source-map "^0.7.3" 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: supports-color@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"