diff --git a/docs/angular/api-angular/builders/ng-packagr-lite.md b/docs/angular/api-angular/builders/ng-packagr-lite.md new file mode 100644 index 0000000000000..c03f75dee5e31 --- /dev/null +++ b/docs/angular/api-angular/builders/ng-packagr-lite.md @@ -0,0 +1,35 @@ +# ng-packagr-lite + +Build an Angular library for incremental building + +Builder properties can be configured in angular.json when defining the builder, or when invoking it. + +## Properties + +### project + +Type: `string` + +The file path for the ng-packagr configuration file, relative to the current workspace. + +### tsConfig + +Type: `string` + +The full path for the TypeScript configuration file, relative to the current workspace. + +### updateBuildableProjectDepsInPackageJson + +Default: `true` + +Type: `boolean` + +Update buildable project dependencies in package.json + +### watch + +Default: `false` + +Type: `boolean` + +Run build when files change. diff --git a/docs/angular/api-angular/builders/package.md b/docs/angular/api-angular/builders/package.md index a06bf34c84f5e..506a5054cecdd 100644 --- a/docs/angular/api-angular/builders/package.md +++ b/docs/angular/api-angular/builders/package.md @@ -1,6 +1,6 @@ # package -Build an Angular library +Build and package an Angular library for publishing Builder properties can be configured in angular.json when defining the builder, or when invoking it. diff --git a/docs/node/api-angular/builders/ng-packagr-lite.md b/docs/node/api-angular/builders/ng-packagr-lite.md new file mode 100644 index 0000000000000..7e7f576f2efb1 --- /dev/null +++ b/docs/node/api-angular/builders/ng-packagr-lite.md @@ -0,0 +1,36 @@ +# ng-packagr-lite + +Build an Angular library for incremental building + +Builder properties can be configured in workspace.json when defining the builder, or when invoking it. +Read more about how to use builders and the CLI here: https://nx.dev/node/guides/cli. + +## Properties + +### project + +Type: `string` + +The file path for the ng-packagr configuration file, relative to the current workspace. + +### tsConfig + +Type: `string` + +The full path for the TypeScript configuration file, relative to the current workspace. + +### updateBuildableProjectDepsInPackageJson + +Default: `true` + +Type: `boolean` + +Update buildable project dependencies in package.json + +### watch + +Default: `false` + +Type: `boolean` + +Run build when files change. diff --git a/docs/node/api-angular/builders/package.md b/docs/node/api-angular/builders/package.md index f18a698df5eb4..67a6e8615644b 100644 --- a/docs/node/api-angular/builders/package.md +++ b/docs/node/api-angular/builders/package.md @@ -1,6 +1,6 @@ # package -Build an Angular library +Build and package an Angular library for publishing Builder properties can be configured in workspace.json when defining the builder, or when invoking it. Read more about how to use builders and the CLI here: https://nx.dev/node/guides/cli. diff --git a/docs/react/api-angular/builders/ng-packagr-lite.md b/docs/react/api-angular/builders/ng-packagr-lite.md new file mode 100644 index 0000000000000..667a7b33abdd1 --- /dev/null +++ b/docs/react/api-angular/builders/ng-packagr-lite.md @@ -0,0 +1,36 @@ +# ng-packagr-lite + +Build an Angular library for incremental building + +Builder properties can be configured in workspace.json when defining the builder, or when invoking it. +Read more about how to use builders and the CLI here: https://nx.dev/react/guides/cli. + +## Properties + +### project + +Type: `string` + +The file path for the ng-packagr configuration file, relative to the current workspace. + +### tsConfig + +Type: `string` + +The full path for the TypeScript configuration file, relative to the current workspace. + +### updateBuildableProjectDepsInPackageJson + +Default: `true` + +Type: `boolean` + +Update buildable project dependencies in package.json + +### watch + +Default: `false` + +Type: `boolean` + +Run build when files change. diff --git a/docs/react/api-angular/builders/package.md b/docs/react/api-angular/builders/package.md index d50b8636ffcb1..3d66db0326156 100644 --- a/docs/react/api-angular/builders/package.md +++ b/docs/react/api-angular/builders/package.md @@ -1,6 +1,6 @@ # package -Build an Angular library +Build and package an Angular library for publishing Builder properties can be configured in workspace.json when defining the builder, or when invoking it. Read more about how to use builders and the CLI here: https://nx.dev/react/guides/cli. diff --git a/e2e/angular/src/angular-library.test.ts b/e2e/angular/src/angular-library.test.ts new file mode 100644 index 0000000000000..990d49bde8227 --- /dev/null +++ b/e2e/angular/src/angular-library.test.ts @@ -0,0 +1,176 @@ +import { toClassName } from '@nrwl/workspace'; +import { + forEachCli, + newProject, + readJson, + runCLI, + uniq, + updateFile, +} from '@nrwl/e2e/utils'; + +forEachCli('angular', (cli) => { + ['publishable', 'buildable'].forEach((testConfig) => { + describe('Build Angular library', () => { + /** + * Graph: + * + * childLib + * / + * parentLib => + * \ + * \ + * childLib2 + * + */ + let parentLib: string; + let childLib: string; + let childLib2: string; + + beforeEach(() => { + parentLib = uniq('parentlib'); + childLib = uniq('childlib'); + childLib2 = uniq('childlib2'); + + newProject(); + + if (testConfig === 'buildable') { + runCLI( + `generate @nrwl/angular:library ${parentLib} --buildable=true --no-interactive` + ); + runCLI( + `generate @nrwl/angular:library ${childLib} --buildable=true --no-interactive` + ); + runCLI( + `generate @nrwl/angular:library ${childLib2} --buildable=true --no-interactive` + ); + } else { + runCLI( + `generate @nrwl/angular:library ${parentLib} --publishable=true --importPath=@proj/${parentLib} --no-interactive` + ); + runCLI( + `generate @nrwl/angular:library ${childLib} --publishable=true --importPath=@proj/${childLib} --no-interactive` + ); + runCLI( + `generate @nrwl/angular:library ${childLib2} --publishable=true --importPath=@proj/${childLib2} --no-interactive` + ); + + // create secondary entrypoint + updateFile( + `libs/${childLib}/sub/package.json`, + ` + { + "ngPackage": {} + } + ` + ); + updateFile( + `libs/${childLib}/sub/src/lib/sub.module.ts`, + ` + import { NgModule } from '@angular/core'; + import { CommonModule } from '@angular/common'; + @NgModule({ imports: [CommonModule] }) + export class SubModule {} + ` + ); + + updateFile( + `libs/${childLib}/sub/src/public_api.ts`, + `export * from './lib/sub.module';` + ); + + updateFile( + `libs/${childLib}/sub/src/index.ts`, + `export * from './public_api';` + ); + + updateFile(`tsconfig.base.json`, (s) => { + return s.replace( + `"@proj/${childLib}": ["libs/${childLib}/src/index.ts"],`, + `"@proj/${childLib}": ["libs/${childLib}/src/index.ts"], + "@proj/${childLib}/sub": ["libs/${childLib}/sub/src/index.ts"], + ` + ); + }); + } + + // create dependencies by importing + const createDep = (parent, children: string[]) => { + let moduleContent = ` + import { NgModule } from '@angular/core'; + import { CommonModule } from '@angular/common'; + ${children + .map( + (entry) => + `import { ${toClassName( + entry + )}Module } from '@proj/${entry}';` + ) + .join('\n')} + `; + + if (testConfig === 'publishable') { + moduleContent += ` + import { SubModule } from '@proj/${childLib}/sub'; + + @NgModule({ + imports: [CommonModule, ${children + .map((entry) => `${toClassName(entry)}Module`) + .join(',')}, SubModule] + }) + export class ${toClassName(parent)}Module {}`; + } + + updateFile( + `libs/${parent}/src/lib/${parent}.module.ts`, + moduleContent + ); + }; + + createDep(parentLib, [childLib, childLib2]); + }); + + it('should throw an error if the dependent library has not been built before building the parent lib', () => { + expect.assertions(2); + + try { + runCLI(`build ${parentLib}`); + } catch (e) { + expect(e.stderr.toString()).toContain( + `Some of the project ${parentLib}'s dependencies have not been built yet. Please build these libraries before:` + ); + expect(e.stderr.toString()).toContain(`${childLib}`); + } + }); + + it('should build the library when it does not have any deps', () => { + const libOutput = runCLI(`build ${childLib}`); + expect(libOutput).toContain(`Built @proj/${childLib}`); + }); + + it('should properly add references to any dependency into the parent package.json', () => { + const childLibOutput = runCLI(`build ${childLib}`); + const childLib2Output = runCLI(`build ${childLib2}`); + const parentLibOutput = runCLI(`build ${parentLib}`); + + expect(childLibOutput).toContain(`Built @proj/${childLib}`); + expect(childLib2Output).toContain(`Built @proj/${childLib2}`); + expect(parentLibOutput).toContain(`Built @proj/${parentLib}`); + + const jsonFile = readJson(`dist/libs/${parentLib}/package.json`); + // expect(jsonFile.dependencies).toEqual({ tslib: '^2.0.0' }); + + expect(jsonFile.dependencies['tslib']).toEqual('^2.0.0'); + expect(jsonFile.dependencies[`@proj/${childLib}`]).toBeDefined(); + expect(jsonFile.dependencies[`@proj/${childLib2}`]).toBeDefined(); + expect(jsonFile.peerDependencies['@angular/common']).toBeDefined(); + expect(jsonFile.peerDependencies['@angular/core']).toBeDefined(); + }); + }); + }); +}); + +forEachCli('nx', () => { + describe('Build Angular library', () => { + it('should work', async () => {}, 1000000); + }); +}); diff --git a/e2e/angular/src/angular-package.test.ts b/e2e/angular/src/angular-package.test.ts deleted file mode 100644 index ce3f2eb151d7b..0000000000000 --- a/e2e/angular/src/angular-package.test.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { toClassName } from '@nrwl/workspace'; -import { - forEachCli, - newProject, - readJson, - runCLI, - uniq, - updateFile, -} from '@nrwl/e2e/utils'; - -forEachCli('angular', (cli) => { - describe('Build Angular library', () => { - /** - * Graph: - * - * childLib - * / - * parentLib => - * \ - * \ - * childLib2 - * - */ - let parentLib: string; - let childLib: string; - let childLib2: string; - - beforeEach(() => { - parentLib = uniq('parentlib'); - childLib = uniq('childlib'); - childLib2 = uniq('childlib2'); - - newProject(); - - runCLI( - `generate @nrwl/angular:library ${parentLib} --publishable=true --importPath=@proj/${parentLib} --no-interactive` - ); - runCLI( - `generate @nrwl/angular:library ${childLib} --publishable=true --importPath=@proj/${childLib} --no-interactive` - ); - runCLI( - `generate @nrwl/angular:library ${childLib2} --publishable=true --importPath=@proj/${childLib2} --no-interactive` - ); - - // create secondary entrypoint - updateFile( - `libs/${childLib}/sub/package.json`, - ` - { - "ngPackage": {} - } - ` - ); - updateFile( - `libs/${childLib}/sub/src/lib/sub.module.ts`, - ` - import { NgModule } from '@angular/core'; - import { CommonModule } from '@angular/common'; - @NgModule({ imports: [CommonModule] }) - export class SubModule {} - ` - ); - - updateFile( - `libs/${childLib}/sub/src/public_api.ts`, - `export * from './lib/sub.module';` - ); - - updateFile( - `libs/${childLib}/sub/src/index.ts`, - `export * from './public_api';` - ); - - updateFile(`tsconfig.base.json`, (s) => { - return s.replace( - `"@proj/${childLib}": ["libs/${childLib}/src/index.ts"],`, - `"@proj/${childLib}": ["libs/${childLib}/src/index.ts"], - "@proj/${childLib}/sub": ["libs/${childLib}/sub/src/index.ts"], - ` - ); - }); - - // create dependencies by importing - const createDep = (parent, children: string[]) => { - updateFile( - `libs/${parent}/src/lib/${parent}.module.ts`, - ` - import { NgModule } from '@angular/core'; - import { CommonModule } from '@angular/common'; - ${children - .map( - (entry) => - `import { ${toClassName( - entry - )}Module } from '@proj/${entry}';` - ) - .join('\n')} - import { SubModule } from '@proj/${childLib}/sub'; - - @NgModule({ - imports: [CommonModule, ${children - .map((entry) => `${toClassName(entry)}Module`) - .join(',')}, SubModule] - }) - export class ${toClassName(parent)}Module {} - ` - ); - }; - - createDep(parentLib, [childLib, childLib2]); - }); - - it('should throw an error if the dependent library has not been built before building the parent lib', () => { - expect.assertions(2); - - try { - runCLI(`build ${parentLib}`); - } catch (e) { - expect(e.stderr.toString()).toContain( - `Some of the project ${parentLib}'s dependencies have not been built yet. Please build these libraries before:` - ); - expect(e.stderr.toString()).toContain(`${childLib}`); - } - }); - - it('should build the library when it does not have any deps', () => { - const parentLibOutput = runCLI(`build ${childLib}`); - expect(parentLibOutput).toContain(`Built @proj/${childLib}`); - }); - - it('should properly add references to any dependency into the parent package.json', () => { - const childLibOutput = runCLI(`build ${childLib}`); - const childLib2Output = runCLI(`build ${childLib2}`); - const parentLibOutput = runCLI(`build ${parentLib}`); - - expect(childLibOutput).toContain(`Built @proj/${childLib}`); - expect(childLib2Output).toContain(`Built @proj/${childLib2}`); - expect(parentLibOutput).toContain(`Built @proj/${parentLib}`); - - const jsonFile = readJson(`dist/libs/${parentLib}/package.json`); - expect(jsonFile.dependencies).toEqual({ tslib: '^2.0.0' }); - - expect(jsonFile.peerDependencies[`@proj/${childLib}`]).toBeDefined(); - expect(jsonFile.peerDependencies[`@proj/${childLib2}`]).toBeDefined(); - expect(jsonFile.peerDependencies['@angular/common']).toBeDefined(); - expect(jsonFile.peerDependencies['@angular/core']).toBeDefined(); - }); - }); -}); - -forEachCli('nx', () => { - describe('Build Angular library', () => { - it('should work', async () => {}, 1000000); - }); -}); diff --git a/package.json b/package.json index 13b5bcd4c1d7a..e9fd5e32feae0 100644 --- a/package.json +++ b/package.json @@ -160,6 +160,7 @@ "identity-obj-proxy": "3.0.0", "ignore": "^5.0.4", "import-fresh": "^3.1.0", + "injection-js": "^2.3.1", "jasmine-core": "~2.99.1", "jasmine-marbles": "~0.6.0", "jasmine-spec-reporter": "~4.2.1", @@ -263,5 +264,8 @@ "pre-push": "yarn check-commit && yarn documentation && pretty-quick --check" } }, - "dependencies": {} + "dependencies": {}, + "resolutions": { + "ng-packagr/rxjs": "6.6.2" + } } diff --git a/packages/angular/builders.json b/packages/angular/builders.json index 216b2bc760a84..0d17263632493 100644 --- a/packages/angular/builders.json +++ b/packages/angular/builders.json @@ -1,10 +1,15 @@ { "$schema": "@angular-devkit/architect/src/builders-schema.json", "builders": { + "ng-packagr-lite": { + "implementation": "./src/builders/ng-packagr-lite/ng-packagr-lite.impl", + "schema": "./src/builders/ng-packagr-lite/schema.json", + "description": "Build an Angular library for incremental building" + }, "package": { "implementation": "./src/builders/package/package.impl", "schema": "./src/builders/package/schema.json", - "description": "Build an Angular library" + "description": "Build and package an Angular library for publishing" } } } diff --git a/packages/angular/src/builders/ng-packagr-lite/ng-packagr-adjustments/entry-point.di.ts b/packages/angular/src/builders/ng-packagr-lite/ng-packagr-adjustments/entry-point.di.ts new file mode 100644 index 0000000000000..32a9efb39efdd --- /dev/null +++ b/packages/angular/src/builders/ng-packagr-lite/ng-packagr-adjustments/entry-point.di.ts @@ -0,0 +1,46 @@ +/** + * Wires everything together and provides it to the DI system, taking what + * we still want from the original ng-packagr library and replacing those + * where Nx takes over with Nx specific functions + */ + +import { + COMPILE_NGC_TOKEN, + COMPILE_NGC_TRANSFORM, +} from 'ng-packagr/lib/ng-package/entry-point/compile-ngc.di'; +import { + NX_WRITE_BUNDLES_TRANSFORM, + NX_WRITE_BUNDLES_TRANSFORM_TOKEN, +} from './write-bundles'; +import { + WRITE_PACKAGE_TRANSFORM, + WRITE_PACKAGE_TRANSFORM_TOKEN, +} from 'ng-packagr/lib/ng-package/entry-point/write-package.di'; +import { InjectionToken, Provider } from 'injection-js'; +import { Transform } from 'ng-packagr/lib/graph/transform'; +import { + provideTransform, + TransformProvider, +} from 'ng-packagr/lib/graph/transform.di'; +import { entryPointTransformFactory } from 'ng-packagr/lib/ng-package/entry-point/entry-point.transform'; + +export const NX_ENTRY_POINT_TRANSFORM_TOKEN = new InjectionToken( + `nx.v1.entryPointTransform` +); + +export const NX_ENTRY_POINT_TRANSFORM: TransformProvider = provideTransform({ + provide: NX_ENTRY_POINT_TRANSFORM_TOKEN, + useFactory: entryPointTransformFactory, + deps: [ + COMPILE_NGC_TOKEN, + NX_WRITE_BUNDLES_TRANSFORM_TOKEN, + WRITE_PACKAGE_TRANSFORM_TOKEN, + ], +}); + +export const NX_ENTRY_POINT_PROVIDERS: Provider[] = [ + NX_ENTRY_POINT_TRANSFORM, + COMPILE_NGC_TRANSFORM, + NX_WRITE_BUNDLES_TRANSFORM, + WRITE_PACKAGE_TRANSFORM, +]; diff --git a/packages/angular/src/builders/ng-packagr-lite/ng-packagr-adjustments/init-tsconfig.ts b/packages/angular/src/builders/ng-packagr-lite/ng-packagr-adjustments/init-tsconfig.ts new file mode 100644 index 0000000000000..ba00ede22f0a1 --- /dev/null +++ b/packages/angular/src/builders/ng-packagr-lite/ng-packagr-adjustments/init-tsconfig.ts @@ -0,0 +1,40 @@ +/** + * Adapted from the original ngPackagr source. + * + * This initialization however does not print a warning when Ivy is disabled, + * since the incremental build packages are not intended for distribution + */ + +import { msg } from 'ng-packagr/lib/utils/log'; +import { + Transform, + transformFromPromise, +} from 'ng-packagr/lib/graph/transform'; +import { EntryPointNode, isEntryPoint } from 'ng-packagr/lib/ng-package/nodes'; +import { initializeTsConfig } from 'ng-packagr/lib/ts/tsconfig'; +import { + provideTransform, + TransformProvider, +} from 'ng-packagr/lib/graph/transform.di'; +import { + DEFAULT_TS_CONFIG_TOKEN, + INIT_TS_CONFIG_TOKEN, +} from 'ng-packagr/lib/ng-package/entry-point/init-tsconfig.di'; +import { ParsedConfiguration } from '@angular/compiler-cli'; + +export const initTsConfigTransformFactory = ( + defaultTsConfig: ParsedConfiguration +): Transform => + transformFromPromise(async (graph) => { + // Initialize tsconfig for each entry point + const entryPoints = graph.filter(isEntryPoint) as EntryPointNode[]; + initializeTsConfig(defaultTsConfig, entryPoints); + + return graph; + }); + +export const NX_INIT_TS_CONFIG_TRANSFORM: TransformProvider = provideTransform({ + provide: INIT_TS_CONFIG_TOKEN, + useFactory: initTsConfigTransformFactory, + deps: [DEFAULT_TS_CONFIG_TOKEN], +}); diff --git a/packages/angular/src/builders/ng-packagr-lite/ng-packagr-adjustments/package.di.ts b/packages/angular/src/builders/ng-packagr-lite/ng-packagr-adjustments/package.di.ts new file mode 100644 index 0000000000000..51d953a385970 --- /dev/null +++ b/packages/angular/src/builders/ng-packagr-lite/ng-packagr-adjustments/package.di.ts @@ -0,0 +1,54 @@ +/** + * Adapted from the original ng-packagr + * + * Wires everything together and provides it to the DI, but exchanges the parts + * we want to implement with Nx specific functions + */ + +import { InjectionToken, Provider } from 'injection-js'; +import { Transform } from 'ng-packagr/lib/graph/transform'; +import { + provideTransform, + TransformProvider, +} from 'ng-packagr/lib/graph/transform.di'; +import { PROJECT_TOKEN } from 'ng-packagr/lib/project.di'; +import { + DEFAULT_OPTIONS_PROVIDER, + OPTIONS_TOKEN, +} from 'ng-packagr/lib/ng-package/options.di'; +import { + DEFAULT_TS_CONFIG_PROVIDER, + INIT_TS_CONFIG_TOKEN, +} from 'ng-packagr/lib/ng-package/entry-point/init-tsconfig.di'; +import { + ANALYSE_SOURCES_TOKEN, + ANALYSE_SOURCES_TRANSFORM, +} from 'ng-packagr/lib/ng-package/entry-point/analyse-sources.di'; +import { NX_INIT_TS_CONFIG_TRANSFORM } from './init-tsconfig'; +import { NX_ENTRY_POINT_TRANSFORM_TOKEN } from './entry-point.di'; +import { PACKAGE_TRANSFORM } from 'ng-packagr/lib/ng-package/package.di'; +import { packageTransformFactory } from 'ng-packagr/lib/ng-package/package.transform'; + +export const PACKAGE_TRANSFORM_TOKEN = new InjectionToken( + `nx.v1.packageTransform` +); + +export const NX_PACKAGE_TRANSFORM: TransformProvider = provideTransform({ + provide: PACKAGE_TRANSFORM_TOKEN, + useFactory: packageTransformFactory, + deps: [ + PROJECT_TOKEN, + OPTIONS_TOKEN, + INIT_TS_CONFIG_TOKEN, + ANALYSE_SOURCES_TOKEN, + NX_ENTRY_POINT_TRANSFORM_TOKEN, + ], +}); + +export const NX_PACKAGE_PROVIDERS: Provider[] = [ + NX_PACKAGE_TRANSFORM, + DEFAULT_OPTIONS_PROVIDER, + DEFAULT_TS_CONFIG_PROVIDER, + NX_INIT_TS_CONFIG_TRANSFORM, + ANALYSE_SOURCES_TRANSFORM, +]; diff --git a/packages/angular/src/builders/ng-packagr-lite/ng-packagr-adjustments/write-bundles.ts b/packages/angular/src/builders/ng-packagr-lite/ng-packagr-adjustments/write-bundles.ts new file mode 100644 index 0000000000000..eb09662a2d560 --- /dev/null +++ b/packages/angular/src/builders/ng-packagr-lite/ng-packagr-adjustments/write-bundles.ts @@ -0,0 +1,121 @@ +/** + * Adapted from original ng-packagr + * + * Exclude the UMD bundling and minification + * which is not needed for incremental compilation + */ + +import { InjectionToken } from 'injection-js'; +import { + Transform, + transformFromPromise, +} from 'ng-packagr/lib/graph/transform'; +import { + provideTransform, + TransformProvider, +} from 'ng-packagr/lib/graph/transform.di'; +import { + EntryPointNode, + isEntryPoint, + isEntryPointInProgress, +} from 'ng-packagr/lib/ng-package/nodes'; +import { NgEntryPoint } from 'ng-packagr/lib/ng-package/entry-point/entry-point'; +import { rollupBundleFile } from 'ng-packagr/lib/flatten/rollup'; +import * as log from 'ng-packagr/lib/utils/log'; +import { DependencyList } from 'ng-packagr/lib/flatten/external-module-id-strategy'; +import { BuildGraph } from 'ng-packagr/lib/graph/build-graph'; +import { unique } from 'ng-packagr/lib/utils/array'; + +export const nxWriteBundlesTransform: Transform = transformFromPromise( + async (graph) => { + const entryPoint = graph.find(isEntryPointInProgress()) as EntryPointNode; + const { + destinationFiles, + entryPoint: ngEntryPoint, + tsConfig, + } = entryPoint.data; + const cache = entryPoint.cache; + + // Add UMD module IDs for dependencies + const dependencyUmdIds = entryPoint + .filter(isEntryPoint) + .map((ep) => ep.data.entryPoint) + .reduce((prev, ep: NgEntryPoint) => { + prev[ep.moduleId] = ep.umdId; + + return prev; + }, {}); + + const { fesm2015, esm2015 } = destinationFiles; + + const opts = { + sourceRoot: tsConfig.options.sourceRoot, + amd: { id: ngEntryPoint.amdId }, + umdModuleIds: { + ...ngEntryPoint.umdModuleIds, + ...dependencyUmdIds, + }, + entry: esm2015, + dependencyList: getDependencyListForGraph(graph), + }; + + log.info('Bundling to FESM2015'); + // @ts-ignore + cache.rollupFESMCache = await rollupBundleFile({ + ...opts, + moduleName: ngEntryPoint.moduleId, + format: 'es', + dest: fesm2015, + // @ts-ignore + cache: cache.rollupFESMCache, + }); + } +); + +/** Get all list of dependencies for the entire 'BuildGraph' */ +function getDependencyListForGraph(graph: BuildGraph): DependencyList { + // We need to do this because if A dependency on bundled B + // And A has a secondary entry point A/1 we want only to bundle B if it's used. + // Also if A/1 depends on A we don't want to bundle A thus we mark this a dependency. + + const dependencyList: DependencyList = { + dependencies: [], + bundledDependencies: [], + }; + + for (const entry of graph.filter(isEntryPoint)) { + const { + bundledDependencies = [], + dependencies = {}, + peerDependencies = {}, + } = entry.data.entryPoint.packageJson; + dependencyList.bundledDependencies = unique( + dependencyList.bundledDependencies.concat(bundledDependencies) + ); + dependencyList.dependencies = unique( + dependencyList.dependencies.concat( + Object.keys(dependencies), + Object.keys(peerDependencies), + entry.data.entryPoint.moduleId + ) + ); + } + + if (dependencyList.bundledDependencies.length) { + log.warn( + `Inlining of 'bundledDependencies' has been deprecated in version 5 and will be removed in future versions.` + + '\n' + + `List the dependency in the 'peerDependencies' section instead.` + ); + } + + return dependencyList; +} + +export const NX_WRITE_BUNDLES_TRANSFORM_TOKEN = new InjectionToken( + `nx.v1.writeBundlesTransform` +); +export const NX_WRITE_BUNDLES_TRANSFORM: TransformProvider = provideTransform({ + provide: NX_WRITE_BUNDLES_TRANSFORM_TOKEN, + useFactory: () => nxWriteBundlesTransform, +}); diff --git a/packages/angular/src/builders/ng-packagr-lite/ng-packagr-lite.impl.ts b/packages/angular/src/builders/ng-packagr-lite/ng-packagr-lite.impl.ts new file mode 100644 index 0000000000000..8b9e98ee6b123 --- /dev/null +++ b/packages/angular/src/builders/ng-packagr-lite/ng-packagr-lite.impl.ts @@ -0,0 +1,47 @@ +import { BuilderContext, createBuilder } from '@angular-devkit/architect'; +import { JsonObject } from '@angular-devkit/core'; +import * as ng from '@angular/compiler-cli'; +import { resolve } from 'path'; +import { + DependentBuildableProjectNode, + updatePaths, +} from '@nrwl/workspace/src/utils/buildable-libs-utils'; +import { + NX_PACKAGE_PROVIDERS, + NX_PACKAGE_TRANSFORM, +} from './ng-packagr-adjustments/package.di'; +import { NgPackagr } from 'ng-packagr'; +import { NX_ENTRY_POINT_PROVIDERS } from './ng-packagr-adjustments/entry-point.di'; +import { + BuildAngularLibraryBuilderOptions, + createLibraryBuilder, +} from '../package/package.impl'; + +async function initializeNgPackgrLite( + options: BuildAngularLibraryBuilderOptions & JsonObject, + context: BuilderContext, + projectDependencies: DependentBuildableProjectNode[] +): Promise { + // const packager = (await import('ng-packagr')).ngPackagr(); + const packager = new NgPackagr([ + // Add default providers to this list. + ...NX_PACKAGE_PROVIDERS, + ...NX_ENTRY_POINT_PROVIDERS, + ]); + packager.forProject(resolve(context.workspaceRoot, options.project)); + packager.withBuildTransform(NX_PACKAGE_TRANSFORM.provide); + + if (options.tsConfig) { + // read the tsconfig and modify its path in memory to + // pass it on to ngpackagr + const parsedTSConfig = ng.readConfiguration(options.tsConfig); + updatePaths(projectDependencies, parsedTSConfig.options.paths); + packager.withTsConfig(parsedTSConfig); + } + + return packager; +} + +export default createBuilder & any>( + createLibraryBuilder(initializeNgPackgrLite) +); diff --git a/packages/angular/src/builders/ng-packagr-lite/schema.json b/packages/angular/src/builders/ng-packagr-lite/schema.json new file mode 100644 index 0000000000000..e334ee9151d10 --- /dev/null +++ b/packages/angular/src/builders/ng-packagr-lite/schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "title": "ng-packagr Target", + "description": "ng-packagr target options for Build Architect. Use to build library projects.", + "type": "object", + "properties": { + "project": { + "type": "string", + "description": "The file path for the ng-packagr configuration file, relative to the current workspace." + }, + "tsConfig": { + "type": "string", + "description": "The full path for the TypeScript configuration file, relative to the current workspace." + }, + "watch": { + "type": "boolean", + "description": "Run build when files change.", + "default": false + }, + "updateBuildableProjectDepsInPackageJson": { + "type": "boolean", + "description": "Update buildable project dependencies in package.json", + "default": true + } + }, + "additionalProperties": false, + "required": ["project"] +} diff --git a/packages/angular/src/builders/package/package.impl.ts b/packages/angular/src/builders/package/package.impl.ts index c7a6d2fe3d544..5f4f8f87e424e 100644 --- a/packages/angular/src/builders/package/package.impl.ts +++ b/packages/angular/src/builders/package/package.impl.ts @@ -54,43 +54,57 @@ async function initializeNgPackagr( return packager; } -export function run( - options: BuildAngularLibraryBuilderOptions & JsonObject, - context: BuilderContext -): Observable { - const projGraph = createProjectGraph(); - const { target, dependencies } = calculateProjectDependencies( - projGraph, - context - ); - return of(checkDependentProjectsHaveBeenBuilt(context, dependencies)).pipe( - switchMap((result) => { - if (result) { - return from(initializeNgPackagr(options, context, dependencies)).pipe( - switchMap((packager) => - options.watch ? packager.watch() : packager.build() - ), - tap(() => { - if ( - dependencies.length > 0 && - options.updateBuildableProjectDepsInPackageJson - ) { - updateBuildableProjectPackageJsonDependencies( - context, - target, - dependencies, - options.buildableProjectDepsInPackageJsonType - ); - } - }), - mapTo({ success: true }) - ); - } else { - // just pass on the result - return of({ success: false }); - } - }) - ); +/** + * Creates a builder function that executes the library build of an Angular + * package using ng-packagr + * @param initializeNgPackagr function that returns an ngPackagr instance to use for the build + */ +export function createLibraryBuilder( + initializeNgPackagr: ( + options: BuildAngularLibraryBuilderOptions & JsonObject, + context: BuilderContext, + projectDependencies: DependentBuildableProjectNode[] + ) => Promise +) { + return function run( + options: BuildAngularLibraryBuilderOptions & JsonObject, + context: BuilderContext + ): Observable { + const projGraph = createProjectGraph(); + const { target, dependencies } = calculateProjectDependencies( + projGraph, + context + ); + return of(checkDependentProjectsHaveBeenBuilt(context, dependencies)).pipe( + switchMap((result) => { + if (result) { + return from(initializeNgPackagr(options, context, dependencies)).pipe( + switchMap((packager) => + options.watch ? packager.watch() : packager.build() + ), + tap(() => { + if ( + dependencies.length > 0 && + options.updateBuildableProjectDepsInPackageJson + ) { + updateBuildableProjectPackageJsonDependencies( + context, + target, + dependencies + ); + } + }), + mapTo({ success: true }) + ); + } else { + // just pass on the result + return of({ success: false }); + } + }) + ); + }; } -export default createBuilder & any>(run); +export default createBuilder & any>( + createLibraryBuilder(initializeNgPackagr) +); diff --git a/packages/angular/src/builders/package/schema.json b/packages/angular/src/builders/package/schema.json index 51de68fb75f15..6d6053dae64a5 100644 --- a/packages/angular/src/builders/package/schema.json +++ b/packages/angular/src/builders/package/schema.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema", "title": "ng-packagr Target", - "description": "ng-packagr target options for Build Architect. Use to build library projects.", + "description": "ng-packagr target options for Build Architect. Use to build and package library projects for publishing.", "type": "object", "properties": { "project": { diff --git a/packages/angular/src/schematics/library/lib/update-project.ts b/packages/angular/src/schematics/library/lib/update-project.ts index a1ea4f977eb53..0f347aaf50d5e 100644 --- a/packages/angular/src/schematics/library/lib/update-project.ts +++ b/packages/angular/src/schematics/library/lib/update-project.ts @@ -143,8 +143,14 @@ export function updateProject(options: NormalizedSchema): Rule { if (!options.publishable && !options.buildable) { delete fixedProject.architect.build; } else { - // adjust the builder path to our custom one - fixedProject.architect.build.builder = '@nrwl/angular:package'; + if (options.publishable) { + // adjust the builder path to our custom one + fixedProject.architect.build.builder = '@nrwl/angular:package'; + } else { + // adjust the builder path to our custom one + fixedProject.architect.build.builder = + '@nrwl/angular:ng-packagr-lite'; + } } delete fixedProject.architect.test; diff --git a/yarn.lock b/yarn.lock index e3a05457b9fa9..294d711923fe3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13031,7 +13031,7 @@ ini@1.3.5, ini@^1.3.2, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== -injection-js@^2.2.1: +injection-js@^2.2.1, injection-js@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/injection-js/-/injection-js-2.3.1.tgz#c6b7594ab0bd5da73eb8ea246fe19d98d74508cc" integrity sha512-t+kpDAOL/DUZ68JncAhsb8C91qhJ6dXRMcOuvJfNA7sp63etdiQe6KQoxE/nZ5b2eTi0TQX6OothOCm89cLAJQ== @@ -19935,14 +19935,14 @@ rxjs@6.5.5: dependencies: tslib "^1.9.0" -rxjs@6.6.2: +rxjs@6.6.2, rxjs@^6.5.0: version "6.6.2" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.2.tgz#8096a7ac03f2cc4fe5860ef6e572810d9e01c0d2" integrity sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg== dependencies: tslib "^1.9.0" -rxjs@^6.1.0, rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.5.0, rxjs@^6.5.4, rxjs@^6.6.0: +rxjs@^6.1.0, rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.5.4, rxjs@^6.6.0: version "6.6.0" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.0.tgz#af2901eedf02e3a83ffa7f886240ff9018bbec84" integrity sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==