Skip to content

Commit

Permalink
feat: enable tsconfig customization thru the programmatic API (#517)
Browse files Browse the repository at this point in the history
Closes #256
  • Loading branch information
dherges authored Jan 20, 2018
1 parent 542aed2 commit 8b04d44
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 97 deletions.
2 changes: 1 addition & 1 deletion src/lib/ng-package-format/artefacts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as path from 'path';
import * as ts from 'typescript';
import { TsConfig } from '../steps/ngc';
import { TsConfig } from '../steps/ngc-tsconfig';
import { NgEntryPoint } from './entry-point';
import { NgPackage } from './package';

Expand Down
26 changes: 22 additions & 4 deletions src/lib/ng-v5/packagr.spec.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,38 @@
import * as ng from '@angular/compiler-cli';
import { expect } from 'chai';
import { provideProject, PROJECT_TOKEN, ngPackagr, NgPackagr } from './packagr';
import { DEFAULT_TS_CONFIG_TOKEN } from '../steps/ngc-tsconfig';

describe(`ngPackagr()`, () => {

it(`should return a NgPackagr instance`, () => {
const foo = ngPackagr();
expect(foo).to.be.an.instanceOf(NgPackagr);
});

xit(`should return something with pre-configured defaults`, () => {
// TODO
it(`should have a default tsconfig`, () => {
const toBeTested = ngPackagr();
const defaultTsConfigProvider = toBeTested['providers'].filter(p => (p as any).provide === DEFAULT_TS_CONFIG_TOKEN);
expect(defaultTsConfigProvider).to.have.length(1);
});

describe(`withTsConfig()`, () => {
it(`should return self instance for chaining`, () => {
const toBeTested = ngPackagr();
const mockConfig = ('foo' as any) as ng.ParsedConfiguration;
expect(toBeTested.withTsConfig(mockConfig)).to.equal(toBeTested);
});
it(`should override the default tsconfig provider`, () => {
const mockConfig = ('foo' as any) as ng.ParsedConfiguration;
const toBeTested = ngPackagr().withTsConfig(mockConfig);
const tsConfigProviders = toBeTested['providers'].filter(p => (p as any).provide === DEFAULT_TS_CONFIG_TOKEN);

expect(tsConfigProviders).to.have.length(2);
expect((tsConfigProviders[1] as any).useValue).to.equal('foo');
});
});
});

describe(`provideProject()`, () => {

it(`should return the ValueProvider`, () => {
const provider = provideProject('foo');
expect(provider.provide).to.equal(PROJECT_TOKEN);
Expand Down
43 changes: 30 additions & 13 deletions src/lib/ng-v5/packagr.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
import { InjectionToken, Provider, ReflectiveInjector, ValueProvider } from 'injection-js';
import { buildNgPackage } from '../steps/build-ng-package';
import { BUILD_NG_PACKAGE_TOKEN, BUILD_NG_PACKAGE_PROVIDER, BuildCallSignature } from '../steps/build-ng-package';
import {
TsConfig,
DEFAULT_TS_CONFIG_PROVIDER,
DEFAULT_TS_CONFIG_TOKEN,
PREPARE_TS_CONFIG_PROVIDER
} from '../steps/ngc-tsconfig';
import { ENTRY_POINT_TRANSFORMS_PROVIDER } from '../steps/entry-point-transforms';

export class NgPackagr {

constructor(
private providers: Provider[]
) {}
constructor(private providers: Provider[]) {}

public withProviders(providers: Provider[]): NgPackagr {
this.providers = [
...this.providers,
...providers
];
this.providers = [...this.providers, ...providers];

return this;
}

/** Overwrites the default TypeScript configuration. */
public withTsConfig(defaultValues: TsConfig): NgPackagr {
this.providers.push({
provide: DEFAULT_TS_CONFIG_TOKEN,
useValue: defaultValues
});

return this;
}
Expand All @@ -20,14 +31,20 @@ export class NgPackagr {
const injector = ReflectiveInjector.resolveAndCreate(this.providers);
const project = injector.get(PROJECT_TOKEN);

const buildNgPackage: BuildCallSignature = injector.get(BUILD_NG_PACKAGE_TOKEN);

return buildNgPackage({ project });
}

}

export const ngPackagr = (): NgPackagr => new NgPackagr([
// TODO: default providers come here
]);
export const ngPackagr = (): NgPackagr =>
new NgPackagr([
// Add default providers to this list.
BUILD_NG_PACKAGE_PROVIDER,
ENTRY_POINT_TRANSFORMS_PROVIDER,
DEFAULT_TS_CONFIG_PROVIDER,
PREPARE_TS_CONFIG_PROVIDER
]);

export const PROJECT_TOKEN = new InjectionToken<string>('ng.v5.project');

Expand Down
86 changes: 51 additions & 35 deletions src/lib/steps/build-ng-package.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,64 @@
import { InjectionToken, FactoryProvider } from 'injection-js';
import * as path from 'path';
import { CliArguments } from '../commands/build.command';
import { NgArtefacts } from '../ng-package-format/artefacts';
import { NgPackage } from '../ng-package-format/package';
import { copyFiles } from '../util/copy';
import * as log from '../util/log';
import { rimraf } from '../util/rimraf';
import { BuildStep } from '../deprecations';
import { discoverPackages } from './init';
import { transformSources } from './entry-point-transforms';
import { ENTRY_POINT_TRANSFORMS_TOKEN } from './entry-point-transforms';

// XX: should eventually become a BuildStep
export async function buildNgPackage(opts: CliArguments): Promise<void> {
log.info(`Building Angular Package`);

let ngPackage: NgPackage;
try {
// READ `NgPackage` from either 'package.json', 'ng-package.json', or 'ng-package.js'
ngPackage = await discoverPackages(opts);

// clean the primary dest folder (should clean all secondary module directories as well)
await rimraf(ngPackage.dest);

const artefacts = new NgArtefacts(ngPackage.primary, ngPackage);
await transformSources({ artefacts, entryPoint: ngPackage.primary, pkg: ngPackage });
for (const secondary of ngPackage.secondaries) {
const artefacts = new NgArtefacts(secondary, ngPackage);
await transformSources({ artefacts, entryPoint: secondary, pkg: ngPackage });
}
export function buildNgPackageFactory(entryPointTransforms: BuildStep) {

await copyFiles(`${ngPackage.src}/README.md`, ngPackage.dest);
await copyFiles(`${ngPackage.src}/LICENSE`, ngPackage.dest);

// clean the working directory for a successful build only
await rimraf(ngPackage.workingDirectory);
log.success(`Built Angular Package!
- from: ${ngPackage.src}
- to: ${ngPackage.dest}
`);
} catch (error) {
// Report error messages and throw the error further up
log.error(error);
if (ngPackage) {
log.info(`Build failed. The working directory was not pruned. Files are stored at ${ngPackage.workingDirectory}.`);
}
return async function buildNgPackage(opts: CliArguments): Promise<void> {
log.info(`Building Angular Package`);

let ngPackage: NgPackage;
try {
// READ `NgPackage` from either 'package.json', 'ng-package.json', or 'ng-package.js'
ngPackage = await discoverPackages(opts);

// clean the primary dest folder (should clean all secondary module directories as well)
await rimraf(ngPackage.dest);

// Sequentially build entry points
const entryPoints = [ ngPackage.primary, ...ngPackage.secondaries ];
for (const entryPoint of entryPoints) {
// Prepare artefacts. Will be populated by the entry point transformations
const artefacts = new NgArtefacts(ngPackage.primary, ngPackage);
await entryPointTransforms({ artefacts, entryPoint, pkg: ngPackage });
}

await copyFiles(`${ngPackage.src}/README.md`, ngPackage.dest);
await copyFiles(`${ngPackage.src}/LICENSE`, ngPackage.dest);

throw error;
}
// clean the working directory for a successful build only
await rimraf(ngPackage.workingDirectory);
log.success(`Built Angular Package!
- from: ${ngPackage.src}
- to: ${ngPackage.dest}
`);
} catch (error) {
// Report error messages and throw the error further up
log.error(error);
if (ngPackage) {
log.info(`Build failed. The working directory was not pruned. Files are stored at ${ngPackage.workingDirectory}.`);
}

throw error;
}
};
}

export type BuildCallSignature = (opts: CliArguments) => Promise<void>;

export const BUILD_NG_PACKAGE_TOKEN = new InjectionToken<BuildCallSignature>('ng.v5.buildNgPackage');

export const BUILD_NG_PACKAGE_PROVIDER: FactoryProvider = {
provide: BUILD_NG_PACKAGE_TOKEN,
useFactory: buildNgPackageFactory,
deps: [ ENTRY_POINT_TRANSFORMS_TOKEN ]
};
17 changes: 14 additions & 3 deletions src/lib/steps/entry-point-transforms.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import * as path from 'path';
import { InjectionToken, FactoryProvider } from 'injection-js';
import { NgArtefacts } from '../ng-package-format/artefacts';
import { NgEntryPoint } from '../ng-package-format/entry-point';
import { NgPackage } from '../ng-package-format/package';
import { BuildStep } from '../deprecations';
import { writePackage } from '../steps/package';
import { processAssets } from '../steps/assets';
import { ngc, prepareTsConfig, collectTemplateAndStylesheetFiles, inlineTemplatesAndStyles } from '../steps/ngc';
import { ngc, collectTemplateAndStylesheetFiles, inlineTemplatesAndStyles } from '../steps/ngc';
import { minifyJsFile } from '../steps/uglify';
import { remapSourceMap, relocateSourceMapSources } from '../steps/sorcery';
import { flattenToFesm15, flattenToUmd } from '../steps/rollup';
Expand All @@ -14,14 +15,15 @@ import { copySourceFilesToDestination } from '../steps/transfer';
import * as log from '../util/log';
import { ensureUnixPath } from '../util/path';
import { rimraf } from '../util/rimraf';
import { PREPARE_TS_CONFIG_TOKEN } from './ngc-tsconfig';

/**
* Transforms TypeScript source files to Angular Package Format.
*
* @param entryPoint The entry point that will be transpiled to a set of artefacts.
*/
export const transformSources: BuildStep =
async (args): Promise<void> => {
export function transformSourcesFactory(prepareTsConfig: BuildStep) {
return async (args): Promise<void> => {
const { artefacts, entryPoint, pkg } = args;
log.info(`Building from sources for entry point '${entryPoint.moduleId}'`);

Expand Down Expand Up @@ -98,3 +100,12 @@ export const transformSources: BuildStep =

log.success(`Built ${entryPoint.moduleId}`);
}
}

export const ENTRY_POINT_TRANSFORMS_TOKEN = new InjectionToken<BuildStep>('ng.v5.entryPointTransforms');

export const ENTRY_POINT_TRANSFORMS_PROVIDER: FactoryProvider = {
provide: ENTRY_POINT_TRANSFORMS_TOKEN,
useFactory: transformSourcesFactory,
deps: [ PREPARE_TS_CONFIG_TOKEN ]
};
80 changes: 80 additions & 0 deletions src/lib/steps/ngc-tsconfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import * as ng from '@angular/compiler-cli';
// XX: has or is using name 'ParsedConfiguration' ... but cannot be named
import { ParsedConfiguration } from '@angular/compiler-cli';
import { InjectionToken, FactoryProvider } from 'injection-js';
import * as path from 'path';
import * as ts from 'typescript';
import { BuildStep } from '../deprecations';

/**
* TypeScript configuration used internally (marker typer).
*/
export type TsConfig = ng.ParsedConfiguration;

/**
* Reads the default TypeScript configuration.
*/
export function defaultTsConfigFactory() {
return ng.readConfiguration(path.resolve(__dirname, '..', 'conf', 'tsconfig.ngc.json'));
}

export const DEFAULT_TS_CONFIG_TOKEN = new InjectionToken<TsConfig>('ng.v5.defaultTsConfig');

export const DEFAULT_TS_CONFIG_PROVIDER: FactoryProvider = {
provide: DEFAULT_TS_CONFIG_TOKEN,
useFactory: defaultTsConfigFactory,
deps: []
};

/**
* Prepares TypeScript Compiler and Angular Compiler options by overriding the default config
* with entry point-specific values.
*/
export const prepareTsConfigFactory: (def: TsConfig) => BuildStep = defaultTsConfig => ({
artefacts,
entryPoint,
pkg
}) => {
const basePath = path.dirname(entryPoint.entryFilePath);

// Resolve defaults from DI token
const tsConfig = { ...defaultTsConfig };

tsConfig.rootNames = [entryPoint.entryFilePath];
tsConfig.options.flatModuleId = entryPoint.moduleId;
tsConfig.options.flatModuleOutFile = `${entryPoint.flatModuleFile}.js`;
tsConfig.options.basePath = basePath;
tsConfig.options.baseUrl = basePath;
tsConfig.options.rootDir = basePath;
tsConfig.options.outDir = artefacts.outDir;
tsConfig.options.genDir = artefacts.outDir;

if (entryPoint.languageLevel) {
// ng.readConfiguration implicitly converts "es6" to "lib.es6.d.ts", etc.
tsConfig.options.lib = entryPoint.languageLevel.map(lib => `lib.${lib}.d.ts`);
}

switch (entryPoint.jsxConfig) {
case 'preserve':
tsConfig.options.jsx = ts.JsxEmit.Preserve;
break;
case 'react':
tsConfig.options.jsx = ts.JsxEmit.React;
break;
case 'react-native':
tsConfig.options.jsx = ts.JsxEmit.ReactNative;
break;
default:
break;
}

artefacts.tsConfig = tsConfig;
};

export const PREPARE_TS_CONFIG_TOKEN = new InjectionToken<BuildStep>('ng.v5.prepareTsConfig');

export const PREPARE_TS_CONFIG_PROVIDER: FactoryProvider = {
provide: PREPARE_TS_CONFIG_TOKEN,
useFactory: prepareTsConfigFactory,
deps: [DEFAULT_TS_CONFIG_TOKEN]
};
42 changes: 1 addition & 41 deletions src/lib/steps/ngc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,47 +14,7 @@ import * as log from '../util/log';
// ... @link https://github.com/angular/angular/blob/24bf3e2a251634811096b939e61d63297934579e/packages/compiler-cli/src/main.ts#L36-L38
import { createEmitCallback } from '../util/ngc-patches';
import { componentTransformer } from '../util/ts-transformers';

/** TypeScript configuration used internally (marker typer). */
export type TsConfig = ng.ParsedConfiguration;

/** Prepares TypeScript Compiler and Angular Compiler option. */
export const prepareTsConfig: BuildStep =
({ artefacts, entryPoint, pkg }) => {
const basePath = path.dirname(entryPoint.entryFilePath);

// Read the default configuration and overwrite package-specific options
const tsConfig = ng.readConfiguration(path.resolve(__dirname, '..', 'conf', 'tsconfig.ngc.json'));
tsConfig.rootNames = [ entryPoint.entryFilePath ];
tsConfig.options.flatModuleId = entryPoint.moduleId;
tsConfig.options.flatModuleOutFile = `${entryPoint.flatModuleFile}.js`;
tsConfig.options.basePath = basePath;
tsConfig.options.baseUrl = basePath;
tsConfig.options.rootDir = basePath;
tsConfig.options.outDir = artefacts.outDir;
tsConfig.options.genDir = artefacts.outDir;

if (entryPoint.languageLevel) {
// ng.readConfiguration implicitly converts "es6" to "lib.es6.d.ts", etc.
tsConfig.options.lib = entryPoint.languageLevel.map(lib => `lib.${lib}.d.ts`);
}

switch (entryPoint.jsxConfig) {
case 'preserve':
tsConfig.options.jsx = ts.JsxEmit.Preserve;
break;
case 'react':
tsConfig.options.jsx = ts.JsxEmit.React;
break;
case 'react-native':
tsConfig.options.jsx = ts.JsxEmit.ReactNative;
break;
default:
break;
}

artefacts.tsConfig = tsConfig;
}
import { TsConfig } from './ngc-tsconfig';

/** Transforms TypeScript AST */
const transformSources =
Expand Down

0 comments on commit 8b04d44

Please sign in to comment.