├── apps
├── vite
│ ├── .gitignore
│ ├── src
│ │ ├── vite-env.d.ts
│ │ ├── main.tsx
│ │ ├── App.css
│ │ ├── App.tsx
│ │ ├── index.css
│ │ └── assets
│ │ │ └── react.svg
│ ├── tsconfig.node.json
│ ├── vite.config.ts
│ ├── index.html
│ ├── tsconfig.json
│ ├── package.json
│ └── public
│ │ └── vite.svg
├── cra
│ ├── .gitignore
│ ├── src
│ │ ├── hooks
│ │ │ └── test.tsx
│ │ ├── react-app-env.d.ts
│ │ ├── index.tsx
│ │ └── components
│ │ │ ├── app.spec.tsx
│ │ │ └── app.tsx
│ ├── tsconfig.paths.json
│ ├── public
│ │ └── index.html
│ ├── tsconfig.json
│ ├── package.json
│ └── craco.config.js
├── nextjs
│ ├── .gitignore
│ ├── pages
│ │ └── index.tsx
│ ├── next-env.d.ts
│ ├── package.json
│ ├── tsconfig.json
│ └── next.config.js
├── webpack
│ ├── src
│ │ └── index.ts
│ ├── tsconfig.json
│ ├── tsconfig.build.json
│ ├── package.json
│ └── webpack.config.js
├── ts-node
│ ├── tsconfig.json
│ ├── src
│ │ └── index.ts
│ └── package.json
├── storybook
│ ├── tsconfig.json
│ ├── src
│ │ ├── components
│ │ │ └── foo.tsx
│ │ └── index.stories.tsx
│ ├── .storybook
│ │ ├── preview.ts
│ │ └── main.ts
│ ├── babel.config.json
│ └── package.json
├── jest-babel
│ ├── tsconfig.json
│ ├── src
│ │ └── index.ts
│ ├── test
│ │ └── index.spec.ts
│ ├── babel.config.js
│ └── package.json
├── nestjs
│ ├── nest-cli.json
│ ├── tsconfig.build.json
│ ├── src
│ │ ├── main.ts
│ │ ├── app.service.ts
│ │ ├── app.module.ts
│ │ └── app.controller.ts
│ ├── tsconfig.json
│ └── package.json
├── rollup
│ ├── tsconfig.json
│ ├── src
│ │ └── index.tsx
│ ├── tsconfig.build.json
│ ├── rollup.config.ts
│ └── package.json
└── jest-tsjest
│ ├── src
│ └── index.ts
│ ├── tsconfig.json
│ ├── test
│ └── index.spec.ts
│ ├── jest.config.ts
│ └── package.json
├── .github
├── FUNDING.yml
└── workflows
│ └── tests.yml
├── .npmrc
├── packages
├── foo
│ ├── src
│ │ └── index.ts
│ ├── tsconfig.json
│ ├── tsconfig.build.json
│ └── package.json
├── bar
│ ├── tsconfig.json
│ ├── src
│ │ └── index.ts
│ ├── tsconfig.build.json
│ └── package.json
└── components
│ ├── tsconfig.json
│ ├── tsconfig.build.json
│ ├── src
│ └── button.tsx
│ └── package.json
├── .eslintignore
├── pnpm-workspace.yaml
├── .gitignore
├── media
├── monorepo.png
├── find-usage.gif
└── build-output.png
├── .eslintrc.js
├── renovate.json
├── tsconfig.json
├── tsconfig.build.json
├── package.json
├── LICENSE
└── README.md
/apps/vite/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: NiGhTTraX
2 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | strict-peer-dependencies=true
2 |
--------------------------------------------------------------------------------
/apps/cra/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | .eslintcache
3 |
--------------------------------------------------------------------------------
/apps/nextjs/.gitignore:
--------------------------------------------------------------------------------
1 | .next
2 |
3 | .vercel
4 |
--------------------------------------------------------------------------------
/apps/webpack/src/index.ts:
--------------------------------------------------------------------------------
1 | import "@nighttrax/bar";
2 |
--------------------------------------------------------------------------------
/packages/foo/src/index.ts:
--------------------------------------------------------------------------------
1 | export const meaningOfLife = 42;
2 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | !.*
3 | dist
4 | build
5 | .next
6 |
--------------------------------------------------------------------------------
/apps/cra/src/hooks/test.tsx:
--------------------------------------------------------------------------------
1 | export const useTest = () => 23;
2 |
--------------------------------------------------------------------------------
/apps/vite/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'packages/**'
3 | - 'apps/**'
4 |
--------------------------------------------------------------------------------
/apps/cra/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/apps/ts-node/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json"
3 | }
4 |
--------------------------------------------------------------------------------
/apps/webpack/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json"
3 | }
4 |
--------------------------------------------------------------------------------
/packages/bar/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json"
3 | }
4 |
--------------------------------------------------------------------------------
/packages/foo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json"
3 | }
4 |
--------------------------------------------------------------------------------
/apps/storybook/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | **/dist
3 | results
4 | .nyc_output
5 | *.tsbuildinfo
6 |
--------------------------------------------------------------------------------
/media/monorepo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whatever/hmmm/master/media/monorepo.png
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: "@nighttrax/eslint-config-tsx",
3 | };
4 |
--------------------------------------------------------------------------------
/media/find-usage.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whatever/hmmm/master/media/find-usage.gif
--------------------------------------------------------------------------------
/media/build-output.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whatever/hmmm/master/media/build-output.png
--------------------------------------------------------------------------------
/apps/storybook/src/components/foo.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export const Foo = () => foo ;
4 |
--------------------------------------------------------------------------------
/apps/jest-babel/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "types": ["jest"]
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/apps/jest-babel/src/index.ts:
--------------------------------------------------------------------------------
1 | import { meaningOfLife } from "@nighttrax/foo";
2 |
3 | export const whatIsTheMeaningOfLife = () => meaningOfLife;
4 |
--------------------------------------------------------------------------------
/apps/nestjs/nest-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "collection": "@nestjs/schematics",
3 | "sourceRoot": "src",
4 | "entryFile": "apps/nestjs/src/main"
5 | }
6 |
--------------------------------------------------------------------------------
/apps/rollup/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "resolveJsonModule": true,
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/apps/jest-tsjest/src/index.ts:
--------------------------------------------------------------------------------
1 | import { meaningOfLife } from "@nighttrax/foo";
2 |
3 | export const whatIsTheMeaningOfLife = () => meaningOfLife;
4 |
--------------------------------------------------------------------------------
/apps/ts-node/src/index.ts:
--------------------------------------------------------------------------------
1 | import { meaningOfLife } from "@nighttrax/foo";
2 |
3 | // eslint-disable-next-line no-console
4 | console.log(meaningOfLife);
5 |
--------------------------------------------------------------------------------
/packages/bar/src/index.ts:
--------------------------------------------------------------------------------
1 | import { meaningOfLife } from "@nighttrax/foo";
2 |
3 | // eslint-disable-next-line no-console
4 | console.log(meaningOfLife);
5 |
--------------------------------------------------------------------------------
/apps/nextjs/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Button } from "@nighttrax/components/button";
3 |
4 | export default () => ;
5 |
--------------------------------------------------------------------------------
/apps/rollup/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { meaningOfLife } from "@nighttrax/foo";
2 |
3 | // eslint-disable-next-line no-console
4 | console.log(meaningOfLife + 3);
5 |
--------------------------------------------------------------------------------
/apps/jest-tsjest/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "resolveJsonModule": true,
5 | "types": ["jest"]
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/packages/components/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "jsx": "react",
5 | "esModuleInterop": true
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/apps/webpack/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "noEmit": false,
5 | "declaration": false,
6 | "declarationMap": false
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/apps/nestjs/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | /* Extend the config that contains the path aliases. */
3 | "extends": "./tsconfig.json",
4 |
5 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
6 | }
7 |
--------------------------------------------------------------------------------
/packages/bar/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.build.json",
3 |
4 | "compilerOptions": {
5 | "outDir": "./dist"
6 | },
7 |
8 | "include": [
9 | "src/**/*"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/packages/foo/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.build.json",
3 |
4 | "compilerOptions": {
5 | "outDir": "./dist"
6 | },
7 |
8 | "include": [
9 | "src/**/*"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/apps/jest-babel/test/index.spec.ts:
--------------------------------------------------------------------------------
1 | import { whatIsTheMeaningOfLife } from "../src";
2 |
3 | describe("meaning of life", () => {
4 | it("should be 42", () => {
5 | expect(whatIsTheMeaningOfLife()).toEqual(42);
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/apps/jest-tsjest/test/index.spec.ts:
--------------------------------------------------------------------------------
1 | import { whatIsTheMeaningOfLife } from "../src";
2 |
3 | describe("meaning of life", () => {
4 | it("should be 42", () => {
5 | expect(whatIsTheMeaningOfLife()).toEqual(42);
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/apps/nextjs/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/apps/rollup/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | /* Use the config the HAS the aliases. */
3 | "extends": "../../tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./dist"
6 | },
7 | "include": [
8 | "src/**/*"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/apps/storybook/.storybook/preview.ts:
--------------------------------------------------------------------------------
1 | export const parameters = {
2 | actions: { argTypesRegex: "^on[A-Z].*" },
3 | controls: {
4 | matchers: {
5 | color: /(background|color)$/i,
6 | date: /Date$/,
7 | },
8 | },
9 | };
10 |
--------------------------------------------------------------------------------
/apps/vite/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/apps/nestjs/src/main.ts:
--------------------------------------------------------------------------------
1 | import { NestFactory } from "@nestjs/core";
2 | import { AppModule } from "./app.module";
3 |
4 | async function bootstrap() {
5 | const app = await NestFactory.create(AppModule);
6 | await app.listen(3000);
7 | }
8 | bootstrap();
9 |
--------------------------------------------------------------------------------
/apps/cra/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import { App } from "@components/app";
4 |
5 | ReactDOM.render(
6 |
7 |
8 | ,
9 | document.getElementById("root")
10 | );
11 |
--------------------------------------------------------------------------------
/packages/components/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.build.json",
3 |
4 | "compilerOptions": {
5 | "outDir": "./dist",
6 | "jsx": "react",
7 | "esModuleInterop": true
8 | },
9 |
10 | "include": [
11 | "src/**/*"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ],
5 | "ignorePresets": [":ignoreModulesAndTests"],
6 | "rangeStrategy": "replace",
7 | "automerge": true,
8 | "automergeType": "branch",
9 | "minimumReleaseAge": "14 days",
10 | "internalChecksFilter": "strict"
11 | }
12 |
--------------------------------------------------------------------------------
/apps/vite/vite.config.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | import { defineConfig } from "vite";
3 | import react from "@vitejs/plugin-react";
4 | import tsconfigPaths from "vite-tsconfig-paths";
5 |
6 | export default defineConfig({
7 | plugins: [react(), tsconfigPaths()],
8 | });
9 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.build.json",
3 |
4 | "compilerOptions": {
5 | "baseUrl": ".",
6 | "paths": {
7 | "@nighttrax/components/*": ["packages/components/src/*"],
8 | "@nighttrax/*": ["packages/*/src"]
9 | },
10 | "noEmit": true
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/apps/nestjs/src/app.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from "@nestjs/common";
2 | import { meaningOfLife } from "@nighttrax/foo";
3 |
4 | @Injectable()
5 | export class AppService {
6 | // eslint-disable-next-line class-methods-use-this
7 | getHello = (): string => `The meaning of life is: ${meaningOfLife}`;
8 | }
9 |
--------------------------------------------------------------------------------
/apps/vite/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App";
4 | import "./index.css";
5 |
6 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
7 |
8 |
9 |
10 | );
11 |
--------------------------------------------------------------------------------
/apps/nestjs/src/app.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from "@nestjs/common";
2 | import { AppController } from "./app.controller";
3 | import { AppService } from "./app.service";
4 |
5 | @Module({
6 | imports: [],
7 | controllers: [AppController],
8 | providers: [AppService],
9 | })
10 | export class AppModule {}
11 |
--------------------------------------------------------------------------------
/apps/storybook/src/index.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@nighttrax/components/button";
2 | import React from "react";
3 | import { Foo } from "./components/foo";
4 |
5 | export default {
6 | title: "Index",
7 | };
8 |
9 | export const Local = () => ;
10 |
11 | export const Monorepo = () => ;
12 |
--------------------------------------------------------------------------------
/apps/cra/src/components/app.spec.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render } from "react-dom";
3 | import { App } from "./app";
4 |
5 | describe("App", () => {
6 | it("should render without crashing", () => {
7 | const div = document.createElement("div");
8 | render( , div);
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/apps/storybook/babel.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "sourceType": "unambiguous",
3 | "presets": [
4 | [
5 | "@babel/preset-env",
6 | {
7 | "targets": {
8 | "chrome": 100
9 | }
10 | }
11 | ],
12 | "@babel/preset-typescript",
13 | "@babel/preset-react"
14 | ],
15 | "plugins": []
16 | }
--------------------------------------------------------------------------------
/apps/nestjs/src/app.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Get } from "@nestjs/common";
2 | import { AppService } from "./app.service";
3 |
4 | @Controller()
5 | export class AppController {
6 | constructor(private readonly appService: AppService) {}
7 |
8 | @Get()
9 | getHello(): string {
10 | return this.appService.getHello();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/apps/cra/tsconfig.paths.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 |
5 | "paths": {
6 | "@nighttrax/components/*": ["../../packages/components/src/*"],
7 | "@nighttrax/*": ["../../packages/*/src"],
8 |
9 | "@components/*": ["./src/components/*"],
10 | "@hooks/*": ["./src/hooks/*"]
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/components/src/button.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-alert */
2 | import { meaningOfLife } from "@nighttrax/foo";
3 | import React, { FC } from "react";
4 |
5 | export const Button: FC = () => (
6 | alert(`the meaning of life is ${meaningOfLife}`)}
9 | >
10 | Click me
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/apps/cra/src/components/app.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Button } from "@nighttrax/components/button";
3 | import { meaningOfLife } from "@nighttrax/foo";
4 | import { useTest } from "@hooks/test";
5 |
6 | export const App = () => {
7 | useTest();
8 |
9 | return (
10 |
11 | {meaningOfLife}
12 |
13 |
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/apps/jest-babel/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ["@babel/preset-env", { targets: { node: "current" } }],
4 | "@babel/preset-typescript",
5 | ],
6 |
7 | plugins: [
8 | [
9 | "module-resolver",
10 | {
11 | alias: {
12 | "^@nighttrax/(.+)": "../../packages/\\1/src",
13 | },
14 | },
15 | ],
16 | ],
17 | };
18 |
--------------------------------------------------------------------------------
/apps/vite/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/apps/nestjs/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 |
4 | "compilerOptions": {
5 | "module": "commonjs",
6 | "declaration": true,
7 | "removeComments": true,
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "allowSyntheticDefaultImports": true,
11 | "target": "es2017",
12 | "sourceMap": true,
13 | "outDir": "./dist",
14 | "incremental": true,
15 | "noEmit": false
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es5",
5 | "sourceMap": true,
6 | "strict": true,
7 | "declaration": true,
8 | "declarationMap": true,
9 | "noEmitOnError": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "types": [],
13 | "jsx": "react",
14 | "noEmit": false
15 | },
16 | "exclude": [
17 | "node_modules",
18 | "dist"
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/packages/foo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nighttrax/foo",
3 | "version": "1.0.0",
4 | "main": "dist/index",
5 | "types": "dist/index",
6 | "files": [
7 | "dist"
8 | ],
9 | "scripts": {
10 | "build": "pnpm run clean && pnpm run compile",
11 | "clean": "rimraf ./dist",
12 | "compile": "tsc -p tsconfig.build.json",
13 | "prepublishOnly": "pnpm run build"
14 | },
15 | "devDependencies": {
16 | "rimraf": "~5.0.0",
17 | "typescript": "~4.9.0"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/apps/jest-tsjest/jest.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "@jest/types";
2 | import { pathsToModuleNameMapper } from "ts-jest";
3 | // Load the config which holds the path aliases.
4 | import { compilerOptions } from "../../tsconfig.json";
5 |
6 | const config: Config.InitialOptions = {
7 | preset: "ts-jest",
8 |
9 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, {
10 | // This has to match the baseUrl defined in tsconfig.json.
11 | prefix: "/../../",
12 | }),
13 | };
14 |
15 | export default config;
16 |
--------------------------------------------------------------------------------
/apps/cra/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 | React App
12 |
13 |
14 | You need to enable JavaScript to run this app.
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/apps/ts-node/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nighttrax/ts-node",
3 | "description": "Example of using ts-node in a TS monorepo",
4 | "private": true,
5 | "version": "1.0.0",
6 | "scripts": {
7 | "start": "ts-node -r tsconfig-paths/register src/index.ts",
8 | "test": "pnpm run start"
9 | },
10 | "dependencies": {
11 | "@nighttrax/foo": "workspace:*"
12 | },
13 | "devDependencies": {
14 | "@types/node": "~20.11.0",
15 | "ts-node": "~10.9.0",
16 | "tsconfig-paths": "~4.2.0",
17 | "typescript": "~4.9.0"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/bar/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nighttrax/bar",
3 | "version": "1.0.0",
4 | "main": "dist/index",
5 | "types": "dist/index",
6 | "files": [
7 | "dist"
8 | ],
9 | "scripts": {
10 | "build": "pnpm run clean && pnpm run compile",
11 | "clean": "rimraf ./dist",
12 | "compile": "tsc -p tsconfig.build.json",
13 | "prepublishOnly": "pnpm run build"
14 | },
15 | "dependencies": {
16 | "@nighttrax/foo": "~1.0.0"
17 | },
18 | "devDependencies": {
19 | "rimraf": "~5.0.0",
20 | "typescript": "~4.9.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/apps/jest-tsjest/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nighttrax/jest-example",
3 | "description": "Example of using jest with a TS monorepo",
4 | "private": true,
5 | "version": "1.0.0",
6 | "scripts": {
7 | "test": "jest"
8 | },
9 | "dependencies": {
10 | "@nighttrax/foo": "workspace:*"
11 | },
12 | "devDependencies": {
13 | "@types/jest": "~29.5.0",
14 | "@jest/types": "~29.6.0",
15 | "@types/node": "~20.11.0",
16 | "jest": "~29.7.0",
17 | "ts-jest": "~29.1.0",
18 | "ts-node": "~10.9.0",
19 | "typescript": "~4.9.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/apps/rollup/rollup.config.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | import typescript from "@rollup/plugin-typescript";
3 | import { defineConfig } from "rollup";
4 | import pkg from "./package.json";
5 |
6 | export default defineConfig({
7 | input: "src/index.tsx",
8 | // Without this, rollup would inline our monorepo dependencies.
9 | external: Object.keys(pkg.dependencies),
10 | plugins: [
11 | typescript({
12 | tsconfig: "./tsconfig.build.json",
13 | }),
14 | ],
15 | output: [{ dir: "./dist", format: "cjs", sourcemap: true }],
16 | });
17 |
--------------------------------------------------------------------------------
/apps/webpack/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nighttrax/webpack-example",
3 | "description": "Example of using webpack in a TS monorepo",
4 | "private": true,
5 | "version": "1.0.0",
6 | "scripts": {
7 | "clean": "rimraf dist",
8 | "build": "webpack"
9 | },
10 | "dependencies": {
11 | "@nighttrax/bar": "workspace:*"
12 | },
13 | "devDependencies": {
14 | "rimraf": "~5.0.0",
15 | "webpack": "~5.90.0",
16 | "webpack-cli": "~5.1.0",
17 | "ts-loader": "~9.5.0",
18 | "tsconfig-paths-webpack-plugin": "~4.1.0",
19 | "typescript": "~4.9.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/apps/jest-babel/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nighttrax/jest-babel-example",
3 | "description": "Example of using jest + babel with a TS monorepo",
4 | "private": true,
5 | "version": "1.0.0",
6 | "scripts": {
7 | "test": "jest"
8 | },
9 | "dependencies": {
10 | "@nighttrax/foo": "workspace:*"
11 | },
12 | "devDependencies": {
13 | "@babel/core": "~7.24.0",
14 | "@babel/preset-env": "~7.24.0",
15 | "@babel/preset-typescript": "~7.23.0",
16 | "@types/jest": "~29.5.0",
17 | "babel-plugin-module-resolver": "~5.0.0",
18 | "jest": "~29.7.0"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/apps/rollup/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nighttrax/rollup-example",
3 | "private": true,
4 | "version": "1.0.0",
5 | "main": "dist/index.js",
6 | "types": "dist/index.d.ts",
7 | "scripts": {
8 | "clean": "rimraf dist",
9 | "build": "rollup -c --bundleConfigAsCjs"
10 | },
11 | "dependencies": {
12 | "@nighttrax/foo": "workspace:*"
13 | },
14 | "devDependencies": {
15 | "@rollup/plugin-typescript": "~11.1.5",
16 | "@types/node": "~20.11.0",
17 | "rimraf": "~5.0.0",
18 | "rollup": "~4.13.0",
19 | "tslib": "~2.6.0",
20 | "typescript": "~4.9.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/components/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nighttrax/components",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "build": "pnpm run clean && pnpm run compile",
6 | "clean": "rimraf ./dist",
7 | "compile": "tsc -p tsconfig.build.json",
8 | "prepublishOnly": "pnpm run build"
9 | },
10 | "peerDependencies": {
11 | "react": "~18.2.0"
12 | },
13 | "dependencies": {
14 | "@nighttrax/foo": "workspace:*"
15 | },
16 | "devDependencies": {
17 | "@types/react": "~18.2.0",
18 | "react": "~18.2.0",
19 | "react-dom": "~18.2.0",
20 | "rimraf": "~5.0.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/apps/nextjs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nighttrax/nextjs-example",
3 | "private": true,
4 | "version": "1.0.0",
5 | "scripts": {
6 | "clean": "rimraf .next",
7 | "dev": "next dev",
8 | "build": "next build"
9 | },
10 | "dependencies": {
11 | "next": "~14.0.0",
12 | "react": "~18.2.0",
13 | "react-dom": "~18.2.0",
14 | "@nighttrax/components": "workspace:*"
15 | },
16 | "devDependencies": {
17 | "@types/node": "~20.11.0",
18 | "@types/react": "~18.2.0",
19 | "@types/react-dom": "~18.2.0",
20 | "rimraf": "~5.0.0",
21 | "typescript": "~4.9.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/apps/webpack/webpack.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
3 |
4 | module.exports = {
5 | entry: "./src/index.ts",
6 | mode: "development",
7 | output: {
8 | filename: "bundle.js",
9 | },
10 | resolve: {
11 | extensions: [".ts", ".tsx", ".js"],
12 | plugins: [new TsconfigPathsPlugin()],
13 | },
14 | module: {
15 | rules: [
16 | {
17 | test: /\.tsx?$/,
18 | loader: "ts-loader",
19 | options: { configFile: "tsconfig.build.json" },
20 | },
21 | ],
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ts-monorepo",
3 | "description": "Template for setting up a TypeScript monorepo",
4 | "private": true,
5 | "workspaces": [
6 | "packages/*",
7 | "apps/*"
8 | ],
9 | "scripts": {
10 | "docs": "doctoc --title '**Table of content**' README.md",
11 | "clean": "pnpm run -r clean",
12 | "build": "pnpm run -r build",
13 | "test": "pnpm run -r test",
14 | "lint": "eslint --ext js,ts,tsx ."
15 | },
16 | "devDependencies": {
17 | "@nighttrax/eslint-config-tsx": "~12.0.0-alpha.2",
18 | "doctoc": "~2.2.0",
19 | "eslint": "~8.57.0",
20 | "eslint-plugin-import": "~2.29.0",
21 | "typescript": "~4.9.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/apps/cra/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.paths.json",
3 | "compilerOptions": {
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "allowJs": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "noEmit": true,
20 | "jsx": "react-jsx",
21 | "noFallthroughCasesInSwitch": true,
22 | "skipLibCheck": true
23 | },
24 | "include": [
25 | "src"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/apps/vite/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "target": "ESNext",
5 | "useDefineForClassFields": true,
6 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
7 | "allowJs": false,
8 | "skipLibCheck": true,
9 | "esModuleInterop": false,
10 | "allowSyntheticDefaultImports": true,
11 | "strict": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "module": "ESNext",
14 | "moduleResolution": "Node",
15 | "resolveJsonModule": true,
16 | "isolatedModules": true,
17 | "noEmit": true,
18 | "jsx": "react-jsx",
19 | },
20 | "include": ["src"],
21 | "references": [{ "path": "./tsconfig.node.json" }]
22 | }
23 |
--------------------------------------------------------------------------------
/apps/nextjs/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "allowJs": true,
11 | "strict": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve",
20 | "skipLibCheck": true,
21 | "incremental": true
22 | },
23 | "exclude": [
24 | "node_modules"
25 | ],
26 | "include": [
27 | "next-env.d.ts",
28 | "**/*.ts",
29 | "**/*.tsx"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: tests
2 |
3 | on:
4 | push:
5 | pull_request:
6 | branches: [ $default-branch ]
7 |
8 | jobs:
9 | tests:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v4
15 |
16 | - uses: pnpm/action-setup@v3.0.0
17 | with:
18 | version: latest
19 |
20 | - name: Install Node
21 | uses: actions/setup-node@v4
22 | with:
23 | node-version: 20
24 | cache: 'pnpm'
25 |
26 | - name: Install dependencies
27 | run: pnpm i
28 |
29 | - name: Lint
30 | run: pnpm run lint
31 |
32 | - name: Build
33 | run: pnpm run build
34 |
35 | - name: Test
36 | run: pnpm run test
37 |
--------------------------------------------------------------------------------
/apps/vite/src/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | }
13 | .logo:hover {
14 | filter: drop-shadow(0 0 2em #646cffaa);
15 | }
16 | .logo.react:hover {
17 | filter: drop-shadow(0 0 2em #61dafbaa);
18 | }
19 |
20 | @keyframes logo-spin {
21 | from {
22 | transform: rotate(0deg);
23 | }
24 | to {
25 | transform: rotate(360deg);
26 | }
27 | }
28 |
29 | @media (prefers-reduced-motion: no-preference) {
30 | a:nth-of-type(2) .logo {
31 | animation: logo-spin infinite 20s linear;
32 | }
33 | }
34 |
35 | .card {
36 | padding: 2em;
37 | }
38 |
39 | .read-the-docs {
40 | color: #888;
41 | }
42 |
--------------------------------------------------------------------------------
/apps/vite/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nighttrax/vite-example",
3 | "description": "Example of using Vite in a TS monorepo",
4 | "private": true,
5 | "version": "1.0.0",
6 | "type": "module",
7 | "scripts": {
8 | "dev": "vite",
9 | "build": "tsc && vite build",
10 | "preview": "vite preview",
11 | "clean": "rimraf dist"
12 | },
13 | "dependencies": {
14 | "@nighttrax/components": "workspace:*",
15 | "@nighttrax/foo": "workspace:*",
16 | "react": "~18.2.0",
17 | "react-dom": "~18.2.0"
18 | },
19 | "devDependencies": {
20 | "@types/react": "~18.2.0",
21 | "@types/react-dom": "~18.2.0",
22 | "@vitejs/plugin-react": "~4.2.0",
23 | "rimraf": "~5.0.0",
24 | "typescript": "~4.9.3",
25 | "vite-tsconfig-paths": "~4.2.0",
26 | "vite": "~5.1.0"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/apps/nestjs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nestjs",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "clean": "rimraf dist",
7 | "prebuild": "pnpm run clean",
8 | "build": "nest build",
9 | "start:prod": "node dist/apps/nestjs/src/main",
10 | "start:dev": "nest start --watch"
11 | },
12 | "dependencies": {
13 | "@nestjs/common": "~10.3.0",
14 | "@nestjs/core": "~10.3.0",
15 | "@nestjs/platform-express": "~10.3.0",
16 | "@nighttrax/foo": "workspace:*",
17 | "reflect-metadata": "~0.1.13",
18 | "rimraf": "~5.0.0",
19 | "rxjs": "~7.8.0"
20 | },
21 | "devDependencies": {
22 | "@nestjs/cli": "~10.3.0",
23 | "@types/express": "~4.17.11",
24 | "@types/node": "~20.11.0",
25 | "ts-node": "~10.9.0",
26 | "tsconfig-paths": "~4.2.0",
27 | "typescript": "~4.9.0"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/apps/storybook/.storybook/main.ts:
--------------------------------------------------------------------------------
1 | import { StorybookConfig } from "@storybook/react-webpack5";
2 | import assert from "node:assert";
3 | import { TsconfigPathsPlugin } from "tsconfig-paths-webpack-plugin";
4 |
5 | const config: StorybookConfig = {
6 | stories: ["../src/*.stories.tsx"],
7 | addons: [],
8 | framework: {
9 | name: "@storybook/react-webpack5",
10 | options: {},
11 | },
12 | typescript: {
13 | check: true,
14 | },
15 | webpackFinal: async (config) => {
16 | assert(config.resolve, "Can't extend the Storybook config");
17 |
18 | // eslint-disable-next-line no-param-reassign
19 | config.resolve.plugins = [
20 | ...(config.resolve.plugins || []),
21 | new TsconfigPathsPlugin({
22 | extensions: config.resolve.extensions,
23 | }),
24 | ];
25 |
26 | return config;
27 | },
28 | };
29 |
30 | export default config;
31 |
--------------------------------------------------------------------------------
/apps/storybook/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nighttrax/storybook-example",
3 | "private": true,
4 | "version": "1.0.0",
5 | "dependencies": {
6 | "@nighttrax/components": "workspace:*",
7 | "react": "~18.2.0",
8 | "react-dom": "~18.2.0"
9 | },
10 | "devDependencies": {
11 | "@babel/core": "~7.24.0",
12 | "@babel/preset-env": "~7.24.0",
13 | "@babel/preset-react": "~7.23.0",
14 | "@babel/preset-typescript": "~7.23.0",
15 | "@storybook/core-common": "~7.6.0",
16 | "@storybook/react": "~7.6.0",
17 | "@storybook/react-webpack5": "~7.6.0",
18 | "@types/react": "~18.2.21",
19 | "@types/react-dom": "~18.2.7",
20 | "babel-loader": "~9.1.0",
21 | "require-from-string": "~2.0.2",
22 | "rimraf": "~5.0.0",
23 | "storybook": "~7.6.0",
24 | "tsconfig-paths-webpack-plugin": "~4.1.0",
25 | "typescript": "~4.9.0",
26 | "webpack": "~5.90.0"
27 | },
28 | "scripts": {
29 | "storybook": "storybook dev -p 6006",
30 | "build": "storybook build -o dist --quiet",
31 | "clean": "rimraf dist"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Andrei Picus
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/apps/vite/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@nighttrax/components/button";
2 | import { meaningOfLife } from "@nighttrax/foo";
3 | import React, { useState } from "react";
4 | import reactLogo from "./assets/react.svg";
5 | import "./App.css";
6 |
7 | const App = () => {
8 | const [count, setCount] = useState(meaningOfLife);
9 |
10 | return (
11 |
12 |
20 |
Vite + React
21 |
22 |
23 |
setCount((count) => count + 1)}>
24 | count is {count}
25 |
26 |
27 | Edit src/App.tsx and save to test HMR
28 |
29 |
30 |
31 | Click on the Vite and React logos to learn more
32 |
33 |
34 | );
35 | };
36 |
37 | export default App;
38 |
--------------------------------------------------------------------------------
/apps/vite/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/nextjs/next.config.js:
--------------------------------------------------------------------------------
1 | const isNext12 = (config) => !!config.module.rules.find((rule) => rule.oneOf);
2 |
3 | const updateNextGreaterThan12Config = (config) => {
4 | const oneOfRule = config.module.rules.find((rule) => rule.oneOf);
5 |
6 | // Next 12 has multiple TS loaders, and we need to update all of them.
7 | const tsRules = oneOfRule.oneOf.filter(
8 | (rule) => rule.test && rule.test.toString().includes("tsx|ts")
9 | );
10 |
11 | tsRules.forEach((rule) => {
12 | // eslint-disable-next-line no-param-reassign
13 | rule.include = undefined;
14 | });
15 |
16 | return config;
17 | };
18 |
19 | const updateNextLessThan12Config = (config) => {
20 | // Next < 12 uses a single Babel loader.
21 | const tsRule = config.module.rules.find(
22 | (rule) => rule.test && rule.test.toString().includes("tsx|ts")
23 | );
24 |
25 | tsRule.include = undefined;
26 | tsRule.exclude = /node_modules/;
27 |
28 | return config;
29 | };
30 |
31 | module.exports = {
32 | /**
33 | * You can use the following experimental flag if you're on Next >= 10.1.0.
34 | * Note that this can change/break without warning.
35 | * @see https://github.com/vercel/next.js/pull/22867
36 | */
37 | // experimental: {
38 | // externalDir: true,
39 | // },
40 | webpack: (config) => {
41 | if (isNext12(config)) {
42 | return updateNextGreaterThan12Config(config);
43 | }
44 |
45 | return updateNextLessThan12Config(config);
46 | },
47 | };
48 |
--------------------------------------------------------------------------------
/apps/vite/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
3 | font-size: 16px;
4 | line-height: 24px;
5 | font-weight: 400;
6 |
7 | color-scheme: light dark;
8 | color: rgba(255, 255, 255, 0.87);
9 | background-color: #242424;
10 |
11 | font-synthesis: none;
12 | text-rendering: optimizeLegibility;
13 | -webkit-font-smoothing: antialiased;
14 | -moz-osx-font-smoothing: grayscale;
15 | -webkit-text-size-adjust: 100%;
16 | }
17 |
18 | a {
19 | font-weight: 500;
20 | color: #646cff;
21 | text-decoration: inherit;
22 | }
23 | a:hover {
24 | color: #535bf2;
25 | }
26 |
27 | body {
28 | margin: 0;
29 | display: flex;
30 | place-items: center;
31 | min-width: 320px;
32 | min-height: 100vh;
33 | }
34 |
35 | h1 {
36 | font-size: 3.2em;
37 | line-height: 1.1;
38 | }
39 |
40 | button {
41 | border-radius: 8px;
42 | border: 1px solid transparent;
43 | padding: 0.6em 1.2em;
44 | font-size: 1em;
45 | font-weight: 500;
46 | font-family: inherit;
47 | background-color: #1a1a1a;
48 | cursor: pointer;
49 | transition: border-color 0.25s;
50 | }
51 | button:hover {
52 | border-color: #646cff;
53 | }
54 | button:focus,
55 | button:focus-visible {
56 | outline: 4px auto -webkit-focus-ring-color;
57 | }
58 |
59 | @media (prefers-color-scheme: light) {
60 | :root {
61 | color: #213547;
62 | background-color: #ffffff;
63 | }
64 | a:hover {
65 | color: #747bff;
66 | }
67 | button {
68 | background-color: #f9f9f9;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/apps/cra/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nighttrax/cra-example",
3 | "description": "Example of using create-react-app in a TS monorepo",
4 | "private": true,
5 | "version": "1.0.0",
6 | "dependencies": {
7 | "@nighttrax/foo": "workspace:*",
8 | "@nighttrax/components": "workspace:*",
9 | "react": "~18.2.0",
10 | "react-dom": "~18.2.0"
11 | },
12 | "devDependencies": {
13 | "@types/node": "~20.11.0",
14 | "@babel/core": "~7.24.0",
15 | "@babel/plugin-syntax-flow": "~7.23.0",
16 | "@babel/plugin-transform-react-jsx": "~7.23.0",
17 | "@craco/craco": "~7.1.0",
18 | "@types/express": "~4.17.13",
19 | "@types/jest": "~29.5.0",
20 | "@types/react": "~18.2.0",
21 | "@types/react-dom": "~18.2.0",
22 | "autoprefixer": "~10.4.0",
23 | "cross-env": "~7.0.2",
24 | "jest": "~29.7.0",
25 | "postcss": "~8.4.31",
26 | "react-scripts": "~5.0.0",
27 | "rimraf": "~5.0.0",
28 | "ts-jest": "~29.1.0",
29 | "tsconfig-paths-webpack-plugin": "~4.1.0",
30 | "typescript": "~4.9.0",
31 | "ts-node": "~10.9.1"
32 | },
33 | "scripts": {
34 | "clean": "rimraf dist",
35 | "start": "cross-env SKIP_PREFLIGHT_CHECK=true craco start",
36 | "build": "cross-env SKIP_PREFLIGHT_CHECK=true craco build",
37 | "test": "cross-env SKIP_PREFLIGHT_CHECK=true craco test --watchAll=false"
38 | },
39 | "browserslist": {
40 | "production": [
41 | ">0.2%",
42 | "not dead",
43 | "not op_mini all"
44 | ],
45 | "development": [
46 | "last 1 chrome version",
47 | "last 1 firefox version",
48 | "last 1 safari version"
49 | ]
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/apps/cra/craco.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
3 | const ForkTSCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
4 | const { pathsToModuleNameMapper } = require("ts-jest");
5 | const { compilerOptions } = require("./tsconfig.paths.json");
6 |
7 | module.exports = {
8 | eslint: { enable: false },
9 | webpack: {
10 | configure: (config) => {
11 | // Remove ModuleScopePlugin which throws when we try to import something
12 | // outside of src/.
13 | config.resolve.plugins.pop();
14 |
15 | // Resolve the path aliases.
16 | config.resolve.plugins.push(new TsconfigPathsPlugin());
17 |
18 | // Let Babel compile outside of src/.
19 | const oneOfRule = config.module.rules.find((rule) => rule.oneOf);
20 | const tsRule = oneOfRule.oneOf.find((rule) =>
21 | rule.test.toString().includes("ts|tsx")
22 | );
23 |
24 | tsRule.include = undefined;
25 | tsRule.exclude = /node_modules/;
26 |
27 | return config;
28 | },
29 | plugins: {
30 | remove: [
31 | // This plugin is too old and causes problems in monorepos. We'll
32 | // replace it with a newer version.
33 | "ForkTsCheckerWebpackPlugin",
34 | ],
35 | add: [
36 | // Use newer version of ForkTSCheckerWebpackPlugin to type check
37 | // files across the monorepo.
38 | new ForkTSCheckerWebpackPlugin({
39 | issue: {
40 | // The exclude rules are copied from CRA.
41 | exclude: [
42 | {
43 | file: "**/src/**/__tests__/**",
44 | },
45 | {
46 | file: "**/src/**/?(*.)(spec|test).*",
47 | },
48 | {
49 | file: "**/src/setupProxy.*",
50 | },
51 | {
52 | file: "**/src/setupTests.*",
53 | },
54 | ],
55 | },
56 | }),
57 | ],
58 | },
59 | },
60 | jest: {
61 | configure: {
62 | preset: "ts-jest",
63 |
64 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, {
65 | // This has to match the baseUrl defined in tsconfig.json.
66 | prefix: "/",
67 | }),
68 | },
69 | },
70 | };
71 |
--------------------------------------------------------------------------------
/apps/vite/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 
5 |
6 | Template project for setting up a TypeScript monorepo
7 |
8 | [](https://github.com/NiGhTTraX/ts-monorepo/actions/workflows/tests.yml)
9 |
10 |
11 |
12 | ----
13 |
14 |
15 |
16 | **Table of content**
17 |
18 | - [Features](#features)
19 | - [Setup](#setup)
20 | - [Docs](#docs)
21 | - [Packages vs apps](#packages-vs-apps)
22 | - [Integrations](#integrations)
23 | - [ts-node](#ts-node)
24 | - [Babel](#babel)
25 | - [webpack](#webpack)
26 | - [jest](#jest)
27 | - [create-react-app](#create-react-app)
28 | - [Vite](#vite)
29 | - [NextJS](#nextjs)
30 | - [NestJS](#nestjs)
31 | - [Storybook](#storybook)
32 |
33 |
34 |
35 | ## Features
36 |
37 | > The main focus of this repo is making the **`Go to definition`** feature in IDEs work without any surprises, meaning it will work after a fresh clone without needing to build the project.
38 |
39 | 
40 |
41 | > The secondary focus is to remove surprises when **publishing** packages. The repo is set up so that each package gets a clean build output without any artifacts from other packages.
42 |
43 | 
44 |
45 | > Everything else is kept to a **minimum**. Apart from my personal [ESLint config](.eslintrc.js) to keep the code clean, there are no extra tools included — you're free to customize this to your own needs after cloning. Compilation targets, module systems, tree shaking etc. are left up to you to decide.
46 |
47 | ## Setup
48 |
49 | This repo uses [pnpm](https://pnpm.io/), but should work fine with any of the following:
50 |
51 | - [yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces/)
52 | - [npm 7 workspaces](https://docs.npmjs.com/cli/v7/using-npm/workspaces)
53 | - [npm < 7 and `lerna bootstrap`](https://github.com/lerna/lerna/blob/main/commands/bootstrap/README.md)
54 |
55 | I strongly recommend `pnpm` over the other solutions, not only because it's usually faster, but because it avoids dependency problems caused by hoisting (see https://github.com/NiGhTTraX/ts-monorepo/commit/d93139166b25fab15e9538df58a7d06270b846c9 as an example).
56 |
57 | ```sh
58 | # Install pnpm with your preferred method: https://pnpm.io/installation.
59 | npm i -g pnpm
60 |
61 | # Install all dependencies.
62 | pnpm i
63 | ```
64 |
65 | ## Docs
66 |
67 | See the following blog posts:
68 |
69 | - [How to set up a TypeScript monorepo and make Go to definition work](https://medium.com/@NiGhTTraX/how-to-set-up-a-typescript-monorepo-with-lerna-c6acda7d4559)
70 | - [Making TypeScript monorepos play nice with other tools](https://medium.com/@NiGhTTraX/making-typescript-monorepos-play-nice-with-other-tools-a8d197fdc680)
71 |
72 | If you're looking for the project references solution checkout the [`project-references`](https://github.com/NiGhTTraX/ts-monorepo/tree/project-references) branch.
73 |
74 | ## Packages vs apps
75 |
76 | This repo contains two types of workspaces:
77 |
78 | - `packages`: meant to be published to npm and installed,
79 | - `apps`: meant to be executed.
80 |
81 | A good example to illustrate the difference is `create-react-app`: you wouldn't publish an app like this to npm, you would run it, more specifically you would build the JS bundle and then deploy that somewhere.
82 |
83 | For packages, you don't want to bundle all the monorepo dependencies, and instead publish them individually. That's why packages have a separate build `tsconfig.json` that resolves monorepo dependencies to `node_modules`.
84 |
85 | ## Integrations
86 |
87 | ### ts-node
88 |
89 | Use [tsconfig-paths](https://www.npmjs.com/package/tsconfig-paths) to resolve the path aliases at runtime:
90 |
91 | ```json
92 | {
93 | "scripts": {
94 | "start": "ts-node -r tsconfig-paths/register src/index.ts"
95 | }
96 | }
97 | ```
98 |
99 | See the full example [here](apps/ts-node).
100 |
101 | ### Babel
102 |
103 | Use [babel-plugin-module-resolver](https://www.npmjs.com/package/babel-plugin-module-resolver) to resolve the path aliases:
104 |
105 | ```js
106 | module.exports = {
107 | presets: [
108 | ["@babel/preset-env", { targets: { node: "current" } }],
109 | "@babel/preset-typescript",
110 | ],
111 |
112 | plugins: [
113 | [
114 | "module-resolver",
115 | {
116 | alias: {
117 | "^@nighttrax/(.+)": "../\\1/src",
118 | },
119 | },
120 | ],
121 | ],
122 | };
123 | ```
124 |
125 | See the full example [here](apps/jest-babel).
126 |
127 | ### webpack
128 |
129 | Use [tsconfig-paths-webpack-plugin](https://www.npmjs.com/package/tsconfig-paths-webpack-plugin) to resolve the path aliases:
130 |
131 | ```js
132 | const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
133 |
134 | module.exports = {
135 | resolve: {
136 | plugins: [new TsconfigPathsPlugin()]
137 | }
138 | };
139 | ```
140 |
141 | See the full example [here](apps/webpack).
142 |
143 | ### jest
144 |
145 | If you use `Babel` then see [this example](apps/jest-babel) from the [Babel](#babel) section above.
146 |
147 | If you use [ts-jest](https://github.com/kulshekhar/ts-jest) then you can use its `pathsToModuleNameMapper` helper:
148 |
149 | ```js
150 | const { pathsToModuleNameMapper } = require("ts-jest");
151 | const { compilerOptions } = require("../../tsconfig.json");
152 |
153 | module.exports = {
154 | preset: "ts-jest",
155 |
156 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, {
157 | // This has to match the baseUrl defined in tsconfig.json.
158 | prefix: "/../../",
159 | }),
160 | };
161 | ```
162 |
163 | See the full example [here](apps/jest-tsjest).
164 |
165 | ### create-react-app
166 |
167 | Use [craco](https://www.npmjs.com/package/@craco/craco) or [react-app-rewired](https://www.npmjs.com/package/react-app-rewired) to extend CRA's webpack config and apply the [tsconfig-paths-webpack-plugin](https://www.npmjs.com/package/tsconfig-paths-webpack-plugin):
168 |
169 | ```js
170 | const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
171 |
172 | module.exports = (config) => {
173 | // Remove the ModuleScopePlugin which throws when we
174 | // try to import something outside of src/.
175 | config.resolve.plugins.pop();
176 |
177 | // Resolve the path aliases.
178 | config.resolve.plugins.push(new TsconfigPathsPlugin());
179 |
180 | // Let Babel compile outside of src/.
181 | const oneOfRule = config.module.rules.find((rule) => rule.oneOf);
182 | const tsRule = oneOfRule.oneOf.find((rule) =>
183 | rule.test.toString().includes("ts|tsx")
184 | );
185 | tsRule.include = undefined;
186 | tsRule.exclude = /node_modules/;
187 |
188 | return config;
189 | };
190 | ```
191 |
192 | See the full example [here](apps/cra). For tests, see the [jest example](#jest).
193 |
194 | ### Vite
195 |
196 | Use [vite-tsconfig-paths](https://www.npmjs.com/package/vite-tsconfig-paths) in the Vite config:
197 |
198 | ```typescript
199 | import { defineConfig } from "vite";
200 | import react from "@vitejs/plugin-react";
201 | import tsconfigPaths from "vite-tsconfig-paths";
202 |
203 | export default defineConfig({
204 | plugins: [
205 | react(),
206 | tsconfigPaths()
207 | ],
208 | });
209 | ```
210 |
211 | See full example [here](apps/vite).
212 |
213 | ### NextJS
214 |
215 | Extend Next's webpack config to enable compiling packages from the monorepo:
216 |
217 | ```js
218 | module.exports = {
219 | webpack: (config) => {
220 | // Let Babel compile outside of src/.
221 | const tsRule = config.module.rules.find(
222 | (rule) => rule.test && rule.test.toString().includes("tsx|ts")
223 | );
224 | tsRule.include = undefined;
225 | tsRule.exclude = /node_modules/;
226 |
227 | return config;
228 | },
229 | };
230 | ```
231 |
232 | See the full example [here](apps/nextjs).
233 |
234 | ### NestJS
235 |
236 | Include the path aliases in both `tsconfig.json` and `tsconfig.build.json` and tell NestJS where to find the `main.js` file:
237 |
238 | ```json
239 | {
240 | "collection": "@nestjs/schematics",
241 | "sourceRoot": "src",
242 | "entryFile": "apps/nestjs/src/main"
243 | }
244 | ```
245 |
246 | See the full example [here](apps/nestjs).
247 |
248 | ### Storybook
249 |
250 | [Extend Storybook's webpack config](https://storybook.js.org/docs/react/builders/webpack#typescript-module-resolution) and apply the [tsconfig-paths-webpack-plugin](https://www.npmjs.com/package/tsconfig-paths-webpack-plugin):
251 |
252 | ```js
253 | const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
254 |
255 | module.exports = {
256 | webpackFinal: async (config) => {
257 | config.resolve.plugins = [
258 | ...(config.resolve.plugins || []),
259 | new TsconfigPathsPlugin({
260 | extensions: config.resolve.extensions,
261 | }),
262 | ];
263 | return config;
264 | },
265 | };
266 | ```
267 |
268 | See the full example [here](apps/storybook).
269 |
--------------------------------------------------------------------------------