From cca91fcfc6ccd59b9931a3c4868ce7d32b147d2e Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 15 May 2024 11:51:40 +0200 Subject: [PATCH] Angular: Consider style inject option Currently, every kind of style is injected into the iframe HTML, although some of them are marked with inject:false to not inject them, but rather to leave it to the user to dynamically load them if required --- .../src/preview/iframe-webpack.config.ts | 9 +- .../src/builders/build-storybook/index.ts | 183 ++++++--------- .../src/builders/start-storybook/index.ts | 221 +++++++----------- .../src/builders/utils/run-compodoc.spec.ts | 31 +-- .../src/builders/utils/run-compodoc.ts | 46 ++-- .../angular/src/builders/utils/setup.ts | 118 ++++++++++ .../src/builders/utils/standalone-options.ts | 16 +- .../angular/src/server/angular-cli-webpack.js | 14 +- .../server/framework-preset-angular-cli.ts | 66 +----- .../angular/src/server/preset-options.ts | 8 +- 10 files changed, 335 insertions(+), 377 deletions(-) create mode 100644 code/frameworks/angular/src/builders/utils/setup.ts diff --git a/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts b/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts index ef510ef2378f..6eca9b6a3dff 100644 --- a/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts +++ b/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts @@ -2,6 +2,7 @@ import { dirname, join, resolve } from 'path'; import { DefinePlugin, HotModuleReplacementPlugin, ProgressPlugin, ProvidePlugin } from 'webpack'; import type { Configuration } from 'webpack'; import HtmlWebpackPlugin from 'html-webpack-plugin'; + // @ts-expect-error (I removed this on purpose, because it's incorrect) import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin'; import TerserWebpackPlugin from 'terser-webpack-plugin'; @@ -53,7 +54,11 @@ const storybookPaths: Record = { }; export default async ( - options: Options & { typescriptOptions: TypescriptOptions } + options: Options & { + typescriptOptions: TypescriptOptions; + /* Build entries, which should not be linked in the iframe HTML file */ + excludeChunks?: string[]; + } ): Promise => { const { outputDir = join('.', 'public'), @@ -64,6 +69,7 @@ export default async ( previewUrl, typescriptOptions, features, + excludeChunks = [], } = options; const isProd = configType === 'PRODUCTION'; @@ -172,6 +178,7 @@ export default async ( alwaysWriteToDisk: true, inject: false, template, + excludeChunks, templateParameters: { version: packageJson.version, globals: { diff --git a/code/frameworks/angular/src/builders/build-storybook/index.ts b/code/frameworks/angular/src/builders/build-storybook/index.ts index 25f7faeb5268..97cc2985488f 100644 --- a/code/frameworks/angular/src/builders/build-storybook/index.ts +++ b/code/frameworks/angular/src/builders/build-storybook/index.ts @@ -1,18 +1,8 @@ -import { - BuilderContext, - BuilderHandlerFn, - BuilderOutput, - BuilderOutputLike, - Target, - createBuilder, - targetFromTargetString, -} from '@angular-devkit/architect'; +import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect'; import { JsonObject } from '@angular-devkit/core'; -import { from, of, throwError } from 'rxjs'; -import { catchError, map, mapTo, switchMap } from 'rxjs/operators'; import { sync as findUpSync } from 'find-up'; import { sync as readUpSync } from 'read-pkg-up'; -import { BrowserBuilderOptions, StylePreprocessorOptions } from '@angular-devkit/build-angular'; +import { StylePreprocessorOptions } from '@angular-devkit/build-angular'; import { CLIOptions } from '@storybook/types'; import { getEnvConfig, versions } from '@storybook/core-common'; @@ -22,11 +12,13 @@ import { buildStaticStandalone, withTelemetry } from '@storybook/core-server'; import { AssetPattern, SourceMapUnion, + StyleClass, StyleElement, } from '@angular-devkit/build-angular/src/builders/browser/schema'; import { StandaloneOptions } from '../utils/standalone-options'; import { runCompodoc } from '../utils/run-compodoc'; import { errorSummary, printErrorDetails } from '../utils/error-handler'; +import { setup } from '../utils/setup'; addToGlobalContext('cliVersion', versions.storybook); @@ -59,112 +51,77 @@ export type StorybookBuilderOptions = JsonObject & { export type StorybookBuilderOutput = JsonObject & BuilderOutput & { [key: string]: any }; -type StandaloneBuildOptions = StandaloneOptions & { outputDir: string }; - -const commandBuilder: BuilderHandlerFn = ( - options, - context -): BuilderOutputLike => { - const builder = from(setup(options, context)).pipe( - switchMap(({ tsConfig }) => { - const docTSConfig = findUpSync('tsconfig.doc.json', { cwd: options.configDir }); - const runCompodoc$ = options.compodoc - ? runCompodoc( - { compodocArgs: options.compodocArgs, tsconfig: docTSConfig ?? tsConfig }, - context - ).pipe(mapTo({ tsConfig })) - : of({}); - - return runCompodoc$.pipe(mapTo({ tsConfig })); - }), - map(({ tsConfig }) => { - getEnvConfig(options, { - staticDir: 'SBCONFIG_STATIC_DIR', - outputDir: 'SBCONFIG_OUTPUT_DIR', - configDir: 'SBCONFIG_CONFIG_DIR', - }); - - const { - browserTarget, - stylePreprocessorOptions, - styles, - configDir, - docs, - loglevel, - test, - outputDir, - quiet, - enableProdMode = true, - webpackStatsJson, - statsJson, - debugWebpack, - disableTelemetry, - assets, - previewUrl, - sourceMap = false, - } = options; - - const standaloneOptions: StandaloneBuildOptions = { - packageJson: readUpSync({ cwd: __dirname }).packageJson, - configDir, - ...(docs ? { docs } : {}), - loglevel, - outputDir, - test, - quiet, - enableProdMode, - disableTelemetry, - angularBrowserTarget: browserTarget, - angularBuilderContext: context, - angularBuilderOptions: { - ...(stylePreprocessorOptions ? { stylePreprocessorOptions } : {}), - ...(styles ? { styles } : {}), - ...(assets ? { assets } : {}), - sourceMap, - }, - tsConfig, - webpackStatsJson, - statsJson, - debugWebpack, - previewUrl, - }; - - return standaloneOptions; - }), - switchMap((standaloneOptions) => runInstance({ ...standaloneOptions, mode: 'static' })), - map(() => { - return { success: true }; - }) - ); - - return builder as any as BuilderOutput; -}; +type StandaloneBuildOptions = StandaloneOptions & { outputDir: string; excludeChunks: string[] }; -export default createBuilder(commandBuilder); +const commandBuilder = async ( + options: StorybookBuilderOptions, + context: BuilderContext +): Promise => { + const { tsConfig, angularBuilderContext, angularBuilderOptions } = await setup(options, context); -async function setup(options: StorybookBuilderOptions, context: BuilderContext) { - let browserOptions: (JsonObject & BrowserBuilderOptions) | undefined; - let browserTarget: Target | undefined; + const docTSConfig = findUpSync('tsconfig.doc.json', { cwd: options.configDir }); - if (options.browserTarget) { - browserTarget = targetFromTargetString(options.browserTarget); - browserOptions = await context.validateOptions( - await context.getTargetOptions(browserTarget), - await context.getBuilderNameForTarget(browserTarget) + if (options.compodoc) { + await runCompodoc( + { compodocArgs: options.compodocArgs, tsconfig: docTSConfig ?? tsConfig }, + context ); } - return { - tsConfig: - options.tsConfig ?? - findUpSync('tsconfig.json', { cwd: options.configDir }) ?? - browserOptions.tsConfig, + getEnvConfig(options, { + staticDir: 'SBCONFIG_STATIC_DIR', + outputDir: 'SBCONFIG_OUTPUT_DIR', + configDir: 'SBCONFIG_CONFIG_DIR', + }); + + const { + configDir, + docs, + loglevel, + test, + outputDir, + quiet, + enableProdMode = true, + webpackStatsJson, + statsJson, + debugWebpack, + disableTelemetry, + previewUrl, + } = options; + + const standaloneOptions: StandaloneBuildOptions = { + packageJson: readUpSync({ cwd: __dirname }).packageJson, + configDir, + ...(docs ? { docs } : {}), + excludeChunks: angularBuilderOptions.styles + ?.filter((style) => typeof style !== 'string' && style.inject === false) + .map((s: StyleClass) => s.bundleName), + loglevel, + outputDir, + test, + quiet, + enableProdMode, + disableTelemetry, + angularBrowserTarget: options.browserTarget, + angularBuilderContext, + angularBuilderOptions, + tsConfig, + webpackStatsJson, + statsJson, + debugWebpack, + previewUrl, }; -} -function runInstance(options: StandaloneBuildOptions) { - return from( - withTelemetry( + await runInstance({ ...standaloneOptions, mode: 'static' }); + + return { success: true }; +}; + +export default createBuilder(commandBuilder); + +async function runInstance(options: StandaloneBuildOptions) { + try { + await withTelemetry( 'build', { cliOptions: options, @@ -172,6 +129,8 @@ function runInstance(options: StandaloneBuildOptions) { printError: printErrorDetails, }, () => buildStaticStandalone(options) - ) - ).pipe(catchError((error: any) => throwError(errorSummary(error)))); + ); + } catch (error) { + throw new Error(errorSummary(error)); + } } diff --git a/code/frameworks/angular/src/builders/start-storybook/index.ts b/code/frameworks/angular/src/builders/start-storybook/index.ts index 2ecbb63c8a0f..cf33cd4b4e1c 100644 --- a/code/frameworks/angular/src/builders/start-storybook/index.ts +++ b/code/frameworks/angular/src/builders/start-storybook/index.ts @@ -1,15 +1,6 @@ -import { - BuilderContext, - BuilderHandlerFn, - BuilderOutput, - Target, - createBuilder, - targetFromTargetString, -} from '@angular-devkit/architect'; +import { BuilderHandlerFn, BuilderOutput, createBuilder } from '@angular-devkit/architect'; import { JsonObject } from '@angular-devkit/core'; -import { BrowserBuilderOptions, StylePreprocessorOptions } from '@angular-devkit/build-angular'; -import { from, Observable, of } from 'rxjs'; -import { map, switchMap, mapTo } from 'rxjs/operators'; +import { StylePreprocessorOptions } from '@angular-devkit/build-angular'; import { sync as findUpSync } from 'find-up'; import { sync as readUpSync } from 'read-pkg-up'; @@ -20,11 +11,13 @@ import { buildDevStandalone, withTelemetry } from '@storybook/core-server'; import { AssetPattern, SourceMapUnion, + StyleClass, StyleElement, } from '@angular-devkit/build-angular/src/builders/browser/schema'; import { StandaloneOptions } from '../utils/standalone-options'; import { runCompodoc } from '../utils/run-compodoc'; import { printErrorDetails, errorSummary } from '../utils/error-handler'; +import { setup } from '../utils/setup'; addToGlobalContext('cliVersion', versions.storybook); @@ -64,131 +57,96 @@ export type StorybookBuilderOptions = JsonObject & { export type StorybookBuilderOutput = JsonObject & BuilderOutput & {}; -const commandBuilder: BuilderHandlerFn = (options, context) => { - const builder = from(setup(options, context)).pipe( - switchMap(({ tsConfig }) => { - const docTSConfig = findUpSync('tsconfig.doc.json', { cwd: options.configDir }); - - const runCompodoc$ = options.compodoc - ? runCompodoc( - { - compodocArgs: [...options.compodocArgs, ...(options.quiet ? ['--silent'] : [])], - tsconfig: docTSConfig ?? tsConfig, - }, - context - ).pipe(mapTo({ tsConfig })) - : of({}); +const commandBuilder: BuilderHandlerFn = async (options, context) => { + const { tsConfig, angularBuilderContext, angularBuilderOptions } = await setup(options, context); - return runCompodoc$.pipe(mapTo({ tsConfig })); - }), - map(({ tsConfig }) => { - getEnvConfig(options, { - port: 'SBCONFIG_PORT', - host: 'SBCONFIG_HOSTNAME', - staticDir: 'SBCONFIG_STATIC_DIR', - configDir: 'SBCONFIG_CONFIG_DIR', - ci: 'CI', - }); + const docTSConfig = findUpSync('tsconfig.doc.json', { cwd: options.configDir }); - options.port = parseInt(`${options.port}`, 10); + if (options.compodoc) { + await runCompodoc( + { + compodocArgs: [...options.compodocArgs, ...(options.quiet ? ['--silent'] : [])], + tsconfig: docTSConfig ?? tsConfig, + }, + context + ); + } - const { - browserTarget, - stylePreprocessorOptions, - styles, - ci, - configDir, - docs, - host, - https, - port, - quiet, - enableProdMode = false, - smokeTest, - sslCa, - sslCert, - sslKey, - disableTelemetry, - assets, - initialPath, - open, - debugWebpack, - loglevel, - webpackStatsJson, - statsJson, - previewUrl, - sourceMap = false, - } = options; + getEnvConfig(options, { + port: 'SBCONFIG_PORT', + host: 'SBCONFIG_HOSTNAME', + staticDir: 'SBCONFIG_STATIC_DIR', + configDir: 'SBCONFIG_CONFIG_DIR', + ci: 'CI', + }); - const standaloneOptions: StandaloneOptions = { - packageJson: readUpSync({ cwd: __dirname }).packageJson, - ci, - configDir, - ...(docs ? { docs } : {}), - host, - https, - port, - quiet, - enableProdMode, - smokeTest, - sslCa, - sslCert, - sslKey, - disableTelemetry, - angularBrowserTarget: browserTarget, - angularBuilderContext: context, - angularBuilderOptions: { - ...(stylePreprocessorOptions ? { stylePreprocessorOptions } : {}), - ...(styles ? { styles } : {}), - ...(assets ? { assets } : {}), - sourceMap, - }, - tsConfig, - initialPath, - open, - debugWebpack, - webpackStatsJson, - statsJson, - loglevel, - previewUrl, - }; + options.port = parseInt(`${options.port}`, 10); + + const { + browserTarget, + ci, + configDir, + docs, + host, + https, + port, + quiet, + enableProdMode = false, + smokeTest, + sslCa, + sslCert, + sslKey, + disableTelemetry, + initialPath, + open, + debugWebpack, + loglevel, + webpackStatsJson, + statsJson, + previewUrl, + } = options; + + const standaloneOptions: StandaloneOptions = { + packageJson: readUpSync({ cwd: __dirname }).packageJson, + ci, + configDir, + ...(docs ? { docs } : {}), + excludeChunks: angularBuilderOptions.styles + ?.filter((style) => typeof style !== 'string' && style.inject === false) + .map((s: StyleClass) => s.bundleName), + host, + https, + port, + quiet, + enableProdMode, + smokeTest, + sslCa, + sslCert, + sslKey, + disableTelemetry, + angularBrowserTarget: browserTarget, + angularBuilderContext, + angularBuilderOptions, + tsConfig, + initialPath, + open, + debugWebpack, + webpackStatsJson, + statsJson, + loglevel, + previewUrl, + }; - return standaloneOptions; - }), - switchMap((standaloneOptions) => runInstance(standaloneOptions)), - map((port: number) => { - return { success: true, info: { port } }; - }) - ); + const devPort = await runInstance(standaloneOptions); - return builder as any as BuilderOutput; + return { success: true, info: { port: devPort } }; }; export default createBuilder(commandBuilder); -async function setup(options: StorybookBuilderOptions, context: BuilderContext) { - let browserOptions: (JsonObject & BrowserBuilderOptions) | undefined; - let browserTarget: Target | undefined; - - if (options.browserTarget) { - browserTarget = targetFromTargetString(options.browserTarget); - browserOptions = await context.validateOptions( - await context.getTargetOptions(browserTarget), - await context.getBuilderNameForTarget(browserTarget) - ); - } - - return { - tsConfig: - options.tsConfig ?? - findUpSync('tsconfig.json', { cwd: options.configDir }) ?? - browserOptions.tsConfig, - }; -} -function runInstance(options: StandaloneOptions) { - return new Observable((observer) => { - // This Observable intentionally never complete, leaving the process running ;) - withTelemetry( +async function runInstance(options: StandaloneOptions): Promise { + try { + const { port } = await withTelemetry( 'dev', { cliOptions: options, @@ -196,10 +154,9 @@ function runInstance(options: StandaloneOptions) { printError: printErrorDetails, }, () => buildDevStandalone(options) - ) - .then(({ port }) => observer.next(port)) - .catch((error) => { - observer.error(errorSummary(error)); - }); - }); + ); + return port; + } catch (error) { + throw new Error(errorSummary(error)); + } } diff --git a/code/frameworks/angular/src/builders/utils/run-compodoc.spec.ts b/code/frameworks/angular/src/builders/utils/run-compodoc.spec.ts index cf0686b11b1d..a8f7428e7cec 100644 --- a/code/frameworks/angular/src/builders/utils/run-compodoc.spec.ts +++ b/code/frameworks/angular/src/builders/utils/run-compodoc.spec.ts @@ -1,7 +1,6 @@ // eslint-disable-next-line import/no-extraneous-dependencies import { vi, describe, afterEach, it, expect } from 'vitest'; import { LoggerApi } from '@angular-devkit/core/src/logger'; -import { take } from 'rxjs/operators'; import { BuilderContext } from '@angular-devkit/architect'; import { runCompodoc } from './run-compodoc'; @@ -37,15 +36,13 @@ describe('runCompodoc', () => { } as BuilderContext; it('should run compodoc with tsconfig from context', async () => { - runCompodoc( + await runCompodoc( { compodocArgs: [], tsconfig: 'path/to/tsconfig.json', }, builderContextMock - ) - .pipe(take(1)) - .subscribe(); + ); expect(mockRunScript).toHaveBeenCalledWith( 'compodoc', @@ -56,15 +53,13 @@ describe('runCompodoc', () => { }); it('should run compodoc with tsconfig from compodocArgs', async () => { - runCompodoc( + await runCompodoc( { compodocArgs: ['-p', 'path/to/tsconfig.stories.json'], tsconfig: 'path/to/tsconfig.json', }, builderContextMock - ) - .pipe(take(1)) - .subscribe(); + ); expect(mockRunScript).toHaveBeenCalledWith( 'compodoc', @@ -75,15 +70,13 @@ describe('runCompodoc', () => { }); it('should run compodoc with default output folder.', async () => { - runCompodoc( + await runCompodoc( { compodocArgs: [], tsconfig: 'path/to/tsconfig.json', }, builderContextMock - ) - .pipe(take(1)) - .subscribe(); + ); expect(mockRunScript).toHaveBeenCalledWith( 'compodoc', @@ -94,15 +87,13 @@ describe('runCompodoc', () => { }); it('should run with custom output folder specified with --output compodocArgs', async () => { - runCompodoc( + await runCompodoc( { compodocArgs: ['--output', 'path/to/customFolder'], tsconfig: 'path/to/tsconfig.json', }, builderContextMock - ) - .pipe(take(1)) - .subscribe(); + ); expect(mockRunScript).toHaveBeenCalledWith( 'compodoc', @@ -113,15 +104,13 @@ describe('runCompodoc', () => { }); it('should run with custom output folder specified with -d compodocArgs', async () => { - runCompodoc( + await runCompodoc( { compodocArgs: ['-d', 'path/to/customFolder'], tsconfig: 'path/to/tsconfig.json', }, builderContextMock - ) - .pipe(take(1)) - .subscribe(); + ); expect(mockRunScript).toHaveBeenCalledWith( 'compodoc', diff --git a/code/frameworks/angular/src/builders/utils/run-compodoc.ts b/code/frameworks/angular/src/builders/utils/run-compodoc.ts index e926c041bfa8..f916840e4631 100644 --- a/code/frameworks/angular/src/builders/utils/run-compodoc.ts +++ b/code/frameworks/angular/src/builders/utils/run-compodoc.ts @@ -13,34 +13,30 @@ const toRelativePath = (pathToTsConfig: string) => { return path.isAbsolute(pathToTsConfig) ? path.relative('.', pathToTsConfig) : pathToTsConfig; }; -export const runCompodoc = ( +export const runCompodoc = async ( { compodocArgs, tsconfig }: { compodocArgs: string[]; tsconfig: string }, context: BuilderContext -): Observable => { - return new Observable((observer) => { - const tsConfigPath = toRelativePath(tsconfig); - const finalCompodocArgs = [ - ...(hasTsConfigArg(compodocArgs) ? [] : ['-p', tsConfigPath]), - ...(hasOutputArg(compodocArgs) ? [] : ['-d', `${context.workspaceRoot || '.'}`]), - ...compodocArgs, - ]; +): Promise => { + const tsConfigPath = toRelativePath(tsconfig); + const finalCompodocArgs = [ + ...(hasTsConfigArg(compodocArgs) ? [] : ['-p', tsConfigPath]), + ...(hasOutputArg(compodocArgs) ? [] : ['-d', `${context.workspaceRoot || '.'}`]), + ...compodocArgs, + ]; - const packageManager = JsPackageManagerFactory.getPackageManager(); + const packageManager = JsPackageManagerFactory.getPackageManager(); - try { - const stdout = packageManager.runPackageCommandSync( - 'compodoc', - finalCompodocArgs, - context.workspaceRoot, - 'inherit' - ); + try { + const stdout = packageManager.runPackageCommandSync( + 'compodoc', + finalCompodocArgs, + context.workspaceRoot, + 'inherit' + ); - context.logger.info(stdout); - observer.next(); - observer.complete(); - } catch (e) { - context.logger.error(e); - observer.error(); - } - }); + context.logger.info(stdout); + } catch (e) { + context.logger.error(e); + throw e; + } }; diff --git a/code/frameworks/angular/src/builders/utils/setup.ts b/code/frameworks/angular/src/builders/utils/setup.ts new file mode 100644 index 000000000000..b7187260e6c7 --- /dev/null +++ b/code/frameworks/angular/src/builders/utils/setup.ts @@ -0,0 +1,118 @@ +import { Target, targetFromTargetString } from '@angular-devkit/architect'; +import { BuilderContext } from '@angular-devkit/architect'; +import { JsonObject, logging } from '@angular-devkit/core'; +import { sync as findUpSync } from 'find-up'; +import { BrowserBuilderOptions, StylePreprocessorOptions } from '@angular-devkit/build-angular'; +import { logger } from '@storybook/node-logger'; +import { + AssetPattern, + SourceMapUnion, + StyleElement, +} from '@angular-devkit/build-angular/src/builders/browser/schema'; + +type AngularBuilderOptions = { + stylePreprocessorOptions?: StylePreprocessorOptions; + styles?: StyleElement[]; + assets?: AssetPattern[]; + sourceMap?: SourceMapUnion; +}; + +type Options = AngularBuilderOptions & { + browserTarget?: string | null; + tsConfig?: string; + configDir?: string; +}; + +export async function setup(options: Options, context: BuilderContext) { + let browserOptions: (JsonObject & BrowserBuilderOptions) | undefined; + let browserTarget: Target | undefined; + + if (options.browserTarget) { + browserTarget = targetFromTargetString(options.browserTarget); + browserOptions = await context.validateOptions( + await context.getTargetOptions(browserTarget), + await context.getBuilderNameForTarget(browserTarget) + ); + } + + const tsConfig = + options.tsConfig ?? + findUpSync('tsconfig.json', { cwd: options.configDir }) ?? + browserOptions.tsConfig; + + const angularBuilderContext = getBuilderContext(context); + + const angularBuilderOptions = await getBuilderOptions( + options.browserTarget, + { + ...(options.stylePreprocessorOptions + ? { stylePreprocessorOptions: options.stylePreprocessorOptions } + : {}), + ...(options.styles ? { styles: options.styles } : {}), + ...(options.assets ? { assets: options.assets } : {}), + sourceMap: options.sourceMap ?? false, + }, + tsConfig, + options.configDir, + angularBuilderContext + ); + + return { + tsConfig, + angularBuilderContext, + angularBuilderOptions, + }; +} + +/** + * Get Builder Context + * If storybook is not start by angular builder create dumb BuilderContext + */ +function getBuilderContext(builderContext: BuilderContext): BuilderContext { + return ( + builderContext ?? + ({ + target: { project: 'noop-project', builder: '', options: {} }, + workspaceRoot: process.cwd(), + getProjectMetadata: () => ({}), + getTargetOptions: () => ({}), + logger: new logging.Logger('Storybook'), + } as unknown as BuilderContext) + ); +} + +/** + * Get builder options + * Merge target options from browser target and from storybook options + */ +async function getBuilderOptions( + angularBrowserTarget: string, + angularBuilderOptions: AngularBuilderOptions, + tsConfig: string, + configDir: string, + builderContext: BuilderContext +) { + /** + * Get Browser Target options + */ + let browserTargetOptions: JsonObject = {}; + + if (angularBrowserTarget) { + const browserTarget = targetFromTargetString(angularBrowserTarget); + + browserTargetOptions = await builderContext.getTargetOptions(browserTarget); + } + + /** + * Merge target options from browser target options and from storybook options + */ + const builderOptions = { + ...browserTargetOptions, + ...angularBuilderOptions, + tsConfig: + tsConfig ?? findUpSync('tsconfig.json', { cwd: configDir }) ?? browserTargetOptions.tsConfig, + }; + logger.info(`=> Using angular project with "tsConfig:${builderOptions.tsConfig}"`); + + return builderOptions; +} diff --git a/code/frameworks/angular/src/builders/utils/standalone-options.ts b/code/frameworks/angular/src/builders/utils/standalone-options.ts index ef73d78f01b5..42f8a417b0c8 100644 --- a/code/frameworks/angular/src/builders/utils/standalone-options.ts +++ b/code/frameworks/angular/src/builders/utils/standalone-options.ts @@ -1,11 +1,7 @@ import { BuilderContext } from '@angular-devkit/architect'; -import { - AssetPattern, - SourceMapUnion, - StyleElement, - StylePreprocessorOptions, -} from '@angular-devkit/build-angular/src/builders/browser/schema'; + import { LoadOptions, CLIOptions, BuilderOptions } from '@storybook/types'; +import { AngularBuilderOptions } from '../../server/framework-preset-angular-cli'; export type StandaloneOptions = CLIOptions & LoadOptions & @@ -13,12 +9,8 @@ export type StandaloneOptions = CLIOptions & mode?: 'static' | 'dev'; enableProdMode: boolean; angularBrowserTarget?: string | null; - angularBuilderOptions?: Record & { - styles?: StyleElement[]; - stylePreprocessorOptions?: StylePreprocessorOptions; - assets?: AssetPattern[]; - sourceMap?: SourceMapUnion; - }; + angularBuilderOptions?: AngularBuilderOptions; angularBuilderContext?: BuilderContext | null; tsConfig?: string; + excludeChunks?: string[]; }; diff --git a/code/frameworks/angular/src/server/angular-cli-webpack.js b/code/frameworks/angular/src/server/angular-cli-webpack.js index 621680125536..32b31c1418a1 100644 --- a/code/frameworks/angular/src/server/angular-cli-webpack.js +++ b/code/frameworks/angular/src/server/angular-cli-webpack.js @@ -56,6 +56,7 @@ exports.getWebpackConfig = async (baseConfig, { builderOptions, builderContext } */ const { getCommonConfig, getStylesConfig, getDevServerConfig, getTypeScriptConfig } = getAngularWebpackUtils(); + const { config: cliConfig } = await generateI18nBrowserWebpackConfigFromContext( { // Default options @@ -65,10 +66,15 @@ exports.getWebpackConfig = async (baseConfig, { builderOptions, builderContext } // Options provided by user ...builderOptions, - styles: builderOptions.styles - ?.map((style) => (typeof style === 'string' ? style : style.input)) - .filter((style) => typeof style === 'string' || style.inject !== false), - + styles: builderOptions.styles?.map((style) => + typeof style === 'string' + ? { + input: style, + inject: true, + bundleName: style.split('/').pop(), + } + : style + ), // Fixed options optimization: false, namedChunks: false, diff --git a/code/frameworks/angular/src/server/framework-preset-angular-cli.ts b/code/frameworks/angular/src/server/framework-preset-angular-cli.ts index 059d8b30f4d1..9c52447b3faf 100644 --- a/code/frameworks/angular/src/server/framework-preset-angular-cli.ts +++ b/code/frameworks/angular/src/server/framework-preset-angular-cli.ts @@ -1,9 +1,6 @@ import webpack from 'webpack'; import { logger } from '@storybook/node-logger'; import { AngularLegacyBuildOptionsError } from '@storybook/core-events/server-errors'; -import { BuilderContext, targetFromTargetString } from '@angular-devkit/architect'; -import { sync as findUpSync } from 'find-up'; -import { JsonObject, logging } from '@angular-devkit/core'; import { getWebpackConfig as getCustomWebpackConfig } from './angular-cli-webpack'; import { moduleIsAvailable } from './utils/module-is-available'; @@ -17,74 +14,15 @@ export async function webpackFinal(baseConfig: webpack.Configuration, options: P checkForLegacyBuildOptions(options); - const builderContext = getBuilderContext(options); - const builderOptions = await getBuilderOptions(options, builderContext); - return getCustomWebpackConfig(baseConfig, { builderOptions: { watch: options.configType === 'DEVELOPMENT', - ...builderOptions, + ...options.angularBuilderOptions, }, - builderContext, + builderContext: options.angularBuilderContext, }); } -/** - * Get Builder Context - * If storybook is not start by angular builder create dumb BuilderContext - */ -function getBuilderContext(options: PresetOptions): BuilderContext { - return ( - options.angularBuilderContext ?? - ({ - target: { project: 'noop-project', builder: '', options: {} }, - workspaceRoot: process.cwd(), - getProjectMetadata: () => ({}), - getTargetOptions: () => ({}), - logger: new logging.Logger('Storybook'), - } as unknown as BuilderContext) - ); -} - -/** - * Get builder options - * Merge target options from browser target and from storybook options - */ -async function getBuilderOptions( - options: PresetOptions, - builderContext: BuilderContext -): Promise { - /** - * Get Browser Target options - */ - let browserTargetOptions: JsonObject = {}; - if (options.angularBrowserTarget) { - const browserTarget = targetFromTargetString(options.angularBrowserTarget); - - logger.info( - `=> Using angular browser target options from "${browserTarget.project}:${ - browserTarget.target - }${browserTarget.configuration ? `:${browserTarget.configuration}` : ''}"` - ); - browserTargetOptions = await builderContext.getTargetOptions(browserTarget); - } - - /** - * Merge target options from browser target options and from storybook options - */ - const builderOptions = { - ...browserTargetOptions, - ...(options.angularBuilderOptions as JsonObject), - tsConfig: - options.tsConfig ?? - findUpSync('tsconfig.json', { cwd: options.configDir }) ?? - browserTargetOptions.tsConfig, - }; - logger.info(`=> Using angular project with "tsConfig:${builderOptions.tsConfig}"`); - - return builderOptions; -} - /** * Checks if using legacy configuration that doesn't use builder and logs message referring to migration docs. */ diff --git a/code/frameworks/angular/src/server/preset-options.ts b/code/frameworks/angular/src/server/preset-options.ts index 5412d5a19482..6b6c19d5ad39 100644 --- a/code/frameworks/angular/src/server/preset-options.ts +++ b/code/frameworks/angular/src/server/preset-options.ts @@ -1,17 +1,13 @@ import { Options as CoreOptions } from '@storybook/types'; import { BuilderContext } from '@angular-devkit/architect'; -import { StylePreprocessorOptions } from '@angular-devkit/build-angular'; -import { StyleElement } from '@angular-devkit/build-angular/src/builders/browser/schema'; +import { JsonObject } from '@angular-devkit/core'; export type PresetOptions = CoreOptions & { /* Allow to get the options of a targeted "browser builder" */ angularBrowserTarget?: string | null; /* Defined set of options. These will take over priority from angularBrowserTarget options */ - angularBuilderOptions?: { - styles?: StyleElement[]; - stylePreprocessorOptions?: StylePreprocessorOptions; - }; + angularBuilderOptions?: JsonObject; /* Angular context from builder */ angularBuilderContext?: BuilderContext | null; tsConfig?: string;