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

Size limit support #691

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
15 changes: 11 additions & 4 deletions packages/docs/generate-docs.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,24 @@ const makeType = ({ type, typeName, multiple }) => {
return multiple ? `${typeString}[]` : typeString;
};

/** Determine supported scope for option */
const makeScope = ({ scope }) => {
return scope || 'Global'
}

/** Generate a table for an array of options */
function generateOptionTable(options) {
let text = dedent`\n
| Name | Type | Description |
| ---- | ---- | ----------- |
| Name | Type | Scope | Description |
| ---- | ---- | ----- | ----------- |
`;

Object.entries(options).forEach(([option, value]) => {
if (option.includes('-')) {
return;
}

text += `\n| ${option} | ${makeType(value)} | ${value.description} |`;
text += `\n| ${option} | ${makeType(value)} | ${makeScope(value)} | ${value.description} |`;
});

return text;
Expand All @@ -82,7 +87,7 @@ async function generateConfigDocs() {

\`@design-systems/cli\` supports a wide array of configuration files.

Add one of the following to to the root of the project:
Add one of the following to to the root of the project and/or the root of a given submodule:

- a \`ds\` key in the \`package.json\`
- \`.dsrc\`
Expand All @@ -93,6 +98,8 @@ async function generateConfigDocs() {
- \`ds.config.js\`
- \`ds.config.json\`

!> The package-specific configuration feature is in very early stages, and only supports options with the **Local** scope.

## Structure

The config is structured with each key being a command name and the value being an object configuring options.
Expand Down
2 changes: 2 additions & 0 deletions packages/plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { Overwrite } from 'utility-types';
export type Option = AppOption & {
/** Whether the Option should be configurable via ds.config.json */
config?: boolean;
/** Whether or not the option is available in the global or local scope */
scope?: string;
};

interface Configurable {
Expand Down
2 changes: 2 additions & 0 deletions plugins/size/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
},
"dependencies": {
"@design-systems/cli-utils": "link:../../packages/cli-utils",
"cosmiconfig": "7.0.0",
"@design-systems/plugin": "link:../../packages/plugin",
"@royriojas/get-exports-from-file": "https://github.com/hipstersmoothie/get-exports-from-file#all",
"change-case": "4.1.1",
Expand All @@ -42,6 +43,7 @@
"table": "6.0.7",
"terser-webpack-plugin": "4.1.0",
"tslib": "2.0.1",
"utility-types": "3.10.0",
"webpack": "4.44.1",
"webpack-bundle-analyzer": "3.8.0",
"webpack-inject-plugin": "1.5.5",
Expand Down
7 changes: 7 additions & 0 deletions plugins/size/src/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ const command: CliCommand = {
description: 'Failure Threshold for Size',
config: true
},
{
name: 'sizeLimit',
type: Number,
description: 'Size limit failure threshold',
config: true,
scope: 'Local'
},
{
name: 'merge-base',
type: String,
Expand Down
10 changes: 8 additions & 2 deletions plugins/size/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,15 @@ export default class SizePlugin implements Plugin<SizeArgs> {
});
const header = args.css ? cssHeader : defaultHeader;

const underFailureThreshold = size &&
size.percent <= FAILURE_THRESHOLD ||
size.percent === Infinity;
const underSizeLimit = size.localBudget ? size.pr.js + size.pr.css <= size.localBudget : true;
const success = underFailureThreshold && underSizeLimit;

await reportResults(
name,
size.percent <= FAILURE_THRESHOLD || size.percent === Infinity,
success,
Boolean(args.comment),
table(
args.detailed
Expand All @@ -107,7 +113,7 @@ export default class SizePlugin implements Plugin<SizeArgs> {
createDiff();
}

if (size && size.percent > FAILURE_THRESHOLD && size.percent !== Infinity) {
if (!success) {
process.exit(1);
}
}
Expand Down
15 changes: 15 additions & 0 deletions plugins/size/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export interface SizeArgs {
ignore?: string[]
/** The registry to install packages from */
registry?: string
/** Size limit failure threshold */
sizeLimit?: number
/** Size Failure Threshold */
failureThreshold?: number
/** Run the plugin against merge base. (Will be slower due to additional build process) */
Expand All @@ -43,6 +45,8 @@ export interface Size {
js: number
/** Top level exports of package */
exported?: Export[]
/** Maximum bundle size as defined by the package */
limit?: number
}

export interface SizeResult {
Expand All @@ -52,6 +56,8 @@ export interface SizeResult {
pr: Size
/** The difference between sizes */
percent: number
/** The total number of bytes allowed as defined in the local changeset */
localBudget?: number
}

export interface ConfigOptions {
Expand Down Expand Up @@ -88,6 +94,15 @@ export interface GetSizesOptions extends CommonCalcSizeOptions {
analyze?: boolean
/** What port to start the analyzer on */
analyzerPort?: number
/** Working directory to execute analysis from */
dir: string
}

export interface LoadPackageOptions {
/** The name of the package to get size for */
name: string
/** The registry to install packages from */
registry?: string
}

type Scope = 'pr' | 'master'
Expand Down
45 changes: 42 additions & 3 deletions plugins/size/src/utils/BuildUtils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { execSync } from 'child_process';
import { execSync, ExecSyncOptions } from 'child_process';
import os from 'os';
import path from 'path';
import { getMonorepoRoot, createLogger } from '@design-systems/cli-utils';
import fs from 'fs-extra';
import { getMonorepoRoot, createLogger, getLogLevel } from '@design-systems/cli-utils';
import { mockPackage } from './CalcSizeUtils';
import { LoadPackageOptions } from '../interfaces';

const logger = createLogger({ scope: 'size' });

Expand All @@ -11,7 +14,7 @@ export function buildPackages(args: {
mergeBase: string
/** Build command for merge base */
buildCommand: string
}) {
}): string {
const id = Math.random().toString(36).substring(7);
const dir = path.join(os.tmpdir(), `commit-build-${id}`);
const root = getMonorepoRoot();
Expand Down Expand Up @@ -54,3 +57,39 @@ export function getLocalPackage(

return path.join(local, path.relative(getMonorepoRoot(), pkg.location));
}

/** Install package to tmp dir */
export async function loadPackage(options: LoadPackageOptions): Promise<string> {
const dir = mockPackage();
const execOptions: ExecSyncOptions = {
cwd: dir,
stdio: getLogLevel() === 'trace' ? 'inherit' : 'ignore'
};
try {
const browsersList = path.join(getMonorepoRoot(), '.browserslistrc');
if (fs.existsSync(browsersList)) {
fs.copyFileSync(browsersList, path.join(dir, '.browserslistrc'));
}

const npmrc = path.join(getMonorepoRoot(), '.npmrc');
if (options.registry && fs.existsSync(npmrc)) {
fs.copyFileSync(npmrc, path.join(dir, '.npmrc'));
}

logger.debug(`Installing: ${options.name}`);
if (options.registry) {
execSync(
`yarn add ${options.name} --registry ${options.registry}`,
execOptions
);
} else {
execSync(`yarn add ${options.name}`, execOptions);
}
} catch (error) {
logger.debug(error);
logger.warn(`Could not find package ${options.name}...`);
return './';
}

return dir;
}
34 changes: 30 additions & 4 deletions plugins/size/src/utils/CalcSizeUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { monorepoName, createLogger } from '@design-systems/cli-utils';
import { cosmiconfigSync as load } from 'cosmiconfig';
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 would prefer to use @design-systems/load-config here, but trying to link that resulted in a weird circular dependency issue that broke installation (see previous build). Got tired of fighting with that, but if someone else wants to tackle that optimization then by all means.

import path from 'path';
import fs from 'fs-extra';
import os from 'os';
Expand All @@ -20,7 +21,7 @@ import {
DiffSizeForPackageOptions
} from '../interfaces';
import { getSizes } from './WebpackUtils';
import { getLocalPackage } from './BuildUtils';
import { getLocalPackage, loadPackage } from './BuildUtils';

const RUNTIME_SIZE = 537;

Expand All @@ -39,6 +40,21 @@ const cssHeader = [

const defaultHeader = ['master', 'pr', '+/-', '%'];

function loadConfig(cwd: string) {
return load('ds', {
searchPlaces: [
'package.json',
`.dsrc`,
`.dsrc.json`,
`.dsrc.yaml`,
`.dsrc.yml`,
`.dsrc.js`,
`ds.config.js`,
`ds.config.json`,
]
}).search(cwd)?.config;
}

/** Calculate the bundled CSS and JS size. */
async function calcSizeForPackage({
name,
Expand All @@ -50,15 +66,23 @@ async function calcSizeForPackage({
registry,
local
}: CommonOptions & CommonCalcSizeOptions): Promise<Size> {
const packageName = local ? getLocalPackage(importName, local) : name;
const dir = await loadPackage({
name: packageName,
registry
});
const sizes = await getSizes({
name: local ? getLocalPackage(importName, local) : name,
name: packageName,
importName,
scope,
persist,
chunkByExport,
diff,
registry
registry,
dir
});
const packageConfig = loadConfig(path.join(dir, 'node_modules', packageName));
fs.removeSync(dir);

const js = sizes.filter((size) => !size.chunkNames.includes('css'));
const css = sizes.filter((size) => size.chunkNames.includes('css'));
Expand All @@ -76,6 +100,7 @@ async function calcSizeForPackage({
js: js.length ? js.reduce((acc, i) => i.size + acc, 0) - RUNTIME_SIZE : 0, // Minus webpack runtime size;
css: css.length ? css.reduce((acc, i) => i.size + acc, 0) : 0,
exported: sizes,
limit: packageConfig?.size?.sizeLimit
};
}

Expand Down Expand Up @@ -129,11 +154,12 @@ async function diffSizeForPackage({
master,
pr,
percent,
localBudget: pr.limit
};
}

/** Create a mock npm package in a tmp dir on the system. */
export function mockPackage() {
export function mockPackage(): string {
const id = Math.random().toString(36).substring(7);
const dir = path.join(os.tmpdir(), `package-size-${id}`);

Expand Down
Loading