Skip to content

Commit

Permalink
new: Refactor shared lib logic. (#20)
Browse files Browse the repository at this point in the history
* Improve shared lib handling.

* Fix tests.

* Remove unused code.
  • Loading branch information
milesj authored Jan 16, 2021
1 parent b182e5d commit abe3275
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 203 deletions.
63 changes: 37 additions & 26 deletions src/BundleArtifact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import { isObject, Path, SettingMap, toArray } from '@boost/common';
import { createDebugger, Debugger } from '@boost/debug';
import Artifact from './Artifact';
import { DEFAULT_FORMAT, NODE_SUPPORTED_VERSIONS, NPM_SUPPORTED_VERSIONS } from './constants';
import { supportRanks } from './helpers/getLowestSupport';
import { getRollupConfig } from './rollup/config';
import { BuildOptions, BundleBuild, Format, Platform, Support } from './types';

export default class BundleArtifact extends Artifact<BundleBuild> {
cache?: RollupCache;

// Config object in which inputs are grouped in
configGroup: number = 0;

// Input file path relative to the package root
inputFile: string = '';

Expand All @@ -20,6 +22,9 @@ export default class BundleArtifact extends Artifact<BundleBuild> {
// Output file name without extension
outputName: string = '';

// Are multiple builds writing to the lib folder
sharedLib: boolean = false;

protected debug!: Debugger;

static generateBuild(format: Format, support: Support, platforms: Platform[]): BundleBuild {
Expand Down Expand Up @@ -125,16 +130,18 @@ export default class BundleArtifact extends Artifact<BundleBuild> {
);
}

getOutputExtension(format: Format): string {
return format === 'cjs' || format === 'mjs' ? format : 'js';
}

getOutputFile(format: Format): string {
return `./${format}/${this.outputName}.${this.getOutputExtension(format)}`;
}
getOutputMetadata(format: Format, platform: Platform) {
const ext = format === 'cjs' || format === 'mjs' ? format : 'js';
const folder = format === 'lib' && this.sharedLib ? `lib/${platform}` : format;
const file = `${this.outputName}.${ext}`;
const path = `./${folder}/${file}`;

getOutputFolderPath(format: Format): Path {
return this.package.path.append(format);
return {
ext,
file,
folder,
path,
};
}

getStatsFileName(): string {
Expand All @@ -151,6 +158,12 @@ export default class BundleArtifact extends Artifact<BundleBuild> {

protected addEnginesToPackageJson() {
const pkg = this.package.packageJson;
const supportRanks: Record<Support, number> = {
legacy: 1,
stable: 2,
current: 3,
experimental: 4,
};

// Update with the lowest supported node version
const nodeBuild = [...this.builds]
Expand Down Expand Up @@ -178,49 +191,47 @@ export default class BundleArtifact extends Artifact<BundleBuild> {

const pkg = this.package.packageJson;

this.builds.forEach(({ format }) => {
const ext = this.getOutputExtension(format);
this.builds.forEach(({ format, platform }) => {
const { path } = this.getOutputMetadata(format, platform);
const isNode = format === 'lib' || format === 'cjs' || format === 'mjs';

if (this.outputName === 'index') {
if (isNode) {
pkg.main = `./${format}/index.${ext}`;
pkg.main = path;
} else if (format === 'esm') {
pkg.module = `./esm/index.${ext}`;
pkg.module = path;
} else if (format === 'umd') {
pkg.browser = `./umd/index.${ext}`;
pkg.browser = path;
}
}

// Bin field may be an object
if (this.outputName === 'bin' && !isObject(pkg.bin)) {
if (isNode) {
pkg.bin = `./${format}/bin.${ext}`;
}
if (this.outputName === 'bin' && !isObject(pkg.bin) && isNode) {
pkg.bin = path;
}
});
}

protected addExportsToPackageJson() {
const pkg = this.package.packageJson;
const paths: SettingMap = {};
let hasLib = false;
let libPath = '';

this.builds.forEach(({ format }) => {
const path = this.getOutputFile(format);
this.builds.forEach(({ format, platform }) => {
const { path } = this.getOutputMetadata(format, platform);

if (format === 'mjs' || format === 'esm') {
paths.import = path;
} else if (format === 'cjs') {
paths.require = path;
} else if (format === 'lib') {
hasLib = true;
libPath = path;
}
});

// Must come after import/require
if (hasLib) {
paths.default = this.getOutputFile('lib');
if (libPath) {
paths.default = libPath;
}

const exportCount = Object.keys(paths).length;
Expand All @@ -235,7 +246,7 @@ export default class BundleArtifact extends Artifact<BundleBuild> {
Object.assign(pkg.exports, {
'./package.json': './package.json',
[this.outputName === 'index' ? '.' : `./${this.outputName}`]:
exportCount === 1 && hasLib ? paths.default : paths,
exportCount === 1 && libPath ? paths.default : paths,
});
}
}
Expand Down
41 changes: 11 additions & 30 deletions src/Packemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@ import { createDebugger, Debugger } from '@boost/debug';
import { Event } from '@boost/event';
import { Context, PooledPipeline } from '@boost/pipeline';
import BundleArtifact from './BundleArtifact';
import { getLowestSupport } from './helpers/getLowestSupport';
import Package from './Package';
import PackageValidator from './PackageValidator';
import Project from './Project';
import { buildBlueprint, validateBlueprint } from './schemas';
import {
BuildOptions,
BundleBuild,
DeclarationType,
PackemonPackage,
Platform,
Expand Down Expand Up @@ -203,28 +201,30 @@ export default class Packemon {
const typesBuilds: Record<string, TypesBuild> = {};
const sharedLib = this.requiresSharedLib(pkg);

pkg.configs.forEach((config) => {
pkg.configs.forEach((config, index) => {
Object.entries(config.inputs).forEach(([outputName, inputFile]) => {
const artifact = new BundleArtifact(
pkg,
// Must be unique per input to avoid references
config.formats.map((format) =>
format === 'lib' && sharedLib
? BundleArtifact.generateBuild('lib', sharedLib.support, [sharedLib.platform])
: BundleArtifact.generateBuild(format, config.support, config.platforms),
BundleArtifact.generateBuild(format, config.support, config.platforms),
),
);
artifact.configGroup = index;
artifact.inputFile = inputFile;
artifact.outputName = outputName;
artifact.namespace = config.namespace;
artifact.sharedLib = sharedLib;

pkg.addArtifact(artifact);

typesBuilds[outputName] = { inputFile, outputName };
});
});

if (declarationType !== 'none') {
const artifact = new TypesArtifact(pkg, Object.values(typesBuilds));

artifact.declarationType = declarationType;

pkg.addArtifact(artifact);
Expand Down Expand Up @@ -259,11 +259,10 @@ export default class Packemon {
/**
* Format "lib" is a shared format across all platforms,
* and when a package wants to support multiple platforms,
* we must down-level the "lib" format to the lowest platform.
* we must account for this and alter the output paths.
*/
protected requiresSharedLib(pkg: Package): BundleBuild | null {
protected requiresSharedLib(pkg: Package): boolean {
const platformsToCheck = new Set<Platform>();
const build: BundleBuild = { format: 'lib', platform: 'node', support: 'stable' };
let libFormatCount = 0;

pkg.configs.forEach((config) => {
Expand All @@ -272,31 +271,13 @@ export default class Packemon {
});

config.formats.forEach((format) => {
if (format !== 'lib') {
return;
}

libFormatCount += 1;

// From widest to narrowest requirements
if (platformsToCheck.has('browser')) {
build.platform = 'browser';
} else if (platformsToCheck.has('native')) {
build.platform = 'native';
} else if (platformsToCheck.has('node')) {
build.platform = 'node';
if (format === 'lib') {
libFormatCount += 1;
}

// Return the lowest supported target
build.support = getLowestSupport(build.support, config.support);
});
});

if (platformsToCheck.size > 1 && libFormatCount > 1) {
return build;
}

return null;
return platformsToCheck.size > 1 && libFormatCount > 1;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/components/PackageRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default function PackageRow({ package: pkg }: PackageRowProps) {
</Box>

{pkg.artifacts.map((artifact) => (
<ArtifactRow key={artifact.getLabel()} artifact={artifact} />
<ArtifactRow key={String(artifact)} artifact={artifact} />
))}
</Box>
);
Expand Down
12 changes: 0 additions & 12 deletions src/helpers/getLowestSupport.ts

This file was deleted.

60 changes: 48 additions & 12 deletions src/rollup/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,37 +26,73 @@ function getRollupModuleFormat(format: Format): ModuleFormat {
return 'cjs';
}

function getRollupExternalPaths(artifact: BundleArtifact, ext?: string): Record<string, string> {
const paths: Record<string, string> = {};

artifact.package.artifacts.forEach((art) => {
const bundle = art as BundleArtifact;
function getSiblingArtifacts(artifact: BundleArtifact): BundleArtifact[] {
return artifact.package.artifacts.filter((bundle) => {
if (bundle === artifact) {
return false;
}

// Don't include non-bundle artifacts. We can't use `instanceof`
// Don't include non-bundle artifacts. We also can't use `instanceof`
// because of circular dependencies, boo!
if ('outputName' in bundle) {
return 'configGroup' in bundle;
}) as BundleArtifact[];
}

function getRollupPaths(artifact: BundleArtifact, ext: string): Record<string, string> {
const paths: Record<string, string> = {};

getSiblingArtifacts(artifact).forEach((bundle) => {
if (bundle.configGroup === artifact.configGroup) {
// All output files are in the same directory, so we can hard-code a relative path
paths[bundle.getInputPath().path()] = `./${bundle.outputName}${ext ? `.${ext}` : ''}`;
paths[bundle.getInputPath().path()] = `./${bundle.outputName}.${ext}`;
}
});

return paths;
}

export function getRollupExternals(artifact: BundleArtifact) {
const siblingInputs = new Set<string>();
const foreignInputs = new Set<string>();

getSiblingArtifacts(artifact).forEach((bundle) => {
const inputPath = bundle.getInputPath().path();

if (bundle.configGroup === artifact.configGroup) {
siblingInputs.add(inputPath);
} else {
foreignInputs.add(inputPath);
}
});

return (id: string, parent: string = '<unknown>') => {
if (siblingInputs.has(id)) {
return true;
} else if (foreignInputs.has(id)) {
throw new Error(
`Unexpected foreign input import. May only import sibling files within the same \`inputs\` configuration group. File "${parent}" attempted to import "${id}".`,
);
}

return false;
};
}

export function getRollupOutputConfig(
artifact: BundleArtifact,
features: FeatureFlags,
build: BundleBuild,
): OutputOptions {
const { format, platform, support } = build;
const name = artifact.outputName;
const ext = artifact.getOutputExtension(format);
const { ext, folder } = artifact.getOutputMetadata(format, platform);

const output: OutputOptions = {
dir: artifact.getOutputFolderPath(format).path(),
dir: artifact.package.path.append(folder).path(),
format: getRollupModuleFormat(format),
originalFormat: format,
// Map our externals to local paths with trailing extension
paths: getRollupExternalPaths(artifact, ext),
paths: getRollupPaths(artifact, ext),
// Use our extension for file names
assetFileNames: '../assets/[name]-[hash][extname]',
chunkFileNames: `${name}-[hash].${ext}`,
Expand Down Expand Up @@ -98,7 +134,7 @@ export function getRollupConfig(artifact: BundleArtifact, features: FeatureFlags

const config: RollupOptions = {
cache: artifact.cache,
external: Object.keys(getRollupExternalPaths(artifact)),
external: getRollupExternals(artifact),
input: inputPath.path(),
output: [],
// Shared output plugins
Expand Down
Loading

0 comments on commit abe3275

Please sign in to comment.