diff --git a/app/angular/src/builders/build-storybook/index.spec.ts b/app/angular/src/builders/build-storybook/index.spec.ts index af9681b7b8e0..9ebae8813f14 100644 --- a/app/angular/src/builders/build-storybook/index.spec.ts +++ b/app/angular/src/builders/build-storybook/index.spec.ts @@ -55,7 +55,7 @@ describe('Build Storybook Builder', () => { jest.clearAllMocks(); }); - it('should work', async () => { + it('should start storybook with angularBrowserTarget', async () => { const run = await architect.scheduleBuilder('@storybook/angular:build-storybook', { browserTarget: 'angular-cli:build-2', compodoc: false, @@ -69,7 +69,6 @@ describe('Build Storybook Builder', () => { expect(cpSpawnMock.spawn).not.toHaveBeenCalledWith(); expect(buildStandaloneMock).toHaveBeenCalledWith({ angularBrowserTarget: 'angular-cli:build-2', - browserTarget: 'angular-cli:build-2', configDir: '.storybook', docsMode: false, loglevel: undefined, @@ -79,6 +78,34 @@ describe('Build Storybook Builder', () => { mode: 'static', compodoc: false, compodocArgs: ['-e', 'json'], + tsConfig: 'src/tsconfig.app.json', + }); + }); + + it('should start storybook with tsConfig', async () => { + const run = await architect.scheduleBuilder('@storybook/angular:build-storybook', { + tsConfig: 'path/to/tsConfig.json', + compodoc: false, + }); + + const output = await run.result; + + await run.stop(); + + expect(output.success).toBeTruthy(); + expect(cpSpawnMock.spawn).not.toHaveBeenCalledWith(); + expect(buildStandaloneMock).toHaveBeenCalledWith({ + angularBrowserTarget: null, + configDir: '.storybook', + docsMode: false, + loglevel: undefined, + quiet: false, + outputDir: 'storybook-static', + staticDir: [], + mode: 'static', + compodoc: false, + compodocArgs: ['-e', 'json'], + tsConfig: 'path/to/tsConfig.json', }); }); @@ -102,7 +129,6 @@ describe('Build Storybook Builder', () => { ]); expect(buildStandaloneMock).toHaveBeenCalledWith({ angularBrowserTarget: 'angular-cli:build-2', - browserTarget: 'angular-cli:build-2', configDir: '.storybook', docsMode: false, loglevel: undefined, @@ -112,6 +138,7 @@ describe('Build Storybook Builder', () => { mode: 'static', compodoc: true, compodocArgs: ['-e', 'json'], + tsConfig: 'src/tsconfig.app.json', }); }); }); diff --git a/app/angular/src/builders/build-storybook/index.ts b/app/angular/src/builders/build-storybook/index.ts index 3be09098e457..81e42710ac29 100644 --- a/app/angular/src/builders/build-storybook/index.ts +++ b/app/angular/src/builders/build-storybook/index.ts @@ -3,11 +3,12 @@ import { BuilderOutput, createBuilder, targetFromTargetString, + Target, } from '@angular-devkit/architect'; import { JsonObject } from '@angular-devkit/core'; import { from, Observable, of } from 'rxjs'; import { CLIOptions } from '@storybook/core-common'; -import { map, switchMap } from 'rxjs/operators'; +import { map, switchMap, mapTo } from 'rxjs/operators'; // eslint-disable-next-line import/no-extraneous-dependencies import buildStandalone, { StandaloneOptions } from '@storybook/angular/standalone'; @@ -15,7 +16,8 @@ import { BrowserBuilderOptions } from '@angular-devkit/build-angular'; import { runCompodoc } from '../utils/run-compodoc'; export type StorybookBuilderOptions = JsonObject & { - browserTarget: string; + browserTarget?: string | null; + tsConfig?: string; compodoc: boolean; compodocArgs: string[]; } & Pick< @@ -33,18 +35,24 @@ function commandBuilder( context: BuilderContext ): Observable { return from(setup(options, context)).pipe( - switchMap(({ browserOptions }) => - options.compodoc - ? runCompodoc( - { compodocArgs: options.compodocArgs, tsconfig: browserOptions.tsConfig }, - context + switchMap(({ tsConfig }) => { + const runCompodoc$ = options.compodoc + ? runCompodoc({ compodocArgs: options.compodocArgs, tsconfig: tsConfig }, context).pipe( + mapTo({ tsConfig }) ) - : of({}) - ), - map(() => ({ - ...options, - angularBrowserTarget: options.browserTarget, - })), + : of({}); + + return runCompodoc$.pipe(mapTo({ tsConfig })); + }), + map(({ tsConfig }) => { + const { browserTarget, ...otherOptions } = options; + + return { + ...otherOptions, + angularBrowserTarget: browserTarget, + tsConfig, + }; + }), switchMap((standaloneOptions) => runInstance({ ...standaloneOptions, mode: 'static' })), map(() => { return { success: true }; @@ -53,15 +61,19 @@ function commandBuilder( } async function setup(options: StorybookBuilderOptions, context: BuilderContext) { - const browserTarget = targetFromTargetString(options.browserTarget); - const browserOptions = await context.validateOptions( - await context.getTargetOptions(browserTarget), - await context.getBuilderNameForTarget(browserTarget) - ); + 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 { - browserOptions, - browserTarget, + tsConfig: options.tsConfig ?? browserOptions.tsConfig ?? undefined, }; } diff --git a/app/angular/src/builders/build-storybook/schema.json b/app/angular/src/builders/build-storybook/schema.json index 122ec971de32..952a05069933 100644 --- a/app/angular/src/builders/build-storybook/schema.json +++ b/app/angular/src/builders/build-storybook/schema.json @@ -7,7 +7,12 @@ "browserTarget": { "type": "string", "description": "Build target to be served in project-name:builder:config format. Should generally target on the builder: '@angular-devkit/build-angular:browser'. Useful for Storybook to use options (styles, assets, ...).", - "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$" + "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$", + "default": null + }, + "tsConfig": { + "type": "string", + "description": "The full path for the TypeScript configuration file, relative to the current workspace." }, "staticDir": { "type": "array", @@ -56,5 +61,12 @@ } }, "additionalProperties": false, - "required": ["browserTarget"] + "oneOf": [ + { + "required": ["browserTarget"] + }, + { + "required": ["tsConfig"] + } + ] } diff --git a/app/angular/src/builders/start-storybook/index.spec.ts b/app/angular/src/builders/start-storybook/index.spec.ts index 9ae5cf74ea30..35dece6ca548 100644 --- a/app/angular/src/builders/start-storybook/index.spec.ts +++ b/app/angular/src/builders/start-storybook/index.spec.ts @@ -55,7 +55,7 @@ describe('Start Storybook Builder', () => { jest.clearAllMocks(); }); - it('should work', async () => { + it('should start storybook with angularBrowserTarget', async () => { const run = await architect.scheduleBuilder('@storybook/angular:start-storybook', { browserTarget: 'angular-cli:build-2', port: 4400, @@ -70,7 +70,6 @@ describe('Start Storybook Builder', () => { expect(cpSpawnMock.spawn).not.toHaveBeenCalledWith(); expect(buildStandaloneMock).toHaveBeenCalledWith({ angularBrowserTarget: 'angular-cli:build-2', - browserTarget: 'angular-cli:build-2', ci: false, configDir: '.storybook', docsMode: false, @@ -85,6 +84,40 @@ describe('Start Storybook Builder', () => { staticDir: [], compodoc: false, compodocArgs: ['-e', 'json'], + tsConfig: 'src/tsconfig.app.json', + }); + }); + + it('should start storybook with tsConfig', async () => { + const run = await architect.scheduleBuilder('@storybook/angular:start-storybook', { + tsConfig: 'path/to/tsConfig.json', + port: 4400, + compodoc: false, + }); + + const output = await run.result; + + await run.stop(); + + expect(output.success).toBeTruthy(); + expect(cpSpawnMock.spawn).not.toHaveBeenCalledWith(); + expect(buildStandaloneMock).toHaveBeenCalledWith({ + angularBrowserTarget: null, + ci: false, + configDir: '.storybook', + docsMode: false, + host: 'localhost', + https: false, + port: 4400, + quiet: false, + smokeTest: false, + sslCa: undefined, + sslCert: undefined, + sslKey: undefined, + staticDir: [], + compodoc: false, + compodocArgs: ['-e', 'json'], + tsConfig: 'path/to/tsConfig.json', }); }); @@ -108,7 +141,6 @@ describe('Start Storybook Builder', () => { ]); expect(buildStandaloneMock).toHaveBeenCalledWith({ angularBrowserTarget: 'angular-cli:build-2', - browserTarget: 'angular-cli:build-2', ci: false, configDir: '.storybook', docsMode: false, @@ -123,6 +155,7 @@ describe('Start Storybook Builder', () => { staticDir: [], compodoc: true, compodocArgs: ['-e', 'json'], + tsConfig: 'src/tsconfig.app.json', }); }); }); diff --git a/app/angular/src/builders/start-storybook/index.ts b/app/angular/src/builders/start-storybook/index.ts index 6d1d90a208c7..aac56c0563f0 100644 --- a/app/angular/src/builders/start-storybook/index.ts +++ b/app/angular/src/builders/start-storybook/index.ts @@ -3,19 +3,21 @@ import { BuilderOutput, createBuilder, targetFromTargetString, + Target, } from '@angular-devkit/architect'; import { JsonObject } from '@angular-devkit/core'; import { BrowserBuilderOptions } from '@angular-devkit/build-angular'; import { from, Observable, of } from 'rxjs'; import { CLIOptions } from '@storybook/core-common'; -import { map, switchMap } from 'rxjs/operators'; +import { map, switchMap, mapTo } from 'rxjs/operators'; // eslint-disable-next-line import/no-extraneous-dependencies import buildStandalone, { StandaloneOptions } from '@storybook/angular/standalone'; import { runCompodoc } from '../utils/run-compodoc'; export type StorybookBuilderOptions = JsonObject & { - browserTarget: string; + browserTarget?: string | null; + tsConfig?: string; compodoc: boolean; compodocArgs: string[]; } & Pick< @@ -44,18 +46,24 @@ function commandBuilder( context: BuilderContext ): Observable { return from(setup(options, context)).pipe( - switchMap(({ browserOptions }) => - options.compodoc - ? runCompodoc( - { compodocArgs: options.compodocArgs, tsconfig: browserOptions.tsConfig }, - context + switchMap(({ tsConfig }) => { + const runCompodoc$ = options.compodoc + ? runCompodoc({ compodocArgs: options.compodocArgs, tsconfig: tsConfig }, context).pipe( + mapTo({ tsConfig }) ) - : of({}) - ), - map(() => ({ - ...options, - angularBrowserTarget: options.browserTarget, - })), + : of({}); + + return runCompodoc$.pipe(mapTo({ tsConfig })); + }), + map(({ tsConfig }) => { + const { browserTarget, ...otherOptions } = options; + + return { + ...otherOptions, + angularBrowserTarget: browserTarget, + tsConfig, + }; + }), switchMap((standaloneOptions) => runInstance(standaloneOptions)), map(() => { return { success: true }; @@ -64,15 +72,19 @@ function commandBuilder( } async function setup(options: StorybookBuilderOptions, context: BuilderContext) { - const browserTarget = targetFromTargetString(options.browserTarget); - const browserOptions = await context.validateOptions( - await context.getTargetOptions(browserTarget), - await context.getBuilderNameForTarget(browserTarget) - ); + 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 { - browserOptions, - browserTarget, + tsConfig: options.tsConfig ?? browserOptions.tsConfig ?? undefined, }; } diff --git a/app/angular/src/builders/start-storybook/schema.json b/app/angular/src/builders/start-storybook/schema.json index b73918547642..cbc6a6de71b4 100644 --- a/app/angular/src/builders/start-storybook/schema.json +++ b/app/angular/src/builders/start-storybook/schema.json @@ -7,7 +7,12 @@ "browserTarget": { "type": "string", "description": "Build target to be served in project-name:builder:config format. Should generally target on the builder: '@angular-devkit/build-angular:browser'. Useful for Storybook to use options (styles, assets, ...).", - "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$" + "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$", + "default": null + }, + "tsConfig": { + "type": "string", + "description": "The full path for the TypeScript configuration file, relative to the current workspace." }, "port": { "type": "number", @@ -83,5 +88,12 @@ } }, "additionalProperties": false, - "required": ["browserTarget"] + "oneOf": [ + { + "required": ["browserTarget"] + }, + { + "required": ["tsConfig"] + } + ] } diff --git a/app/angular/src/server/__mocks-ng-workspace__/with-lib/angular.json b/app/angular/src/server/__mocks-ng-workspace__/with-lib/angular.json new file mode 100644 index 000000000000..1e9be4468f64 --- /dev/null +++ b/app/angular/src/server/__mocks-ng-workspace__/with-lib/angular.json @@ -0,0 +1,28 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "pattern-lib": { + "projectType": "library", + "root": "projects/pattern-lib", + "sourceRoot": "projects/pattern-lib/src", + "prefix": "lib", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:ng-packagr", + "options": { + "tsConfig": "projects/pattern-lib/tsconfig.lib.json", + "project": "projects/pattern-lib/ng-package.json" + }, + "configurations": { + "production": { + "tsConfig": "projects/pattern-lib/tsconfig.lib.prod.json" + } + } + } + } + } + }, + "defaultProject": "pattern-lib" +} diff --git a/app/angular/src/server/__mocks-ng-workspace__/with-lib/projects/pattern-lib/src/main.ts b/app/angular/src/server/__mocks-ng-workspace__/with-lib/projects/pattern-lib/src/main.ts new file mode 100644 index 000000000000..63b661e3bd7e --- /dev/null +++ b/app/angular/src/server/__mocks-ng-workspace__/with-lib/projects/pattern-lib/src/main.ts @@ -0,0 +1,2 @@ +// To avoid "No inputs were found in config file" tsc error +export const not = 'empty'; diff --git a/app/angular/src/server/__mocks-ng-workspace__/with-lib/projects/pattern-lib/tsconfig.lib.json b/app/angular/src/server/__mocks-ng-workspace__/with-lib/projects/pattern-lib/tsconfig.lib.json new file mode 100644 index 000000000000..6e06ad542ed3 --- /dev/null +++ b/app/angular/src/server/__mocks-ng-workspace__/with-lib/projects/pattern-lib/tsconfig.lib.json @@ -0,0 +1,25 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "../../out-tsc/lib", + "target": "es2015", + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "types": [], + "lib": [ + "dom", + "es2018" + ] + }, + "angularCompilerOptions": { + "skipTemplateCodegen": true, + "strictMetadataEmit": true, + "enableResourceInlining": true + }, + "exclude": [ + "src/test.ts", + "**/*.spec.ts" + ] +} diff --git a/app/angular/src/server/__mocks-ng-workspace__/with-lib/tsconfig.json b/app/angular/src/server/__mocks-ng-workspace__/with-lib/tsconfig.json new file mode 100644 index 000000000000..ed46a09da328 --- /dev/null +++ b/app/angular/src/server/__mocks-ng-workspace__/with-lib/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "sourceMap": true, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "skipLibCheck": true, + "target": "es5", + "lib": ["es2017", "dom"] + } +} diff --git a/app/angular/src/server/__mocks-ng-workspace__/without-projects-entry/projects/pattern-lib/src/main.ts b/app/angular/src/server/__mocks-ng-workspace__/without-projects-entry/projects/pattern-lib/src/main.ts new file mode 100644 index 000000000000..63b661e3bd7e --- /dev/null +++ b/app/angular/src/server/__mocks-ng-workspace__/without-projects-entry/projects/pattern-lib/src/main.ts @@ -0,0 +1,2 @@ +// To avoid "No inputs were found in config file" tsc error +export const not = 'empty'; diff --git a/app/angular/src/server/__mocks-ng-workspace__/without-projects-entry/projects/pattern-lib/tsconfig.lib.json b/app/angular/src/server/__mocks-ng-workspace__/without-projects-entry/projects/pattern-lib/tsconfig.lib.json new file mode 100644 index 000000000000..6e06ad542ed3 --- /dev/null +++ b/app/angular/src/server/__mocks-ng-workspace__/without-projects-entry/projects/pattern-lib/tsconfig.lib.json @@ -0,0 +1,25 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "../../out-tsc/lib", + "target": "es2015", + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "types": [], + "lib": [ + "dom", + "es2018" + ] + }, + "angularCompilerOptions": { + "skipTemplateCodegen": true, + "strictMetadataEmit": true, + "enableResourceInlining": true + }, + "exclude": [ + "src/test.ts", + "**/*.spec.ts" + ] +} diff --git a/app/angular/src/server/__mocks-ng-workspace__/without-projects-entry/tsconfig.json b/app/angular/src/server/__mocks-ng-workspace__/without-projects-entry/tsconfig.json new file mode 100644 index 000000000000..ed46a09da328 --- /dev/null +++ b/app/angular/src/server/__mocks-ng-workspace__/without-projects-entry/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "sourceMap": true, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "skipLibCheck": true, + "target": "es5", + "lib": ["es2017", "dom"] + } +} diff --git a/app/angular/src/server/__mocks-ng-workspace__/without-tsConfig/angular.json b/app/angular/src/server/__mocks-ng-workspace__/without-tsConfig/angular.json new file mode 100644 index 000000000000..2b203f2551ec --- /dev/null +++ b/app/angular/src/server/__mocks-ng-workspace__/without-tsConfig/angular.json @@ -0,0 +1,16 @@ +{ + "version": 1, + "projects": { + "foo-project": { + "root": "", + "architect": { + "build": { + "options": { + "assets": [] + } + } + } + } + }, + "defaultProject": "foo-project" +} diff --git a/app/angular/src/server/__mocks-ng-workspace__/without-tsConfig/src/main.ts b/app/angular/src/server/__mocks-ng-workspace__/without-tsConfig/src/main.ts new file mode 100644 index 000000000000..63b661e3bd7e --- /dev/null +++ b/app/angular/src/server/__mocks-ng-workspace__/without-tsConfig/src/main.ts @@ -0,0 +1,2 @@ +// To avoid "No inputs were found in config file" tsc error +export const not = 'empty'; diff --git a/app/angular/src/server/__mocks-ng-workspace__/without-tsConfig/src/tsconfig.app.json b/app/angular/src/server/__mocks-ng-workspace__/without-tsConfig/src/tsconfig.app.json new file mode 100644 index 000000000000..644f410d7fb1 --- /dev/null +++ b/app/angular/src/server/__mocks-ng-workspace__/without-tsConfig/src/tsconfig.app.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "baseUrl": "./", + "module": "es2015", + "types": ["node"] + }, + "exclude": ["karma.ts", "**/*.spec.ts"] +} diff --git a/app/angular/src/server/__mocks-ng-workspace__/without-tsConfig/tsconfig.json b/app/angular/src/server/__mocks-ng-workspace__/without-tsConfig/tsconfig.json new file mode 100644 index 000000000000..ed46a09da328 --- /dev/null +++ b/app/angular/src/server/__mocks-ng-workspace__/without-tsConfig/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "sourceMap": true, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "skipLibCheck": true, + "target": "es5", + "lib": ["es2017", "dom"] + } +} diff --git a/app/angular/src/server/angular-devkit-build-webpack.ts b/app/angular/src/server/angular-devkit-build-webpack.ts index 9e0d540fa386..bd176b118ff4 100644 --- a/app/angular/src/server/angular-devkit-build-webpack.ts +++ b/app/angular/src/server/angular-devkit-build-webpack.ts @@ -70,9 +70,8 @@ const buildWebpackConfigOptions = async ( ): Promise => { const { options: projectBuildOptions = {} } = target; - const requiredOptions = ['tsConfig', 'assets', 'optimization']; - - if (!requiredOptions.every((key) => key in projectBuildOptions)) { + const requiredOptions = ['tsConfig']; + if (!requiredOptions.every((key) => !!projectBuildOptions[key])) { throw new Error( `Missing required options in project target. Check "${requiredOptions.join(', ')}"` ); diff --git a/app/angular/src/server/framework-preset-angular-cli.test.ts b/app/angular/src/server/framework-preset-angular-cli.test.ts index 6a02df735558..6fbf8498b376 100644 --- a/app/angular/src/server/framework-preset-angular-cli.test.ts +++ b/app/angular/src/server/framework-preset-angular-cli.test.ts @@ -141,7 +141,7 @@ describe('framework-preset-angular-cli', () => { }); it('throws error', async () => { await expect(() => webpackFinal(newWebpackConfiguration(), options)).rejects.toThrowError( - 'Missing required options in project target. Check "tsConfig, assets, optimization"' + 'Missing required options in project target. Check "tsConfig"' ); expect(logger.error).toHaveBeenCalledWith(`=> Could not get angular cli webpack config`); }); @@ -353,6 +353,19 @@ describe('framework-preset-angular-cli', () => { }); }); + describe('when angular.json haven\'t "options.tsConfig" config', () => { + beforeEach(() => { + initMockWorkspace('without-tsConfig'); + }); + + it('throws error', async () => { + await expect(() => webpackFinal(newWebpackConfiguration(), options)).rejects.toThrowError( + 'Missing required options in project target. Check "tsConfig"' + ); + expect(logger.error).toHaveBeenCalledWith(`=> Could not get angular cli webpack config`); + }); + }); + describe('when is a nx with angular.json', () => { beforeEach(() => { initMockWorkspace('with-nx'); @@ -509,6 +522,60 @@ describe('framework-preset-angular-cli', () => { }); }); + describe('when angular.json have only one lib project', () => { + beforeEach(() => { + initMockWorkspace('with-lib'); + }); + + it('should extends webpack base config', async () => { + const baseWebpackConfig = newWebpackConfiguration(); + const webpackFinalConfig = await webpackFinal(baseWebpackConfig, options); + + expect(webpackFinalConfig).toEqual({ + ...baseWebpackConfig, + entry: [...(baseWebpackConfig.entry as any[])], + module: { ...baseWebpackConfig.module, rules: expect.anything() }, + plugins: expect.anything(), + resolve: { + ...baseWebpackConfig.resolve, + modules: expect.arrayContaining(baseWebpackConfig.resolve.modules), + // the base resolve.plugins are not kept 🤷‍♂️ + plugins: expect.not.arrayContaining(baseWebpackConfig.resolve.plugins), + }, + resolveLoader: expect.anything(), + }); + }); + + it('should set webpack "module.rules"', async () => { + const baseWebpackConfig = newWebpackConfiguration(); + const webpackFinalConfig = await webpackFinal(baseWebpackConfig, options); + + expect(webpackFinalConfig.module.rules).toEqual([ + { + exclude: [], + test: /\.css$/, + use: expect.anything(), + }, + { + exclude: [], + test: /\.scss$|\.sass$/, + use: expect.anything(), + }, + { + exclude: [], + test: /\.less$/, + use: expect.anything(), + }, + { + exclude: [], + test: /\.styl$/, + use: expect.anything(), + }, + ...baseWebpackConfig.module.rules, + ]); + }); + }); + describe('when angular.json have some config', () => { beforeEach(() => { initMockWorkspace('some-config'); @@ -545,6 +612,28 @@ describe('framework-preset-angular-cli', () => { expect(logger.info).toHaveBeenNthCalledWith(3, '=> Using angular-cli webpack config'); }); }); + + describe('with only tsConfig option', () => { + beforeEach(() => { + initMockWorkspace('without-projects-entry'); + options = { + tsConfig: 'projects/pattern-lib/tsconfig.lib.json', + angularBrowserTarget: null, + } as Options; + }); + it('should log', async () => { + const baseWebpackConfig = newWebpackConfiguration(); + await webpackFinal(baseWebpackConfig, options); + + expect(logger.info).toHaveBeenCalledTimes(3); + expect(logger.info).toHaveBeenNthCalledWith(1, '=> Loading angular-cli config'); + expect(logger.info).toHaveBeenNthCalledWith( + 2, + '=> Using default angular project with "tsConfig:projects/pattern-lib/tsconfig.lib.json"' + ); + expect(logger.info).toHaveBeenNthCalledWith(3, '=> Using angular-cli webpack config'); + }); + }); }); const newWebpackConfiguration = ( diff --git a/app/angular/src/server/framework-preset-angular-cli.ts b/app/angular/src/server/framework-preset-angular-cli.ts index a696bbc839e0..ffc90c3c9a2d 100644 --- a/app/angular/src/server/framework-preset-angular-cli.ts +++ b/app/angular/src/server/framework-preset-angular-cli.ts @@ -19,6 +19,7 @@ import { filterOutStylingRules } from './utils/filter-out-styling-rules'; export type Options = CoreOptions & { angularBrowserTarget?: string; + tsConfig?: string; }; export async function webpackFinal(baseConfig: webpack.Configuration, options: Options) { @@ -45,26 +46,36 @@ export async function webpackFinal(baseConfig: webpack.Configuration, options: O // Find angular project target let project: workspaces.ProjectDefinition; let target: workspaces.TargetDefinition; - let browserTarget; try { - browserTarget = options.angularBrowserTarget - ? targetFromTargetString(options.angularBrowserTarget) - : ({ - configuration: undefined, - project: getDefaultProjectName(workspaceConfig), - target: 'build', - } as Target); - - const fondProject = findAngularProjectTarget( - workspaceConfig, - browserTarget.project, - browserTarget.target - ); - project = fondProject.project; - target = fondProject.target; - logger.info( - `=> Using angular project "${browserTarget.project}:${browserTarget.target}" for configuring Storybook` - ); + // Default behavior when `angularBrowserTarget` are not explicitly defined to null + if (options.angularBrowserTarget !== null) { + const browserTarget = options.angularBrowserTarget + ? targetFromTargetString(options.angularBrowserTarget) + : ({ + configuration: undefined, + project: getDefaultProjectName(workspaceConfig), + target: 'build', + } as Target); + + const fondProject = findAngularProjectTarget( + workspaceConfig, + browserTarget.project, + browserTarget.target + ); + project = fondProject.project; + target = fondProject.target; + + logger.info( + `=> Using angular project "${browserTarget.project}:${browserTarget.target}" for configuring Storybook` + ); + } + // Start storybook when only tsConfig is provided. + if (options.angularBrowserTarget === null && options.tsConfig) { + logger.info(`=> Using default angular project with "tsConfig:${options.tsConfig}"`); + + project = { root: '', extensions: {}, targets: undefined }; + target = { builder: '', options: { tsConfig: options.tsConfig } }; + } } catch (error) { logger.error(`=> Could not find angular project: ${error.message}`); logger.info(`=> Fail to load angular-cli config. Using base config`); @@ -72,7 +83,7 @@ export async function webpackFinal(baseConfig: webpack.Configuration, options: O } // Use angular-cli to get some webpack config - let angularCliWebpackConfig; + let angularCliWebpackConfig: AngularCliWebpackConfig; try { angularCliWebpackConfig = await extractAngularCliWebpackConfig(dirToSearch, project, target); logger.info(`=> Using angular-cli webpack config`); diff --git a/app/angular/standalone.d.ts b/app/angular/standalone.d.ts index 267ec8613a20..978437c490b6 100644 --- a/app/angular/standalone.d.ts +++ b/app/angular/standalone.d.ts @@ -5,7 +5,8 @@ export type StandaloneOptions = Partial< LoadOptions & BuilderOptions & { mode?: 'static' | 'dev'; - angularBrowserTarget: string; + angularBrowserTarget?: string | null; + tsConfig?: string; } >; diff --git a/examples/angular-cli/angular.json b/examples/angular-cli/angular.json index f06a4cc5ca48..bc919d189431 100644 --- a/examples/angular-cli/angular.json +++ b/examples/angular-cli/angular.json @@ -104,6 +104,24 @@ } } } + }, + "without-browser-target": { + "root": "", + "projectType": "library", + "architect": { + "storybook": { + "builder": "@storybook/angular:start-storybook", + "options": { + "tsConfig": "src/tsconfig.app.json" + } + }, + "build-storybook": { + "builder": "@storybook/angular:build-storybook", + "options": { + "tsConfig": "src/tsconfig.app.json" + } + } + } } }, "defaultProject": "angular-cli"