Create Multi-Module Project
1.Why a multi-module project?
Throughout this course we will build many small React projects — one per lesson — to demonstrate different concepts in isolation. Instead of keeping each project as a standalone repository with its own node_modules and its own copy of every dependency, we will organize them as a monorepo using Bun workspaces.
A multi-module setup gives us several benefits:
- Single source of truth for dependency versions —
react,vite,typescriptand all other tools are pinned once in the rootpackage.json. Every child project consumes the same version, so lessons stay consistent with each other. - One shared
node_modules— Bun hoists dependencies to the workspace root, so installing a new lesson project does not download the same packages again. - A single
bun install— running install at the root installs dependencies for every project in the workspace at once. - Easy cross-project references — later lessons can depend on code from earlier lessons by name, without publishing anything to a registry.
2.What is a Bun workspace?
A Bun workspace is a set of related packages managed from a single root package.json. The root declares which sub-directories are members of the workspace via the workspaces field:
{ "workspaces": ["*"]}The "*" pattern means “every direct subdirectory that contains a package.json is a workspace member”. Each member keeps its own package.json, but all dependencies are resolved and deduplicated from the root.
When a child package lists a dependency with version "*", Bun resolves that dependency from the version declared at the root. This is how we keep versions consistent across projects.
3.Convert the project
We will now convert the single react-project created in the previous lesson into a workspace member of a parent project.
Create the root `package.json`
In the parent directory that contains react-project/, create a new package.json. It declares the workspace and holds the concrete versions of every dependency shared by child projects.
{ "name": "react-course", "private": true, "workspaces": ["*"], "dependencies": { "react": "^19.2.4", "react-dom": "^19.2.4" }, "devDependencies": { "@eslint/js": "^9.39.4", "@types/node": "^24.12.0", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.1", "eslint": "^9.39.4", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.5.2", "globals": "^17.4.0", "typescript": "~5.9.3", "typescript-eslint": "^8.57.0", "vite": "^8.0.1" }}Key points:
"private": true— the workspace root is never published to a registry."workspaces": ["*"]— every direct subdirectory with apackage.json(currently justreact-project) becomes a workspace member.dependenciesanddevDependencies— the single source of truth for versions. Moving them here from the childpackage.jsonmeans every child project will use the exact same version.
Update the child `package.json`
In react-project/package.json, replace every concrete dependency version with "*". This tells Bun to resolve the dependency from the workspace root rather than from the npm registry.
{ "name": "react-project", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", "preview": "vite preview" }, "dependencies": { "react": "*", "react-dom": "*" }, "devDependencies": { "@eslint/js": "*", "@types/node": "*", "@types/react": "*", "@types/react-dom": "*", "@vitejs/plugin-react": "*", "eslint": "*", "eslint-plugin-react-hooks": "*", "eslint-plugin-react-refresh": "*", "globals": "*", "typescript": "*", "typescript-eslint": "*", "vite": "*" }}Notice that:
"name","version","type"and"scripts"stay in the child project — each project still has its own identity and its own build/dev commands.- All dependency versions are now
"*", which resolves to the version declared at the workspace root.
Install dependencies from the root
Delete the node_modules/ directory and bun.lock file inside react-project/ (if they exist from the previous lesson), then run bun install from the parent directory:
rm -rf react-project/node_modules react-project/bun.lockbun installBun will:
- Create a single
node_modules/at the workspace root with all hoisted dependencies. - Create a single
bun.lockat the root that locks versions for every workspace member. - Link
react-projectinto the workspace so it can import its dependencies normally.
Run the project
Child project scripts still work exactly the same. From inside react-project/:
cd react-projectbun run devYou can also run a child script from the root using Bun’s --filter flag, which is convenient when you have multiple projects:
bun --filter react-project dev4.Resulting structure
After the conversion the directory layout looks like this:
react/├── package.json # workspace root (pinned versions, "workspaces": ["*"])├── bun.lock # single lockfile for the whole workspace├── node_modules/ # hoisted dependencies shared by all members└── react-project/ ├── package.json # workspace member ("*" versions) ├── vite.config.ts ├── tsconfig.json └── src/From now on, every new lesson project we create will be added as another subdirectory next to react-project/, and it will automatically join the workspace — no extra configuration required.