chore(web): clean up webpack setup (#7411)
This commit is contained in:
parent
24c9164ec7
commit
9c1ae3a1b0
@ -1,4 +1,3 @@
|
||||
import { serializeJson } from '@nrwl/workspace';
|
||||
import {
|
||||
checkFilesDoNotExist,
|
||||
checkFilesExist,
|
||||
@ -42,20 +41,19 @@ describe('React Applications', () => {
|
||||
|
||||
await testGeneratedApp(appName, {
|
||||
checkStyles: true,
|
||||
checkProdBuild: true,
|
||||
checkLinter: true,
|
||||
checkE2E: true,
|
||||
});
|
||||
}, 500000);
|
||||
|
||||
it('should support vendor sourcemaps', () => {
|
||||
it('should support sourcemaps', () => {
|
||||
const appName = uniq('app');
|
||||
|
||||
runCLI(`generate @nrwl/react:app ${appName} --no-interactive`);
|
||||
|
||||
runCLI(`build ${appName} --sourceMap`);
|
||||
runCLI(`build ${appName} --sourceMap --outputHashing none`);
|
||||
|
||||
checkFilesExist(`dist/apps/${appName}/vendor.js.map`);
|
||||
checkFilesExist(`dist/apps/${appName}/main.esm.js.map`);
|
||||
}, 250000);
|
||||
|
||||
it('should be able to generate a publishable react lib', async () => {
|
||||
@ -114,7 +112,6 @@ describe('React Applications', () => {
|
||||
|
||||
await testGeneratedApp(appName, {
|
||||
checkStyles: true,
|
||||
checkProdBuild: false,
|
||||
checkLinter: false,
|
||||
checkE2E: false,
|
||||
});
|
||||
@ -145,7 +142,6 @@ describe('React Applications', () => {
|
||||
|
||||
await testGeneratedApp(appName, {
|
||||
checkStyles: true,
|
||||
checkProdBuild: false,
|
||||
checkLinter: false,
|
||||
checkE2E: false,
|
||||
});
|
||||
@ -185,18 +181,10 @@ describe('React Applications', () => {
|
||||
|
||||
await testGeneratedApp(styledComponentsApp, {
|
||||
checkStyles: false,
|
||||
checkProdBuild: true,
|
||||
checkLinter: false,
|
||||
checkE2E: false,
|
||||
});
|
||||
|
||||
expect(readFile(`dist/apps/${styledComponentsApp}/main.js`)).toContain(
|
||||
'app__StyledApp'
|
||||
);
|
||||
expect(
|
||||
readFile(`dist/apps/${styledComponentsApp}/prod/main.esm.js`)
|
||||
).not.toContain('app__StyledApp');
|
||||
|
||||
const styledJsxApp = uniq('app');
|
||||
|
||||
runCLI(
|
||||
@ -205,7 +193,6 @@ describe('React Applications', () => {
|
||||
|
||||
await testGeneratedApp(styledJsxApp, {
|
||||
checkStyles: false,
|
||||
checkProdBuild: false,
|
||||
checkLinter: false,
|
||||
checkE2E: false,
|
||||
});
|
||||
@ -218,7 +205,6 @@ describe('React Applications', () => {
|
||||
|
||||
await testGeneratedApp(noStylesApp, {
|
||||
checkStyles: false,
|
||||
checkProdBuild: false,
|
||||
checkLinter: false,
|
||||
checkE2E: false,
|
||||
});
|
||||
@ -236,24 +222,23 @@ describe('React Applications', () => {
|
||||
|
||||
runCLI(`generate @nrwl/react:app ${appName} --style=css --no-interactive`);
|
||||
|
||||
// changing browser suporrt of this application
|
||||
// changing browser support of this application
|
||||
updateFile(`apps/${appName}/.browserslistrc`, `IE 11`);
|
||||
|
||||
await testGeneratedApp(appName, {
|
||||
checkStyles: false,
|
||||
checkProdBuild: true,
|
||||
checkLinter: false,
|
||||
checkE2E: false,
|
||||
});
|
||||
|
||||
const filesToCheck = [
|
||||
`dist/apps/${appName}/prod/polyfills.es5.js`,
|
||||
`dist/apps/${appName}/prod/main.es5.js`,
|
||||
`dist/apps/${appName}/polyfills.es5.js`,
|
||||
`dist/apps/${appName}/main.es5.js`,
|
||||
];
|
||||
|
||||
checkFilesExist(...filesToCheck);
|
||||
|
||||
expect(readFile(`dist/apps/${appName}/prod/index.html`)).toContain(
|
||||
expect(readFile(`dist/apps/${appName}/index.html`)).toContain(
|
||||
`<script src="main.esm.js" type="module"></script><script src="main.es5.js" nomodule defer></script>`
|
||||
);
|
||||
}, 250000);
|
||||
@ -312,7 +297,6 @@ describe('React Applications', () => {
|
||||
|
||||
await testGeneratedApp(appName, {
|
||||
checkStyles: true,
|
||||
checkProdBuild: false,
|
||||
checkLinter: false,
|
||||
checkE2E: false,
|
||||
});
|
||||
@ -321,7 +305,6 @@ describe('React Applications', () => {
|
||||
async function testGeneratedApp(
|
||||
appName,
|
||||
opts: {
|
||||
checkProdBuild: boolean;
|
||||
checkStyles: boolean;
|
||||
checkLinter: boolean;
|
||||
checkE2E: boolean;
|
||||
@ -332,45 +315,23 @@ describe('React Applications', () => {
|
||||
expect(lintResults).toContain('All files pass linting.');
|
||||
}
|
||||
|
||||
runCLI(`build ${appName}`);
|
||||
let filesToCheck = [
|
||||
runCLI(`build ${appName} --outputHashing none`);
|
||||
const filesToCheck = [
|
||||
`dist/apps/${appName}/index.html`,
|
||||
`dist/apps/${appName}/polyfills.js`,
|
||||
`dist/apps/${appName}/runtime.js`,
|
||||
`dist/apps/${appName}/vendor.js`,
|
||||
`dist/apps/${appName}/main.js`,
|
||||
`dist/apps/${appName}/runtime.esm.js`,
|
||||
`dist/apps/${appName}/polyfills.esm.js`,
|
||||
`dist/apps/${appName}/main.esm.js`,
|
||||
];
|
||||
if (opts.checkStyles) {
|
||||
filesToCheck.push(`dist/apps/${appName}/styles.js`);
|
||||
}
|
||||
checkFilesExist(...filesToCheck);
|
||||
|
||||
expect(readFile(`dist/apps/${appName}/main.js`)).toContain(
|
||||
'function App() {'
|
||||
);
|
||||
|
||||
if (opts.checkProdBuild) {
|
||||
const prodOutputPath = `dist/apps/${appName}/prod`;
|
||||
runCLI(
|
||||
`build ${appName} --prod --output-hashing none --outputPath ${prodOutputPath}`
|
||||
);
|
||||
filesToCheck = [
|
||||
`${prodOutputPath}/index.html`,
|
||||
`${prodOutputPath}/runtime.esm.js`,
|
||||
`${prodOutputPath}/polyfills.esm.js`,
|
||||
`${prodOutputPath}/main.esm.js`,
|
||||
];
|
||||
if (opts.checkStyles) {
|
||||
filesToCheck.push(`${prodOutputPath}/styles.css`);
|
||||
filesToCheck.push(`dist/apps/${appName}/styles.css`);
|
||||
}
|
||||
checkFilesExist(...filesToCheck);
|
||||
|
||||
if (opts.checkStyles) {
|
||||
expect(readFile(`${prodOutputPath}/index.html`)).toContain(
|
||||
expect(readFile(`dist/apps/${appName}/index.html`)).toContain(
|
||||
`<link rel="stylesheet" href="styles.css">`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const testResults = await runCLIAsync(`test ${appName}`);
|
||||
expect(testResults.combinedOutput).toContain(
|
||||
@ -385,7 +346,7 @@ describe('React Applications', () => {
|
||||
}
|
||||
});
|
||||
|
||||
describe('--style option', () => {
|
||||
fdescribe('--style option', () => {
|
||||
beforeAll(() => newProject());
|
||||
|
||||
it.each`
|
||||
@ -420,11 +381,7 @@ describe('--style option', () => {
|
||||
`body { font-family: "Comic Sans MS"; }`
|
||||
);
|
||||
|
||||
runCLI(`build ${appName}`);
|
||||
|
||||
expect(readFile(`dist/apps/${appName}/styles.js`)).toMatch(/Comic Sans MS/);
|
||||
|
||||
runCLI(`build ${appName} --prod --output-hashing none`);
|
||||
runCLI(`build ${appName} --outputHashing none`);
|
||||
|
||||
expect(readFile(`dist/apps/${appName}/styles.css`)).toMatch(
|
||||
/Comic Sans MS/
|
||||
|
||||
@ -24,20 +24,7 @@ describe('Web Components Applications', () => {
|
||||
const lintResults = runCLI(`lint ${appName}`);
|
||||
expect(lintResults).toContain('All files pass linting.');
|
||||
|
||||
runCLI(`build ${appName}`);
|
||||
checkFilesExist(
|
||||
`dist/apps/${appName}/index.html`,
|
||||
`dist/apps/${appName}/runtime.js`,
|
||||
`dist/apps/${appName}/polyfills.js`,
|
||||
`dist/apps/${appName}/main.js`,
|
||||
`dist/apps/${appName}/styles.js`
|
||||
);
|
||||
|
||||
expect(readFile(`dist/apps/${appName}/main.js`)).toContain(
|
||||
'class AppElement'
|
||||
);
|
||||
|
||||
runCLI(`build ${appName} --prod --output-hashing none`);
|
||||
runCLI(`build ${appName} --outputHashing none`);
|
||||
checkFilesExist(
|
||||
`dist/apps/${appName}/index.html`,
|
||||
`dist/apps/${appName}/runtime.esm.js`,
|
||||
@ -93,7 +80,7 @@ describe('Web Components Applications', () => {
|
||||
`dist/apps/${appName}/_should_remove.txt`,
|
||||
`dist/apps/_should_not_remove.txt`
|
||||
);
|
||||
runCLI(`build ${appName}`);
|
||||
runCLI(`build ${appName} --outputHashing none`);
|
||||
runCLI(`build ${libName}`);
|
||||
checkFilesDoNotExist(
|
||||
`dist/apps/${appName}/_should_remove.txt`,
|
||||
@ -103,11 +90,11 @@ describe('Web Components Applications', () => {
|
||||
|
||||
// `delete-output-path`
|
||||
createFile(`dist/apps/${appName}/_should_keep.txt`);
|
||||
runCLI(`build ${appName} --delete-output-path=false`);
|
||||
runCLI(`build ${appName} --delete-output-path=false --outputHashing none`);
|
||||
checkFilesExist(`dist/apps/${appName}/_should_keep.txt`);
|
||||
|
||||
createFile(`dist/libs/${libName}/_should_keep.txt`);
|
||||
runCLI(`build ${libName} --delete-output-path=false`);
|
||||
runCLI(`build ${libName} --delete-output-path=false --outputHashing none`);
|
||||
checkFilesExist(`dist/libs/${libName}/_should_keep.txt`);
|
||||
}, 120000);
|
||||
|
||||
@ -118,7 +105,7 @@ describe('Web Components Applications', () => {
|
||||
|
||||
updateFile(`apps/${appName}/browserslist`, `IE 9-11`);
|
||||
|
||||
runCLI(`build ${appName} --prod --outputHashing=none`);
|
||||
runCLI(`build ${appName} --outputHashing=none`);
|
||||
|
||||
checkFilesExist(
|
||||
`dist/apps/${appName}/main.esm.js`,
|
||||
@ -159,9 +146,9 @@ describe('Web Components Applications', () => {
|
||||
`;
|
||||
return newContent;
|
||||
});
|
||||
runCLI(`build ${appName}`);
|
||||
runCLI(`build ${appName} --outputHashing none`);
|
||||
|
||||
expect(readFile(`dist/apps/${appName}/main.js`)).toMatch(
|
||||
expect(readFile(`dist/apps/${appName}/main.esm.js`)).toMatch(
|
||||
/Reflect\.metadata/
|
||||
);
|
||||
|
||||
@ -172,9 +159,9 @@ describe('Web Components Applications', () => {
|
||||
return JSON.stringify(json);
|
||||
});
|
||||
|
||||
runCLI(`build ${appName}`);
|
||||
runCLI(`build ${appName} --outputHashing none`);
|
||||
|
||||
expect(readFile(`dist/apps/${appName}/main.js`)).not.toMatch(
|
||||
expect(readFile(`dist/apps/${appName}/main.esm.js`)).not.toMatch(
|
||||
/Reflect\.metadata/
|
||||
);
|
||||
}, 120000);
|
||||
@ -254,7 +241,7 @@ describe('CLI - Environment Variables', () => {
|
||||
|
||||
updateFile(main2, `${newCode2}\n${content2}`);
|
||||
|
||||
runCLI(`run-many --target=build --all`, {
|
||||
runCLI(`run-many --target build --all --no-optimization`, {
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_ENV: 'test',
|
||||
@ -332,13 +319,13 @@ describe('Build Options', () => {
|
||||
return config;
|
||||
});
|
||||
|
||||
runCLI(`build ${appName}`);
|
||||
runCLI(`build ${appName} --outputHashing none --optimization false`);
|
||||
|
||||
const distPath = `dist/apps/${appName}`;
|
||||
const scripts = readFile(`${distPath}/scripts.js`);
|
||||
const styles = readFile(`${distPath}/styles.js`);
|
||||
const styles = readFile(`${distPath}/styles.css`);
|
||||
const barScripts = readFile(`${distPath}/${barScriptsBundleName}.js`);
|
||||
const barStyles = readFile(`${distPath}/${barStylesBundleName}.js`);
|
||||
const barStyles = readFile(`${distPath}/${barStylesBundleName}.css`);
|
||||
|
||||
expect(scripts).toContain(fooJsContent);
|
||||
expect(scripts).not.toContain(barJsContent);
|
||||
|
||||
@ -7,7 +7,6 @@ import {
|
||||
readFile,
|
||||
readJson,
|
||||
readProjectConfig,
|
||||
readWorkspaceConfig,
|
||||
removeProject,
|
||||
rmDist,
|
||||
runCLI,
|
||||
@ -669,13 +668,13 @@ describe('print-affected', () => {
|
||||
);
|
||||
|
||||
expect(resWithDeps.tasks[0]).toMatchObject({
|
||||
id: `${myapp}:build`,
|
||||
id: `${myapp}:build:production`,
|
||||
overrides: {},
|
||||
target: {
|
||||
project: myapp,
|
||||
target: 'build',
|
||||
},
|
||||
command: `${runNx} build ${myapp}`,
|
||||
command: `${runNx} build ${myapp} --configuration production`,
|
||||
outputs: [`dist/apps/${myapp}`],
|
||||
});
|
||||
|
||||
|
||||
@ -222,7 +222,7 @@
|
||||
"strip-json-comments": "^3.1.1",
|
||||
"style-loader": "^3.3.0",
|
||||
"styled-components": "5.0.0",
|
||||
"stylus": "0.54.5",
|
||||
"stylus": "^0.55.0",
|
||||
"stylus-loader": "^6.2.0",
|
||||
"tailwindcss": "^2.2.17",
|
||||
"tcp-port-used": "^1.0.2",
|
||||
|
||||
@ -12,6 +12,7 @@ export function addProject(host: Tree, options: NormalizedSchema) {
|
||||
targets.build = {
|
||||
builder: '@nrwl/next:build',
|
||||
outputs: ['{options.outputPath}'],
|
||||
defaultConfiguration: 'production',
|
||||
options: {
|
||||
root: options.appProjectRoot,
|
||||
outputPath: joinPathFragments('dist', options.appProjectRoot),
|
||||
|
||||
@ -58,8 +58,7 @@ function getWebpackConfig(config: Configuration) {
|
||||
const babelLoader = config.module.rules.find(
|
||||
(rule) =>
|
||||
typeof rule !== 'string' &&
|
||||
rule.loader &&
|
||||
rule.loader.toString().includes('babel-loader')
|
||||
rule.loader?.toString().includes('babel-loader')
|
||||
);
|
||||
if (babelLoader && typeof babelLoader !== 'string') {
|
||||
babelLoader.options['plugins'] = [
|
||||
|
||||
@ -263,7 +263,6 @@ Object {
|
||||
});
|
||||
expect(targetConfig.build.configurations.production).toEqual({
|
||||
optimization: true,
|
||||
extractCss: true,
|
||||
extractLicenses: true,
|
||||
fileReplacements: [
|
||||
{
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { NormalizedSchema } from '../schema';
|
||||
import {
|
||||
joinPathFragments,
|
||||
addProjectConfiguration,
|
||||
joinPathFragments,
|
||||
ProjectConfiguration,
|
||||
TargetConfiguration,
|
||||
} from '@nrwl/devkit';
|
||||
@ -38,6 +38,7 @@ function createBuildTarget(options: NormalizedSchema): TargetConfiguration {
|
||||
return {
|
||||
executor: '@nrwl/web:build',
|
||||
outputs: ['{options.outputPath}'],
|
||||
defaultConfiguration: 'production',
|
||||
options: {
|
||||
outputPath: joinPathFragments('dist', options.appProjectRoot),
|
||||
index: joinPathFragments(options.appProjectRoot, 'src/index.html'),
|
||||
@ -84,7 +85,6 @@ function createBuildTarget(options: NormalizedSchema): TargetConfiguration {
|
||||
optimization: true,
|
||||
outputHashing: 'all',
|
||||
sourceMap: false,
|
||||
extractCss: true,
|
||||
namedChunks: false,
|
||||
extractLicenses: true,
|
||||
vendorChunk: false,
|
||||
|
||||
@ -95,7 +95,7 @@
|
||||
"source-map": "0.7.3",
|
||||
"source-map-loader": "^3.0.0",
|
||||
"style-loader": "^3.3.0",
|
||||
"stylus": "0.54.5",
|
||||
"stylus": "^0.55.0",
|
||||
"stylus-loader": "^6.2.0",
|
||||
"terser": "4.3.8",
|
||||
"terser-webpack-plugin": "^5.1.1",
|
||||
|
||||
@ -15,24 +15,24 @@ import {
|
||||
} from '@nrwl/workspace/src/utilities/buildable-libs-utils';
|
||||
import { readTsConfig } from '@nrwl/workspace/src/utilities/typescript';
|
||||
|
||||
import { writeIndexHtml } from '../../utils/third-party/cli-files/utilities/index-file/write-index-html';
|
||||
import { CrossOriginValue } from '../../utils/third-party/cli-files/utilities/index-file/augment-index-html';
|
||||
import { BuildBrowserFeatures } from '../../utils/third-party/utils/build-browser-features';
|
||||
|
||||
import { normalizeWebBuildOptions } from '../../utils/normalize';
|
||||
import { getWebConfig } from '../../utils/web.config';
|
||||
import type { BuildBuilderOptions } from '../../utils/types';
|
||||
import { deleteOutputDir } from '../../utils/delete-output-dir';
|
||||
import type { ExtraEntryPoint } from '../../utils/third-party/browser/schema';
|
||||
import type { BuildBuilderOptions } from '../../utils/shared-models';
|
||||
import { ExtraEntryPoint } from '../../utils/shared-models';
|
||||
import { getEmittedFiles, runWebpack } from '../../utils/run-webpack';
|
||||
import { BuildBrowserFeatures } from '../../utils/webpack/build-browser-features';
|
||||
import { deleteOutputDir } from '../../utils/fs';
|
||||
import {
|
||||
CrossOriginValue,
|
||||
writeIndexHtml,
|
||||
} from '../../utils/webpack/write-index-html';
|
||||
|
||||
export interface WebBuildBuilderOptions extends BuildBuilderOptions {
|
||||
export interface WebBuildExecutorOptions extends BuildBuilderOptions {
|
||||
index: string;
|
||||
budgets?: any[];
|
||||
baseHref?: string;
|
||||
deployUrl?: string;
|
||||
|
||||
extractCss?: boolean;
|
||||
crossOrigin?: CrossOriginValue;
|
||||
|
||||
polyfills?: string;
|
||||
@ -58,7 +58,7 @@ export interface WebBuildBuilderOptions extends BuildBuilderOptions {
|
||||
}
|
||||
|
||||
function getWebpackConfigs(
|
||||
options: WebBuildBuilderOptions,
|
||||
options: WebBuildExecutorOptions,
|
||||
context: ExecutorContext
|
||||
): Configuration[] {
|
||||
const metadata = context.workspace.projects[context.projectName];
|
||||
@ -115,7 +115,7 @@ function getWebpackConfigs(
|
||||
}
|
||||
|
||||
export async function* run(
|
||||
options: WebBuildBuilderOptions,
|
||||
options: WebBuildExecutorOptions,
|
||||
context: ExecutorContext
|
||||
) {
|
||||
// Node versions 12.2-12.8 has a bug where prod builds will hang for 2-3 minutes
|
||||
|
||||
@ -11,7 +11,7 @@ import { map, tap } from 'rxjs/operators';
|
||||
import * as WebpackDevServer from 'webpack-dev-server';
|
||||
|
||||
import { normalizeWebBuildOptions } from '../../utils/normalize';
|
||||
import { WebBuildBuilderOptions } from '../build/build.impl';
|
||||
import { WebBuildExecutorOptions } from '../build/build.impl';
|
||||
import { getDevServerConfig } from '../../utils/devserver.config';
|
||||
import {
|
||||
calculateProjectDependencies,
|
||||
@ -100,9 +100,9 @@ export default async function* devServerExecutor(
|
||||
function getBuildOptions(
|
||||
options: WebDevServerOptions,
|
||||
context: ExecutorContext
|
||||
): WebBuildBuilderOptions {
|
||||
): WebBuildExecutorOptions {
|
||||
const target = parseTargetString(options.buildTarget);
|
||||
const overrides: Partial<WebBuildBuilderOptions> = {
|
||||
const overrides: Partial<WebBuildExecutorOptions> = {
|
||||
watch: false,
|
||||
};
|
||||
if (options.maxWorkers) {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { dirname } from 'path';
|
||||
|
||||
import { AssetGlobPattern } from '../../../utils/types';
|
||||
import { AssetGlobPattern } from '../../../utils/shared-models';
|
||||
import { normalizeAssets, normalizePluginPath } from '../../../utils/normalize';
|
||||
import { WebPackageOptions } from '../schema';
|
||||
|
||||
|
||||
@ -19,8 +19,7 @@ import {
|
||||
} from '@nrwl/workspace/src/utilities/buildable-libs-utils';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
|
||||
import { AssetGlobPattern } from '../../utils/types';
|
||||
import { deleteOutputDir } from '../../utils/delete-output-dir';
|
||||
import { AssetGlobPattern } from '../../utils/shared-models';
|
||||
import { WebPackageOptions } from './schema';
|
||||
import { runRollup } from './lib/run-rollup';
|
||||
import {
|
||||
@ -28,6 +27,7 @@ import {
|
||||
normalizePackageOptions,
|
||||
} from './lib/normalize';
|
||||
import { analyze } from './lib/analyze-plugin';
|
||||
import { deleteOutputDir } from '../../utils/fs';
|
||||
|
||||
// These use require because the ES import isn't correct.
|
||||
const commonjs = require('@rollup/plugin-commonjs');
|
||||
|
||||
@ -269,7 +269,6 @@ describe('app', () => {
|
||||
});
|
||||
expect(architectConfig.build.configurations.production).toEqual({
|
||||
optimization: true,
|
||||
extractCss: true,
|
||||
extractLicenses: true,
|
||||
fileReplacements: [
|
||||
{
|
||||
|
||||
@ -23,7 +23,7 @@ import { cypressProjectGenerator } from '@nrwl/cypress';
|
||||
import { Linter, lintProjectGenerator } from '@nrwl/linter';
|
||||
import { jestProjectGenerator } from '@nrwl/jest';
|
||||
|
||||
import { WebBuildBuilderOptions } from '../../executors/build/build.impl';
|
||||
import { WebBuildExecutorOptions } from '../../executors/build/build.impl';
|
||||
import { Schema } from './schema';
|
||||
|
||||
interface NormalizedSchema extends Schema {
|
||||
@ -50,7 +50,7 @@ function addBuildTarget(
|
||||
project: ProjectConfiguration,
|
||||
options: NormalizedSchema
|
||||
): ProjectConfiguration {
|
||||
const buildOptions: WebBuildBuilderOptions = {
|
||||
const buildOptions: WebBuildExecutorOptions = {
|
||||
outputPath: joinPathFragments('dist', options.appProjectRoot),
|
||||
index: joinPathFragments(options.appProjectRoot, 'src/index.html'),
|
||||
baseHref: '/',
|
||||
@ -66,7 +66,7 @@ function addBuildTarget(
|
||||
],
|
||||
scripts: [],
|
||||
};
|
||||
const productionBuildOptions: Partial<WebBuildBuilderOptions> = {
|
||||
const productionBuildOptions: Partial<WebBuildExecutorOptions> = {
|
||||
fileReplacements: [
|
||||
{
|
||||
replace: joinPathFragments(
|
||||
@ -82,7 +82,6 @@ function addBuildTarget(
|
||||
optimization: true,
|
||||
outputHashing: 'all',
|
||||
sourceMap: false,
|
||||
extractCss: true,
|
||||
namedChunks: false,
|
||||
extractLicenses: true,
|
||||
vendorChunk: false,
|
||||
@ -95,6 +94,7 @@ function addBuildTarget(
|
||||
build: {
|
||||
executor: '@nrwl/web:build',
|
||||
outputs: ['{options.outputPath}'],
|
||||
defaultConfiguration: 'production',
|
||||
options: buildOptions,
|
||||
configurations: {
|
||||
production: productionBuildOptions,
|
||||
|
||||
@ -4,7 +4,7 @@ import { Configuration, WebpackPluginInstance } from 'webpack';
|
||||
import { LicenseWebpackPlugin } from 'license-webpack-plugin';
|
||||
import * as CopyWebpackPlugin from 'copy-webpack-plugin';
|
||||
import * as TerserWebpackPlugin from 'terser-webpack-plugin';
|
||||
import { AssetGlobPattern, BuildBuilderOptions } from './types';
|
||||
import { AssetGlobPattern, BuildBuilderOptions } from './shared-models';
|
||||
import { getOutputHashFormat } from './hash-format';
|
||||
|
||||
// Inlining tsconfig-paths-webpack-plugin with a patch
|
||||
|
||||
@ -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);
|
||||
}
|
||||
@ -3,17 +3,17 @@ import { Configuration as WebpackDevServerConfiguration } from 'webpack-dev-serv
|
||||
import * as path from 'path';
|
||||
|
||||
import { getWebConfig } from './web.config';
|
||||
import { WebBuildBuilderOptions } from '../executors/build/build.impl';
|
||||
import { WebBuildExecutorOptions } from '../executors/build/build.impl';
|
||||
import { WebDevServerOptions } from '../executors/dev-server/dev-server.impl';
|
||||
import { buildServePath } from './serve-path';
|
||||
import { OptimizationOptions } from './types';
|
||||
import { OptimizationOptions } from './shared-models';
|
||||
import { readFileSync } from 'fs-extra';
|
||||
|
||||
export function getDevServerConfig(
|
||||
workspaceRoot: string,
|
||||
projectRoot: string,
|
||||
sourceRoot: string,
|
||||
buildOptions: WebBuildBuilderOptions,
|
||||
buildOptions: WebBuildExecutorOptions,
|
||||
serveOptions: WebDevServerOptions
|
||||
): Partial<WebpackDevServerConfiguration> {
|
||||
const webpackConfig = getWebConfig(
|
||||
@ -37,7 +37,7 @@ export function getDevServerConfig(
|
||||
function getDevServerPartial(
|
||||
root: string,
|
||||
options: WebDevServerOptions,
|
||||
buildOptions: WebBuildBuilderOptions
|
||||
buildOptions: WebBuildExecutorOptions
|
||||
): WebpackDevServerConfiguration {
|
||||
const servePath = buildServePath(buildOptions);
|
||||
|
||||
@ -49,7 +49,7 @@ function getDevServerPartial(
|
||||
port: options.port,
|
||||
headers: { 'Access-Control-Allow-Origin': '*' },
|
||||
historyApiFallback: {
|
||||
index: `${servePath}/${path.basename(buildOptions.index)}`,
|
||||
index: `${servePath}${path.basename(buildOptions.index)}`,
|
||||
disableDotRule: true,
|
||||
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
|
||||
},
|
||||
|
||||
@ -1,13 +1,5 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import { existsSync } from 'fs';
|
||||
import * as path from 'path';
|
||||
import { isDirectory } from './is-directory';
|
||||
import { existsSync, removeSync, statSync } from 'fs-extra';
|
||||
|
||||
export function findUp(
|
||||
names: string | string[],
|
||||
@ -60,3 +52,23 @@ export function findAllNodeModules(from: string, root?: string) {
|
||||
|
||||
return nodeModules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an output directory, but error out if it's the root of the project.
|
||||
*/
|
||||
export function deleteOutputDir(root: string, outputPath: string) {
|
||||
const resolvedOutputPath = path.resolve(root, outputPath);
|
||||
if (resolvedOutputPath === root) {
|
||||
throw new Error('Output path MUST not be project root directory!');
|
||||
}
|
||||
|
||||
removeSync(resolvedOutputPath);
|
||||
}
|
||||
|
||||
export function isDirectory(path: string) {
|
||||
try {
|
||||
return statSync(path).isDirectory();
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,3 @@
|
||||
// Originally from devkit.
|
||||
// See: https://github.com/angular/angular-cli/blob/2c8b12f/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/utils.ts
|
||||
export interface HashFormat {
|
||||
chunk: string;
|
||||
extract: string;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { normalizeBuildOptions } from './normalize';
|
||||
import { BuildBuilderOptions } from './types';
|
||||
import { BuildBuilderOptions } from './shared-models';
|
||||
|
||||
import * as fs from 'fs';
|
||||
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
import { WebBuildBuilderOptions } from '../executors/build/build.impl';
|
||||
import { WebBuildExecutorOptions } from '../executors/build/build.impl';
|
||||
import { normalizePath } from '@nrwl/devkit';
|
||||
import { basename, dirname, relative, resolve } from 'path';
|
||||
import { AssetGlobPattern, BuildBuilderOptions } from './types';
|
||||
import {
|
||||
AssetGlobPattern,
|
||||
BuildBuilderOptions,
|
||||
ExtraEntryPoint,
|
||||
ExtraEntryPointClass,
|
||||
} from './shared-models';
|
||||
import { statSync } from 'fs';
|
||||
|
||||
export interface FileReplacement {
|
||||
@ -96,10 +101,10 @@ function normalizeFileReplacements(
|
||||
}
|
||||
|
||||
export function normalizeWebBuildOptions(
|
||||
options: WebBuildBuilderOptions,
|
||||
options: WebBuildExecutorOptions,
|
||||
root: string,
|
||||
sourceRoot: string
|
||||
): WebBuildBuilderOptions {
|
||||
): WebBuildExecutorOptions {
|
||||
return {
|
||||
...normalizeBuildOptions(options, root, sourceRoot),
|
||||
optimization:
|
||||
@ -116,7 +121,9 @@ export function normalizeWebBuildOptions(
|
||||
};
|
||||
}
|
||||
|
||||
export function convertBuildOptions(buildOptions: WebBuildBuilderOptions): any {
|
||||
export function convertBuildOptions(
|
||||
buildOptions: WebBuildExecutorOptions
|
||||
): any {
|
||||
const options = buildOptions as any;
|
||||
return <any>{
|
||||
...options,
|
||||
@ -125,3 +132,42 @@ export function convertBuildOptions(buildOptions: WebBuildBuilderOptions): any {
|
||||
lazyModules: [] as string[],
|
||||
};
|
||||
}
|
||||
|
||||
export type NormalizedEntryPoint = Required<Omit<ExtraEntryPointClass, 'lazy'>>;
|
||||
|
||||
export function normalizeExtraEntryPoints(
|
||||
extraEntryPoints: ExtraEntryPoint[],
|
||||
defaultBundleName: string
|
||||
): NormalizedEntryPoint[] {
|
||||
return extraEntryPoints.map((entry) => {
|
||||
let normalizedEntry;
|
||||
if (typeof entry === 'string') {
|
||||
normalizedEntry = {
|
||||
input: entry,
|
||||
inject: true,
|
||||
bundleName: defaultBundleName,
|
||||
};
|
||||
} else {
|
||||
const { lazy, inject = true, ...newEntry } = entry;
|
||||
const injectNormalized = entry.lazy !== undefined ? !entry.lazy : inject;
|
||||
let bundleName;
|
||||
|
||||
if (entry.bundleName) {
|
||||
bundleName = entry.bundleName;
|
||||
} else if (!injectNormalized) {
|
||||
// Lazy entry points use the file name as bundle name.
|
||||
bundleName = basename(
|
||||
normalizePath(
|
||||
entry.input.replace(/\.(js|css|scss|sass|less|styl)$/i, '')
|
||||
)
|
||||
);
|
||||
} else {
|
||||
bundleName = defaultBundleName;
|
||||
}
|
||||
|
||||
normalizedEntry = { ...newEntry, inject: injectNormalized, bundleName };
|
||||
}
|
||||
|
||||
return normalizedEntry;
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { WebBuildBuilderOptions } from '../executors/build/build.impl';
|
||||
import { WebBuildExecutorOptions } from '../executors/build/build.impl';
|
||||
|
||||
export function buildServePath(browserOptions: WebBuildBuilderOptions) {
|
||||
export function buildServePath(browserOptions: WebBuildExecutorOptions) {
|
||||
let servePath =
|
||||
_findDefaultServePath(browserOptions.baseHref, browserOptions.deployUrl) ||
|
||||
'/';
|
||||
|
||||
120
packages/web/src/utils/shared-models.ts
Normal file
120
packages/web/src/utils/shared-models.ts
Normal 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;
|
||||
}
|
||||
425
packages/web/src/utils/third-party/browser/schema.ts
vendored
425
packages/web/src/utils/third-party/browser/schema.ts
vendored
@ -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[];
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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()}`
|
||||
: '';
|
||||
}
|
||||
@ -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() ?? '');
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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}]]`;
|
||||
}
|
||||
@ -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'));
|
||||
@ -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>"`;
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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/, '');
|
||||
}
|
||||
@ -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';
|
||||
}
|
||||
@ -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());
|
||||
},
|
||||
})
|
||||
);
|
||||
}),
|
||||
};
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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';
|
||||
@ -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');
|
||||
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -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 || [],
|
||||
};
|
||||
}
|
||||
@ -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 };
|
||||
}
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
@ -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[];
|
||||
}
|
||||
@ -1,20 +1,16 @@
|
||||
import * as path from 'path';
|
||||
import { basename, posix, resolve } from 'path';
|
||||
import { posix, resolve } from 'path';
|
||||
import { readTsConfig } from '@nrwl/workspace/src/utilities/typescript';
|
||||
import { ScriptTarget } from 'typescript';
|
||||
import { getHashDigest, interpolateName } from 'loader-utils';
|
||||
import { Configuration } from 'webpack';
|
||||
|
||||
// TODO @FrozenPandaz we should remove the following imports
|
||||
import { getBrowserConfig } from './third-party/cli-files/models/webpack-configs/browser';
|
||||
import { getCommonConfig } from './third-party/cli-files/models/webpack-configs/common';
|
||||
import { getStylesConfig } from './third-party/cli-files/models/webpack-configs/styles';
|
||||
import { IndexHtmlWebpackPlugin } from './third-party/cli-files/plugins/index-html-webpack-plugin';
|
||||
import { generateEntryPoints } from './third-party/cli-files/utilities/package-chunk-sort';
|
||||
|
||||
import { WebBuildBuilderOptions } from '../executors/build/build.impl';
|
||||
import { WebBuildExecutorOptions } from '../executors/build/build.impl';
|
||||
import { convertBuildOptions } from './normalize';
|
||||
import { getBaseWebpackPartial } from './config';
|
||||
import { getBrowserConfig } from './webpack/partials/browser';
|
||||
import { getCommonConfig } from './webpack/partials/common';
|
||||
import { getStylesConfig } from './webpack/partials/styles';
|
||||
import MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
import webpackMerge = require('webpack-merge');
|
||||
import postcssImports = require('postcss-import');
|
||||
@ -31,7 +27,7 @@ export function getWebConfig(
|
||||
workspaceRoot,
|
||||
projectRoot,
|
||||
sourceRoot,
|
||||
options: WebBuildBuilderOptions,
|
||||
options: WebBuildExecutorOptions,
|
||||
esm?: boolean,
|
||||
isScriptOptimizeOn?: boolean,
|
||||
configuration?: string
|
||||
@ -69,53 +65,18 @@ export function getWebConfig(
|
||||
esm,
|
||||
isScriptOptimizeOn
|
||||
),
|
||||
getStylesPartial(
|
||||
wco.root,
|
||||
wco.projectRoot,
|
||||
wco.buildOptions,
|
||||
options.extractCss
|
||||
),
|
||||
getStylesPartial(wco.root, wco.projectRoot, wco.buildOptions, true),
|
||||
getCommonPartial(wco),
|
||||
getBrowserPartial(wco, options, isScriptOptimizeOn),
|
||||
getBrowserPartial(wco, options),
|
||||
]);
|
||||
}
|
||||
|
||||
function getBrowserPartial(
|
||||
wco: any,
|
||||
options: WebBuildBuilderOptions,
|
||||
isScriptOptimizeOn: boolean
|
||||
) {
|
||||
const config = getBrowserConfig(wco);
|
||||
|
||||
if (!isScriptOptimizeOn) {
|
||||
const {
|
||||
deployUrl,
|
||||
subresourceIntegrity,
|
||||
scripts = [],
|
||||
styles = [],
|
||||
index,
|
||||
baseHref,
|
||||
} = options;
|
||||
|
||||
config.plugins.push(
|
||||
new IndexHtmlWebpackPlugin({
|
||||
indexPath: resolve(wco.root, index),
|
||||
outputPath: basename(index),
|
||||
baseHref,
|
||||
entrypoints: generateEntryPoints({ scripts, styles }),
|
||||
deployUrl,
|
||||
sri: subresourceIntegrity,
|
||||
moduleEntrypoints: [],
|
||||
noModuleEntrypoints: ['polyfills-es5'],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return config;
|
||||
function getBrowserPartial(wco: any, options: WebBuildExecutorOptions) {
|
||||
return getBrowserConfig(wco);
|
||||
}
|
||||
|
||||
function _getBaseWebpackPartial(
|
||||
options: WebBuildBuilderOptions,
|
||||
options: WebBuildExecutorOptions,
|
||||
esm: boolean,
|
||||
isScriptOptimizeOn: boolean,
|
||||
emitDecoratorMetadata: boolean,
|
||||
@ -300,9 +261,7 @@ export function getPolyfillsPartial(
|
||||
// Safari 10.1 supports <script type="module"> but not <script nomodule>.
|
||||
// Need to patch it up so the browser doesn't load both sets.
|
||||
config.entry.polyfills = [
|
||||
require.resolve(
|
||||
'@nrwl/web/src/utils/third-party/cli-files/models/safari-nomodule.js'
|
||||
),
|
||||
require.resolve('@nrwl/web/src/utils/webpack/safari-nomodule.js'),
|
||||
...(polyfills ? [polyfills] : []),
|
||||
];
|
||||
} else if (es2015Polyfills && !esm && isScriptOptimizeOn) {
|
||||
|
||||
@ -1,11 +1,3 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import { fs, vol } from 'memfs';
|
||||
jest.mock('fs', () => fs);
|
||||
import { ScriptTarget } from 'typescript';
|
||||
@ -1,21 +1,7 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as browserslist from 'browserslist';
|
||||
import { feature, features } from 'caniuse-lite';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
const fullDifferentialEnv = process.env['NG_BUILD_DIFFERENTIAL_FULL'];
|
||||
export const fullDifferential =
|
||||
fullDifferentialEnv !== undefined &&
|
||||
fullDifferentialEnv !== '0' &&
|
||||
fullDifferentialEnv.toLowerCase() !== 'false';
|
||||
|
||||
export class BuildBrowserFeatures {
|
||||
private readonly _supportedBrowsers: string[];
|
||||
private readonly _es6TargetOrLater: boolean;
|
||||
@ -1,12 +1,5 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import { ExtraEntryPoint } from '../../browser/schema';
|
||||
import { normalizeExtraEntryPoints } from '../models/webpack-configs/utils';
|
||||
import { normalizeExtraEntryPoints } from '../normalize';
|
||||
import { ExtraEntryPoint } from '../shared-models';
|
||||
|
||||
export function generateEntryPoints(appConfig: {
|
||||
styles: ExtraEntryPoint[];
|
||||
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import { LicenseWebpackPlugin } from 'license-webpack-plugin';
|
||||
import { WebpackConfigOptions } from '../build-options';
|
||||
import { WebpackConfigOptions } from '../../shared-models';
|
||||
|
||||
const SubresourceIntegrityPlugin = require('webpack-subresource-integrity');
|
||||
|
||||
@ -50,7 +50,7 @@ export function getBrowserConfig(wco: WebpackConfigOptions) {
|
||||
: false,
|
||||
},
|
||||
optimization: {
|
||||
runtimeChunk: !!buildOptions.runtimeChunk ? 'single' : false,
|
||||
runtimeChunk: 'single',
|
||||
splitChunks: {
|
||||
maxAsyncRequests: Infinity,
|
||||
cacheGroups: {
|
||||
@ -1,56 +1,33 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import * as path from 'path';
|
||||
import { ScriptTarget } from 'typescript';
|
||||
import * as webpack from 'webpack';
|
||||
import { Compiler, Configuration } from 'webpack';
|
||||
|
||||
import { ExtraEntryPoint } from '../../../browser/schema';
|
||||
import { BuildBrowserFeatures, fullDifferential } from '../../../utils';
|
||||
import { manglingDisabled } from '../../../utils/mangle-options';
|
||||
import { ScriptsWebpackPlugin } from '../../plugins/scripts-webpack-plugin';
|
||||
import { findAllNodeModules, findUp } from '../../utilities/find-up';
|
||||
import { WebpackConfigOptions } from '../build-options';
|
||||
import {
|
||||
getEsVersionForFileName,
|
||||
getOutputHashFormat,
|
||||
normalizeExtraEntryPoints,
|
||||
} from './utils';
|
||||
import { ScriptsWebpackPlugin } from '../plugins/scripts-webpack-plugin';
|
||||
import { ExtraEntryPoint, WebpackConfigOptions } from '../../shared-models';
|
||||
import { BuildBrowserFeatures } from '../build-browser-features';
|
||||
import { getOutputHashFormat } from '../../hash-format';
|
||||
import { normalizeExtraEntryPoints } from '../../normalize';
|
||||
import CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
||||
|
||||
export const GLOBAL_DEFS_FOR_TERSER = {
|
||||
ngDevMode: false,
|
||||
ngI18nClosureMode: false,
|
||||
};
|
||||
|
||||
export const GLOBAL_DEFS_FOR_TERSER_WITH_AOT = {
|
||||
...GLOBAL_DEFS_FOR_TERSER,
|
||||
ngJitMode: false,
|
||||
};
|
||||
import { findAllNodeModules, findUp } from '../../fs';
|
||||
|
||||
const ProgressPlugin = require('webpack/lib/ProgressPlugin');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
|
||||
// tslint:disable-next-line:no-any
|
||||
const g: any = typeof global !== 'undefined' ? global : {};
|
||||
|
||||
// tslint:disable-next-line:no-big-function
|
||||
export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
|
||||
const { ContextReplacementPlugin, debug } = webpack;
|
||||
const { ContextReplacementPlugin } = webpack;
|
||||
|
||||
const { root, projectRoot, sourceRoot, buildOptions, tsConfig } = wco;
|
||||
const { styles: stylesOptimization, scripts: scriptsOptimization } =
|
||||
buildOptions.optimization;
|
||||
const {
|
||||
styles: stylesSourceMap,
|
||||
scripts: scriptsSourceMap,
|
||||
vendor: vendorSourceMap,
|
||||
} = buildOptions.sourceMap;
|
||||
|
||||
let stylesOptimization: boolean;
|
||||
let scriptsOptimization: boolean;
|
||||
if (typeof buildOptions.optimization === 'object') {
|
||||
scriptsOptimization = buildOptions.optimization.scripts;
|
||||
stylesOptimization = buildOptions.optimization.styles;
|
||||
} else {
|
||||
scriptsOptimization = stylesOptimization = !!buildOptions.optimization;
|
||||
}
|
||||
|
||||
const nodeModules = findUp('node_modules', projectRoot);
|
||||
if (!nodeModules) {
|
||||
@ -61,36 +38,20 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
|
||||
const extraPlugins: any[] = [];
|
||||
const entryPoints: { [key: string]: string[] } = {};
|
||||
|
||||
const targetInFileName = getEsVersionForFileName(
|
||||
fullDifferential
|
||||
? buildOptions.scriptTargetOverride
|
||||
: tsConfig.options.target,
|
||||
buildOptions.esVersionInFileName
|
||||
);
|
||||
|
||||
if (buildOptions.main) {
|
||||
entryPoints['main'] = [path.resolve(root, buildOptions.main)];
|
||||
}
|
||||
|
||||
let differentialLoadingNeeded = false;
|
||||
if (wco.buildOptions.platform !== 'server') {
|
||||
const buildBrowserFeatures = new BuildBrowserFeatures(
|
||||
projectRoot,
|
||||
tsConfig.options.target || ScriptTarget.ES5
|
||||
);
|
||||
|
||||
differentialLoadingNeeded =
|
||||
const differentialLoadingNeeded =
|
||||
buildBrowserFeatures.isDifferentialLoadingNeeded();
|
||||
|
||||
if (
|
||||
(buildOptions.scriptTargetOverride || tsConfig.options.target) ===
|
||||
ScriptTarget.ES5
|
||||
) {
|
||||
if (
|
||||
buildOptions.es5BrowserSupport ||
|
||||
(buildOptions.es5BrowserSupport === undefined &&
|
||||
buildBrowserFeatures.isEs5SupportNeeded())
|
||||
) {
|
||||
if (tsConfig.options.target === ScriptTarget.ES5) {
|
||||
if (buildBrowserFeatures.isEs5SupportNeeded()) {
|
||||
// The nomodule polyfill needs to be inject prior to any script and be
|
||||
// outside of webpack compilation because otherwise webpack will cause the
|
||||
// script to be wrapped in window["webpackJsonp"] which causes this to fail.
|
||||
@ -106,32 +67,16 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
|
||||
|
||||
// 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
|
||||
const polyfillsChunkName = 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) {
|
||||
if (buildOptions.polyfills) {
|
||||
entryPoints[polyfillsChunkName].push(
|
||||
path.resolve(root, buildOptions.polyfills)
|
||||
);
|
||||
@ -146,25 +91,6 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
|
||||
];
|
||||
}
|
||||
|
||||
if (!buildOptions.aot) {
|
||||
entryPoints['polyfills'] = [
|
||||
...(entryPoints['polyfills'] || []),
|
||||
path.join(__dirname, '..', 'jit-polyfills.js'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (buildOptions.profile || process.env['NG_BUILD_PROFILING']) {
|
||||
extraPlugins.push(
|
||||
new debug.ProfilingPlugin({
|
||||
outputPath: path.resolve(
|
||||
root,
|
||||
`chrome-profiler-events${targetInFileName}.json`
|
||||
),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// determine hashing format
|
||||
const hashFormat = getOutputHashFormat(buildOptions.outputHashing || 'none');
|
||||
|
||||
@ -212,7 +138,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
|
||||
extraPlugins.push(
|
||||
new ScriptsWebpackPlugin({
|
||||
name: bundleName,
|
||||
sourceMap: scriptsSourceMap,
|
||||
sourceMap: !!buildOptions.sourceMap,
|
||||
filename: `${path.basename(bundleName)}${hash}.js`,
|
||||
scripts: script.paths,
|
||||
basePath: sourceRoot,
|
||||
@ -234,8 +160,9 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
|
||||
const data = JSON.stringify(
|
||||
compilation.getStats().toJson('verbose')
|
||||
);
|
||||
compilation.assets[`stats${targetInFileName}.json`] =
|
||||
new webpack.sources.RawSource(data);
|
||||
compilation.assets[`stats.json`] = new webpack.sources.RawSource(
|
||||
data
|
||||
);
|
||||
});
|
||||
}
|
||||
})()
|
||||
@ -243,7 +170,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
|
||||
}
|
||||
|
||||
let sourceMapUseRule;
|
||||
if ((scriptsSourceMap || stylesSourceMap) && vendorSourceMap) {
|
||||
if (!!buildOptions.sourceMap) {
|
||||
sourceMapUseRule = {
|
||||
use: [
|
||||
{
|
||||
@ -282,25 +209,6 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
|
||||
}
|
||||
|
||||
if (scriptsOptimization) {
|
||||
let angularGlobalDefinitions = {
|
||||
ngDevMode: false,
|
||||
ngI18nClosureMode: false,
|
||||
};
|
||||
|
||||
if (GLOBAL_DEFS_FOR_TERSER) {
|
||||
angularGlobalDefinitions = GLOBAL_DEFS_FOR_TERSER;
|
||||
}
|
||||
|
||||
if (buildOptions.aot) {
|
||||
// Also try to load AOT-only global definitions.
|
||||
if (GLOBAL_DEFS_FOR_TERSER_WITH_AOT) {
|
||||
angularGlobalDefinitions = {
|
||||
...angularGlobalDefinitions,
|
||||
...GLOBAL_DEFS_FOR_TERSER_WITH_AOT,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Investigate why this fails for some packages: wco.supportES2015 ? 6 : 5;
|
||||
const terserEcma = 5;
|
||||
|
||||
@ -314,28 +222,15 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
|
||||
},
|
||||
// On server, we don't want to compress anything. We still set the ngDevMode = false for it
|
||||
// to remove dev code, and ngI18nClosureMode to remove Closure compiler i18n code
|
||||
compress:
|
||||
buildOptions.platform == 'server'
|
||||
? {
|
||||
compress: {
|
||||
ecma: terserEcma,
|
||||
global_defs: angularGlobalDefinitions,
|
||||
keep_fnames: true,
|
||||
}
|
||||
: {
|
||||
ecma: terserEcma,
|
||||
pure_getters: buildOptions.buildOptimizer,
|
||||
// TODO(jack): Investigate options to enable further optimizations
|
||||
// pure_getters: true,
|
||||
// PURE comments work best with 3 passes.
|
||||
// See https://github.com/webpack/webpack/issues/2899#issuecomment-317425926.
|
||||
passes: buildOptions.buildOptimizer ? 3 : 1,
|
||||
global_defs: angularGlobalDefinitions,
|
||||
// passes: 3,
|
||||
},
|
||||
// We also want to avoid mangling on server.
|
||||
// Name mangling is handled within the browser builder
|
||||
mangle:
|
||||
!manglingDisabled &&
|
||||
buildOptions.platform !== 'server' &&
|
||||
(!differentialLoadingNeeded ||
|
||||
(differentialLoadingNeeded && fullDifferential)),
|
||||
mangle: true,
|
||||
};
|
||||
|
||||
const es5TerserOptions = {
|
||||
@ -348,7 +243,6 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
|
||||
...terserOptions.output,
|
||||
ecma: 5,
|
||||
},
|
||||
mangle: !manglingDisabled && buildOptions.platform !== 'server',
|
||||
};
|
||||
|
||||
extraMinimizers.push(
|
||||
@ -366,7 +260,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
|
||||
profile: buildOptions.statsJson,
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.mjs', '.js'],
|
||||
symlinks: !buildOptions.preserveSymlinks,
|
||||
symlinks: true,
|
||||
modules: [wco.tsConfig.options.baseUrl || projectRoot, 'node_modules'],
|
||||
alias,
|
||||
},
|
||||
@ -380,9 +274,6 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
|
||||
publicPath: buildOptions.deployUrl,
|
||||
},
|
||||
watch: buildOptions.watch,
|
||||
watchOptions: {
|
||||
poll: buildOptions.poll,
|
||||
},
|
||||
performance: {
|
||||
hints: false,
|
||||
},
|
||||
@ -1,51 +1,31 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as path from 'path';
|
||||
import { RuleSetRule } from 'webpack';
|
||||
|
||||
import {
|
||||
PostcssCliResources,
|
||||
RawCssLoader,
|
||||
RemoveHashPlugin,
|
||||
} from '../../plugins/webpack';
|
||||
import { BuildOptions } from '../build-options';
|
||||
import { getOutputHashFormat, normalizeExtraEntryPoints } from './utils';
|
||||
import { RemoveEmptyScriptsPlugin } from '../../plugins/remove-empty-scripts-plugin';
|
||||
import { WebBuildExecutorOptions } from '../../../executors/build/build.impl';
|
||||
import { RemoveEmptyScriptsPlugin } from '../plugins/remove-empty-scripts-plugin';
|
||||
import { getOutputHashFormat } from '../../hash-format';
|
||||
import { normalizeExtraEntryPoints } from '../../normalize';
|
||||
import { PostcssCliResources } from '../plugins/postcss-cli-resources';
|
||||
import { RemoveHashPlugin } from '../plugins/remove-hash-plugin';
|
||||
import MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
|
||||
const autoprefixer = require('autoprefixer');
|
||||
const postcssImports = require('postcss-import');
|
||||
|
||||
/**
|
||||
* Enumerate loaders and their dependencies from this file to let the dependency validator
|
||||
* know they are used.
|
||||
*
|
||||
* require('style-loader')
|
||||
* require('postcss-loader')
|
||||
* require('stylus')
|
||||
* require('stylus-loader')
|
||||
* require('less')
|
||||
* require('less-loader')
|
||||
* require('node-sass')
|
||||
* require('sass-loader')
|
||||
*/
|
||||
// tslint:disable-next-line:no-big-function
|
||||
const RawCssLoader = require.resolve(
|
||||
path.join(__dirname, '../plugins/raw-css-loader.js')
|
||||
);
|
||||
|
||||
export function getStylesConfig(
|
||||
root: string,
|
||||
buildOptions: BuildOptions,
|
||||
buildOptions: WebBuildExecutorOptions,
|
||||
includePaths: string[]
|
||||
) {
|
||||
const entryPoints: { [key: string]: string[] } = {};
|
||||
const globalStylePaths: string[] = [];
|
||||
const extraPlugins = [];
|
||||
|
||||
const cssSourceMap = buildOptions.sourceMap.styles;
|
||||
const cssSourceMap = !!buildOptions.sourceMap;
|
||||
|
||||
// Determine hashing format.
|
||||
const hashFormat = getOutputHashFormat(buildOptions.outputHashing as string);
|
||||
@ -78,7 +58,6 @@ export function getStylesConfig(
|
||||
PostcssCliResources({
|
||||
baseHref: buildOptions.baseHref,
|
||||
deployUrl: buildOptions.deployUrl,
|
||||
resourcesOutputPath: buildOptions.resourcesOutputPath,
|
||||
loader,
|
||||
filename: `[name]${hashFormat.file}.[ext]`,
|
||||
}),
|
||||
@ -178,24 +157,24 @@ export function getStylesConfig(
|
||||
];
|
||||
|
||||
// load component css as raw strings
|
||||
const componentsSourceMap = !!(
|
||||
cssSourceMap &&
|
||||
const componentsSourceMap = !!(cssSourceMap &&
|
||||
// Never use component css sourcemap when style optimizations are on.
|
||||
// It will just increase bundle size without offering good debug experience.
|
||||
!buildOptions.optimization.styles &&
|
||||
typeof buildOptions.optimization === 'undefined'
|
||||
? true
|
||||
: typeof buildOptions.optimization === 'boolean'
|
||||
? !buildOptions.optimization
|
||||
: buildOptions.optimization.styles &&
|
||||
// Inline all sourcemap types except hidden ones, which are the same as no sourcemaps
|
||||
// for component css.
|
||||
!buildOptions.sourceMap.hidden
|
||||
);
|
||||
buildOptions.sourceMap !== 'hidden');
|
||||
|
||||
const rules: RuleSetRule[] = baseRules.map(({ test, use }) => ({
|
||||
exclude: globalStylePaths,
|
||||
test,
|
||||
use: [
|
||||
{ loader: require.resolve('raw-loader') },
|
||||
// Including RawCssLoader here because per v4.x release notes for postcss-loader under breaking changes:
|
||||
// "loader output only CSS, so you need to use css-loader/file-loader/raw-loader to inject code inside bundle"
|
||||
RawCssLoader,
|
||||
{ loader: RawCssLoader },
|
||||
{
|
||||
loader: require.resolve('postcss-loader'),
|
||||
options: {
|
||||
@ -203,13 +182,14 @@ export function getStylesConfig(
|
||||
postcssOptions: postcssOptionsCreator(componentsSourceMap),
|
||||
},
|
||||
},
|
||||
...(use as any[]),
|
||||
...(Array.isArray(use) ? use : []),
|
||||
],
|
||||
}));
|
||||
|
||||
// load global css as css files
|
||||
if (globalStylePaths.length > 0) {
|
||||
const globalSourceMap = !!cssSourceMap && !buildOptions.sourceMap.hidden;
|
||||
const globalSourceMap =
|
||||
!!cssSourceMap && buildOptions.sourceMap !== 'hidden';
|
||||
|
||||
rules.push(
|
||||
...baseRules.map(({ test, use }) => {
|
||||
@ -217,13 +197,11 @@ export function getStylesConfig(
|
||||
include: globalStylePaths,
|
||||
test,
|
||||
use: [
|
||||
buildOptions.extractCss
|
||||
? {
|
||||
{
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
options: { esModule: true },
|
||||
}
|
||||
: require.resolve('style-loader'),
|
||||
RawCssLoader,
|
||||
},
|
||||
{ loader: RawCssLoader },
|
||||
{
|
||||
loader: require.resolve('postcss-loader'),
|
||||
options: {
|
||||
@ -238,7 +216,6 @@ export function getStylesConfig(
|
||||
);
|
||||
}
|
||||
|
||||
if (buildOptions.extractCss) {
|
||||
extraPlugins.push(
|
||||
// extract global css from js files into own css file
|
||||
new MiniCssExtractPlugin({
|
||||
@ -247,7 +224,6 @@ export function getStylesConfig(
|
||||
// suppress empty .js files in css only entry points
|
||||
new RemoveEmptyScriptsPlugin()
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
entry: entryPoints,
|
||||
@ -44,7 +44,8 @@ async function resolve(
|
||||
}
|
||||
|
||||
module.exports.postcss = true;
|
||||
export default (options: PostcssCliResourcesOptions) => {
|
||||
|
||||
export function PostcssCliResources(options: PostcssCliResourcesOptions) {
|
||||
const {
|
||||
deployUrl = '',
|
||||
baseHref = '',
|
||||
@ -198,4 +199,4 @@ export default (options: PostcssCliResourcesOptions) => {
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
5
packages/web/src/utils/webpack/plugins/raw-css-loader.ts
Normal file
5
packages/web/src/utils/webpack/plugins/raw-css-loader.ts
Normal 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}]]`;
|
||||
}
|
||||
@ -1,11 +1,4 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import { HashFormat } from '../models/webpack-configs/utils';
|
||||
import { HashFormat } from '../../hash-format';
|
||||
|
||||
type Compiler = any;
|
||||
|
||||
@ -1,20 +1,3 @@
|
||||
// tslint:disable
|
||||
// TODO: cleanup this file, it's copied as is from Angular CLI.
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import { interpolateName } from 'loader-utils';
|
||||
import * as path from 'path';
|
||||
import { Compiler } from 'webpack';
|
||||
@ -1,14 +1,16 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import { dirname, join } from 'path';
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
import { EmittedFile } from '../run-webpack';
|
||||
import { ExtraEntryPoint } from '../shared-models';
|
||||
import { interpolateEnvironmentVariablesToIndex } from '../interpolate-env-variables-to-index';
|
||||
import { generateEntryPoints } from './package-chunk-sort';
|
||||
import { createHash } from 'crypto';
|
||||
import { RawSource, ReplaceSource } from 'webpack-sources';
|
||||
|
||||
function stripBom(data: string) {
|
||||
return data.replace(/^\uFEFF/, '');
|
||||
}
|
||||
|
||||
const parse5 = require('parse5');
|
||||
|
||||
export type LoadOutputFileFunctionType = (file: string) => string;
|
||||
@ -267,3 +269,77 @@ function _generateSriAttributes(content: string) {
|
||||
|
||||
return [{ name: 'integrity', value: `${algo}-${hash}` }];
|
||||
}
|
||||
|
||||
type ExtensionFilter = '.js' | '.css';
|
||||
|
||||
export interface WriteIndexHtmlOptions {
|
||||
outputPath: string;
|
||||
indexPath: string;
|
||||
files?: EmittedFile[];
|
||||
noModuleFiles?: EmittedFile[];
|
||||
moduleFiles?: EmittedFile[];
|
||||
baseHref?: string;
|
||||
deployUrl?: string;
|
||||
sri?: boolean;
|
||||
scripts?: ExtraEntryPoint[];
|
||||
styles?: ExtraEntryPoint[];
|
||||
postTransform?: IndexHtmlTransform;
|
||||
crossOrigin?: CrossOriginValue;
|
||||
}
|
||||
|
||||
export type IndexHtmlTransform = (content: string) => Promise<string>;
|
||||
|
||||
export async function writeIndexHtml({
|
||||
outputPath,
|
||||
indexPath,
|
||||
files = [],
|
||||
noModuleFiles = [],
|
||||
moduleFiles = [],
|
||||
baseHref,
|
||||
deployUrl,
|
||||
sri = false,
|
||||
scripts = [],
|
||||
styles = [],
|
||||
postTransform,
|
||||
crossOrigin,
|
||||
}: WriteIndexHtmlOptions) {
|
||||
let content = readFileSync(indexPath).toString();
|
||||
content = stripBom(content);
|
||||
content = augmentIndexHtml({
|
||||
input: outputPath,
|
||||
inputContent: interpolateEnvironmentVariablesToIndex(content, deployUrl),
|
||||
baseHref,
|
||||
deployUrl,
|
||||
crossOrigin,
|
||||
sri,
|
||||
entrypoints: generateEntryPoints({ scripts, styles }),
|
||||
files: filterAndMapBuildFiles(files, ['.js', '.css']),
|
||||
noModuleFiles: filterAndMapBuildFiles(noModuleFiles, '.js'),
|
||||
moduleFiles: filterAndMapBuildFiles(moduleFiles, '.js'),
|
||||
loadOutputFile: (filePath) =>
|
||||
readFileSync(join(dirname(outputPath), filePath)).toString(),
|
||||
});
|
||||
if (postTransform) {
|
||||
content = await postTransform(content);
|
||||
}
|
||||
|
||||
writeFileSync(outputPath, content);
|
||||
}
|
||||
|
||||
function filterAndMapBuildFiles(
|
||||
files: EmittedFile[],
|
||||
extensionFilter: ExtensionFilter | ExtensionFilter[]
|
||||
): FileInfo[] {
|
||||
const filteredFiles: FileInfo[] = [];
|
||||
const validExtensions: string[] = Array.isArray(extensionFilter)
|
||||
? extensionFilter
|
||||
: [extensionFilter];
|
||||
|
||||
for (const { file, name, extension, initial } of files) {
|
||||
if (name && initial && validExtensions.includes(extension)) {
|
||||
filteredFiles.push({ file, extension, name });
|
||||
}
|
||||
}
|
||||
|
||||
return filteredFiles;
|
||||
}
|
||||
14
yarn.lock
14
yarn.lock
@ -23801,6 +23801,20 @@ stylus@0.54.8, stylus@^0.54.8:
|
||||
semver "^6.3.0"
|
||||
source-map "^0.7.3"
|
||||
|
||||
stylus@^0.55.0:
|
||||
version "0.55.0"
|
||||
resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.55.0.tgz#bd404a36dd93fa87744a9dd2d2b1b8450345e5fc"
|
||||
integrity sha512-MuzIIVRSbc8XxHH7FjkvWqkIcr1BvoMZoR/oFuAJDlh7VSaNJzrB4uJ38GRQa+mWjLXODAMzeDe0xi9GYbGwnw==
|
||||
dependencies:
|
||||
css "^3.0.0"
|
||||
debug "~3.1.0"
|
||||
glob "^7.1.6"
|
||||
mkdirp "~1.0.4"
|
||||
safer-buffer "^2.1.2"
|
||||
sax "~1.2.4"
|
||||
semver "^6.3.0"
|
||||
source-map "^0.7.3"
|
||||
|
||||
supports-color@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user