Publish a library with multiple packages using Turborepo Type: blog post Language: en-US Canonical URL: https://simonboisset.com/en/blog/share-packages-monorepo Published: February 10, 2022 Summary: Multiple packages? Now that we know how to publish a package on npm, we will see how to manage a library that has multiple packages. You can check out this e... Multiple packages? Now that we know how to publish a package on npm, we will see how to manage a library that has multiple packages. You can check out this example: https://github.com/simonboisset/validest to see a concrete application. Configuration To manage multiple modules together we will use Turborepo that you have already seen in a previous article. npx create-turbo@latest You can delete the different examples of base packages, we will create our own later. Builder We will start by creating a package to manage the builds of our libraries. This one will not be published but only shared in our monorepo. Create a builder folder in packages then create the following files: package.json { "name": "builder", "private": true, "version": "0.1.0", "bin": "index.js", "dependencies": { "esbuild": "^0.14.42", "esbuild-node-externals": "^1.4.1" } } index.js #!/usr/bin/env node const esbuild = require("esbuild"); const { nodeExternalsPlugin } = require("esbuild-node-externals"); esbuild .build({ entryPoints: ["./src/index.ts"], outfile: "dist/index.js", bundle: true, minify: true, treeShaking: true, platform: "node", format: "cjs", target: "node14", plugins: [nodeExternalsPlugin()], }) .catch(() => process.exit(1)); Our packages Now we can create our different packages. For the example we will create two. Core Still in the packages folder we will create a core folder and the following files: package.json { "name": "@example/core", "version": "0.1.0", "description": "A description", "main": "dist/index.js", "typings": "dist/index.d.ts", "files": ["dist"], "scripts": { "build": "rm -rf dist && builder && tsc" }, "author": "Your name", "license": "MIT", "devDependencies": { "builder": "", "typescript": "^4.7.4" } } You will notice that we used a scope for our package name @example. To use your own you must create an organization with the same name on npm. src/index.ts export const hello = (name: string) => { return "Hello " + name; }; tsconfig.json { "extends": "builder/ts.json", "include": ["src"], "exclude": ["src//.test.ts"], "compilerOptions": { "outDir": "dist", "rootDir": "src", "module": "esnext", "target": "esnext", "lib": ["dom", "dom.iterable", "esnext"], "declaration": true, "strict": true, "moduleResolution": "node", "jsx": "react", "skipLibCheck": true, "esModuleInterop": true, "emitDeclarationOnly": true } } Age Same thing for our second package @example/age package.json We add a dependency to core: "dependencies": { "@example/core": "0.1.0" }, index.ts import { hello } from "@example/core"; export const age = (name: string, age: number) => { return hello(name) + " you are " + age; }; Turbo build Our two packages are ready, we just have to build them together. We will still have to set up Turborepo to build core before age. At the root of the repo: turbo.json { "pipeline": { "build": { "dependsOn": ["^build"], "outputs": ["dist/"] } } } package.json { "name": "example", "version": "0.1.0", "private": true, "workspaces": ["packages/"], "scripts": { "build": "turbo run build --filter=@example/" }, "devDependencies": { "prettier": "latest", "turbo": "latest" }, "engines": { "npm": ">=7.0.0", "node": ">=14.0.0" }, "prettier": { "singleQuote": true, "tabWidth": 2, "printWidth": 120 }, "packageManager": "yarn@1.22.5" } We can already run the following commands for a first build: yarn yarn build Test If you want to add tests I recommend Vitest: https://vitest.dev/. You can follow the docs of Turborepo: https://turbo.build/repo/docs/handbook/testing about it. Versioning A somewhat complex step is to manage the correct versions of dependencies. The simplest thing is to consider that all our packages have the same version number. We will still have to use a script to increment our version number (patch, minor or major) of dependencies and packages before each publication. For this we use turboversion: yarn add -W -D turboversion The script will be executed before each publication in our CI. To explain quickly what it does, it will scan our entire monorepo and increment the version number (patch, minor or major) of the dependencies and packages of our scope example. Publication We will take back our Github Action from the previous article to adapt it to our monorepo: .github/workflows/publish.yml name: Publish on: workflowdispatch: inputs: release: description: "major | minor | patch" required: true default: "patch" type: choice options: major minor patch jobs: publish-new-version: runs-on: ubuntu-latest steps: name: 🔌 Checkout uses: actions/checkout@v3 name: 🏗 Setup Node uses: actions/setup-node@v3 with: node-version: 16.x cache: "yarn" registry-url: https://registry.npmjs.org/ scope: "@example" name: ⏳ Yarn install run: yarn name: 🚀 Publish New Version env: NODEAUTHTOKEN: ${{secrets.NPMTOKEN}} run: | git config --local user.email "youremail" git config --local user.name "yourname" yarn turboversion example ${{github.event.inputs.release}} yarn publish:lib PACKAGEVERSION=$(node -p "require('./package.json').version") git commit -a -m "v${PACKAGEVERSION}" git push There you go, you can now publish your library and all its packages with a consistent version number. --- I'm Simon Boisset, freelance fullstack developer. I mainly work with React, React Native and Node.js. I'm available for development or consulting missions. Feel free to contact me on my website: https://simonboisset.com/. Related links Blog index: https://simonboisset.com/en/blog Website: https://simonboisset.com/en