Socket
Socket
Sign inDemoInstall

@datadog/build-plugin

Package Overview
Dependencies
Maintainers
3
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@datadog/build-plugin - npm Package Compare versions

Comparing version 0.4.5 to 1.0.0

dist/__tests__/BaseClass.test.d.ts

10

dist/__tests__/helpers.test.js

@@ -49,2 +49,12 @@ "use strict";

});
test('It should getContext with and without constructor', () => {
const { getContext } = require('../helpers');
const BasicClass = function BasicClass() { };
const instance1 = new BasicClass();
const instance2 = new BasicClass();
instance2.constructor = null;
expect(() => {
getContext([instance1, instance2]);
}).not.toThrow();
});
});

3

dist/__tests__/helpers/testHelpers.d.ts

@@ -1,5 +0,6 @@

import { Stats, Report, Compilation, Compiler } from '../../types';
import { BundlerStats, Stats, Report, Compilation, Compiler } from '../../types';
export declare const mockStats: Stats;
export declare const mockBundler: BundlerStats;
export declare const mockReport: Report;
export declare const mockCompilation: Compilation;
export declare const mockCompiler: Compiler;

@@ -28,7 +28,18 @@ "use strict";

};
exports.mockBundler = {
webpack: exports.mockStats,
esbuild: {
warnings: [],
errors: [],
entrypoints: [],
duration: 0,
inputs: {},
outputs: {},
},
};
exports.mockReport = {
timings: {
tapables: {},
loaders: {},
modules: {},
tapables: new Map(),
loaders: new Map(),
modules: new Map(),
},

@@ -35,0 +46,0 @@ dependencies: {},

@@ -1,5 +0,6 @@

import { Module, Compilation } from './types';
import { Module, Compilation, Context } from './types';
export declare const getPluginName: (opts: string | {
name: string;
}) => string;
export declare const formatContext: (context?: string) => string;
export declare const getDisplayName: (name: string, context?: string | undefined) => string;

@@ -12,1 +13,3 @@ export declare const formatModuleName: (name: string, context: string) => string;

export declare const formatDuration: (duration: number) => string;
export declare const writeFile: (filePath: string, content: any) => Promise<unknown>;
export declare const getContext: (args: any[]) => Context[];

@@ -6,8 +6,14 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const fs_extra_1 = require("fs-extra");
exports.getPluginName = (opts) => typeof opts === 'string' ? opts : opts.name;
// We want to ensure context ends with a slash.
exports.formatContext = (context = '') => {
return context.endsWith('/') ? context : `${context}/`;
};
// Format a module name by trimming the user's specific part out.
exports.getDisplayName = (name, context) => {
let toReturn = name;
if (context && name.split(context).pop()) {
toReturn = name.split(context).pop();
const nameSplit = name.split(exports.formatContext(context));
if (context && nameSplit.length) {
toReturn = nameSplit.pop();
}

@@ -19,3 +25,5 @@ return (toReturn

// Remove everything in front of /node_modules
.replace(/(.*)?\/node_modules\//, '/node_modules/'));
.replace(/(.*)?\/node_modules\//, '/node_modules/')
// Remove any prefixing ../
.replace(/^((\.)*\/)+/, ''));
};

@@ -28,3 +36,3 @@ exports.formatModuleName = (name, context) => name

// let's do the same so we can integrate better with it.
.replace(context, '.');
.replace(exports.formatContext(context), './');
// Find the module name and format it the same way as webpack.

@@ -78,1 +86,17 @@ exports.getModuleName = (module, context, compilation) => {

};
// Make it so if JSON.stringify fails it rejects the promise and not the whole process.
exports.writeFile = (filePath, content) => {
return new Promise((resolve) => {
return fs_extra_1.outputFile(filePath, JSON.stringify(content, null, 4)).then(resolve);
});
};
exports.getContext = (args) => {
return args.map((arg) => {
var _a, _b;
return ({
type: (_b = (_a = arg === null || arg === void 0 ? void 0 : arg.constructor) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : typeof arg,
name: arg === null || arg === void 0 ? void 0 : arg.name,
value: typeof arg === 'string' ? arg : undefined,
});
});
};

@@ -6,4 +6,4 @@ "use strict";

describe('Renderer', () => {
test('It should outputGenerals the same with Webpack 5 and 4', () => {
const { outputGenerals } = require('../renderer');
test('It should outputWebpack the same with Webpack 5 and 4', () => {
const { outputWebpack } = require('../renderer');
const ar = [{ name: 'element1' }, { name: 'element2' }];

@@ -25,4 +25,4 @@ const obj = { obj0: ar[0], obj1: ar[1] };

const statsWebpack5 = Object.assign(Object.assign({}, statsDefault), { compilation: Object.assign(Object.assign({}, statsDefault.compilation), { emittedAssets: set, modules: set, entries: map, chunks: set }) });
const outputWebpack4 = outputGenerals(statsWebpack4);
const outputWebpack5 = outputGenerals(statsWebpack5);
const outputWebpack4 = outputWebpack(statsWebpack4);
const outputWebpack5 = outputWebpack(statsWebpack5);
expect(outputWebpack4).toBe(outputWebpack5);

@@ -29,0 +29,0 @@ });

@@ -5,145 +5,12 @@ "use strict";

// Copyright 2019-Present Datadog, Inc.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const testHelpers_1 = require("../../../__tests__/helpers/testHelpers");
const path_1 = __importDefault(require("path"));
const aggregator_1 = require("../aggregator");
const exec = require('util').promisify(require('child_process').exec);
const PROJECTS_ROOT = path_1.default.join(__dirname, '../../../__tests__/mocks/projects');
describe('Aggregator', () => {
beforeAll(() => __awaiter(void 0, void 0, void 0, function* () {
yield exec(`yarn build`);
}), 20000);
for (const version of [4, 5]) {
describe(`Webpack ${version}`, () => {
let statsJson;
let dependenciesJson;
const WEBPACK_ROOT = path_1.default.join(PROJECTS_ROOT, `./webpack${version}`);
const OUTPUT = path_1.default.join(WEBPACK_ROOT, './webpack-profile-debug/');
beforeAll(() => __awaiter(void 0, void 0, void 0, function* () {
const output = yield exec(`yarn workspace webpack${version} build`);
// eslint-disable-next-line no-console
console.log(`Build ${version} :`, output.stderr);
statsJson = require(path_1.default.join(OUTPUT, './stats.json'));
dependenciesJson = require(path_1.default.join(OUTPUT, './dependencies.json'));
}), 20000);
test('It should aggregate metrics without throwing.', () => {
const { getMetrics } = require('../aggregator');
const opts = { context: '', filters: [], tags: [] };
expect(() => {
getMetrics(testHelpers_1.mockReport, testHelpers_1.mockStats, opts);
}).not.toThrow();
});
describe('Modules', () => {
let metrics;
beforeAll(() => {
const indexed = aggregator_1.getIndexed(statsJson, WEBPACK_ROOT);
metrics = aggregator_1.getModules(statsJson, dependenciesJson, indexed, WEBPACK_ROOT);
});
test('It should give module metrics.', () => {
expect(metrics.length).not.toBe(0);
});
test(`It should filter out webpack's modules.`, () => {
expect(metrics.find((m) => {
return m.tags.find((t) => /^moduleName:webpack\/runtime/.test(t));
})).toBeUndefined();
});
test(`It should add tags about the entry and the chunk.`, () => {
for (const metric of metrics) {
expect(metric.tags).toContain('entryName:yolo');
expect(metric.tags).toContain('entryName:cheesecake');
expect(metric.tags).toContain('chunkName:yolo');
expect(metric.tags).toContain('chunkName:cheesecake');
}
});
test('It should have 3 metrics per module.', () => {
const modules = [
'./src/file0000.js',
'./src/file0001.js',
'./workspaces/app/file0000.js',
'./workspaces/app/file0001.js',
];
for (const module of modules) {
const modulesMetrics = metrics.filter((m) => m.tags.includes(`moduleName:${module}`));
expect(modulesMetrics.length).toBe(3);
}
});
});
describe('Entries', () => {
let metrics;
beforeAll(() => {
const indexed = aggregator_1.getIndexed(statsJson, WEBPACK_ROOT);
metrics = aggregator_1.getEntries(statsJson, indexed);
});
test('It should give entries metrics.', () => {
expect(metrics.length).not.toBe(0);
});
test('It should give 4 metrics per entry.', () => {
const entries = ['yolo', 'cheesecake'];
for (const entry of entries) {
const entriesMetrics = metrics.filter((m) => m.tags.includes(`entryName:${entry}`));
expect(entriesMetrics.length).toBe(4);
}
});
});
describe('Chunks', () => {
let metrics;
beforeAll(() => {
const indexed = aggregator_1.getIndexed(statsJson, WEBPACK_ROOT);
metrics = aggregator_1.getChunks(statsJson, indexed);
});
test('It should give chunks metrics.', () => {
expect(metrics.length).not.toBe(0);
});
test('It should give 2 metrics per chunk.', () => {
const chunks = ['yolo', 'cheesecake'];
for (const chunk of chunks) {
const chunksMetrics = metrics.filter((m) => m.tags.includes(`chunkName:${chunk}`));
expect(chunksMetrics.length).toBe(2);
}
});
test(`It should add tags about the entry.`, () => {
for (const metric of metrics) {
expect(metric.tags.join(',')).toMatch(/entryName:(yolo|cheesecake)/);
}
});
});
describe('Assets', () => {
let metrics;
beforeAll(() => {
const indexed = aggregator_1.getIndexed(statsJson, WEBPACK_ROOT);
metrics = aggregator_1.getAssets(statsJson, indexed);
});
test('It should give assets metrics.', () => {
expect(metrics.length).not.toBe(0);
});
test('It should give 1 metric per asset.', () => {
const assets = ['yolo.js', 'cheesecake.js'];
for (const asset of assets) {
const assetsMetrics = metrics.filter((m) => m.tags.includes(`assetName:${asset}`));
expect(assetsMetrics.length).toBe(1);
}
});
test(`It should add tags about the entry and the chunk.`, () => {
for (const metric of metrics) {
expect(metric.tags).toContain('entryName:yolo');
expect(metric.tags).toContain('entryName:cheesecake');
expect(metric.tags).toContain('chunkName:yolo');
expect(metric.tags).toContain('chunkName:cheesecake');
}
});
});
});
}
test('It should aggregate metrics without throwing.', () => {
const { getMetrics } = require('../aggregator');
const opts = { context: '', filters: [], tags: [] };
expect(() => {
getMetrics(opts, testHelpers_1.mockReport, testHelpers_1.mockStats);
}).not.toThrow();
});
});

@@ -18,2 +18,6 @@ "use strict";

const buildPluginMock = {
log: (...args) => {
// eslint-disable-next-line no-console
console.log(...args);
},
options: {},

@@ -25,3 +29,3 @@ };

report: testHelpers_1.mockReport,
stats: testHelpers_1.mockStats,
bundler: testHelpers_1.mockBundler,
});

@@ -28,0 +32,0 @@ expect(typeof obj).toBe('object');

@@ -1,18 +0,3 @@

import { Chunk, Report, StatsJson, Stats, Module, LocalModules, IndexedObject } from '../../types';
import { Metric, MetricToSend, GetMetricsOptions } from './types';
export declare const getFromId: (coll: any[], id: string) => any;
export declare const foundInModules: (input: {
modules?: Module[] | undefined;
}, identifier?: string | undefined) => boolean;
export declare const getChunksFromModule: (stats: StatsJson, chunksPerId: {
[key: string]: Chunk;
}, module: Module) => Chunk[];
export declare const getEntriesFromChunk: (stats: StatsJson, chunk: Chunk, indexed: IndexedObject, parentEntries?: Set<string>, parentChunks?: Set<string>) => Set<string>;
export declare const getEntryTags: (entries: Set<string>) => string[];
export declare const getChunkTags: (chunks: Chunk[]) => string[];
export declare const getModules: (stats: StatsJson, dependencies: LocalModules, indexed: IndexedObject, context: string) => Metric[];
export declare const getChunks: (stats: StatsJson, indexed: IndexedObject) => Metric[];
export declare const getAssets: (stats: StatsJson, indexed: IndexedObject) => Metric[];
export declare const getEntries: (stats: StatsJson, indexed: IndexedObject) => Metric[];
export declare const getIndexed: (stats: StatsJson, context: string) => IndexedObject;
export declare const getMetrics: (report: Report, stats: Stats, opts: GetMetricsOptions) => MetricToSend[];
import { Report, BundlerStats } from '../../types';
import { MetricToSend, GetMetricsOptions } from './types';
export declare const getMetrics: (opts: GetMetricsOptions, report: Report, bundler: BundlerStats) => MetricToSend[];

@@ -5,396 +5,53 @@ "use strict";

// Copyright 2019-Present Datadog, Inc.
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const helpers_1 = require("../../helpers");
const helpers_2 = require("./helpers");
const flattened = (arr) => [].concat(...arr);
const getType = (name) => (name.includes('.') ? name.split('.').pop() : 'unknown');
const getGenerals = (timings, stats) => [
{
metric: 'modules.count',
type: 'count',
value: stats.modules.length,
tags: [],
},
{
metric: 'chunks.count',
type: 'count',
value: stats.chunks.length,
tags: [],
},
{
metric: 'assets.count',
type: 'count',
value: stats.assets.length,
tags: [],
},
{
metric: 'plugins.count',
type: 'count',
value: Object.keys(timings.tapables).length,
tags: [],
},
{
metric: 'loaders.count',
type: 'count',
value: Object.keys(timings.loaders).length,
tags: [],
},
{
metric: 'warnings.count',
type: 'count',
value: stats.warnings.length,
tags: [],
},
{
metric: 'errors.count',
type: 'count',
value: stats.errors.length,
tags: [],
},
{
metric: 'entries.count',
type: 'count',
value: Object.keys(stats.entrypoints).length,
tags: [],
},
{
metric: 'compilation.duration',
type: 'duration',
value: stats.time,
tags: [],
},
];
const getDependencies = (modules) => flattened(modules.map((m) => [
{
metric: 'modules.dependencies',
type: 'count',
value: m.dependencies.length,
tags: [`moduleName:${m.name}`, `moduleType:${getType(m.name)}`],
},
{
metric: 'modules.dependents',
type: 'count',
value: m.dependents.length,
tags: [`moduleName:${m.name}`, `moduleType:${getType(m.name)}`],
},
]));
const getPlugins = (plugins) => {
const helpers_1 = require("./helpers");
const wp = __importStar(require("./metrics/webpack"));
const es = __importStar(require("./metrics/esbuild"));
const common_1 = require("./metrics/common");
const getWebpackMetrics = (statsJson, opts) => {
const metrics = [];
for (const plugin of Object.values(plugins)) {
let pluginDuration = 0;
let pluginCount = 0;
for (const hook of Object.values(plugin.hooks)) {
let hookDuration = 0;
pluginCount += hook.values.length;
for (const v of hook.values) {
const duration = v.end - v.start;
hookDuration += duration;
pluginDuration += duration;
}
metrics.push({
metric: 'plugins.hooks.duration',
type: 'duration',
value: hookDuration,
tags: [`pluginName:${plugin.name}`, `hookName:${hook.name}`],
}, {
metric: 'plugins.hooks.increment',
type: 'count',
value: hook.values.length,
tags: [`pluginName:${plugin.name}`, `hookName:${hook.name}`],
});
}
metrics.push({
metric: 'plugins.duration',
type: 'duration',
value: pluginDuration,
tags: [`pluginName:${plugin.name}`],
}, {
metric: 'plugins.increment',
type: 'count',
value: pluginCount,
tags: [`pluginName:${plugin.name}`],
});
}
const indexed = wp.getIndexed(statsJson, opts.context);
metrics.push(...wp.getModules(statsJson, indexed, opts.context));
metrics.push(...wp.getChunks(statsJson, indexed));
metrics.push(...wp.getAssets(statsJson, indexed));
metrics.push(...wp.getEntries(statsJson, indexed));
return metrics;
};
const getLoaders = (loaders) => flattened(Object.values(loaders).map((loader) => [
{
metric: 'loaders.duration',
type: 'duration',
value: loader.duration,
tags: [`loaderName:${loader.name}`],
},
{
metric: 'loaders.increment',
type: 'count',
value: loader.increment,
tags: [`loaderName:${loader.name}`],
},
]));
// Register the imported tree of a module
const findDependencies = (moduleName, dependencies, moduleDeps = new Set()) => {
if (!dependencies[moduleName]) {
return moduleDeps;
}
for (const dependency of dependencies[moduleName].dependencies) {
if (!moduleDeps.has(dependency)) {
moduleDeps.add(dependency);
findDependencies(dependency, dependencies, moduleDeps);
}
}
return moduleDeps;
const getEsbuildMetrics = (stats, opts) => {
const metrics = [];
const indexed = es.getIndexed(stats, opts.context);
metrics.push(...es.getModules(stats, indexed, opts.context));
metrics.push(...es.getAssets(stats, indexed, opts.context));
metrics.push(...es.getEntries(stats, indexed, opts.context));
return metrics;
};
exports.getFromId = (coll, id) => coll.find((c) => c.id === id);
exports.foundInModules = (input, identifier) => {
if (!identifier || !input.modules || !input.modules.length) {
return false;
}
return !!input.modules.find((m) => {
if (m.identifier && m.identifier === identifier) {
return true;
// eslint-disable-next-line no-underscore-dangle
exports.getMetrics = (opts, report, bundler) => {
const { timings, dependencies } = report;
const metrics = [];
metrics.push(...common_1.getGenerals(common_1.getGeneralReport(report, bundler)));
if (timings) {
if (timings.tapables) {
metrics.push(...common_1.getPlugins(timings.tapables));
}
else if (m._identifier && m._identifier === identifier) {
return true;
if (timings.loaders) {
metrics.push(...common_1.getLoaders(timings.loaders));
}
if (m.modules && m.modules.length) {
return exports.foundInModules(m, identifier);
}
});
};
exports.getChunksFromModule = (stats, chunksPerId, module) => {
if (module.chunks.length) {
return module.chunks.map((c) => chunksPerId[c]);
}
// Find the chunks from the chunk list directly.
// Webpack may not have registered module's chunks in some cases.
// eslint-disable-next-line no-underscore-dangle
return stats.chunks.filter((c) => exports.foundInModules(c, module.identifier || module._identifier));
};
exports.getEntriesFromChunk = (stats, chunk, indexed, parentEntries = new Set(), parentChunks = new Set()) => {
const entry = indexed.entriesPerChunkId[chunk.id];
if (entry) {
parentEntries.add(entry.name);
if (dependencies) {
metrics.push(...common_1.getDependencies(Object.values(dependencies)));
}
// Escape cyclic dependencies.
if (parentChunks.has(chunk.id)) {
return parentEntries;
if (bundler.webpack) {
const statsJson = bundler.webpack.toJson({ children: false });
metrics.push(...getWebpackMetrics(statsJson, opts));
}
parentChunks.add(chunk.id);
chunk.parents.forEach((p) => {
const parentChunk = indexed.chunksPerId[p];
if (parentChunk) {
exports.getEntriesFromChunk(stats, parentChunk, indexed, parentEntries, parentChunks);
}
});
return parentEntries;
};
exports.getEntryTags = (entries) => Array.from(entries).map((e) => `entryName:${e}`);
exports.getChunkTags = (chunks) => flattened(chunks
.map((c) => {
if (c.names && c.names.length) {
return c.names.map((n) => `chunkName:${n}`);
if (bundler.esbuild) {
metrics.push(...getEsbuildMetrics(bundler.esbuild, opts));
}
})
.filter((c) => c));
const getMetricsFromModule = (stats, dependencies, indexed, context, module) => {
const chunks = exports.getChunksFromModule(stats, indexed.chunksPerId, module);
const entries = new Set();
for (const chunk of chunks) {
exports.getEntriesFromChunk(stats, chunk, indexed, entries);
}
const chunkTags = exports.getChunkTags(chunks);
const entryTags = exports.getEntryTags(entries);
const moduleName = helpers_1.getDisplayName(module.name, context);
// The reason we have to do two loops over modules.
const tree = Array.from(findDependencies(module.name, dependencies)).map((dependencyName) => indexed.modulesPerName[dependencyName]);
const treeSize = tree.reduce((previous, current) => {
return previous + helpers_1.getModuleSize(current);
}, 0);
return [
{
metric: 'modules.size',
type: 'size',
value: module.size,
tags: [
`moduleName:${moduleName}`,
`moduleType:${getType(moduleName)}`,
...entryTags,
...chunkTags,
],
},
{
metric: 'modules.tree.size',
type: 'size',
value: treeSize,
tags: [
`moduleName:${moduleName}`,
`moduleType:${getType(moduleName)}`,
...entryTags,
...chunkTags,
],
},
{
metric: 'modules.tree.count',
type: 'count',
value: tree.length,
tags: [
`moduleName:${moduleName}`,
`moduleType:${getType(moduleName)}`,
...entryTags,
...chunkTags,
],
},
];
};
exports.getModules = (stats, dependencies, indexed, context) => {
return flattened(Object.values(indexed.modulesPerName).map((module) => {
return getMetricsFromModule(stats, dependencies, indexed, context, module);
}));
};
// Find in entries.chunks
exports.getChunks = (stats, indexed) => {
const chunks = stats.chunks;
return flattened(chunks.map((chunk) => {
const entryTags = exports.getEntryTags(exports.getEntriesFromChunk(stats, chunk, indexed));
const chunkName = chunk.names.length ? chunk.names.join(' ') : chunk.id;
return [
{
metric: 'chunks.size',
type: 'size',
value: chunk.size,
tags: [`chunkName:${chunkName}`, ...entryTags],
},
{
metric: 'chunks.modules.count',
type: 'count',
value: chunk.modules.length,
tags: [`chunkName:${chunkName}`, ...entryTags],
},
];
}));
};
exports.getAssets = (stats, indexed) => {
const assets = stats.assets;
return assets.map((asset) => {
const chunks = asset.chunks.map((c) => indexed.chunksPerId[c]);
const entries = new Set();
for (const chunk of chunks) {
exports.getEntriesFromChunk(stats, chunk, indexed, entries);
}
const chunkTags = exports.getChunkTags(chunks);
const entryTags = exports.getEntryTags(entries);
const assetName = asset.name;
return {
metric: 'assets.size',
type: 'size',
value: asset.size,
tags: [
`assetName:${assetName}`,
`assetType:${getType(assetName)}`,
...chunkTags,
...entryTags,
],
};
});
};
exports.getEntries = (stats, indexed) => flattened(Object.keys(stats.entrypoints).map((entryName) => {
const entry = stats.entrypoints[entryName];
const chunks = entry.chunks.map((chunkId) => indexed.chunksPerId[chunkId]);
let size = 0;
let moduleCount = 0;
let assetsCount = 0;
for (const chunk of chunks) {
size += chunk.size;
moduleCount += chunk.modules.length;
assetsCount += chunk.files.length;
}
return [
{
metric: 'entries.size',
type: 'size',
value: size,
tags: [`entryName:${entryName}`],
},
{
metric: 'entries.chunks.count',
type: 'count',
value: chunks.length,
tags: [`entryName:${entryName}`],
},
{
metric: 'entries.modules.count',
type: 'count',
value: moduleCount,
tags: [`entryName:${entryName}`],
},
{
metric: 'entries.assets.count',
type: 'count',
value: assetsCount,
tags: [`entryName:${entryName}`],
},
];
}));
exports.getIndexed = (stats, context) => {
// Gather all modules.
const modulesPerName = {};
const chunksPerId = {};
const entriesPerChunkId = {};
const addModule = (module) => {
// console.log('Add Module', module.name);
// No internals.
if (/^webpack\/runtime/.test(module.name)) {
return;
}
// No duplicates.
if (modulesPerName[helpers_1.formatModuleName(module.name, context)]) {
return;
}
// Modules are sometimes registered with their loader.
if (module.name.includes('!')) {
return;
}
modulesPerName[helpers_1.formatModuleName(module.name, context)] = module;
};
for (const [name, entry] of Object.entries(stats.entrypoints)) {
// In webpack4 we don't have the name of the entry here.
entry.name = name;
for (const chunkId of entry.chunks) {
entriesPerChunkId[chunkId] = entry;
}
}
for (const chunk of stats.chunks) {
chunksPerId[chunk.id] = chunk;
}
for (const module of stats.modules) {
// Sometimes modules are grouped together.
if (module.modules && module.modules.length) {
for (const moduleIn of module.modules) {
addModule(moduleIn);
}
}
else {
addModule(module);
}
}
return {
modulesPerName,
chunksPerId,
entriesPerChunkId,
};
};
exports.getMetrics = (report, stats, opts) => {
const statsJson = stats.toJson({ children: false });
const { timings, dependencies } = report;
const metrics = [];
const indexed = exports.getIndexed(statsJson, opts.context);
metrics.push(...getGenerals(timings, statsJson));
metrics.push(...getDependencies(Object.values(dependencies)));
metrics.push(...getPlugins(timings.tapables));
metrics.push(...getLoaders(timings.loaders));
metrics.push(...exports.getModules(statsJson, dependencies, indexed, opts.context));
metrics.push(...exports.getChunks(statsJson, indexed));
metrics.push(...exports.getAssets(statsJson, indexed));
metrics.push(...exports.getEntries(statsJson, indexed));
// Format metrics to be DD ready and apply filters

@@ -412,3 +69,3 @@ const metricsToSend = metrics

}
return metric ? helpers_2.getMetric(metric, opts) : null;
return metric ? helpers_1.getMetric(metric, opts) : null;
})

@@ -415,0 +72,0 @@ .filter((m) => m !== null);

import { Metric, Options, MetricToSend } from './types';
export declare const defaultFilters: ((metric: Metric) => Metric | null)[];
export declare const getMetric: (metric: Metric, opts: Options) => MetricToSend;
export declare const flattened: (arr: any[]) => never[];
export declare const getType: (name: string) => string | undefined;

@@ -58,1 +58,3 @@ "use strict";

});
exports.flattened = (arr) => [].concat(...arr);
exports.getType = (name) => (name.includes('.') ? name.split('.').pop() : 'unknown');
import { BuildPlugin } from '../../webpack';
import { DDHooksContext, MetricToSend } from './types';
export declare const hooks: {
preoutput: (this: BuildPlugin, { report, stats }: DDHooksContext) => Promise<{
preoutput: (this: BuildPlugin, { report, bundler }: DDHooksContext) => Promise<{
metrics: MetricToSend[];

@@ -6,0 +6,0 @@ }>;

@@ -31,3 +31,3 @@ "use strict";

});
const preoutput = function output({ report, stats }) {
const preoutput = function output({ report, bundler }) {
return __awaiter(this, void 0, void 0, function* () {

@@ -37,6 +37,6 @@ const optionsDD = getOptionsDD(this.options.datadog);

try {
metrics = aggregator_1.getMetrics(report, stats, Object.assign(Object.assign({}, optionsDD), { context: this.options.context }));
metrics = aggregator_1.getMetrics(Object.assign(Object.assign({}, optionsDD), { context: this.options.context }), report, bundler);
}
catch (e) {
this.log(`Couldn't aggregate metrics. ${e.toString()}`, 'error');
this.log(`Couldn't aggregate metrics: ${e.stack}`, 'error');
}

@@ -48,3 +48,3 @@ return { metrics };

return __awaiter(this, void 0, void 0, function* () {
const PLUGIN_NAME = this.constructor.name;
const PLUGIN_NAME = this.name;
const duration = Date.now() - start;

@@ -51,0 +51,0 @@ const optionsDD = getOptionsDD(this.options.datadog);

import { HooksContext } from '../types';
import { BuildPlugin } from '../webpack';
export declare const hooks: {
output: (this: BuildPlugin, { report, metrics, stats }: HooksContext) => Promise<void>;
output: (this: BuildPlugin, { report, metrics, bundler }: HooksContext) => Promise<void>;
};

@@ -19,11 +19,4 @@ "use strict";

const path_1 = __importDefault(require("path"));
const fs_extra_1 = require("fs-extra");
const helpers_1 = require("../helpers");
// Make it there so if JSON.stringify fails it rejects the promise and not the whole process.
const writeFile = (filePath, content) => {
return new Promise((resolve) => {
return fs_extra_1.outputFile(filePath, JSON.stringify(content, null, 4)).then(resolve);
});
};
const output = function output({ report, metrics, stats }) {
const output = function output({ report, metrics, bundler }) {
return __awaiter(this, void 0, void 0, function* () {

@@ -37,4 +30,5 @@ const opts = this.options.output;

dependencies: true,
stats: true,
bundler: true,
metrics: true,
result: true,
};

@@ -45,3 +39,3 @@ if (typeof opts === 'object') {

files.dependencies = opts.dependencies || false;
files.stats = opts.stats || false;
files.bundler = opts.bundlerStats || false;
files.metrics = opts.metrics || false;

@@ -56,16 +50,27 @@ }

const filesToWrite = {};
if (files.timings) {
if (files.timings && (report === null || report === void 0 ? void 0 : report.timings)) {
filesToWrite.timings = {
content: {
tapables: report.timings.tapables,
loaders: report.timings.loaders,
modules: report.timings.modules,
tapables: report.timings.tapables
? Array.from(report.timings.tapables.values())
: null,
loaders: report.timings.loaders
? Array.from(report.timings.loaders.values())
: null,
modules: report.timings.modules
? Array.from(report.timings.modules.values())
: null,
},
};
}
if (files.dependencies) {
if (files.dependencies && (report === null || report === void 0 ? void 0 : report.dependencies)) {
filesToWrite.dependencies = { content: report.dependencies };
}
if (files.stats) {
filesToWrite.stats = { content: stats.toJson({ children: false }) };
if (files.bundler) {
if (bundler.webpack) {
filesToWrite.bundler = { content: bundler.webpack.toJson({ children: false }) };
}
if (bundler.esbuild) {
filesToWrite.bundler = { content: bundler.esbuild };
}
}

@@ -78,3 +83,3 @@ if (metrics && files.metrics) {

this.log(`Start writing ${file}.json.`);
return writeFile(path_1.default.join(outputPath, `${file}.json`), filesToWrite[file].content)
return helpers_1.writeFile(path_1.default.join(outputPath, `${file}.json`), filesToWrite[file].content)
.then(() => {

@@ -81,0 +86,0 @@ this.log(`Wrote ${file}.json in ${helpers_1.formatDuration(Date.now() - start)}`);

import { BuildPlugin } from '../webpack';
import { HooksContext, Stats } from '../types';
export declare const outputGenerals: (stats: Stats) => void;
import { HooksContext, Stats, EsbuildStats } from '../types';
export declare const outputWebpack: (stats: Stats) => void;
export declare const outputEsbuild: (stats: EsbuildStats) => void;
export declare const hooks: {
output(this: BuildPlugin, { report, stats }: HooksContext): Promise<void>;
output(this: BuildPlugin, { report, bundler }: HooksContext): Promise<void>;
};

@@ -20,2 +20,3 @@ "use strict";

const chalk_1 = __importDefault(require("chalk"));
const pretty_bytes_1 = __importDefault(require("pretty-bytes"));
const helpers_1 = require("../helpers");

@@ -53,11 +54,21 @@ const TOP = 5;

const outputTapables = (timings) => {
const times = Object.values(timings);
// Sort by time, longest first
times.sort(sortDesc('duration'));
if (!timings) {
return;
}
const times = Array.from(timings.values());
if (!times.length) {
return;
}
// Output
console.log('\n===== Tapables =====');
console.log(`\n=== Top ${TOP} duration ===`);
// Sort by time, longest first
times.sort(sortDesc('duration'));
render(times, (time) => helpers_1.formatDuration(time.duration));
console.log(`\n=== Top ${TOP} hits ===`);
// Sort by time, longest first
times.sort(sortDesc('increment'));
render(times, (plugin) => plugin.increment);
};
exports.outputGenerals = (stats) => {
exports.outputWebpack = (stats) => {
console.log('\n===== General =====');

@@ -87,32 +98,77 @@ // More general stuffs.

};
const outputLoaders = (times) => {
// Sort by time, longest first
const loadersPerTime = Object.values(times).sort(sortDesc('duration'));
// Sort by hits, biggest first
const loadersPerIncrement = Object.values(times).sort(sortDesc('increment'));
exports.outputEsbuild = (stats) => {
console.log('\n===== General =====');
const nbDeps = stats.inputs ? Object.keys(stats.inputs).length : 0;
const nbFiles = stats.outputs ? Object.keys(stats.outputs).length : 0;
const nbWarnings = stats.warnings.length;
const nbErrors = stats.errors.length;
const nbEntries = stats.entrypoints ? Object.keys(stats.entrypoints).length : 0;
console.log(`
nbDeps: ${chalk_1.default.bold(nbDeps.toString())}
nbFiles: ${chalk_1.default.bold(nbFiles.toString())}
nbWarnings: ${chalk_1.default.bold(nbWarnings.toString())}
nbErrors: ${chalk_1.default.bold(nbErrors.toString())}
nbEntries: ${chalk_1.default.bold(nbEntries.toString())}
`);
};
const outputLoaders = (timings) => {
if (!timings) {
return;
}
const times = Array.from(timings.values());
if (!times.length) {
return;
}
// Output
console.log('\n===== Loaders =====');
console.log(`\n=== Top ${TOP} duration ===`);
render(loadersPerTime, (loader) => helpers_1.formatDuration(loader.duration));
// Sort by time, longest first
times.sort(sortDesc('duration'));
render(times, (loader) => helpers_1.formatDuration(loader.duration));
console.log(`\n=== Top ${TOP} hits ===`);
render(loadersPerIncrement, (loader) => loader.increment);
// Sort by hits, biggest first
times.sort(sortDesc('increment'));
render(times, (loader) => loader.increment);
};
const outputModules = (times, deps) => {
// Sort by dependents, biggest first
const modulesPerDependents = Object.values(deps).sort(sortDesc((mod) => mod.dependents.length));
// Sort by dependencies, biggest first
const modulesPerDepencies = Object.values(deps).sort(sortDesc((mod) => mod.dependencies.length));
// Sort by duration, longest first
const modulesPerTime = Object.values(times).sort(sortDesc('duration'));
// Output
console.log('\n===== Modules =====');
console.log(`\n=== Top ${TOP} dependents ===`);
render(modulesPerDependents, (module) => module.dependents.length);
console.log(`\n=== Top ${TOP} dependencies ===`);
render(modulesPerDepencies, (module) => module.dependencies.length);
console.log(`\n=== Top ${TOP} duration ===`);
render(modulesPerTime, (module) => helpers_1.formatDuration(module.duration));
const outputModules = (deps, timings) => {
if (!deps && !timings) {
return;
}
if (deps) {
const dependencies = Object.values(deps);
if (!dependencies.length) {
return;
}
console.log('\n===== Modules =====');
// Sort by dependents, biggest first
dependencies.sort(sortDesc((mod) => mod.dependents.length));
console.log(`\n=== Top ${TOP} dependents ===`);
render(dependencies, (module) => module.dependents.length);
// Sort by dependencies, biggest first
dependencies.sort(sortDesc((mod) => mod.dependencies.length));
console.log(`\n=== Top ${TOP} dependencies ===`);
render(dependencies, (module) => module.dependencies.length);
// Sort by size, biggest first
dependencies.sort(sortDesc('size'));
console.log(`\n=== Top ${TOP} size ===`);
render(dependencies, (module) => pretty_bytes_1.default(module.size));
}
if (timings) {
const times = Array.from(timings.values());
if (!times.length) {
return;
}
console.log('\n===== Modules =====');
// Sort by duration, longest first
times.sort(sortDesc('duration'));
console.log(`\n=== Top ${TOP} duration ===`);
render(times, (module) => helpers_1.formatDuration(module.duration));
// Sort by increment, longest first
times.sort(sortDesc('increment'));
console.log(`\n=== Top ${TOP} hits ===`);
render(times, (module) => module.increment);
}
};
exports.hooks = {
output({ report, stats }) {
output({ report, bundler }) {
return __awaiter(this, void 0, void 0, function* () {

@@ -122,8 +178,16 @@ if (this.options.output === false) {

}
outputTapables(report.timings.tapables);
outputLoaders(report.timings.loaders);
outputModules(report.timings.modules, report.dependencies);
exports.outputGenerals(stats);
if (report) {
outputTapables(report.timings.tapables);
outputLoaders(report.timings.loaders);
outputModules(report.dependencies, report.timings.modules);
}
if (bundler.webpack) {
exports.outputWebpack(bundler.webpack);
}
if (bundler.esbuild) {
exports.outputEsbuild(bundler.esbuild);
}
console.log();
});
},
};

@@ -0,1 +1,3 @@

import { Metafile, Message, BuildOptions } from 'esbuild';
import { MetricToSend } from './hooks/datadog/types';
export declare type HOOKS = 'output';

@@ -8,3 +10,12 @@ export declare type WRAPPED_HOOKS = 'preoutput' | 'output' | 'postoutput';

}
export interface IndexedObject {
export interface EsbuildIndexedObject {
entryNames: Map<string, string>;
inputsDependencies: {
[key: string]: Set<string>;
};
outputsDependencies: {
[key: string]: Set<string>;
};
}
export interface WebpackIndexedObject {
modulesPerName: {

@@ -36,3 +47,3 @@ [key: string]: Module;

dependencies?: boolean;
stats?: boolean;
bundlerStats?: boolean;
metrics?: boolean;

@@ -143,5 +154,5 @@ };

export interface TimingsReport {
tapables: TapableTimings;
loaders: ResultLoaders;
modules: ResultModules;
tapables?: TimingsMap;
loaders?: TimingsMap;
modules?: TimingsMap;
}

@@ -152,7 +163,17 @@ export interface Report {

}
export interface EsbuildStats extends Metafile {
warnings: Message[];
errors: Message[];
entrypoints: BuildOptions['entryPoints'];
duration: number;
}
export interface BundlerStats {
webpack?: Stats;
esbuild?: EsbuildStats;
}
export interface HooksContext {
start: number;
report: Report;
stats: Stats;
[key: string]: any;
bundler: BundlerStats;
metrics?: MetricToSend[];
}

@@ -168,9 +189,10 @@ export interface Context {

duration: number;
context: Context[];
type: TAP_TYPES;
context?: Context[];
type?: TAP_TYPES;
}
export interface TapableTiming {
export interface Timing {
name: string;
duration?: number;
hooks: {
duration: number;
increment: number;
events: {
[key: string]: {

@@ -182,5 +204,3 @@ name: string;

}
export interface TapableTimings {
[key: string]: TapableTiming;
}
export declare type TimingsMap = Map<string, Timing>;
export interface MonitoredTaps {

@@ -193,3 +213,3 @@ [key: string]: any;

hooks: Hooks;
timings: TapableTimings;
timings: TimingsMap;
}

@@ -227,34 +247,5 @@ export declare type TapAsync = (...args: any[]) => void;

module: string;
timings: {
start: number;
end?: number;
};
timings: Value;
loaders: string[];
}
export interface ResultModuleEvent {
name: string;
start: number;
end?: number;
}
export interface ResultModule {
name: string;
increment: number;
duration: number;
loaders: ResultModuleEvent[];
}
export interface ResultLoader {
name: string;
increment: number;
duration: number;
}
export interface ResultModules {
[key: string]: ResultModule;
}
export interface ResultLoaders {
[key: string]: ResultLoader;
}
export interface LoadersResult {
modules: ResultModules;
loaders: ResultLoaders;
}
export interface LocalModule {

@@ -261,0 +252,0 @@ name: string;

{
"name": "@datadog/build-plugin",
"version": "0.4.5",
"version": "1.0.0",
"license": "MIT",

@@ -22,3 +22,3 @@ "author": "Datadog",

"format": "yarn lint --fix",
"lint": "eslint -c ./.eslintrc.js ./**/*.ts",
"lint": "eslint -c ./.eslintrc.js ./**/*.ts --quiet",
"oss": "yarn cli oss -d bin -d src -l mit",

@@ -33,3 +33,3 @@ "prepack": "yarn build",

"fs-extra": "7.0.1",
"webpack": "5.49.0"
"pretty-bytes": "5.6.0"
},

@@ -50,2 +50,3 @@ "husky": {

"clipanion": "2.6.0",
"esbuild": "0.12.26",
"eslint": "6.8.0",

@@ -62,8 +63,10 @@ "eslint-config-prettier": "6.11.0",

"prettier": "2.0.5",
"ts-jest": "^26.1.0",
"typescript": "3.8.3"
"ts-jest": "26.1.0",
"typescript": "3.8.3",
"webpack": "5.49.0"
},
"peerDependencies": {
"esbuild": "*",
"webpack": "*"
}
}

@@ -13,3 +13,3 @@ # Build plugin <!-- omit in toc -->

- This is a bundler plugin (webpack for now, others to come...).
- This is a bundler plugin (webpack and esbuild for now).
- It monitors plugins, loaders, hooks, dependencies, modules, chunks, ...

@@ -29,2 +29,4 @@ - It doesn't add runtime.

- [Usage](#usage)
- [Webpack](#webpack)
- [Esbuild](#esbuild)
- [Configuration](#configuration)

@@ -64,2 +66,4 @@ - [`disabled`](#disabled)

### Webpack
Inside your `webpack.config.js`.

@@ -77,2 +81,19 @@

### Esbuild
Add the plugin to your esbuild configuration.
```js
const esbuild = require('esbuild');
const { BuildPlugin } = require('@datadog/build-plugin/dist/esbuild');
esbuild.build({
[...] // All your configuration needs
plugins: [
[...] // All your plugins
BuildPlugin()
]
})
```
## Configuration

@@ -97,3 +118,3 @@

- `metrics.json`: an array of all the metrics that would be sent to Datadog.
- `stats.json`: the `stats` object of webpack.
- `bundler.json`: some 'stats' from your bundler.
- `timings.json`: timing data for modules, loaders and plugins.

@@ -109,2 +130,3 @@

```
To only output a specified file.

@@ -111,0 +133,0 @@

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc