Skip to content

Commit

Permalink
feat: downlevel es2020 bundle to generate FESM2015
Browse files Browse the repository at this point in the history
  • Loading branch information
alan-agius4 committed Sep 2, 2021
1 parent 61cd015 commit 6cf2514
Show file tree
Hide file tree
Showing 15 changed files with 113 additions and 65 deletions.
4 changes: 2 additions & 2 deletions integration/samples/apf/specs/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ describe('@sample/apf', () => {
});

describe('dist', () => {
it('should contain a total of 62 files', () => {
it('should contain a total of 71 files', () => {
// this is a safe guard / alternative to snapshots in order to
// protect ourselves from doing a change that will emit unexpected files.
const files = glob.sync(path.join(DIST, '**/*'));
expect(files.length).to.equals(62);
expect(files.length).to.equals(71);
});

it(`should contain a README.md file`, () => {
Expand Down
10 changes: 6 additions & 4 deletions integration/samples/apf/specs/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe(`@sample/apf`, () => {
});

Object.entries({
module: 'fesm2020/sample-apf.js',
module: 'fesm2015/sample-apf.js',
es2020: 'fesm2020/sample-apf.js',
esm2020: 'esm2020/sample-apf.js',
fesm2020: 'fesm2020/sample-apf.js',
Expand Down Expand Up @@ -71,7 +71,8 @@ describe(`@sample/apf`, () => {
});

Object.entries({
module: '../fesm2020/sample-apf-secondary.js',
module: '../fesm2015/sample-apf-secondary.js',
fesm2015: '../fesm2015/sample-apf-secondary.js',
es2020: '../fesm2020/sample-apf-secondary.js',
esm2020: '../esm2020/secondary/sample-apf-secondary.js',
fesm2020: '../fesm2020/sample-apf-secondary.js',
Expand Down Expand Up @@ -114,7 +115,8 @@ describe(`@sample/apf`, () => {
});

Object.entries({
module: '../../fesm2020/sample-apf-secondary-testing.js',
module: '../../fesm2015/sample-apf-secondary-testing.js',
fesm2015: '../../fesm2015/sample-apf-secondary-testing.js',
es2020: '../../fesm2020/sample-apf-secondary-testing.js',
esm2020: '../../esm2020/secondary/testing/sample-apf-secondary-testing.js',
fesm2020: '../../fesm2020/sample-apf-secondary-testing.js',
Expand Down Expand Up @@ -157,7 +159,7 @@ describe(`@sample/apf`, () => {
});

Object.entries({
module: '../fesm2020/sample-apf-testing.js',
module: '../fesm2015/sample-apf-testing.js',
es2020: '../fesm2020/sample-apf-testing.js',
esm2020: '../esm2020/testing/sample-apf-testing.js',
fesm2020: '../fesm2020/sample-apf-testing.js',
Expand Down
4 changes: 2 additions & 2 deletions integration/samples/core/specs/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ describe(`@sample/core`, () => {
expect(PACKAGE['name']).to.equal('@sample/core');
});

it(`should reference "module" bundle (FESM2020)`, () => {
expect(PACKAGE['module']).to.equal('fesm2020/sample-core.js');
it(`should reference "module" bundle (FESM2015)`, () => {
expect(PACKAGE['module']).to.equal('fesm2015/sample-core.js');
});

it(`should reference "es2020" bundle (FESM2020)`, () => {
Expand Down
4 changes: 4 additions & 0 deletions integration/samples/core/src/angular.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ export class AngularService {
return [...iterable];
}
}

export async function foo(): Promise<void> {
return undefined;
}
4 changes: 2 additions & 2 deletions integration/samples/material/specs/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ describe(`@sample/material`, () => {
expect(PACKAGE['name']).to.equal('@sample/material');
});

it(`should reference "module" bundle (FESM2020)`, () => {
expect(PACKAGE['module']).to.equal('fesm2020/sample-material.js');
it(`should reference "module" bundle (FESM2015)`, () => {
expect(PACKAGE['module']).to.equal('fesm2015/sample-material.js');
});

it(`should reference "es2020" bundle (FESM2020)`, () => {
Expand Down
4 changes: 2 additions & 2 deletions integration/samples/secondary/specs/ep-feature-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ describe(`@sample/secondary/feature-d`, () => {
expect(PACKAGE['name']).to.equal('@sample/secondary/feature-d');
});

it(`should reference "module" bundle (fesm2020)`, () => {
expect(PACKAGE['module']).to.equal('../fesm2020/sample-secondary-feature-d.js');
it(`should reference "fesm2020" bundle (fesm2020)`, () => {
expect(PACKAGE['fesm2020']).to.equal('../fesm2020/sample-secondary-feature-d.js');
});

it(`should reference "typings" files`, () => {
Expand Down
25 changes: 19 additions & 6 deletions src/lib/flatten/downlevel-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import { TransformResult } from 'rollup';
import * as path from 'path';

import * as log from '../utils/log';
import { generateKey, readCacheEntry, saveCacheEntry } from '../utils/cache';

/**
* Base `tsc` `CompilerOptions` shared among various downleveling methods.
*/
const COMPILER_OPTIONS: CompilerOptions = {
target: ScriptTarget.ES5,
module: ModuleKind.ES2015,
target: ScriptTarget.ES2015,
module: ModuleKind.ESNext,
allowJs: true,
sourceMap: true,
importHelpers: true,
Expand All @@ -18,12 +19,16 @@ const COMPILER_OPTIONS: CompilerOptions = {
};

/**
* Downlevels a .js file from `ES2015` to `ES5`. Internally, uses `tsc`.
*
* Required for some external as they contains `ES2015` syntax such as `const` and `let`
* Downlevels a .js file from `ES2015` to `ES2015`. Internally, uses `tsc`.
*/
export function downlevelCodeWithTsc(code: string, filePath: string): TransformResult {
export async function downlevelCodeWithTsc(code: string, filePath: string): Promise<TransformResult> {
log.debug(`tsc ${filePath}`);
const key = generateKey(code);

const result = await readCacheEntry(key);
if (result) {
return result;
}

const compilerOptions: CompilerOptions = {
...COMPILER_OPTIONS,
Expand All @@ -34,6 +39,14 @@ export function downlevelCodeWithTsc(code: string, filePath: string): TransformR
compilerOptions,
});

saveCacheEntry(
key,
JSON.stringify({
code: outputText,
map: sourceMapText,
}),
);

return {
code: outputText,
map: JSON.parse(sourceMapText),
Expand Down
6 changes: 3 additions & 3 deletions src/lib/flatten/rollup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import * as path from 'path';
export interface RollupOptions {
moduleName: string;
entry: string;
format: rollup.ModuleFormat;
dest: string;
sourceRoot: string;
transform?: TransformHook;
Expand All @@ -24,7 +23,7 @@ export interface RollupOptions {

/** Runs rollup over the given entry file, writes a bundle file. */
export async function rollupBundleFile(opts: RollupOptions): Promise<rollup.RollupCache> {
log.debug(`rollup (v${rollup.VERSION}) ${opts.entry} to ${opts.dest} (${opts.format})`);
log.debug(`rollup (v${rollup.VERSION}) ${opts.entry} to ${opts.dest}`);

// Create the bundle
const bundle = await rollup.rollup({
Expand Down Expand Up @@ -63,8 +62,9 @@ export async function rollupBundleFile(opts: RollupOptions): Promise<rollup.Roll

// Output the bundle to disk
await bundle.write({

name: opts.moduleName,
format: opts.format,
format: 'es',
file: opts.dest,
banner: '',
sourcemap: true,
Expand Down
4 changes: 2 additions & 2 deletions src/lib/ng-package/entry-point/entry-point.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe(`NgEntryPoint`, () => {
expect(primaryEntryPoint.entryFilePath).to.equal(resolve(testSourcePath, 'public-api.ts'));
expect(primaryEntryPoint.isSecondaryEntryPoint).to.be.false;
expect(primaryEntryPoint.destinationPath).to.equal(resolve(testDestinationPath));
expect(Object.keys(primaryEntryPoint.destinationFiles).length).to.equal(3);
expect(Object.keys(primaryEntryPoint.destinationFiles).length).to.equal(4);
expect(primaryEntryPoint.entryFile).to.equal('public-api.ts');
expect(primaryEntryPoint.cssUrl).to.be.undefined; // TODO: should default to 'inline'.
expect(primaryEntryPoint.flatModuleFile).to.equal('example-test-entry-point');
Expand Down Expand Up @@ -104,7 +104,7 @@ describe(`NgEntryPoint`, () => {
expect(secondaryEntryPoint.entryFilePath).to.equal(resolve(secondaryEntryPointBasePath, 'public-api.ts'));
expect(secondaryEntryPoint.isSecondaryEntryPoint).to.be.true;
expect(secondaryEntryPoint.destinationPath).to.equal(resolve(secondaryData.destinationPath));
expect(Object.keys(secondaryEntryPoint.destinationFiles).length).to.equal(3);
expect(Object.keys(secondaryEntryPoint.destinationFiles).length).to.equal(4);
expect(secondaryEntryPoint.entryFile).to.equal('public-api.ts');
expect(secondaryEntryPoint.cssUrl).to.be.undefined; // TODO: should default to 'inline'.
expect(secondaryEntryPoint.flatModuleFile).to.equal('example-test-entry-point-extra');
Expand Down
3 changes: 3 additions & 0 deletions src/lib/ng-package/entry-point/entry-point.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export interface DestinationFiles {
declarations: string;
/** Absolute path of this entry point `FESM2020` module */
fesm2020: string;
/** Absolute path of this entry point `FESM2015` module */
fesm2015: string;
/** Absolute path of this entry point `ESM2020` module */
esm2020: string;
}
Expand Down Expand Up @@ -88,6 +90,7 @@ export class NgEntryPoint {
declarations: pathJoinWithDest(secondaryDir, `${flatModuleFile}.d.ts`),
esm2020: pathJoinWithDest('esm2020', secondaryDir, `${flatModuleFile}.js`),
fesm2020: pathJoinWithDest('fesm2020', `${flatModuleFile}.js`),
fesm2015: pathJoinWithDest('fesm2015', `${flatModuleFile}.js`),
};
}

Expand Down
31 changes: 26 additions & 5 deletions src/lib/ng-package/entry-point/write-bundles.transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,54 @@ import { transformFromPromise } from '../../graph/transform';
import { isEntryPointInProgress, EntryPointNode } from '../nodes';
import { rollupBundleFile } from '../../flatten/rollup';
import { NgPackagrOptions } from '../options.di';
import { downlevelCodeWithTsc } from '../../flatten/downlevel-plugin';

export const writeBundlesTransform = (options: NgPackagrOptions) =>
transformFromPromise(async graph => {
const entryPoint: EntryPointNode = graph.find(isEntryPointInProgress());
const { destinationFiles, entryPoint: ngEntryPoint, tsConfig } = entryPoint.data;
const cache = entryPoint.cache;
const { fesm2020, esm2020 } = destinationFiles;
const { fesm2020, fesm2015, esm2020 } = destinationFiles;

const spinner = ora({
hideCursor: false,
discardStdin: false,
});

try {
spinner.start('Bundling to FESM2020');
spinner.start('Generating FESM2020');
const rollupFESMCache = await rollupBundleFile({
sourceRoot: tsConfig.options.sourceRoot,
entry: esm2020,
moduleName: ngEntryPoint.moduleId,
format: 'es',
dest: fesm2020,
cache: cache.rollupFESMCache,
cache: cache.rollupFESM2020Cache,
});
spinner.succeed();

if (options.watch) {
cache.rollupFESMCache = rollupFESMCache;
cache.rollupFESM2020Cache = rollupFESMCache;
}
} catch (error) {
spinner.fail();
throw error;
}


try {
spinner.start('Generating FESM2015');
const rollupFESMCache = await rollupBundleFile({
sourceRoot: tsConfig.options.sourceRoot,
entry: esm2020,
moduleName: ngEntryPoint.moduleId,
dest: fesm2015,
transform: downlevelCodeWithTsc,
cache: cache.rollupFESM2015Cache,
});
spinner.succeed();

if (options.watch) {
cache.rollupFESM2015Cache = rollupFESMCache;
}
} catch (error) {
spinner.fail();
Expand Down
3 changes: 2 additions & 1 deletion src/lib/ng-package/entry-point/write-package.transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,11 @@ export const writePackageTransform = (options: NgPackagrOptions) =>
ngEntryPoint,
ngPackage,
{
module: relativeUnixFromDestPath(destinationFiles.fesm2020),
module: relativeUnixFromDestPath(destinationFiles.fesm2015),
es2020: relativeUnixFromDestPath(destinationFiles.fesm2020),
esm2020: relativeUnixFromDestPath(destinationFiles.esm2020),
fesm2020: relativeUnixFromDestPath(destinationFiles.fesm2020),
fesm2015: relativeUnixFromDestPath(destinationFiles.fesm2015),
typings: relativeUnixFromDestPath(destinationFiles.declarations),
// webpack v4+ specific flag to enable advanced optimizations and code splitting
sideEffects: ngEntryPoint.sideEffects,
Expand Down
3 changes: 2 additions & 1 deletion src/lib/ng-package/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ export class EntryPointNode extends Node {
ngccProcessingCache: NgccProcessingCache;
analysesSourcesFileCache: FileCache;
moduleResolutionCache: ts.ModuleResolutionCache;
rollupFESMCache?: RollupCache;
rollupFESM2020Cache?: RollupCache;
rollupFESM2015Cache?: RollupCache;
stylesheetProcessor?: StylesheetProcessor;
oldNgtscProgram?: NgtscProgram;
oldBuilder?: ts.EmitAndSemanticDiagnosticsBuilderProgram;
Expand Down
41 changes: 6 additions & 35 deletions src/lib/styles/stylesheet-processor.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import * as browserslist from 'browserslist';
import * as findCacheDirectory from 'find-cache-dir';
import { tmpdir } from 'os';
import * as cacache from 'cacache';
import postcss from 'postcss';
import * as postcssUrl from 'postcss-url';
import { transform, formatMessages } from 'esbuild';
import * as postcssPresetEnv from 'postcss-preset-env';
import * as log from '../utils/log';
import { readFile } from '../utils/fs';
import { createHash } from 'crypto';
import { extname } from 'path';
import { generateKey, readCacheEntry, saveCacheEntry } from '../utils/cache';

export enum CssUrl {
inline = 'inline',
Expand All @@ -28,16 +24,6 @@ export interface Result {
warnings: string[];
error?: string;
}

const cachePath = findCacheDirectory({ name: 'ng-packagr-styles' }) || tmpdir();
let ngPackagrVersion: string | undefined;
try {
ngPackagrVersion = require('../../../package.json').version;
} catch {
// dev path
ngPackagrVersion = require('../../../../package.json').version;
}

export class StylesheetProcessor {
private browserslistData: string[];
private targets: string[];
Expand Down Expand Up @@ -73,8 +59,8 @@ export class StylesheetProcessor {

if (!content.includes('@import') && !content.includes('@use')) {
// No transitive deps, we can cache more aggressively.
key = generateKey(content, this.browserslistData);
const result = await readCacheEntry(cachePath, key);
key = generateKey(content, ...this.browserslistData);
const result = await readCacheEntry(key);
if (result) {
result.warnings.forEach(msg => log.warn(msg));
return result.css;
Expand All @@ -87,10 +73,10 @@ export class StylesheetProcessor {
// We cannot cache CSS re-rendering phase, because a transitive dependency via (@import) can case different CSS output.
// Example a change in a mixin or SCSS variable.
if (!key) {
key = generateKey(renderedCss, this.browserslistData);
key = generateKey(renderedCss, ...this.browserslistData);
}

const cachedResult = await readCacheEntry(cachePath, key);
const cachedResult = await readCacheEntry(key);
if (cachedResult) {
cachedResult.warnings.forEach(msg => log.warn(msg));
return cachedResult.css;
Expand All @@ -114,9 +100,7 @@ export class StylesheetProcessor {
warnings.push(...(await formatMessages(esBuildWarnings, { kind: 'warning' })));
}

// Add to cache
await cacache.put(
cachePath,
saveCacheEntry(
key,
JSON.stringify({
css: code,
Expand Down Expand Up @@ -212,19 +196,6 @@ export class StylesheetProcessor {
}
}

function generateKey(content: string, browserslistData: string[]): string {
return createHash('sha1').update(ngPackagrVersion).update(content).update(browserslistData.join('')).digest('hex');
}

async function readCacheEntry(cachePath: string, key: string): Promise<Result | undefined> {
const entry = await cacache.get.info(cachePath, key);
if (entry) {
return JSON.parse(await readFile(entry.path, 'utf8'));
}

return undefined;
}

function transformSupportedBrowsersToTargets(supportedBrowsers: string[]): string[] {
const transformed: string[] = [];

Expand Down

0 comments on commit 6cf2514

Please sign in to comment.