Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,8 @@ yarn-error.log
node_modules/
dist/
dist-basic/

.vitrine/*
!.vitrine/project.json
!.vitrine/plans
!.vitrine/assets
75 changes: 75 additions & 0 deletions packages/plugins/injection/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,32 @@
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2019-Present Datadog, Inc.

import { datadogRspackPlugin } from '@datadog/rspack-plugin';
import { datadogWebpackPlugin } from '@datadog/webpack-plugin';
import { outputFileSync } from '@dd/core/helpers/fs';
import { getUniqueId } from '@dd/core/helpers/strings';
import type { Assign, BundlerName, Options, ToInjectItem } from '@dd/core/types';
import { InjectPosition } from '@dd/core/types';
import { AFTER_INJECTION, BEFORE_INJECTION } from '@dd/internal-injection-plugin/constants';
import { addInjections, isFileSupported } from '@dd/internal-injection-plugin/helpers';
import { getOutDir, prepareWorkingDir } from '@dd/tests/_jest/helpers/env';
import {
hardProjectEntries,
defaultPluginOptions,
easyProjectWithCSSEntry,
} from '@dd/tests/_jest/helpers/mocks';
import { BUNDLERS, runBundlers } from '@dd/tests/_jest/helpers/runBundlers';
import type { BundlerConfigFunction } from '@dd/tools/bundlers';
import { configRspack, configWebpack } from '@dd/tools/bundlers';
import { header, licenses } from '@dd/tools/commands/oss/templates';
import { escapeStringForRegExp, execute, red } from '@dd/tools/helpers';
import { rspack } from '@rspack/core';
import chalk from 'chalk';
import { readFileSync } from 'fs';
import { glob } from 'glob';
import nock from 'nock';
import path from 'path';
import webpack from 'webpack';

const FAKE_FILE_PREFIX = 'fake-file-to-inject-';
// Files that we will execute part of the test.
Expand Down Expand Up @@ -578,4 +586,71 @@ describe('Injection Plugin', () => {
});
});
});

// Regression test for https://github.com/DataDog/build-plugins/issues/304
// When output.clean is enabled, webpack/rspack deletes the helper file from outDir
// during emit. On subsequent rebuilds (watch/dev mode), the file is gone and
// module resolution fails.
// We reproduce this by creating a compiler once and running it twice (no close()
// in between), simulating what happens in dev server / watch mode.
describe('output.clean compatibility', () => {
const xpackCases: {
name: string;
bundle: any;
config: BundlerConfigFunction;
plugin: any;
}[] = [
{
name: 'webpack',
bundle: webpack,
config: configWebpack,
plugin: datadogWebpackPlugin,
},
{ name: 'rspack', bundle: rspack, config: configRspack, plugin: datadogRspackPlugin },
];

test.each(xpackCases)(
'$name | Should survive rebuilds with output.clean: true',
async ({ bundle, config, plugin }) => {
const seed = `clean-test.${getUniqueId()}`;
const workingDir = await prepareWorkingDir(seed);
const outDir = getOutDir(workingDir, 'clean');

// Create one compiler and run it twice to simulate watch/dev-mode rebuilds.
// With the bug, output.clean deletes the helper file from outDir
// during the first build's emit, so the second build's resolution fails.
const compiler: any = bundle(
config({
workingDir,
outDir,
entry: { main: easyProjectWithCSSEntry },
node: true,
clean: true,
plugins: [plugin(defaultPluginOptions)],
}),
);

const run = () =>
new Promise<void>((resolve, reject) => {
compiler.run((err: any, stats: any) => {
if (err) {
return reject(err);
}
const { errors } = stats!.compilation;
if (errors?.length) {
return reject(errors[0]);
}
resolve();
});
});

try {
await expect(run()).resolves.toBeUndefined();
await expect(run()).resolves.toBeUndefined();
} finally {
await new Promise<void>((resolve) => compiler.close(() => resolve()));
}
},
);
});
});
9 changes: 8 additions & 1 deletion packages/plugins/injection/src/xpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,15 @@ export const getXpackPlugin =
// Use a narrower identifier to avoid cross build collisions.
const ConcatSource = bundler.sources.ConcatSource;
const id = context.bundler.name;
// Use a cache directory within the project root instead of outDir,
// so that webpack's output.clean option doesn't delete the file
// before it can be resolved as an entry point.
// https://github.com/DataDog/build-plugins/issues/304
const filePath = path.resolve(
context.bundler.outDir,
context.buildRoot,
'node_modules',
'.cache',
'datadog-build-plugins',
`${id}.${InjectPosition.MIDDLE}.${INJECTED_FILE}.js`,
);

Expand Down
4 changes: 3 additions & 1 deletion packages/tools/src/bundlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export type BundlerConfig = {
node?: boolean;
plugins?: any[];
splitting?: boolean; // Enable code splitting for dynamic imports
clean?: boolean; // Enable output.clean for webpack/rspack
};
export type BundlerConfigFunction = (config: BundlerConfig) => BundlerOptions;
export type BundlerRunFn = (bundlerConfig: any) => Promise<{ errors: string[]; result?: any }>;
Expand Down Expand Up @@ -201,6 +202,7 @@ export const configXpack = (config: BundlerConfig): Configuration & RspackOption
path: config.outDir,
filename: `[name].js`,
chunkFilename: 'chunk.[contenthash].js',
clean: config.clean,
},
devtool: 'source-map',
optimization: {
Expand Down Expand Up @@ -319,7 +321,7 @@ export const configVite = (config: BundlerConfig): UserConfig => {
return {
root: config.workingDir,
build: {
emptyOutDir: false,
emptyOutDir: config.clean ?? false,
assetsDir: '', // Disable assets dir to simplify the test.
minify: false,
rollupOptions: baseConfig,
Expand Down
Loading