From 407d71956ecc2f359131849826311e6d4364082b Mon Sep 17 00:00:00 2001 From: Yoann Moinet Date: Mon, 30 Sep 2024 10:53:46 +0200 Subject: [PATCH 1/9] Add some documentation for testing --- packages/tests/README.md | 184 +++++++++++++++++- .../core/plugins/build-report/index.test.ts | 9 +- packages/tests/src/helpers/configBundlers.ts | 10 + .../tests/src/plugins/telemetry/index.test.ts | 3 +- 4 files changed, 191 insertions(+), 15 deletions(-) diff --git a/packages/tests/README.md b/packages/tests/README.md index 0413c256..c6bfa18e 100644 --- a/packages/tests/README.md +++ b/packages/tests/README.md @@ -1,24 +1,196 @@ # Tests -All the workspaces are tested in this workspace.
+All the workspaces are tested in here.
It helps us have a better control over the test specific dependencies. Especially useful for having mock projects, built with specific bundlers and run the real thing. -## Build everything & Run +## Run all the tests ```bash yarn test ``` -## Only build tests +## Run all the tests with all the logs + +By default, jest is in silent mode and won't show any logs. + +```bash +yarn test:noisy +``` + +## Debug a test + +You can target a single file the same as if you were using Jest's CLI. + +Within your test you can then use `.only` or `.skip` to target a single test in particular. ```bash -yarn build:tests +yarn test:noisy packages/tests/... +``` + +## Test a plugin + +Once you have your plugin ready, you can test it in two ways, both are not exclusive. + +The **unit** way, where you test each functions individually, verifying that given an input you get the expected output.
+This doesn't need much explanation as it is pretty straight-forward. + +Or the **integration** way, which will test the plugin within the whole ecosystem, but is a bit more involved to setup correctly.
+Let's talk about this a bit more. + +### Bootstrapping your test + +Here's a bootstrap to get you going: + +```typescript +import type { Options } from '@dd/core/types'; +import type { CleanupFn } from '@dd/tests/helpers/runBundlers'; +import { runBundlers } from '@dd/tests/helpers/runBundlers'; + +describe('My very awesome plugin', () => { + let cleanup: CleanupFn; + + beforeAll(async () => { + const pluginConfig: Options = { + // Add your config in order to enable your plugin in the build. + myNewPlugin: {}, + }; + // Run the build on our basic default project. + cleanup = await runBundlers(pluginConfig); + }); + + afterAll(async () => { + // Clean the generated files (in a seeded directory). + await cleanup(); + }); + + test('Should have done something', () => { + expect(true).toBe(false); + }); +}); ``` -## Only Run +### Bundlers + +We currently support `webpack4`, `webpack5`, `esbuild`, `rollup` and `vite`.
+So we need to ensure that our plugins works everywhere. + +When you use `runBundlers()` in your setup (usually `beforeAll()`), it will run a build on [a very basic default mock project](/packages/tests/src/fixtures/main.js).
+Since it's building in a seeded directory, to avoid any collision, it will also return a cleanup function, that you'll need to use in your teardown (usually `afterAll()`). + +During development, you may want to target a specific bundler, to reduce noise from the others.
+For this, you can use `--bundlers=,` options when running your tests: ```bash -yarn workspace @dd/tests test +yarn test:noisy packages/tests/... --bundlers=webpack4,esbuild +``` + +### More complex projects + +We also have [a more complex project](/packages/tests/src/fixtures/project), with third parties dependencies for instance, that you can use with the `getComplexBuildOverrides()` function.
+To be used as follow: + +```typescript +import { getComplexBuildOverrides } from '@dd/tests/helpers/mocks'; + +[...] + +cleanup = await runBundlers(pluginConfig, getComplexBuildOverrides()); +``` + +If that's still not enough, we have a dynamic project generator too.
+You can generate any size of project you want with `generateProject(nbEntries, nbModules);`.
+It will return the array of entries it created. + +Here's how you'd go with it: + +```typescript +import { getWebpack4Entries } from '@dd/tests/helpers/configBundlers'; +import { generateProject } from '@dd/tests/helpers/generateMassiveProject'; +import { defaultPluginOptions } from '@dd/tests/helpers/mocks'; +import { runBundlers } from '@dd/tests/helpers/runBundlers'; + +describe('Some very massive project', () => { + beforeAll(async () => { + const entries = await generateProject(2, 500); + // Override the default bundler configuration with the new entries. + const bundlerOverrides = { + rollup: { + input: entries, + }, + vite: { + input: entries, + }, + esbuild: { + entryPoints: entries, + }, + // Mode production makes the build waaaaayyyyy too slow. + webpack5: { mode: 'none', entry: entries }, + webpack4: { + mode: 'none', + // Webpack4 needs some help for pnp resolutions. + entry: getWebpack4Entries(entries), + }, + }; + + cleanup = await runBundlers(defaultPluginOptions, bundlerOverrides); + }); + + afterAll(async () => { + await cleanup(); + }); +}); +``` + +### Work with the global context + +The global context is pretty nifty to share data between plugins.
+But, it is a mutable object, so you'll have to keep that in mind when testing around it. + +The best way would be to freeze the content you need to test, at the moment you want to test it: + +```typescript +import type { GlobalContext, Options } from '@dd/core/types'; +import { defaultPluginOptions } from '@dd/tests/helpers/mocks'; +import type { CleanupFn } from '@dd/tests/helpers/runBundlers'; +import { BUNDLERS, runBundlers } from '@dd/tests/helpers/runBundlers'; + +describe('Global Context Plugin', () => { + const initialContexts: Record = {}; + let cleanup: CleanupFn; + + beforeAll(async () => { + const pluginConfig: Options = { + ...defaultPluginOptions, + // Use a custom plugin to intercept contexts to verify it at the moment they're used. + customPlugins: (opts, context) => { + const bundlerName = context.bundler.fullName; + // Freeze the context here. + initialContexts[bundlerName] = JSON.parse(JSON.stringify(context)); + return []; + }, + }; + + cleanup = await runBundlers(pluginConfig); + }); + + afterAll(async () => { + await cleanup(); + }); + + test.each(BUNDLERS)('[$name|$version] Test basic info.', ({ name }) => { + const context = initialContexts[name]; + expect(context).toBeDefined(); + }); +}); +``` + +The issue is that some part of the context are not serialisable.
+So, following the same technique, you should only pick and store the part you need from the context.
+And individually serialise the parts that need to.
+For the `context.build` for instance, you can use the helpers `serializeBuildReport(context.build)` and `unserializeBuildReport(serializedReport)` in order to deep clone it: + +```typescript +buildReports[bundlerName] = unserializeBuildReport(serializeBuildReport(context.build)); ``` diff --git a/packages/tests/src/core/plugins/build-report/index.test.ts b/packages/tests/src/core/plugins/build-report/index.test.ts index 6dfb04a9..11db3148 100644 --- a/packages/tests/src/core/plugins/build-report/index.test.ts +++ b/packages/tests/src/core/plugins/build-report/index.test.ts @@ -16,6 +16,7 @@ import type { BuildReport, SerializedInput, } from '@dd/core/types'; +import { getWebpack4Entries } from '@dd/tests/helpers/configBundlers'; import { generateProject } from '@dd/tests/helpers/generateMassiveProject'; import { defaultEntry, @@ -547,13 +548,7 @@ describe('Build Report Plugin', () => { webpack5: { mode: 'none', entry: entries }, webpack4: { mode: 'none', - // Webpack 4 doesn't support pnp. - entry: Object.fromEntries( - Object.entries(entries).map(([name, filepath]) => [ - name, - `./${path.relative(process.cwd(), require.resolve(filepath))}`, - ]), - ), + entry: getWebpack4Entries(entries), }, }; cleanup = await runBundlers( diff --git a/packages/tests/src/helpers/configBundlers.ts b/packages/tests/src/helpers/configBundlers.ts index b3e281a8..46b5afb2 100644 --- a/packages/tests/src/helpers/configBundlers.ts +++ b/packages/tests/src/helpers/configBundlers.ts @@ -61,6 +61,16 @@ export const getWebpack5Options = ( }; }; +// Webpack 4 doesn't support pnp resolution OOTB. +export const getWebpack4Entries = (entries: Record) => { + return Object.fromEntries( + Object.entries(entries).map(([name, filepath]) => [ + name, + `./${path.relative(process.cwd(), getResolvedPath(filepath))}`, + ]), + ); +}; + export const getWebpack4Options = ( seed: string, pluginOverrides: Partial = {}, diff --git a/packages/tests/src/plugins/telemetry/index.test.ts b/packages/tests/src/plugins/telemetry/index.test.ts index 6b8bbffd..aa1b1cb6 100644 --- a/packages/tests/src/plugins/telemetry/index.test.ts +++ b/packages/tests/src/plugins/telemetry/index.test.ts @@ -5,11 +5,10 @@ import type { Options } from '@dd/core/types'; import { getMetrics } from '@dd/telemetry-plugins/common/aggregator'; import type { MetricToSend } from '@dd/telemetry-plugins/types'; +import { getComplexBuildOverrides } from '@dd/tests/helpers/mocks'; import type { Bundler, CleanupFn } from '@dd/tests/helpers/runBundlers'; import { BUNDLERS, runBundlers } from '@dd/tests/helpers/runBundlers'; -import { getComplexBuildOverrides } from '../../helpers/mocks'; - // Used to intercept metrics. jest.mock('@dd/telemetry-plugins/common/aggregator', () => { const originalModule = jest.requireActual('@dd/telemetry-plugins/common/aggregator'); From d8448fcc94fd2737c194d0bef445b2a1edcdefb6 Mon Sep 17 00:00:00 2001 From: Yoann Moinet Date: Mon, 30 Sep 2024 10:54:10 +0200 Subject: [PATCH 2/9] Update some docs --- README.md | 48 ++------------------------------ packages/core/README.md | 61 +++++++++++++++++++++++++++++++++++------ 2 files changed, 56 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index d0a0779a..2dbb4177 100644 --- a/README.md +++ b/README.md @@ -324,57 +324,15 @@ Your function will receive two arguments: - `options`: The options you passed to the main plugin (including your custom plugins). - `context`: The global context shared accross our plugin. + -```typescript -type GlobalContext = { - // Available from the initialization. - auth?: { - apiKey?: string; - }; - // Available from the initialization. - // More details on the currently running bundler. - bundler: { - name: string; - fullName: string; // Including its variant. - outDir: string; - rawConfig?: any; - variant?: string; // Major version of the bundler (webpack 4, webpack 5) - }; - // Available from `writeBundle`. - build: { - errors: string[]; - warnings: string[]; - entries?: { filepath: string; name: string; size: number; type: string, inputs: Input[], outputs: Output[] }[]; - inputs?: { filepath: string; name: string; size: number; type: string, dependencies: Input[]; dependents: Input[] }[]; - outputs?: { filepath: string; name: string; size: number; type: string, inputs: (Input | Output)[] }[]; - start?: number; - end?: number; - duration?: number; - writeDuration?: number; - }; - // Available from the initialization. - cwd: string; - // Available from `buildStart`. - git?: { - hash: string; - remote: string; - trackedFilesMatcher: [TrackedFilesMatcher](packages/core/src/plugins/git/trackedFilesMatcher.ts); - }; - // Available from the initialization. - start: number; - // Available from the initialization. - version: string; -} -``` + Your function will need to return an array of [Unplugin Plugins definitions](https://unplugin.unjs.io/guide/#supported-hooks). -> [!NOTE] -> Some parts of the context are only available after certain hooks. - ## Contributing -Check out the [CONTRIBUTING.md](CONTRIBUTING.md) file for more information. +Check out [CONTRIBUTING.md](CONTRIBUTING.md) for more information about how to work with the build-plugins ecosystem. ## License diff --git a/packages/core/README.md b/packages/core/README.md index eeefe10c..13485b36 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -1,24 +1,67 @@ # Datadog Build Plugins Core -A set of core functionalities to use within the Datadog Build Plugins. +A set of core functionalities to use within the Datadog Build Plugins ecosystem. -## Plugins +## Table of content + + + + + -### Global Context +## Global Context -Offers to share a global context between all the plugins. +Use a global, shared context within the build plugins ecosystem. ```typescript type GlobalContext = { - cwd: string; - version: string; + // Mirror of the user's config. + auth?: { + apiKey?: string; + }; + // More details on the currently running bundler. bundler: { name: string; - config?: any; + fullName: string; // Including its variant. + outDir: string; // Output directory + // Added in `buildStart`. + rawConfig?: any; + variant: string; // Major version of the bundler (webpack 4, webpack 5) }; -}; + // Added in `writeBundle`. + build: { + errors: string[]; + warnings: string[]; + entries?: { filepath: string; name: string; size: number; type: string, inputs: Input[], outputs: Output[] }[]; + inputs?: { filepath: string; name: string; size: number; type: string, dependencies: Input[]; dependents: Input[] }[]; + outputs?: { filepath: string; name: string; size: number; type: string, inputs: (Input | Output)[] }[]; + start?: number; + end?: number; + duration?: number; + writeDuration?: number; + }; + cwd: string; + // Added in `buildStart`. + git?: { + hash: string; + remote: string; + trackedFilesMatcher: [TrackedFilesMatcher](packages/core/src/plugins/git/trackedFilesMatcher.ts); + }; + inject: (item: { type: 'file' | 'code'; value: string; fallback?: @self }) => void; + start: number; + version: string; +} ``` +> [!NOTE] +> Some parts of the context are only available after certain hooks as stated above. + +## Plugins + +### Build Report + +### Bundler Report + ### Git Plugins Adds repository data to the global context. @@ -40,3 +83,5 @@ Adds repository data to the global context. > [!NOTE] > This won't be added if `options.disabledGit = true` or `options.rum.sourcemaps.disabledGit = true`. + +### Injection Plugin From f0a93508e7ce8418cc994961a95b7f9939ebdeb8 Mon Sep 17 00:00:00 2001 From: Yoann Moinet Date: Mon, 30 Sep 2024 11:26:35 +0200 Subject: [PATCH 3/9] Fix toc generation There was a bug when removing code blocks --- packages/tools/src/commands/integrity/readme.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/tools/src/commands/integrity/readme.ts b/packages/tools/src/commands/integrity/readme.ts index a4de4d86..07d76214 100644 --- a/packages/tools/src/commands/integrity/readme.ts +++ b/packages/tools/src/commands/integrity/readme.ts @@ -51,8 +51,9 @@ const verifyReadmeExists = (pluginPath: string) => { }; const getReadmeToc = (readmeContent: string) => { + // Remove all the code blocks to avoid collisions. + const cleanContent = readmeContent.replace(/```([\s\S](?!```))*[\s\S]```/gm, ''); // Get all titles. - const cleanContent = readmeContent.replace(/```[^`]+```/gm, ''); const titles = cleanContent.match(/^#{1,3} (.*)/gm) || []; // Remove ignored titles. let biggestTitle = 3; From 869ea68e9e5f99f70c300f9050a0000c37c2d3ed Mon Sep 17 00:00:00 2001 From: Yoann Moinet Date: Mon, 30 Sep 2024 11:27:02 +0200 Subject: [PATCH 4/9] Inject global context types --- packages/tools/src/commands/integrity/readme.ts | 17 +++++++++++++++++ packages/tools/src/constants.ts | 1 + 2 files changed, 18 insertions(+) diff --git a/packages/tools/src/commands/integrity/readme.ts b/packages/tools/src/commands/integrity/readme.ts index 07d76214..bbb02583 100644 --- a/packages/tools/src/commands/integrity/readme.ts +++ b/packages/tools/src/commands/integrity/readme.ts @@ -10,6 +10,7 @@ import path from 'path'; import { MD_BUNDLERS_KEY, MD_CONFIGURATION_KEY, + MD_GLOBAL_CONTEXT_KEY, MD_PLUGINS_KEY, MD_TOC_KEY, MD_TOC_OMIT_KEY, @@ -285,6 +286,17 @@ const handlePlugin = async (plugin: Workspace, index: number) => { }; }; +const getGlobalContextType = () => { + // Will capture the first code block after '## Global Context' up to the next title '## '. + const RX = + /## Global Context(!?[\s\S](?!```typescript))+[\s\S](?```typescript([\s\S](?!## ))+)/gm; + const coreReadmeContent = fs.readFileSync( + path.resolve(ROOT, './packages/core/README.md'), + 'utf-8', + ); + return RX.exec(coreReadmeContent)?.groups?.type || ''; +}; + export const updateReadmes = async (plugins: Workspace[], bundlers: Workspace[]) => { // Read the root README.md file. let rootReadmeContent = fs.readFileSync(path.resolve(ROOT, 'README.md'), 'utf-8'); @@ -324,6 +336,11 @@ export const updateReadmes = async (plugins: Workspace[], bundlers: Workspace[]) MD_CONFIGURATION_KEY, fullConfiguration, ); + rootReadmeContent = replaceInBetween( + rootReadmeContent, + MD_GLOBAL_CONTEXT_KEY, + getGlobalContextType(), + ); console.log( ` Inject ${green('configurations')} and ${green('plugins list')} into the root ${green('README.md')}.`, diff --git a/packages/tools/src/constants.ts b/packages/tools/src/constants.ts index 72759e91..5b97337b 100644 --- a/packages/tools/src/constants.ts +++ b/packages/tools/src/constants.ts @@ -19,6 +19,7 @@ export const MD_BUNDLERS_KEY = ''; export const MD_TOC_KEY = ''; export const MD_TOC_OMIT_KEY = ''; export const MD_CONFIGURATION_KEY = ''; +export const MD_GLOBAL_CONTEXT_KEY = ''; export const ALL_BUNDLERS = ['webpack', 'vite', 'esbuild', 'rollup', 'rspack', 'rolldown', 'farm']; export const SUPPORTED_BUNDLERS = ['webpack', 'vite', 'esbuild', 'rollup'] as const; From 92f4680ea7bab97765dabce3bb5f73d5037a6673 Mon Sep 17 00:00:00 2001 From: Yoann Moinet Date: Mon, 30 Sep 2024 12:00:52 +0200 Subject: [PATCH 5/9] Update readmes --- README.md | 69 +++++++++++++++++ packages/core/README.md | 157 +++++++++++++++++++++++++++++++++++++-- packages/tests/README.md | 17 ++++- 3 files changed, 234 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 2dbb4177..54b2bac7 100644 --- a/README.md +++ b/README.md @@ -325,6 +325,75 @@ Your function will receive two arguments: - `context`: The global context shared accross our plugin. +```typescript +type GlobalContext = { + // Mirror of the user's config. + auth?: { + apiKey?: string; + }; + // More details on the currently running bundler. + bundler: { + name: string; + fullName: string; // Including its variant. + outDir: string; // Output directory + // Added in `buildStart`. + rawConfig?: any; + variant: string; // Major version of the bundler (webpack 4, webpack 5) + }; + // Added in `writeBundle`. + build: { + errors: string[]; + warnings: string[]; + // The list of entries used in the build. + entries ? : { + filepath: string; + inputs: Input[], + name: string; + outputs: Output[] + size: number; + type: string, + } []; + // The list of inputs used in the build. + inputs ? : { + filepath: string; + dependencies: Input[]; + dependents: Input[] + name: string; + size: number; + type: string, + } []; + // The list of outputs generated by the build. + outputs ? : { + filepath: string; + name: string; + size: number; + type: string, + // Sourcemaps will use Outputs as their Inputs. + inputs: (Input | Output)[] + } []; + start?: number; + end?: number; + duration?: number; + writeDuration?: number; + }; + cwd: string; + // Added in `buildStart`. + git?: { + hash: string; + remote: string; + trackedFilesMatcher: [TrackedFilesMatcher](packages/core/src/plugins/git/trackedFilesMatcher.ts); + }; + inject: (item: { type: 'file' | 'code'; value: string; fallback?: @self }) => void; + start: number; + version: string; +} +``` + +> [!NOTE] +> Some parts of the context are only available after certain hooks: +> - `context.bundler.rawConfig` is added in the `buildStart` hook. +> - `context.build.*` is populated in the `writeBundle` hook. +> - `context.git.*` is populated in the `buildStart` hook. diff --git a/packages/core/README.md b/packages/core/README.md index 13485b36..2d9a689f 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -1,4 +1,4 @@ -# Datadog Build Plugins Core +# Datadog Build Plugins Core A set of core functionalities to use within the Datadog Build Plugins ecosystem. @@ -7,11 +7,18 @@ A set of core functionalities to use within the Datadog Build Plugins ecosystem. +- [Global Context](#global-context) +- [Internal Plugins](#internal-plugins) + - [Build Report](#build-report) + - [Bundler Report](#bundler-report) + - [Git Plugins](#git-plugins) + - [Injection Plugin](#injection-plugin) ## Global Context -Use a global, shared context within the build plugins ecosystem. +A global, shared context within the build plugins ecosystem.
+It is passed at your plugin's initialization, and is mutated during the build process. ```typescript type GlobalContext = { @@ -32,9 +39,33 @@ type GlobalContext = { build: { errors: string[]; warnings: string[]; - entries?: { filepath: string; name: string; size: number; type: string, inputs: Input[], outputs: Output[] }[]; - inputs?: { filepath: string; name: string; size: number; type: string, dependencies: Input[]; dependents: Input[] }[]; - outputs?: { filepath: string; name: string; size: number; type: string, inputs: (Input | Output)[] }[]; + // The list of entries used in the build. + entries ? : { + filepath: string; + inputs: Input[], + name: string; + outputs: Output[] + size: number; + type: string, + } []; + // The list of inputs used in the build. + inputs ? : { + filepath: string; + dependencies: Input[]; + dependents: Input[] + name: string; + size: number; + type: string, + } []; + // The list of outputs generated by the build. + outputs ? : { + filepath: string; + name: string; + size: number; + type: string, + // Sourcemaps will use Outputs as their Inputs. + inputs: (Input | Output)[] + } []; start?: number; end?: number; duration?: number; @@ -54,17 +85,81 @@ type GlobalContext = { ``` > [!NOTE] -> Some parts of the context are only available after certain hooks as stated above. +> Some parts of the context are only available after certain hooks: +> - `context.bundler.rawConfig` is added in the `buildStart` hook. +> - `context.build.*` is populated in the `writeBundle` hook. +> - `context.git.*` is populated in the `buildStart` hook. -## Plugins +## Internal Plugins + +We have a set of internal plugins that helps implementing customer facing plugins.
+Most of them interacts via the Global Context. ### Build Report +This will populate `context.build` with a bunch of data coming from the build. + +```typescript +{ + build: { + errors: string[]; + warnings: string[]; + // The list of entries used in the build. + entries ? : { + filepath: string; + inputs: Input[], + name: string; + outputs: Output[] + size: number; + type: string, + } []; + // The list of inputs used in the build. + inputs ? : { + filepath: string; + dependencies: Input[]; + dependents: Input[] + name: string; + size: number; + type: string, + } []; + // The list of outputs generated by the build. + outputs ? : { + filepath: string; + name: string; + size: number; + type: string, + // Sourcemaps will use Outputs as their Inputs. + inputs: (Input | Output)[] + } []; + start?: number; + end?: number; + duration?: number; + writeDuration?: number; + }; +} +``` + ### Bundler Report +A very basic report on the currently used bundler.
+It is useful to unify some configurations. + +```typescript +{ + bundler: { + name: string; + fullName: string; // Including its variant. + outDir: string; // Output directory + // Added in `buildStart`. + rawConfig?: any; + variant: string; // Major version of the bundler (webpack 4, webpack 5) + }; +} +``` + ### Git Plugins -Adds repository data to the global context. +Adds repository data to the global context from the `buildStart` hook. ```typescript { @@ -85,3 +180,49 @@ Adds repository data to the global context. > This won't be added if `options.disabledGit = true` or `options.rum.sourcemaps.disabledGit = true`. ### Injection Plugin + +This is used to prepend some code to the produced bundle.
+Particularly useful if you want to share some global context, or to automatically inject some SDK. + +It gives you access to the `context.inject()` function. + +All the injections will be resolved during the `buildStart` hook,
+so you'll have to have submitted your injection prior to that.
+Ideally, you'd submit it during your plugin's initialization. + +#### Distant file + +You can give it a distant file.
+Be mindful that a 5s timeout is enforced. + +```typescript +context.inject({ + type: 'file', + value: 'https://example.com/my_file.js', +}); +``` + +#### Local file + +You also give it a local file.
+While you can use either a relative or absolute path, it's best to use an absolute one.
+Remember that the plugins are also bundled before distribution. + +```typescript +context.inject({ + type: 'file', + value: path.resolve(__dirname, '../my_file.js'), +}); +``` + +#### Raw code + +Or give it any kind of string.
+Be mindful that the code needs to be executable, or the plugins will crash. + +```typescript +context.inject({ + type: 'code', + value: 'console.log("My un-invasive code");', +}); +``` diff --git a/packages/tests/README.md b/packages/tests/README.md index c6bfa18e..fcab578c 100644 --- a/packages/tests/README.md +++ b/packages/tests/README.md @@ -1,10 +1,25 @@ -# Tests +# Tests All the workspaces are tested in here.
It helps us have a better control over the test specific dependencies. Especially useful for having mock projects, built with specific bundlers and run the real thing. +## Table of content + + + + +- [Run all the tests](#run-all-the-tests) +- [Run all the tests with all the logs](#run-all-the-tests-with-all-the-logs) +- [Debug a test](#debug-a-test) +- [Test a plugin](#test-a-plugin) + - [Bootstrapping your test](#bootstrapping-your-test) + - [Bundlers](#bundlers) + - [More complex projects](#more-complex-projects) + - [Work with the global context](#work-with-the-global-context) + + ## Run all the tests ```bash From a27621c95260357ee19b10fb8348b5be9fbadb92 Mon Sep 17 00:00:00 2001 From: Yoann Moinet Date: Mon, 30 Sep 2024 14:58:37 +0200 Subject: [PATCH 6/9] Update tests documentation --- packages/tests/README.md | 73 +++++++++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 9 deletions(-) diff --git a/packages/tests/README.md b/packages/tests/README.md index fcab578c..47b5c3f7 100644 --- a/packages/tests/README.md +++ b/packages/tests/README.md @@ -48,7 +48,7 @@ yarn test:noisy packages/tests/... Once you have your plugin ready, you can test it in two ways, both are not exclusive. -The **unit** way, where you test each functions individually, verifying that given an input you get the expected output.
+The **unit** way, where you test each function individually, verifying that given an input you get the expected output.
This doesn't need much explanation as it is pretty straight-forward. Or the **integration** way, which will test the plugin within the whole ecosystem, but is a bit more involved to setup correctly.
@@ -89,13 +89,13 @@ describe('My very awesome plugin', () => { ### Bundlers We currently support `webpack4`, `webpack5`, `esbuild`, `rollup` and `vite`.
-So we need to ensure that our plugins works everywhere. +So we need to ensure that our plugin works everywhere. -When you use `runBundlers()` in your setup (usually `beforeAll()`), it will run a build on [a very basic default mock project](/packages/tests/src/fixtures/main.js).
+When you use `runBundlers()` in your setup (usually `beforeAll()`), it will run the build of [a very basic default mock project](/packages/tests/src/fixtures/main.js).
Since it's building in a seeded directory, to avoid any collision, it will also return a cleanup function, that you'll need to use in your teardown (usually `afterAll()`). During development, you may want to target a specific bundler, to reduce noise from the others.
-For this, you can use `--bundlers=,` options when running your tests: +For this, you can use the `--bundlers=,` flag when running your tests: ```bash yarn test:noisy packages/tests/... --bundlers=webpack4,esbuild @@ -158,6 +158,10 @@ describe('Some very massive project', () => { }); ``` +> [!NOTE] +> `generateProject()` is not persistent. +> So for now it's only to be used to debug your plugin when necessary. + ### Work with the global context The global context is pretty nifty to share data between plugins.
@@ -181,7 +185,7 @@ describe('Global Context Plugin', () => { // Use a custom plugin to intercept contexts to verify it at the moment they're used. customPlugins: (opts, context) => { const bundlerName = context.bundler.fullName; - // Freeze the context here. + // Freeze the context here, to verify what's available during initialization. initialContexts[bundlerName] = JSON.parse(JSON.stringify(context)); return []; }, @@ -201,11 +205,62 @@ describe('Global Context Plugin', () => { }); ``` -The issue is that some part of the context are not serialisable.
-So, following the same technique, you should only pick and store the part you need from the context.
-And individually serialise the parts that need to.
-For the `context.build` for instance, you can use the helpers `serializeBuildReport(context.build)` and `unserializeBuildReport(serializedReport)` in order to deep clone it: +The issue is that some part of the context are not serialisable. + +So, following the same technique, you should: + +- only pick and store the parts you need from the context. +- individually serialise the parts that need to. + +The `context.build` for instance, isn't serializable, but you can use the helpers `serializeBuildReport(context.build)` and `unserializeBuildReport(serializedReport)` in order to deep clone it: ```typescript buildReports[bundlerName] = unserializeBuildReport(serializeBuildReport(context.build)); ``` + +Giving the following, more involved example: + +```typescript +import { + serializeBuildReport, + unserializeBuildReport, +} from '@dd/core/plugins/build-report/helpers'; +import type { BuildReport, Options } from '@dd/core/types'; +import { defaultPluginOptions } from '@dd/tests/helpers/mocks'; +import type { CleanupFn } from '@dd/tests/helpers/runBundlers'; +import { BUNDLERS, runBundlers } from '@dd/tests/helpers/runBundlers'; + +describe('Build Reports', () => { + const buildReports: Record = {}; + let cleanup: CleanupFn; + + beforeAll(async () => { + const pluginConfig: Options = { + ...defaultPluginOptions, + // Use a custom plugin to intercept contexts to verify it at the moment they're used. + customPlugins: (opts, context) => { + const bundlerName = context.bundler.fullName; + return [{ + name: 'my-custom-plugin', + writeBundle() { + // Freeze the context here, to verify what's available after the writeBundle hook. + const serializedBuildReport = serializeBuildReport(context.build); + buildReports[bundlerName] = unserializeBuildReport(serializedBuildReport); + } + }]; + }, + }; + + cleanup = await runBundlers(pluginConfig); + }); + + afterAll(async () => { + await cleanup(); + }); + + test.each(BUNDLERS)('[$name|$version] Have the build report.', ({ name }) => { + const context = buildReports[name]; + expect(context).toBeDefined(); + }); +}); +``` From fcbc11515932ed37a727cfa79f6a8ea09a395faf Mon Sep 17 00:00:00 2001 From: Yoann Moinet Date: Mon, 30 Sep 2024 15:19:10 +0200 Subject: [PATCH 7/9] Update CONTRIBUTING --- CONTRIBUTING.md | 44 ++++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0c7b529b..59f342dc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -60,9 +60,12 @@ yarn We have two types of workspaces: - `@datadog/*`: The packages we're publishing publically on NPM. - - `@datadog/webpack-plugin`: The webpack plugin. - `@datadog/eslint-plugin`: The eslint plugin. + - `@datadog/rollup-plugin`: The rollup plugin. + - `@datadog/vite-plugin`: The vite plugin. + - `@datadog/webpack-plugin`: The webpack plugin. - `@dd/*`: The packages we're only using internally. + - `@dd/assets` | `./packages/assets`: Only the static files used as assets. - `@dd/core` | `./packages/core`: The core package that contains the shared code between the plugins. - `@dd/factory` | `./packages/factory`: The factory package that contains the logic to aggregate all the plugins together. - `@dd/*-plugin` | `./packages/plugins/*`: The plugins workspaces that contains the plugins. Each plugin is a workspace. @@ -72,6 +75,9 @@ We have two types of workspaces: Here's a diagram to help you understand the structure: ```mermaid +--- +title: Datadog Build Plugins Design +--- stateDiagram-v2 published: Published Packages plugins: Custom Plugins @@ -81,8 +87,10 @@ stateDiagram-v2 customplugin12: @dd/*-plugins customplugin22: @dd/*-plugins customplugin32: [...] - webpackplugin: @datadog/webpack-plugin esbuildplugin: @datadog/esbuild-plugin + viteplugin: @datadog/vite-plugin + rollupplugin: @datadog/rollup-plugin + webpackplugin: @datadog/webpack-plugin tests: @dd/tests tools: @dd/tools factory: @dd/factory @@ -93,12 +101,16 @@ stateDiagram-v2 aplugins: Aggregated List of Plugins cli: Internal CLIs internalPlugins: Internal Plugins + buildReportPlugin: Build Report Plugin + bundlerReportPlugin: Bundler Report Plugin + injectionPlugin: Injection Plugin gitPlugin: Git Plugin - contextPlugin: Global Context Plugin state internalPlugins { + buildReportPlugin + bundlerReportPlugin gitPlugin - contextPlugin + injectionPlugin } state core { @@ -108,8 +120,10 @@ stateDiagram-v2 } state published { - webpackplugin esbuildplugin + viteplugin + rollupplugin + webpackplugin } state plugins { @@ -134,14 +148,14 @@ stateDiagram-v2 customplugin32 } - plugins --> factory: CONFIG_KEY\nhelpers\ntypes\ngetPlugins() + plugins --> factory: CONFIG_KEY
helpers
types
getPlugins() core --> tools: types - core --> factory: Internal Plugins\ntypes - core --> plugins: getLogger()\ntypes + core --> factory: Internal Plugins
types + core --> plugins: getLogger()
types core --> tests: types factory --> plugins: Global Context factory --> published: Unplugin Factory - published --> NPM: types\nhelpers\ndatadogBundlerPlugin + published --> NPM: types
helpers
datadogBundlerPlugin ``` ## Create a new plugin @@ -155,15 +169,7 @@ yarn cli create-plugin ## Tests -```bash -# Build and run all the tests at once. -yarn test - -# Only run the tests. Useful to target a specific file. -yarn test:only -``` - -More details in the [tests README](./packages/tests#readme). +[📝 Full testing documentation ➡️](./packages/tests#readme) > [!IMPORTANT] > If you're modifying a behavior or adding a new feature, update/add the required tests to your PR. @@ -248,6 +254,8 @@ It will: - update headers of each files. - update `LICENSES-3rdparty.csv`, `LICENSE`, `NOTICE` and `README.md` with the correct licenses. +It is also run part of the `yarn cli integrity` CLI. + ## Documentation We try to keep the documentation as up to date as possible. From cd77987d99620e7291b3a2b3787f3113955f48c2 Mon Sep 17 00:00:00 2001 From: Yoann Moinet Date: Mon, 30 Sep 2024 15:36:44 +0200 Subject: [PATCH 8/9] Add link to core doc --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 59f342dc..93a6663f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -167,6 +167,8 @@ Bootstrapping all the files you'll need to start coding. yarn cli create-plugin ``` +Then learn more about what you can use from [the ecosystem](./packages/core). + ## Tests [📝 Full testing documentation ➡️](./packages/tests#readme) From af4916afdce799a071ca75488a90636785aae62f Mon Sep 17 00:00:00 2001 From: Yoann Moinet Date: Thu, 3 Oct 2024 15:03:07 +0200 Subject: [PATCH 9/9] Add precision on variant --- README.md | 2 +- packages/core/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 54b2bac7..2c023e1e 100644 --- a/README.md +++ b/README.md @@ -338,7 +338,7 @@ type GlobalContext = { outDir: string; // Output directory // Added in `buildStart`. rawConfig?: any; - variant: string; // Major version of the bundler (webpack 4, webpack 5) + variant: string; // Major version of the bundler (webpack 4, webpack 5), empty string otherwise. }; // Added in `writeBundle`. build: { diff --git a/packages/core/README.md b/packages/core/README.md index 2d9a689f..916b3bc4 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -33,7 +33,7 @@ type GlobalContext = { outDir: string; // Output directory // Added in `buildStart`. rawConfig?: any; - variant: string; // Major version of the bundler (webpack 4, webpack 5) + variant: string; // Major version of the bundler (webpack 4, webpack 5), empty string otherwise. }; // Added in `writeBundle`. build: {