-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Monorepo first class support #256
Comments
Can you describe more about what you are expecting? Thanks |
FWIW, I have taken a crack at switching the NX plugins repo for my old company across. sevenwestmedia-labs/nx-plugins#61 My main issue so far are console output differences when running vitest via their command runners. I will likely create a vitest plugin soon which will automate the conversion from jest -> vitest in a NX mono repo and provide the executors to run vitest tests for that project |
Jest has a For example, my project could have following file structure:
// my-monorepo/jest.config.mjs
export default {
projects: ["./packages/*/jest*.config.mjs"],
} // my-monorepo/packages/pkg-a/jest.unit.config.mjs
export default {
testMatch: "**/unit-tests/**/*.[jt]s?(x)",
displayName: { name: "pkg-a:unit", color: "blackBright" },
testEnvironment: "jsdom",
} // my-monorepo/packages/pkg-a/jest.e2e.config.mjs
export default {
testMatch: "**/e2e-tests/**/*.[jt]s?(x)",
displayName: { name: "pkg-a:e2e", color: "blackBright" },
testEnvironment: "playwright",
} // my-monorepo/packages/pkg-b/jest.config.mjs
export default {
testMatch: "**/unit-tests/**/*.[jt]s?(x)",
displayName: { name: "pkg-b", color: "magentaBright" },
testEnvironment: "node",
} I can run Jest CLI under |
This could be very useful. I have a project with several independent packages. Each of them is in its own directory, has its own
But, if possible, it would be fantastic to be able to list all the roots and run all the tests for each root. |
Sorry if this is basically a As @ocavue said, it's great for producing an aggregate coverage report across the monorepo by simply running I'm really liking vitest so far, but missing first-class monorepo support is probably a deal-breaker at this time (for my monorepos like |
I like this feature and need it too. Jest is not good for ESM but good for monorepo. Vitest is good for ESM but not support monorepo. |
Any progress in this issue? |
Not the priority right now, but something we want to do. Current priority is making Vitest more stable by closing "bug" issues 😄 Right now most of the people are either on a vacation or doing other OSS projects. And for me the priority is stabilizing and some easy to-do issues. But running one instance of VItest for different packages requires rewriting a lot of the current logic. Also, it's possible to use Vitest with monorepos - the example is Vitest repo itself, we are running tests with But if someone want to tackle this issue, you are welcome 😄 |
Also, It would be great if vitest can aggregate unit test reporter and coverage reporter in monorepo config. |
Monorepo's using Turbo support executing tests for all packages in the repo. Maybe something similar is available with NX. Scripts in root package.json Running Coverage reports generated for individual packages can be combined using the script merge-lcov.sh. |
I believe that jest |
Long post 😅 TL;DR
OverviewHello all 👋🏻 - I am new to Vitest and was also looking for “aggregation” support across sub-packages in a monorepo. In this latest project, I use Turborepo to manage the build orchestration, but other tools (Nx, Learn, etc) fall into the same category. I prototyped a solution in my own repository (I will build a public GitHub repo to showcase it later), and wanted to share my thoughts with the community to drive this forward. I think the first step is for us to decide how we expect/want the API/configuration to look. Disclaimer: I am quite new to Vitest. I have not yet tried out the Vitest UI ( ConfigurationWhat should Vitest support with respect to monorepos?
Note: Maybe (2) goes first as foundation for (1). My prototypingI prototyped something similar to (2) yesterday and was quite happy with the results. The key is to write the raw coverage data to files that can be processed on a final pass. At time of writing, Vitest actually overrides C8’s internal method to not write to disk because it has the coverage data in memory (which has better performance I assume). The final Vitest solution may keep everything in memory too, but for my prototyping I wrote to disk to stay out of Vitest core (for now). I can create a full repo later, but here’s the gist of my solution (warning: it has hacks). Extend the C8 providerI extended the C8 provider to write the coverage data to a project level directory for later processing. I am using C8, so I focused on just extending C8. I assume this could be generalized across all providers with the existing Vitest abstractions in place. // Using a subclass to illustrate the change
class C8CoverageMonorepoProvider extends C8CoverageProvider {
// TBD on the specific monorepo options and how they would play with existing
// vitest options (`root`, `all`, `include`, and `src` come to mind).
readonly projectLevelDirectory: string;
constructor(projectLevelTemporaryDirectory: string) {
super();
this.projectLevelDirectory = projectLevelTemporaryDirectory;
}
async onAfterSuiteRun(afterSuiteRunMeta: AfterSuiteRunMeta) {
super.onAfterSuiteRun(afterSuiteRunMeta);
if (!afterSuiteRunMeta.coverage) return;
// The main change: Write the coverage data
const coverageData = JSON.stringify(afterSuiteRunMeta.coverage);
// Confirm the file name (likely, check C8 implementation)
// For now, just hash it
const hash = createHash(`sha256`, { encoding: `utf8` });
hash.update(coverageData);
const suffix = hash.digest(`hex`).slice(0, 10);
// And write 😄
await writeFile(
join(this.projectLevelDirectory, `coverage-${suffix}.json`),
coverageData,
);
}
} The biggest hack here for those trying to reproduce was pulling the class out of the provider file. An integrated solution would not need to do this. (It would have proper support and not need sub-classing.) // eslint-disable-next-line @typescript-eslint/ban-ts-comment -- Hacking
// @ts-ignore
import { C8CoverageProvider } from '@vitest/coverage-c8/dist/provider-a020853a.js'; Custom coverage providerI wired this up as a custom coverage provider. const C8CoverageMonorepoProviderModule: CoverageProviderModule = {
async getProvider(): Promise<CoverageProvider> {
// TBD how to configure the temporary directory at project root
const directory = `${workspaceRoot}/build/tmp`;
// Clean/create it
if (existsSync(directory)) {
await rm(directory, { recursive: true, force: true });
}
await mkdir(directory, { recursive: true });
// Return the customized provider
return new C8CoverageMonorepoProvider(
directory,
) as unknown as CoverageProvider; // Hacking
},
startCoverage,
stopCoverage,
takeCoverage,
};
export default C8CoverageMonorepoProviderModule; Merge resultsFinally, I wrote a // Target directories
const rootCoverageDirectory = join(`build`, `coverage`);
const temporaryDirectory = join(`build`, `tmp`);
// Find all the workspaces that have coverage
const workspaces = getWorkspaces(); // TBD: Find official npm solution for this
const testedWorkspaces = workspaces.filter((workspace) =>
existsSync(join(workspace, `build`, `coverage`)),
);
// Generate the report
await new Report({
reporter: [`html-spa`],
reportsDirectory: rootCoverageDirectory,
tempDirectory: temporaryDirectory,
all: true,
// `include`, not `src`. We only want workspaces with tests for reporting.
// Using `src` could show dependency files that are exercised. (Note: This
// could represent an option, maybe on/related to `all`, but I prefer the
// “only include packages with tests.”
include: testedWorkspaces.map((workspace) => join(workspace, `src`)),
}).run();
// Clean up temporary directory
rmSync(temporaryDirectory, { recursive: true, force: true }); OrchestrationI use turbo repo, so it handles the orchestration. Essentially, run Final resultI had an aggregated coverage report (using the ConclusionI will be doing the following next, but happy to chat more on this topic.
I hope this helps move the conversation along! Feedback definitely welcome! |
Can you elaborate on what features you want to be scoped to the project? We are thinking about several possible implementations, but they all have their set of drawbacks.
|
To bypass that, we can allow a special property for the projects config: export default {
projects: [
['packages/*', { config: './shared.js' }], // will override config,
['packages2/*', { extends: './shared.js' }], // will merge shared config with values defined in the packages2
['packages3/*'] // require config in the directory
]
} This might allow reusing the same Vitest instance if the config is the same. |
From the discussion above I think the two most important features are:
|
So, support for monorepo is released in 0.30.0. All feedback is welcome. We did not implement any performance optimizations just yet, so I expect if you have a lot of projects in a monorepo you might get various degrees of performance issues because Vitest starts a new Vite server for each project, but I did not test this just yet. If you hit any issues with a monorepo setup, please open a separate issue. |
Clear and concise description of the problem
make monorepo first class support
Suggested solution
make monorepo first class support
Alternative
No response
Additional context
No response
Validations
The text was updated successfully, but these errors were encountered: