From be4b7c5d1b36d25d55d59d1eafd2dcbde36bf490 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Mon, 31 Jan 2022 19:25:44 +0100 Subject: [PATCH] feat(@angular-devkit/build-angular): validate file extensions for `scripts` and `styles` options In some cases unexpected files may be provided which can cause to unsupported or broken behaviour. One such use-case is users can provide TypeScript files as `scripts` input, this would not be processed by the TypeScript compiler, see: https://github.com/angular/angular-cli/issues/17125 and would cause the build to fail with a an unhelpful error message during optimization as the JS optimizers cannot parse TypeScript input. BREAKING CHANGE: `browser` and `karma` builders `script` and `styles` options input files extensions are now validated. Valid extensions for `scripts` are: - `.js` - `.cjs` - `.mjs` - `.jsx` - `.cjsx` - `.mjsx` Valid extensions for `styles` are: - `.css` - `.less` - `.sass` - `.scss` - `.styl` --- .../angular_devkit/build_angular/src/index.md | 18 +--- .../src/builders/browser/schema.json | 89 ++++++++++++------- .../src/builders/karma/schema.json | 89 ++++++++++++------- .../angular_devkit/build_angular/src/index.ts | 2 - .../build_angular/src/utils/build-options.ts | 7 +- .../src/utils/package-chunk-sort.ts | 11 ++- .../src/webpack/configs/styles.ts | 4 +- .../src/webpack/utils/helpers.ts | 12 +-- 8 files changed, 137 insertions(+), 95 deletions(-) diff --git a/goldens/public-api/angular_devkit/build_angular/src/index.md b/goldens/public-api/angular_devkit/build_angular/src/index.md index 30a86d584b93..d9223abf1a97 100644 --- a/goldens/public-api/angular_devkit/build_angular/src/index.md +++ b/goldens/public-api/angular_devkit/build_angular/src/index.md @@ -59,14 +59,14 @@ export interface BrowserBuilderOptions { preserveSymlinks?: boolean; progress?: boolean; resourcesOutputPath?: string; - scripts?: ExtraEntryPoint[]; + scripts?: ScriptElement[]; serviceWorker?: boolean; // @deprecated showCircularDependencies?: boolean; sourceMap?: SourceMapUnion; statsJson?: boolean; stylePreprocessorOptions?: StylePreprocessorOptions; - styles?: ExtraEntryPoint[]; + styles?: StyleElement[]; subresourceIntegrity?: boolean; tsConfig: string; vendorChunk?: boolean; @@ -155,16 +155,6 @@ export type ExecutionTransformer = (input: T) => T | Promise; // @public (undocumented) export type ExtractI18nBuilderOptions = Schema_2 & JsonObject; -// @public (undocumented) -export type ExtraEntryPoint = ExtraEntryPointObject | string; - -// @public (undocumented) -export interface ExtraEntryPointObject { - bundleName?: string; - inject?: boolean; - input: string; -} - // @public (undocumented) export interface FileReplacement { // (undocumented) @@ -193,10 +183,10 @@ export interface KarmaBuilderOptions { preserveSymlinks?: boolean; progress?: boolean; reporters?: string[]; - scripts?: ExtraEntryPoint_2[]; + scripts?: ScriptElement_2[]; sourceMap?: SourceMapUnion_2; stylePreprocessorOptions?: StylePreprocessorOptions_2; - styles?: ExtraEntryPoint_2[]; + styles?: StyleElement_2[]; tsConfig: string; watch?: boolean; webWorkerTsConfig?: string; diff --git a/packages/angular_devkit/build_angular/src/builders/browser/schema.json b/packages/angular_devkit/build_angular/src/builders/browser/schema.json index 3be6b363d425..6281b1b2c83b 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser/schema.json +++ b/packages/angular_devkit/build_angular/src/builders/browser/schema.json @@ -29,7 +29,35 @@ "type": "array", "default": [], "items": { - "$ref": "#/definitions/extraEntryPoint" + "oneOf": [ + { + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "The file to include.", + "pattern": "\\.[cm]?jsx?$" + }, + "bundleName": { + "type": "string", + "pattern": "^[\\w\\-.]*$", + "description": "The bundle name for this extra entry point." + }, + "inject": { + "type": "boolean", + "description": "If the bundle will be referenced in the HTML file.", + "default": true + } + }, + "additionalProperties": false, + "required": ["input"] + }, + { + "type": "string", + "description": "The file to include.", + "pattern": "\\.[cm]?jsx?$" + } + ] } }, "styles": { @@ -37,7 +65,35 @@ "type": "array", "default": [], "items": { - "$ref": "#/definitions/extraEntryPoint" + "oneOf": [ + { + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "The file to include.", + "pattern": "\\.(?:css|scss|sass|less|styl)$" + }, + "bundleName": { + "type": "string", + "pattern": "^[\\w\\-.]*$", + "description": "The bundle name for this extra entry point." + }, + "inject": { + "type": "boolean", + "description": "If the bundle will be referenced in the HTML file.", + "default": true + } + }, + "additionalProperties": false, + "required": ["input"] + }, + { + "type": "string", + "description": "The file to include.", + "pattern": "\\.(?:css|scss|sass|less|styl)$" + } + ] } }, "inlineStyleLanguage": { @@ -437,35 +493,6 @@ } ] }, - "extraEntryPoint": { - "oneOf": [ - { - "type": "object", - "properties": { - "input": { - "type": "string", - "description": "The file to include." - }, - "bundleName": { - "type": "string", - "pattern": "^[\\w\\-.]*$", - "description": "The bundle name for this extra entry point." - }, - "inject": { - "type": "boolean", - "description": "If the bundle will be referenced in the HTML file.", - "default": true - } - }, - "additionalProperties": false, - "required": ["input"] - }, - { - "type": "string", - "description": "The file to include." - } - ] - }, "budget": { "type": "object", "properties": { diff --git a/packages/angular_devkit/build_angular/src/builders/karma/schema.json b/packages/angular_devkit/build_angular/src/builders/karma/schema.json index d527486607f6..e97151694e80 100644 --- a/packages/angular_devkit/build_angular/src/builders/karma/schema.json +++ b/packages/angular_devkit/build_angular/src/builders/karma/schema.json @@ -33,7 +33,35 @@ "type": "array", "default": [], "items": { - "$ref": "#/definitions/extraEntryPoint" + "oneOf": [ + { + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "The file to include.", + "pattern": "\\.[cm]?jsx?$" + }, + "bundleName": { + "type": "string", + "pattern": "^[\\w\\-.]*$", + "description": "The bundle name for this extra entry point." + }, + "inject": { + "type": "boolean", + "description": "If the bundle will be referenced in the HTML file.", + "default": true + } + }, + "additionalProperties": false, + "required": ["input"] + }, + { + "type": "string", + "description": "The file to include.", + "pattern": "\\.[cm]?jsx?$" + } + ] } }, "styles": { @@ -41,7 +69,35 @@ "type": "array", "default": [], "items": { - "$ref": "#/definitions/extraEntryPoint" + "oneOf": [ + { + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "The file to include.", + "pattern": "\\.(?:css|scss|sass|less|styl)$" + }, + "bundleName": { + "type": "string", + "pattern": "^[\\w\\-.]*$", + "description": "The bundle name for this extra entry point." + }, + "inject": { + "type": "boolean", + "description": "If the bundle will be referenced in the HTML file.", + "default": true + } + }, + "additionalProperties": false, + "required": ["input"] + }, + { + "type": "string", + "description": "The file to include.", + "pattern": "\\.(?:css|scss|sass|less|styl)$" + } + ] } }, "inlineStyleLanguage": { @@ -218,35 +274,6 @@ "type": "string" } ] - }, - "extraEntryPoint": { - "oneOf": [ - { - "type": "object", - "properties": { - "input": { - "type": "string", - "description": "The file to include." - }, - "bundleName": { - "type": "string", - "pattern": "^[\\w\\-.]*$", - "description": "The bundle name for this extra entry point." - }, - "inject": { - "type": "boolean", - "description": "If the bundle will be referenced in the HTML file.", - "default": true - } - }, - "additionalProperties": false, - "required": ["input"] - }, - { - "type": "string", - "description": "The file to include." - } - ] } } } diff --git a/packages/angular_devkit/build_angular/src/index.ts b/packages/angular_devkit/build_angular/src/index.ts index 3fd72d916551..5e9829236c5f 100644 --- a/packages/angular_devkit/build_angular/src/index.ts +++ b/packages/angular_devkit/build_angular/src/index.ts @@ -13,8 +13,6 @@ export { AssetPatternClass as AssetPatternObject, Budget, CrossOrigin, - ExtraEntryPoint, - ExtraEntryPointClass as ExtraEntryPointObject, FileReplacement, OptimizationClass as OptimizationObject, OptimizationUnion, diff --git a/packages/angular_devkit/build_angular/src/utils/build-options.ts b/packages/angular_devkit/build_angular/src/utils/build-options.ts index 70715e8c2b7d..a3789254ff97 100644 --- a/packages/angular_devkit/build_angular/src/utils/build-options.ts +++ b/packages/angular_devkit/build_angular/src/utils/build-options.ts @@ -12,12 +12,13 @@ import { AssetPatternClass, Budget, CrossOrigin, - ExtraEntryPoint, I18NTranslation, IndexUnion, InlineStyleLanguage, Localize, + ScriptElement, SourceMapClass, + StyleElement, } from '../builders/browser/schema'; import { Schema as DevServerSchema } from '../builders/dev-server/schema'; import { NormalizedCachedOptions } from './normalize-cache'; @@ -61,8 +62,8 @@ export interface BuildOptions { polyfills?: string; budgets: Budget[]; assets: AssetPatternClass[]; - scripts: ExtraEntryPoint[]; - styles: ExtraEntryPoint[]; + scripts: ScriptElement[]; + styles: StyleElement[]; stylePreprocessorOptions?: { includePaths: string[] }; platform?: 'browser' | 'server'; fileReplacements: NormalizedFileReplacement[]; diff --git a/packages/angular_devkit/build_angular/src/utils/package-chunk-sort.ts b/packages/angular_devkit/build_angular/src/utils/package-chunk-sort.ts index a7a14a3183a6..78ca469f2265 100644 --- a/packages/angular_devkit/build_angular/src/utils/package-chunk-sort.ts +++ b/packages/angular_devkit/build_angular/src/utils/package-chunk-sort.ts @@ -6,18 +6,21 @@ * found in the LICENSE file at https://angular.io/license */ -import { ExtraEntryPoint } from '../builders/browser/schema'; +import { ScriptElement, StyleElement } from '../builders/browser/schema'; import { normalizeExtraEntryPoints } from '../webpack/utils/helpers'; export type EntryPointsType = [name: string, isModule: boolean]; export function generateEntryPoints(options: { - styles: ExtraEntryPoint[]; - scripts: ExtraEntryPoint[]; + styles: StyleElement[]; + scripts: ScriptElement[]; isHMREnabled?: boolean; }): EntryPointsType[] { // Add all styles/scripts, except lazy-loaded ones. - const extraEntryPoints = (extraEntryPoints: ExtraEntryPoint[], defaultBundleName: string) => { + const extraEntryPoints = ( + extraEntryPoints: (ScriptElement | ScriptElement)[], + defaultBundleName: string, + ) => { const entryPoints = normalizeExtraEntryPoints(extraEntryPoints, defaultBundleName) .filter((entry) => entry.inject) .map((entry) => entry.bundleName); diff --git a/packages/angular_devkit/build_angular/src/webpack/configs/styles.ts b/packages/angular_devkit/build_angular/src/webpack/configs/styles.ts index ea81a57a6f4c..f3544e302a92 100644 --- a/packages/angular_devkit/build_angular/src/webpack/configs/styles.ts +++ b/packages/angular_devkit/build_angular/src/webpack/configs/styles.ts @@ -10,7 +10,7 @@ import * as fs from 'fs'; import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import * as path from 'path'; import { Configuration, RuleSetUseItem } from 'webpack'; -import { ExtraEntryPoint } from '../../builders/browser/schema'; +import { StyleElement } from '../../builders/browser/schema'; import { SassWorkerImplementation } from '../../sass/sass-service'; import { WebpackConfigOptions } from '../../utils/build-options'; import { @@ -27,7 +27,7 @@ import { } from '../utils/helpers'; function resolveGlobalStyles( - styleEntrypoints: ExtraEntryPoint[], + styleEntrypoints: StyleElement[], root: string, preserveSymlinks: boolean, ): { entryPoints: Record; noInjectNames: string[]; paths: string[] } { diff --git a/packages/angular_devkit/build_angular/src/webpack/utils/helpers.ts b/packages/angular_devkit/build_angular/src/webpack/utils/helpers.ts index d8a59c72efb9..175c403e4227 100644 --- a/packages/angular_devkit/build_angular/src/webpack/utils/helpers.ts +++ b/packages/angular_devkit/build_angular/src/webpack/utils/helpers.ts @@ -13,11 +13,7 @@ import glob from 'glob'; import * as path from 'path'; import { ScriptTarget } from 'typescript'; import type { Configuration, WebpackOptionsNormalized } from 'webpack'; -import { - AssetPatternClass, - ExtraEntryPoint, - ExtraEntryPointClass, -} from '../../builders/browser/schema'; +import { AssetPatternClass, ScriptElement, StyleElement } from '../../builders/browser/schema'; import { WebpackConfigOptions } from '../../utils/build-options'; import { VERSION } from '../../utils/package-version'; @@ -49,10 +45,10 @@ export function getOutputHashFormat(option: string, length = 20): HashFormat { return hashFormats[option] || hashFormats['none']; } -export type NormalizedEntryPoint = Required; +export type NormalizedEntryPoint = Required>; export function normalizeExtraEntryPoints( - extraEntryPoints: ExtraEntryPoint[], + extraEntryPoints: (ScriptElement | StyleElement)[], defaultBundleName: string, ): NormalizedEntryPoint[] { return extraEntryPoints.map((entry) => { @@ -160,7 +156,7 @@ export function getCacheSettings( export function globalScriptsByBundleName( root: string, - scripts: ExtraEntryPoint[], + scripts: ScriptElement[], ): { bundleName: string; inject: boolean; paths: string[] }[] { return normalizeExtraEntryPoints(scripts, 'scripts').reduce( (prev: { bundleName: string; paths: string[]; inject: boolean }[], curr) => {