Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Packager Refactoring #765

Merged
merged 6 commits into from
May 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/compat/src/default-pipeline.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { App, Addons as CompatAddons, Options, PrebuiltAddons } from '.';
import { toBroccoliPlugin, Packager, Variant } from '@embroider/core';
import { toBroccoliPlugin, PackagerConstructor, Variant } from '@embroider/core';
import { Node } from 'broccoli-node-api';
import writeFile from 'broccoli-file-creator';
import mergeTrees from 'broccoli-merge-trees';
Expand All @@ -12,7 +12,7 @@ export interface PipelineOptions<PackagerOptions> extends Options {

export default function defaultPipeline<PackagerOptions>(
emberApp: object,
packager?: Packager<PackagerOptions>,
packager?: PackagerConstructor<PackagerOptions>,
options?: PipelineOptions<PackagerOptions>
): Node {
let outputPath: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { getOrCreate } from '@embroider/core';
import { getOrCreate } from '@embroider/shared-internals';
import { readFileSync } from 'fs-extra';
import { join } from 'path';
import { JSDOM } from 'jsdom';
import partition from 'lodash/partition';
import zip from 'lodash/zip';
import Placeholder from './html-placeholder';
import { StatSummary } from './stat-summary';
import { Variant } from './packager';

export class HTMLEntrypoint {
private dom: JSDOM;
Expand Down Expand Up @@ -77,7 +77,7 @@ export class HTMLEntrypoint {
}

// bundles maps from input asset to a per-variant map of output assets
render(stats: StatSummary): string {
render(stats: BundleSummary): string {
let insertedLazy = false;
let fastbootVariant = stats.variants.findIndex(v => Boolean(v.runtime === 'fastboot'));
let supportsFastboot = stats.variants.some(v => v.runtime === 'fastboot' || v.runtime === 'all');
Expand Down Expand Up @@ -129,6 +129,17 @@ export class HTMLEntrypoint {
}
}

export interface BundleSummary {
// entrypoints.get(inputAsset).get(variantIndex) === outputAssets
entrypoints: Map<string, Map<number, string[]>>;

// lazyBundles are tracked specifically for fastboot, so these always come
// from the fastboot variant, if any
lazyBundles: Set<string>;

variants: Variant[];
}

function isAbsoluteURL(url: string) {
return /^(?:[a-z]+:)?\/\//i.test(url);
}
Expand Down
5 changes: 4 additions & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
export {
Packager,
PackagerInstance,
PackagerConstructor,
Variant,
applyVariantToBabelConfig,
applyVariantToTemplateCompiler,
getAppMeta,
getPackagerCacheDir,
} from './packager';
export { HTMLEntrypoint, BundleSummary } from './html-entrypoint';
export { Resolver } from './resolver';
export { default as Stage } from './stage';
export { NodeTemplateCompiler, NodeTemplateCompilerParams } from './template-compiler-node';
Expand Down
27 changes: 24 additions & 3 deletions packages/core/src/packager.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import { AppMeta } from '@embroider/shared-internals';
import { readFileSync } from 'fs-extra';
import { join } from 'path';
import { tmpdir } from 'os';

// This is a collection of flags that convey what kind of build you want. They
// are intended to be generic across Packagers, and it's up to Packager authors
// to support each option (or not).
Expand All @@ -23,7 +28,7 @@ export interface Variant {
optimizeForProduction: boolean;
}

export interface Packager<Options> {
export interface PackagerConstructor<Options> {
new (
// where on disk the packager will find the app it's supposed to build. The
// app and its addons will necessarily already be in v2 format, which is
Expand Down Expand Up @@ -54,13 +59,13 @@ export interface Packager<Options> {
// packager is based on a third-party tool, this is where that tool's
// configuration can go.
options?: Options
): PackagerInstance;
): Packager;

// a description for this packager that aids debugging & profiling
annotation: string;
}

export interface PackagerInstance {
export interface Packager {
build(): Promise<void>;
}

Expand All @@ -87,3 +92,19 @@ export function applyVariantToTemplateCompiler(_variant: Variant, templateCompil
// Packagers must call this function anyway because we will.
return templateCompiler;
}

/**
* Get the app meta-data for a package
*/
export function getAppMeta(pathToVanillaApp: string): AppMeta {
return JSON.parse(readFileSync(join(pathToVanillaApp, 'package.json'), 'utf8'))['ember-addon'] as AppMeta;
}

/**
* Get the path to a cache directory in the recommended location
*
* This ensures they have exactly the same lifetime as some of embroider's own caches.
*/
export function getPackagerCacheDir(name: string): string {
return join(tmpdir(), 'embroider', name);
}
8 changes: 5 additions & 3 deletions packages/core/src/to-broccoli-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import Plugin from 'broccoli-plugin';
import { Packager, PackagerInstance, Variant } from './packager';
import { Packager, PackagerConstructor, Variant } from './packager';
import Stage from './stage';

interface BroccoliPackager<Options> {
new (stage: Stage, variants: Variant[], options?: Options): Plugin;
}

export default function toBroccoliPlugin<Options>(packagerClass: Packager<Options>): BroccoliPackager<Options> {
export default function toBroccoliPlugin<Options>(
packagerClass: PackagerConstructor<Options>
): BroccoliPackager<Options> {
class PackagerRunner extends Plugin {
private packager: PackagerInstance | undefined;
private packager: Packager | undefined;
constructor(private stage: Stage, private variants: Variant[], private options?: Options) {
super([stage.tree], {
persistentOutput: true,
Expand Down
43 changes: 43 additions & 0 deletions packages/core/tests/packager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { AppMeta, getAppMeta, getPackagerCacheDir } from '../src';
import { writeJSONSync } from 'fs-extra';
import { join } from 'path';
import { tmpdir } from 'os';
import * as tmp from 'tmp';

tmp.setGracefulCleanup();

describe('getAppMeta', () => {
let name: string, removeCallback: tmp.DirResult['removeCallback'];

beforeEach(() => {
({ name, removeCallback } = tmp.dirSync());

writeJSONSync(join(name, 'package.json'), {
'ember-addon': {
version: 2,
type: 'app',
'auto-upgraded': true,
},
});
});

afterEach(() => {
removeCallback();
});

test('reading the app metadata from a package', () => {
const meta: AppMeta = getAppMeta(name);
expect(meta).toMatchObject({
version: 2,
type: 'app',
'auto-upgraded': true,
});
});
});

describe('getPackagerCacheDir', () => {
test('getting the path to a cache directory', () => {
const cacheDir = getPackagerCacheDir('foo');
expect(cacheDir).toBe(join(tmpdir(), 'embroider', 'foo'));
});
});
4 changes: 2 additions & 2 deletions packages/test-setup/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { PipelineOptions } from '@embroider/compat';
import type { Packager } from '@embroider/core';
import type { PackagerConstructor } from '@embroider/core';
import type { Webpack } from '@embroider/webpack';

type EmberWebpackOptions = typeof Webpack extends Packager<infer Options> ? Options : never;
type EmberWebpackOptions = typeof Webpack extends PackagerConstructor<infer Options> ? Options : never;

// eslint-disable-next-line @typescript-eslint/no-require-imports
const currentEmbroiderVersion = require('../package.json').version;
Expand Down
32 changes: 18 additions & 14 deletions packages/webpack/src/ember-webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,18 @@
getting script vs module context correct).
*/

import { getOrCreate, Variant, applyVariantToBabelConfig } from '@embroider/core';
import { PackagerInstance, AppMeta, Packager } from '@embroider/core';
import {
AppMeta,
HTMLEntrypoint,
BundleSummary,
Packager,
PackagerConstructor,
Variant,
applyVariantToBabelConfig,
getAppMeta,
getPackagerCacheDir,
getOrCreate,
} from '@embroider/core';
import webpack, { Configuration } from 'webpack';
import { readFileSync, outputFileSync, copySync, realpathSync, Stats, statSync, readJsonSync } from 'fs-extra';
import { join, dirname, relative, sep } from 'path';
Expand All @@ -23,8 +33,6 @@ import { format } from 'util';
import { tmpdir } from 'os';
import { warmup as threadLoaderWarmup } from 'thread-loader';
import { Options, BabelLoaderOptions } from './options';
import { HTMLEntrypoint } from './html-entrypoint';
import { StatSummary } from './stat-summary';
import crypto from 'crypto';
import type { HbsLoaderConfig } from '@embroider/hbs-loader';
import semverSatisfies from 'semver/functions/satisfies';
Expand Down Expand Up @@ -59,7 +67,7 @@ function equalAppInfo(left: AppInfo, right: AppInfo): boolean {
// PackagerInstance, but our constructor conforms to Packager. So instead of
// just exporting our class directly, we export a const constructor of the
// correct type.
const Webpack: Packager<Options> = class Webpack implements PackagerInstance {
const Webpack: PackagerConstructor<Options> = class Webpack implements Packager {
static annotation = '@embroider/webpack';

pathToVanillaApp: string;
Expand Down Expand Up @@ -96,7 +104,7 @@ const Webpack: Packager<Options> = class Webpack implements PackagerInstance {
}

private examineApp(): AppInfo {
let meta = JSON.parse(readFileSync(join(this.pathToVanillaApp, 'package.json'), 'utf8'))['ember-addon'] as AppMeta;
let meta = getAppMeta(this.pathToVanillaApp);
let templateCompiler = meta['template-compiler'];
let rootURL = meta['root-url'];
let babel = meta['babel'];
Expand Down Expand Up @@ -289,7 +297,7 @@ const Webpack: Packager<Options> = class Webpack implements PackagerInstance {
}
}

private async writeFiles(stats: StatSummary, { entrypoints, otherAssets }: AppInfo) {
private async writeFiles(stats: BundleSummary, { entrypoints, otherAssets }: AppInfo) {
// we're doing this ourselves because I haven't seen a webpack 4 HTML plugin
// that handles multiple HTML entrypoints correctly.

Expand Down Expand Up @@ -382,8 +390,8 @@ const Webpack: Packager<Options> = class Webpack implements PackagerInstance {
return fileParts.join('.');
}

private summarizeStats(multiStats: webpack.StatsCompilation): StatSummary {
let output: StatSummary = {
private summarizeStats(multiStats: webpack.StatsCompilation): BundleSummary {
let output: BundleSummary = {
entrypoints: new Map(),
lazyBundles: new Set(),
variants: this.variants,
Expand Down Expand Up @@ -563,11 +571,7 @@ function babelLoaderOptions(
// eslint-disable-next-line @typescript-eslint/no-require-imports
applyVariantToBabelConfig(variant, require(appBabelConfigPath)),
{
// Unless explicitly overridden, all stage3 packagers should keep
// persistent caches under `join(tmpdir(), 'embroider')`. An
// important reason is that they should have exactly the same
// lifetime as some of embroider's own caches.
cacheDirectory: join(tmpdir(), 'embroider', 'webpack-babel-loader'),
cacheDirectory: getPackagerCacheDir('webpack-babel-loader'),
},
extraOptions
);
Expand Down
12 changes: 0 additions & 12 deletions packages/webpack/src/stat-summary.ts

This file was deleted.