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

[web-ui] Quick fixes for web-ui #145

Open
wants to merge 43 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
d806111
Re-org timing of stub injected file
yoannmoinet Feb 27, 2025
a4a2613
Fix the prepare-link revert
yoannmoinet Feb 27, 2025
f3efb8b
Fix random rspack crashes
yoannmoinet Feb 28, 2025
e2f6d26
Slightly change esbuild stub file strategy
yoannmoinet Feb 28, 2025
6b45388
Remove unecessary complexity from runBundlers' isolated runs
yoannmoinet Feb 28, 2025
d5001bf
Add isXpack helper to detect webpack/rspack bundlers
yoannmoinet Feb 28, 2025
a02a3c4
Better and stricter separation of needs for the injection plugin
yoannmoinet Feb 28, 2025
6aa1fe8
Widen the file stub solution for xpack to webpack too.
yoannmoinet Feb 28, 2025
6a7fb2a
Small inconsequential diff
yoannmoinet Feb 28, 2025
fc0df3b
Fix analytics
yoannmoinet Feb 28, 2025
5b58a98
Upload artifacts only when failed.
yoannmoinet Feb 28, 2025
c89d1f3
Fix some exports values
yoannmoinet Feb 28, 2025
3563c7e
Increase e2e timeout
yoannmoinet Mar 3, 2025
5796ea2
Add global timeout for CI
yoannmoinet Mar 3, 2025
c9119bc
Add playwright alias
yoannmoinet Mar 3, 2025
e2b7f25
Fix package.json breaking the bundled types
yoannmoinet Mar 3, 2025
6c717e7
Handle 0ms in formatDuration
yoannmoinet Mar 4, 2025
9f5603d
Add time logger
yoannmoinet Mar 4, 2025
50deda4
Add time debugs in some places
yoannmoinet Mar 4, 2025
370674d
Revert version bump
yoannmoinet Mar 4, 2025
08fc2f2
Slight update on metrics sending logs
yoannmoinet Mar 5, 2025
68075c8
Fix esbuild report on external dependencies
yoannmoinet Mar 6, 2025
95770a6
Add more time debug
yoannmoinet Mar 6, 2025
6279350
Add a dimmed starting point for time logs
yoannmoinet Mar 6, 2025
bf3f59e
Move helpers `getAbsolutePath`, `getNearestCommonDirectory` and `debu…
yoannmoinet Mar 7, 2025
08bba97
Factorise build commands
yoannmoinet Mar 7, 2025
a67c63c
Fix rollup's build-report with external dependencies support
yoannmoinet Mar 10, 2025
52250a5
Remove resetModule from jest as it's not needed anymore and prevents …
yoannmoinet Mar 10, 2025
097f92c
Clean readme from unecessary information
yoannmoinet Mar 10, 2025
c76c6b0
Add some tests
yoannmoinet Mar 11, 2025
baa70bd
Better types and comments
yoannmoinet Mar 11, 2025
cdcdf83
Output the working dir in case of no cleanup
yoannmoinet Mar 11, 2025
dbc407e
Fix esbuild external reporting
yoannmoinet Mar 11, 2025
4978e04
Add external reporting to xpack
yoannmoinet Mar 11, 2025
e5b6667
Cover externals in tests
yoannmoinet Mar 11, 2025
0634b2a
Fix rollup's build-report on inputs/outputs
yoannmoinet Mar 11, 2025
4c64d6f
Remove unused dependencies
yoannmoinet Mar 11, 2025
df9bdae
Remove values from tests as bundlers can act differently
yoannmoinet Mar 11, 2025
e7905c6
Revert logger.time helper
yoannmoinet Mar 12, 2025
a91d4d8
Cleaning commented lines
yoannmoinet Mar 12, 2025
794daf9
Some more cleaning
yoannmoinet Mar 12, 2025
59c968a
Revert meta plugin
yoannmoinet Mar 12, 2025
5446e31
Remove comment about solution that isn't working
yoannmoinet Mar 12, 2025
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ jobs:
key: cache-playwright-binaries-${{ hashFiles('yarn.lock') }}

- uses: actions/upload-artifact@v4
if: ${{ !cancelled() && failure() }}
if: ${{ failure() }}
with:
name: playwright
path: |
Expand Down
38 changes: 34 additions & 4 deletions global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,43 @@ import type { Env } from '@dd/core/types';

declare global {
namespace NodeJS {
interface ProcessEnv {
[key: string]: string | undefined;
interface ProcessEnv extends NodeJS.ProcessEnv {
/**
* The environment in which the plugins will execute.
*
* For instance, we only submit logs to Datadog when the environment is `production`.
*/
BUILD_PLUGINS_ENV?: Env;
NO_CLEANUP?: '1';
/**
* Defined in github actions when running in CI.
*/
CI?: '1';
/**
* Defined in github actions when running in CI.
*
* The commit SHA that triggered the workflow.
*/
GITHUB_SHA?: string;
/**
* Run jest in silent mode.
*/
JEST_SILENT?: '1';
/**
* To also build the plugins before running the tests when using `yarn test:unit`.
*/
NEED_BUILD?: '1';
/**
* To skip the cleanup of the temporary working dirs where we build `runBundlers()`.
*/
NO_CLEANUP?: '1';
/**
* The list of bundlers to use in our tests.
*/
REQUESTED_BUNDLERS?: string;
JEST_SILENT?: '1';
/**
* Defined by yarn and targets the root of the project.
*/
PROJECT_CWD?: string;
}
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"loop-published": "yarn workspaces foreach -A --include \"@datadog/*\" --exclude \"@datadog/build-plugins\"",
"loop": "yarn loop-published -pti",
"oss": "yarn cli oss -d packages -l mit",
"playwright": "yarn workspace @dd/tests playwright",
"publish:all": "yarn loop --no-private npm publish",
"typecheck:all": "yarn workspaces foreach -Apti run typecheck",
"version:all": "yarn loop-published version ${0} --immediate",
Expand Down
161 changes: 158 additions & 3 deletions packages/core/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ import type { RequestInit } from 'undici-types';

import type {
BuildReport,
BundlerFullName,
Entry,
File,
GetCustomPlugins,
GlobalContext,
Input,
IterableElement,
Logger,
Output,
RequestOpts,
Expand All @@ -36,9 +39,12 @@ export const formatDuration = (duration: number) => {
const minutes = d.getUTCMinutes();
const seconds = d.getUTCSeconds();
const milliseconds = d.getUTCMilliseconds();
return `${days ? `${days}d ` : ''}${hours ? `${hours}h ` : ''}${minutes ? `${minutes}m ` : ''}${
seconds ? `${seconds}s ` : ''
}${milliseconds ? `${milliseconds}ms` : ''}`.trim();
const timeString =
`${days ? `${days}d ` : ''}${hours ? `${hours}h ` : ''}${minutes ? `${minutes}m ` : ''}${
seconds ? `${seconds}s` : ''
}`.trim();
// Split here so we can show 0ms in case we have a duration of 0.
return `${timeString}${!timeString || milliseconds ? ` ${milliseconds}ms` : ''}`.trim();
};

// https://esbuild.github.io/api/#glob-style-entry-points
Expand Down Expand Up @@ -212,11 +218,18 @@ export const truncateString = (
// Is the file coming from the injection plugin?
export const isInjectionFile = (filename: string) => filename.includes(INJECTED_FILE);

// From a bundler's name, is it part of the "xpack" family?
export const isXpack = (bundlerName: BundlerFullName) =>
['rspack', 'webpack4', 'webpack5', 'webpack'].includes(bundlerName);

// Replacing fs-extra with local helpers.
// Delete folders recursively.
export const rm = async (dir: string) => {
return fsp.rm(dir, { force: true, maxRetries: 3, recursive: true });
};
export const rmSync = async (dir: string) => {
return fs.rmSync(dir, { force: true, maxRetries: 3, recursive: true });
};

// Mkdir recursively.
export const mkdir = async (dir: string) => {
Expand Down Expand Up @@ -411,3 +424,145 @@ export const unserializeBuildReport = (report: SerializedBuildReport): BuildRepo
outputs,
};
};

// Will only prepend the cwd if not already there.
export const getAbsolutePath = (cwd: string, filepath: string) => {
if (isInjectionFile(filepath)) {
return INJECTED_FILE;
}

if (filepath.startsWith(cwd) || path.isAbsolute(filepath)) {
return filepath;
}
return path.resolve(cwd, filepath);
};

// Find the highest package.json from the current directory.
export const getHighestPackageJsonDir = (currentDir: string): string | undefined => {
let highestPackage;
let current = getAbsolutePath(process.cwd(), currentDir);
let currentDepth = current.split('/').length;
while (currentDepth > 0) {
const packagePath = path.resolve(current, `package.json`);
// Check if package.json exists in the current directory.
if (fs.existsSync(packagePath)) {
highestPackage = current;
}
// Remove the last part of the path.
current = current.split('/').slice(0, -1).join('/');
currentDepth--;
}
return highestPackage;
};

// From a list of path, return the nearest common directory.
export const getNearestCommonDirectory = (dirs: string[], cwd?: string) => {
const dirsToCompare = [...dirs];

// We include the CWD because it's part of the paths we want to compare.
if (cwd) {
dirsToCompare.push(cwd);
}

const splitPaths = dirsToCompare.map((dir) => {
const absolutePath = getAbsolutePath(cwd || process.cwd(), dir);
return absolutePath.split(path.sep);
});

// Use the shortest length for faster results.
const minLength = Math.min(...splitPaths.map((parts) => parts.length));
const commonParts = [];

for (let i = 0; i < minLength; i++) {
// We use the first path as our basis.
const component = splitPaths[0][i];
if (splitPaths.every((parts) => parts[i] === component)) {
commonParts.push(component);
} else {
break;
}
}

return commonParts.length > 0
? // Use "|| path.sep" to cover for the [''] case.
commonParts.join(path.sep) || path.sep
: path.sep;
};

// Returns a customPlugin to output some debug files.
type CustomPlugins = ReturnType<GetCustomPlugins>;
export const debugFilesPlugins = (context: GlobalContext): CustomPlugins => {
const rollupPlugin: IterableElement<CustomPlugins>['rollup'] = {
writeBundle(options, bundle) {
outputJsonSync(
path.resolve(context.bundler.outDir, `output.${context.bundler.fullName}.json`),
bundle,
);
},
};

const xpackPlugin: IterableElement<CustomPlugins>['webpack'] &
IterableElement<CustomPlugins>['rspack'] = (compiler) => {
type Stats = Parameters<Parameters<typeof compiler.hooks.done.tap>[1]>[0];

compiler.hooks.done.tap('bundler-outputs', (stats: Stats) => {
const statsJson = stats.toJson({
all: false,
assets: true,
children: true,
chunks: true,
chunkGroupAuxiliary: true,
chunkGroupChildren: true,
chunkGroups: true,
chunkModules: true,
chunkRelations: true,
entrypoints: true,
errors: true,
ids: true,
modules: true,
nestedModules: true,
reasons: true,
relatedAssets: true,
warnings: true,
});
outputJsonSync(
path.resolve(context.bundler.outDir, `output.${context.bundler.fullName}.json`),
statsJson,
);
});
};

return [
{
name: 'build-report',
enforce: 'post',
writeBundle() {
outputJsonSync(
path.resolve(context.bundler.outDir, `report.${context.bundler.fullName}.json`),
serializeBuildReport(context.build),
);
},
},
{
name: 'bundler-outputs',
enforce: 'post',
esbuild: {
setup(build) {
build.onEnd((result) => {
outputJsonSync(
path.resolve(
context.bundler.outDir,
`output.${context.bundler.fullName}.json`,
),
result.metafile,
);
});
},
},
rspack: xpackPlugin,
rollup: rollupPlugin,
vite: rollupPlugin,
webpack: xpackPlugin,
},
];
};
4 changes: 3 additions & 1 deletion packages/factory/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,9 @@ export const getContext = ({
},
};

const passedEnv: Env = (process.env.BUILD_PLUGINS_ENV as Env) || 'development';
// Use "production" if there is no env passed.
const passedEnv: Env = (process.env.BUILD_PLUGINS_ENV as Env) || 'production';
// Fallback to "development" if the passed env is wrong.
const env: Env = ALL_ENVS.includes(passedEnv) ? passedEnv : 'development';
const context: GlobalContext = {
auth: options.auth,
Expand Down
82 changes: 74 additions & 8 deletions packages/plugins/build-report/src/esbuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2019-Present Datadog, Inc.

import { getEsbuildEntries, isInjectionFile } from '@dd/core/helpers';
import { getAbsolutePath, getEsbuildEntries, isInjectionFile } from '@dd/core/helpers';
import type {
Logger,
Entry,
Expand All @@ -12,8 +12,9 @@ import type {
PluginOptions,
ResolvedEntry,
} from '@dd/core/types';
import path from 'path';

import { cleanName, getAbsolutePath, getType } from './helpers';
import { cleanName, getType } from './helpers';

// Re-index metafile data for easier access.
const reIndexMeta = <T>(obj: Record<string, T>, cwd: string) =>
Expand Down Expand Up @@ -138,7 +139,7 @@ export const getEsbuildPlugin = (context: GlobalContext, log: Logger): PluginOpt
// It has no inputs, but still relates to its entryPoint.
if (output.entryPoint && !inputFiles.length) {
const inputFound =
reportInputsIndexed[getAbsolutePath(cwd, output.entryPoint!)];
reportInputsIndexed[getAbsolutePath(cwd, output.entryPoint)];
if (!inputFound) {
log.debug(
`Input ${output.entryPoint} not found for output ${cleanedName}`,
Expand Down Expand Up @@ -263,9 +264,53 @@ export const getEsbuildPlugin = (context: GlobalContext, log: Logger): PluginOpt
}

for (const imported of metaFile.imports) {
const importPath = getAbsolutePath(cwd, imported.path);
const isRelative = imported.path.match(/^\.\.?\//);
const root = isRelative ? path.dirname(filePath) : cwd;
const absoluteImportPath = getAbsolutePath(root, imported.path);

// We need to register external imports, as this is the first time we see them.
if (imported.external) {
if (isFileSupported(imported.path)) {
// If it's an absolute external import,
// we can't trust our own getAbsolutePath().
// We can't know what its "root" could be.
const filepath = isRelative ? absoluteImportPath : imported.path;

// But we can still add it to the report.
const inputFile: Input = references.inputs.report[filepath] || {
filepath,
name: cleanName(context, imported.path),
size: 0,
type: 'external',
dependencies: new Set(),
dependents: new Set(),
};

if ('dependencies' in file) {
// file is an Input, so we add the external to its dependencies,
// and we add file to the external's dependents.
inputFile.dependents.add(file);
file.dependencies.add(inputFile);
}

if ('inputs' in file && !file.inputs.includes(inputFile)) {
// file is an Output, so we add the external to its inputs.
file.inputs.push(inputFile);
}

if (!inputs.includes(inputFile)) {
inputs.push(inputFile);
}

references.inputs.report[filepath] = inputFile;
allImports[inputFile.filepath] = inputFile as T;
}
// We can't follow external imports.
continue;
}

// Look for the other inputs.
getAllImports<T>(importPath, ref, allImports);
getAllImports<T>(absoluteImportPath, ref, allImports);
}

return allImports;
Expand Down Expand Up @@ -303,6 +348,12 @@ export const getEsbuildPlugin = (context: GlobalContext, log: Logger): PluginOpt

// Loop through all inputs to aggregate dependencies and dependents.
for (const input of inputs) {
// The metafile does not contain external dependencies.
// So we can only fill in their dependents.
if (input.type === 'external') {
continue;
}

const metaFile = references.inputs.meta[input.filepath];
if (!metaFile) {
log.debug(`Could not find metafile's ${input.name}`);
Expand All @@ -313,11 +364,26 @@ export const getEsbuildPlugin = (context: GlobalContext, log: Logger): PluginOpt
if (!isFileSupported(dependency.path)) {
continue;
}
const dependencyPath = getAbsolutePath(cwd, dependency.path);
const dependencyFile = references.inputs.report[dependencyPath];

const isRelative = dependency.path.match(/^\.?\.\//);
const root = isRelative ? path.dirname(input.filepath) : cwd;
const absoluteDependencyPath = getAbsolutePath(root, dependency.path);

let dependencyFile: Input | undefined;
if (dependency.external) {
// If it's an absolute external import, we can't trust our own getAbsolutePath().
// We can't know what its "root" could be.
const filepath = isRelative ? absoluteDependencyPath : dependency.path;
// In case of externals, we use their path directly.
dependencyFile = references.inputs.report[filepath];
} else {
dependencyFile = references.inputs.report[absoluteDependencyPath];
}

if (!dependencyFile) {
log.debug(`Could not find input file of ${dependency.path}`);
log.debug(
`Could not find input file of ${dependency.path} imported from ${input.name}`,
);
continue;
}

Expand Down
Loading