Skip to content

Commit

Permalink
Add type-safe event handler for surfacing runtime info in CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
DrewML committed Sep 3, 2019
1 parent ecb0aef commit 71baf5e
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 30 deletions.
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"jsesc": "^2.5.2",
"magic-string": "^0.25.3",
"pretty-bytes": "^5.3.0",
"pretty-ms": "^5.0.0",
"requirejs": "^2.3.6",
"source-map-support": "^0.5.12",
"terser": "^4.2.0"
Expand Down
10 changes: 8 additions & 2 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ import { optimizeThemes } from './optimizeThemes';
import { collectStoreData } from './collectStoreData';
import { findMagentoRoot } from './magentoFS';
import { StoreData } from './types';
import { debugEvent } from './debug';
import { initLogListener } from './cliLogListener';

/**
* @summary Execute the CLI
*/
export async function run(cwd: string) {
initLogListener();

const magentoRoot = await findMagentoRoot(cwd);
if (!magentoRoot) {
throw new Error(`Could not find a Magento 2 installation from ${cwd}`);
Expand All @@ -15,7 +19,9 @@ export async function run(cwd: string) {
const store = await collectStoreData(magentoRoot);
const themesToOptimize = getSupportedAndDeployedThemeIDs(store);

if (!themesToOptimize.length) {
if (themesToOptimize.length) {
debugEvent({ type: 'eligibleThemes', payload: themesToOptimize });
} else {
throw new Error(
'No eligible themes were found to be optimized. For a theme ' +
'to be optimized, it must:\n\n' +
Expand All @@ -26,7 +32,7 @@ export async function run(cwd: string) {
}

const results = await optimizeThemes(magentoRoot, store, themesToOptimize);
console.log(JSON.stringify(results, null, 2));
// TODO: Log some summary
}

function getSupportedAndDeployedThemeIDs(store: StoreData): string[] {
Expand Down
67 changes: 67 additions & 0 deletions src/cliLogListener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { onDebugEvent } from './debug';
import prettyBytes from 'pretty-bytes';
import prettyMS from 'pretty-ms';

export function initLogListener() {
onDebugEvent(event => {
switch (event.type) {
case 'collectStoreData:start': {
console.log(
'Collecting data about themes and modules from the store',
);
break;
}
case 'collectStoreData:end': {
const { timing } = event;
const total = timing.end - timing.start;

console.log(`Data collection completed in ${prettyMS(total)}`);
break;
}
case 'eligibleThemes': {
const themeLines = event.payload.map(n => ` - ${n}`);
const themeLabel = themeLines.length > 1 ? 'themes' : 'theme';
console.log(
[
`Found ${event.payload.length} eligible ${themeLabel} to optimize\n`,
...themeLines,
].join(''),
);
break;
}
case 'createBundle:start': {
const { bundleName, themeID, deps } = event;
console.log(
`Starting to bundle ${bundleName} for ${themeID} with ${deps.length} dependencies`,
);
break;
}
case 'invalidShims': {
const { themeID, deps } = event;
const shimLines = deps.map(d => ` - ${d}\n`);
console.log(
[
'One or more invalid shim configurations were found ',
`while bundling ${themeID}. RequireJS does not support `,
'shim configuration for modules that already call "define". ',
'You may have unexpected results when running the bundle in ',
`your store\n Invalid shims for:\n`,
...shimLines,
].join(''),
);
break;
}
case 'createBundle:end': {
const { timing, themeID, bundleName, bundleSize } = event;
const total = timing.end - timing.start;
const size = prettyBytes(bundleSize);

console.log(
`Finished bundling ${bundleName} (${size} unminified) ` +
`for ${themeID} in ${prettyMS(total)}`,
);
break;
}
}
});
}
18 changes: 17 additions & 1 deletion src/collectStoreData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
getDeployedThemes,
} from './magentoFS';
import { StoreData } from './types';
import { debugEvent, debugTimer } from './debug';

/**
* @summary Collect (in parallel) all the data needed up-front for
Expand All @@ -12,11 +13,26 @@ import { StoreData } from './types';
export async function collectStoreData(
magentoRoot: string,
): Promise<StoreData> {
const eventTime = debugTimer();
debugEvent({ type: 'collectStoreData:start' });

const [enabledModules, components, deployedThemes] = await Promise.all([
getEnabledModules(magentoRoot),
getComponents(magentoRoot),
getDeployedThemes(magentoRoot),
]);

return { enabledModules, components, deployedThemes };
const storeData: StoreData = {
enabledModules,
components,
deployedThemes,
};

debugEvent({
type: 'collectStoreData:end',
storeData,
timing: eventTime(),
});

return storeData;
}
62 changes: 35 additions & 27 deletions src/createBundleFromDeps.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { log } from './log';
import { extname, parse, join } from 'path';
import { getShimsForModule } from './requireConfig';
import {
Expand All @@ -13,6 +12,7 @@ import { readFile } from './fsPromises';
import { MagentoRequireConfig } from './types';
import MagicString, { Bundle } from 'magic-string';
import { createRequireResolver } from './createRequireResolver';
import { debugEvent } from './debug';

/**
* @summary Create a bundle file (compatible with the RequireJS runtime)
Expand All @@ -23,14 +23,23 @@ export async function createBundleFromDeps(
deps: string[],
baseDir: string,
requireConfig: MagentoRequireConfig,
themeID: string,
) {
const resolver = createRequireResolver(requireConfig);
const transformedModules = await Promise.all(
deps.map(d =>
getFinalModuleSource(d, baseDir, resolver, requireConfig),
),
);
const bundle = createBundle(transformedModules);
const { bundle, depsWithInvalidShims } = createBundle(transformedModules);
if (depsWithInvalidShims) {
debugEvent({
type: 'invalidShims',
themeID,
deps: depsWithInvalidShims,
});
}

const bundleFilename = `${parse(bundleName).name}.js`;
const sourcemap = bundle.generateMap({
source: bundleFilename,
Expand All @@ -57,53 +66,52 @@ async function getFinalModuleSource(
const shims = getShimsForModule(dep, requireConfig);
const hasDefine = isAMDWithDefine(source);
const isNamed = isNamedAMD(source);
const hasInvalidShim = hasDefine && !!shims;

if (isHTML) {
return { dep, file: wrapTextModule(dep, source) };
return { dep, file: wrapTextModule(dep, source), hasInvalidShim };
}

if (isNamed) {
return { dep, file: new MagicString(source) };
}

if (hasDefine && shims) {
// Note from the RequireJS docs:
// "Remember: only use shim config for non-AMD scripts,
// scripts that do not already call define(). The shim
// config will not work correctly if used on AMD scripts,
// in particular, the exports and init config will not
// be triggered, and the deps config will be confusing
// for those cases."
// - https://requirejs.org/docs/api.html#config-shim
log.warn(
`Found shim configuration for "${dep}", but it ` +
'is already an AMD module. RequireJS does not support ' +
'shimming AMD modules. You may see unexpected behavior ' +
'as a result.',
);
return { dep, file: new MagicString(source), hasInvalidShim };
}

if (!hasDefine) {
if (shims) {
return { dep, file: wrapShimmedModule(dep, source, shims) };
return {
dep,
file: wrapShimmedModule(dep, source, shims),
hasInvalidShim,
};
}

if (!shims) {
return { dep, file: wrapNonShimmedModule(dep, source) };
return {
dep,
file: wrapNonShimmedModule(dep, source),
hasInvalidShim,
};
}
}

return { dep, file: renameModule(dep, source) };
return { dep, file: renameModule(dep, source), hasInvalidShim };
}

function createBundle(modules: { dep: string; file: MagicString }[]) {
function createBundle(
modules: { dep: string; file: MagicString; hasInvalidShim: boolean }[],
) {
const bundle = new Bundle();
const depsWithInvalidShims: string[] = [];

bundle.prepend(`/* Generated by @magento/baler - ${Date()} */\n\n`);
for (const { dep, file } of modules) {

for (const { dep, file, hasInvalidShim } of modules) {
bundle.addSource({
filename: `../${dep}.js`,
content: file,
});
if (hasInvalidShim) depsWithInvalidShims.push(dep);
}
return bundle;

return { bundle, depsWithInvalidShims };
}
73 changes: 73 additions & 0 deletions src/debug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { StoreData } from './types';

type EventTiming = { start: number; end: number };

type BalerDebugEvent =
| { type: 'collectStoreData:start' }
| {
type: 'collectStoreData:end';
storeData: StoreData;
timing: EventTiming;
}
| { type: 'eligibleThemes'; payload: string[] }
| {
type: 'createBundle:start';
themeID: string;
bundleName: string;
deps: string[];
}
| {
type: 'invalidShims';
themeID: string;
deps: string[];
}
| {
type: 'createBundle:end';
themeID: string;
bundleName: string;
bundleSize: number;
timing: EventTiming;
};

type DebugEventHandler = (event: BalerDebugEvent) => void;

const events: BalerDebugEvent[] = [];

const handlers: Set<DebugEventHandler> = new Set();

/**
* @summary Logs data that may be useful to a user of Baler.
* Currently surfaced via specific CLI flags
*/
export function debugEvent(event: BalerDebugEvent) {
events.push(event);
for (const handler of handlers) {
handler(event);
}
}

export function debugTimer(): () => EventTiming {
const start = Date.now();
return () => {
const end = Date.now();
return { start, end };
};
}

/**
* @summary Get a list of all debug events
*/
export function getEvents() {
return events.slice();
}

/**
* @summary Register a handler to be called whenever a new
* debug event is dispatched
*/
export function onDebugEvent(handler: DebugEventHandler) {
handlers.add(handler);
return () => {
handlers.delete(handler);
};
}
Loading

0 comments on commit 71baf5e

Please sign in to comment.