From 6e932f626060319c8faf56bca06bce53104a9c69 Mon Sep 17 00:00:00 2001 From: ThibaudAv Date: Mon, 2 Aug 2021 12:54:02 +0200 Subject: [PATCH 1/4] feat(angular): support workspace with only lib Note: thow error if project (lib or not) does not have tsConfig options --- .../with-lib/angular.json | 28 ++++++++ .../with-lib/projects/pattern-lib/src/main.ts | 2 + .../projects/pattern-lib/tsconfig.lib.json | 25 +++++++ .../with-lib/tsconfig.json | 13 ++++ .../without-tsConfig/angular.json | 16 +++++ .../without-tsConfig/src/main.ts | 2 + .../without-tsConfig/src/tsconfig.app.json | 9 +++ .../without-tsConfig/tsconfig.json | 13 ++++ .../server/angular-devkit-build-webpack.ts | 5 +- .../framework-preset-angular-cli.test.ts | 69 ++++++++++++++++++- .../server/framework-preset-angular-cli.ts | 2 +- 11 files changed, 179 insertions(+), 5 deletions(-) create mode 100644 app/angular/src/server/__mocks-ng-workspace__/with-lib/angular.json create mode 100644 app/angular/src/server/__mocks-ng-workspace__/with-lib/projects/pattern-lib/src/main.ts create mode 100644 app/angular/src/server/__mocks-ng-workspace__/with-lib/projects/pattern-lib/tsconfig.lib.json create mode 100644 app/angular/src/server/__mocks-ng-workspace__/with-lib/tsconfig.json create mode 100644 app/angular/src/server/__mocks-ng-workspace__/without-tsConfig/angular.json create mode 100644 app/angular/src/server/__mocks-ng-workspace__/without-tsConfig/src/main.ts create mode 100644 app/angular/src/server/__mocks-ng-workspace__/without-tsConfig/src/tsconfig.app.json create mode 100644 app/angular/src/server/__mocks-ng-workspace__/without-tsConfig/tsconfig.json 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-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..8d3c9a6ba37e 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'); diff --git a/app/angular/src/server/framework-preset-angular-cli.ts b/app/angular/src/server/framework-preset-angular-cli.ts index a696bbc839e0..4d36a1a1f31f 100644 --- a/app/angular/src/server/framework-preset-angular-cli.ts +++ b/app/angular/src/server/framework-preset-angular-cli.ts @@ -72,7 +72,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`); From bda825b59fbfd11a047f840a9c60eb787bd0b498 Mon Sep 17 00:00:00 2001 From: ThibaudAv Date: Tue, 3 Aug 2021 09:31:12 +0200 Subject: [PATCH 2/4] feat: start and build storybook by ng Builder with only tsConfig should allow project with only lib, without `@angular-devkit/build-angular:browser` to complete the configuration, to work more simply --- .../builders/build-storybook/index.spec.ts | 33 ++++++++++-- .../src/builders/build-storybook/index.ts | 52 ++++++++++++------- .../src/builders/build-storybook/schema.json | 13 ++++- .../builders/start-storybook/index.spec.ts | 39 ++++++++++++-- .../src/builders/start-storybook/index.ts | 52 ++++++++++++------- .../src/builders/start-storybook/schema.json | 13 ++++- app/angular/standalone.d.ts | 3 +- 7 files changed, 156 insertions(+), 49 deletions(-) diff --git a/app/angular/src/builders/build-storybook/index.spec.ts b/app/angular/src/builders/build-storybook/index.spec.ts index af9681b7b8e0..3c9fd379b9a6 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: undefined, + 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..c2834fdca39c 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; + 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..cd731e5683de 100644 --- a/app/angular/src/builders/build-storybook/schema.json +++ b/app/angular/src/builders/build-storybook/schema.json @@ -9,6 +9,10 @@ "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]+)?$" }, + "tsConfig": { + "type": "string", + "description": "The full path for the TypeScript configuration file, relative to the current workspace." + }, "staticDir": { "type": "array", "description": "Directory where to load static files from, array of strings.", @@ -56,5 +60,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..63809aee5460 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: undefined, + 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..360edf346bc6 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; + 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..698e5315a834 100644 --- a/app/angular/src/builders/start-storybook/schema.json +++ b/app/angular/src/builders/start-storybook/schema.json @@ -9,6 +9,10 @@ "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]+)?$" }, + "tsConfig": { + "type": "string", + "description": "The full path for the TypeScript configuration file, relative to the current workspace." + }, "port": { "type": "number", "description": "Port to listen on.", @@ -83,5 +87,12 @@ } }, "additionalProperties": false, - "required": ["browserTarget"] + "oneOf": [ + { + "required": ["browserTarget"] + }, + { + "required": ["tsConfig"] + } + ] } diff --git a/app/angular/standalone.d.ts b/app/angular/standalone.d.ts index 267ec8613a20..04a28548116b 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; + tsConfig?: string; } >; From d1fcb2458f92064b8ad67552f4e14a5600e35b7b Mon Sep 17 00:00:00 2001 From: ThibaudAv Date: Tue, 3 Aug 2021 11:29:29 +0200 Subject: [PATCH 3/4] feat: angular preset work with only tsConfig should allow project with only lib, without `@angular-devkit/build-angular:browser` to complete the configuration, to work more simply --- .../projects/pattern-lib/src/main.ts | 2 ++ .../projects/pattern-lib/tsconfig.lib.json | 25 +++++++++++++++++++ .../without-projects-entry/tsconfig.json | 13 ++++++++++ .../framework-preset-angular-cli.test.ts | 19 ++++++++++++++ .../server/framework-preset-angular-cli.ts | 13 +++++++--- 5 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 app/angular/src/server/__mocks-ng-workspace__/without-projects-entry/projects/pattern-lib/src/main.ts create mode 100644 app/angular/src/server/__mocks-ng-workspace__/without-projects-entry/projects/pattern-lib/tsconfig.lib.json create mode 100644 app/angular/src/server/__mocks-ng-workspace__/without-projects-entry/tsconfig.json 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/framework-preset-angular-cli.test.ts b/app/angular/src/server/framework-preset-angular-cli.test.ts index 8d3c9a6ba37e..dee4a23a1d95 100644 --- a/app/angular/src/server/framework-preset-angular-cli.test.ts +++ b/app/angular/src/server/framework-preset-angular-cli.test.ts @@ -612,6 +612,25 @@ 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' } 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 4d36a1a1f31f..bebbd117a61e 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) { @@ -66,9 +67,15 @@ export async function webpackFinal(baseConfig: webpack.Configuration, options: O `=> Using angular project "${browserTarget.project}:${browserTarget.target}" for configuring Storybook` ); } catch (error) { - logger.error(`=> Could not find angular project: ${error.message}`); - logger.info(`=> Fail to load angular-cli config. Using base config`); - return baseConfig; + if (!options.tsConfig) { + logger.error(`=> Could not find angular project: ${error.message}`); + logger.info(`=> Fail to load angular-cli config. Using base config`); + return baseConfig; + } + logger.info(`=> Using default angular project with "tsConfig:${options.tsConfig}"`); + + project = { root: '', extensions: {}, targets: undefined }; + target = { builder: '', options: { tsConfig: options.tsConfig } }; } // Use angular-cli to get some webpack config From 1604b617a9db12660ee1ae4a6bc0c737a7ef6930 Mon Sep 17 00:00:00 2001 From: ThibaudAv Date: Tue, 3 Aug 2021 15:46:01 +0200 Subject: [PATCH 4/4] feat: start and build storybook witout browser-target only tsConfig is required and can be directly given to storybook using new Angular builder for SB should allow project with only lib, without `@angular-devkit/build-angular:browser` to complete the configuration, to work more simply --- .../builders/build-storybook/index.spec.ts | 2 +- .../src/builders/build-storybook/index.ts | 2 +- .../src/builders/build-storybook/schema.json | 3 +- .../builders/start-storybook/index.spec.ts | 2 +- .../src/builders/start-storybook/index.ts | 2 +- .../src/builders/start-storybook/schema.json | 3 +- .../framework-preset-angular-cli.test.ts | 5 +- .../server/framework-preset-angular-cli.ts | 58 ++++++++++--------- app/angular/standalone.d.ts | 2 +- examples/angular-cli/angular.json | 18 ++++++ 10 files changed, 62 insertions(+), 35 deletions(-) diff --git a/app/angular/src/builders/build-storybook/index.spec.ts b/app/angular/src/builders/build-storybook/index.spec.ts index 3c9fd379b9a6..9ebae8813f14 100644 --- a/app/angular/src/builders/build-storybook/index.spec.ts +++ b/app/angular/src/builders/build-storybook/index.spec.ts @@ -95,7 +95,7 @@ describe('Build Storybook Builder', () => { expect(output.success).toBeTruthy(); expect(cpSpawnMock.spawn).not.toHaveBeenCalledWith(); expect(buildStandaloneMock).toHaveBeenCalledWith({ - angularBrowserTarget: undefined, + angularBrowserTarget: null, configDir: '.storybook', docsMode: false, loglevel: undefined, diff --git a/app/angular/src/builders/build-storybook/index.ts b/app/angular/src/builders/build-storybook/index.ts index c2834fdca39c..81e42710ac29 100644 --- a/app/angular/src/builders/build-storybook/index.ts +++ b/app/angular/src/builders/build-storybook/index.ts @@ -16,7 +16,7 @@ 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[]; diff --git a/app/angular/src/builders/build-storybook/schema.json b/app/angular/src/builders/build-storybook/schema.json index cd731e5683de..952a05069933 100644 --- a/app/angular/src/builders/build-storybook/schema.json +++ b/app/angular/src/builders/build-storybook/schema.json @@ -7,7 +7,8 @@ "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", diff --git a/app/angular/src/builders/start-storybook/index.spec.ts b/app/angular/src/builders/start-storybook/index.spec.ts index 63809aee5460..35dece6ca548 100644 --- a/app/angular/src/builders/start-storybook/index.spec.ts +++ b/app/angular/src/builders/start-storybook/index.spec.ts @@ -102,7 +102,7 @@ describe('Start Storybook Builder', () => { expect(output.success).toBeTruthy(); expect(cpSpawnMock.spawn).not.toHaveBeenCalledWith(); expect(buildStandaloneMock).toHaveBeenCalledWith({ - angularBrowserTarget: undefined, + angularBrowserTarget: null, ci: false, configDir: '.storybook', docsMode: false, diff --git a/app/angular/src/builders/start-storybook/index.ts b/app/angular/src/builders/start-storybook/index.ts index 360edf346bc6..aac56c0563f0 100644 --- a/app/angular/src/builders/start-storybook/index.ts +++ b/app/angular/src/builders/start-storybook/index.ts @@ -16,7 +16,7 @@ import buildStandalone, { StandaloneOptions } from '@storybook/angular/standalon import { runCompodoc } from '../utils/run-compodoc'; export type StorybookBuilderOptions = JsonObject & { - browserTarget?: string; + browserTarget?: string | null; tsConfig?: string; compodoc: boolean; compodocArgs: string[]; diff --git a/app/angular/src/builders/start-storybook/schema.json b/app/angular/src/builders/start-storybook/schema.json index 698e5315a834..cbc6a6de71b4 100644 --- a/app/angular/src/builders/start-storybook/schema.json +++ b/app/angular/src/builders/start-storybook/schema.json @@ -7,7 +7,8 @@ "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", 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 dee4a23a1d95..6fbf8498b376 100644 --- a/app/angular/src/server/framework-preset-angular-cli.test.ts +++ b/app/angular/src/server/framework-preset-angular-cli.test.ts @@ -616,7 +616,10 @@ describe('framework-preset-angular-cli', () => { describe('with only tsConfig option', () => { beforeEach(() => { initMockWorkspace('without-projects-entry'); - options = { tsConfig: 'projects/pattern-lib/tsconfig.lib.json' } as Options; + options = { + tsConfig: 'projects/pattern-lib/tsconfig.lib.json', + angularBrowserTarget: null, + } as Options; }); it('should log', async () => { const baseWebpackConfig = newWebpackConfiguration(); diff --git a/app/angular/src/server/framework-preset-angular-cli.ts b/app/angular/src/server/framework-preset-angular-cli.ts index bebbd117a61e..ffc90c3c9a2d 100644 --- a/app/angular/src/server/framework-preset-angular-cli.ts +++ b/app/angular/src/server/framework-preset-angular-cli.ts @@ -46,36 +46,40 @@ 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` - ); - } catch (error) { - if (!options.tsConfig) { - logger.error(`=> Could not find angular project: ${error.message}`); - logger.info(`=> Fail to load angular-cli config. Using base config`); - return baseConfig; + // 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` + ); } - logger.info(`=> Using default angular project with "tsConfig:${options.tsConfig}"`); + // 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 } }; + 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`); + return baseConfig; } // Use angular-cli to get some webpack config diff --git a/app/angular/standalone.d.ts b/app/angular/standalone.d.ts index 04a28548116b..978437c490b6 100644 --- a/app/angular/standalone.d.ts +++ b/app/angular/standalone.d.ts @@ -5,7 +5,7 @@ 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"