├── packages ├── foo │ ├── src │ │ └── index.ts │ ├── tsconfig.package.json │ └── package.json ├── bar │ ├── src │ │ └── index.ts │ ├── tsconfig.package.json │ └── package.json ├── baz │ ├── src │ │ └── index.ts │ ├── package.json │ └── tsconfig.package.json └── tsconfig.project.json ├── .gitignore ├── lerna.json ├── tsconfig.json ├── package.json ├── tsconfig.base.json ├── scripts ├── update-package-json.ts └── update-package-tsconfig.ts └── README.md /packages/foo/src/index.ts: -------------------------------------------------------------------------------- 1 | export function Foo() { 2 | return 'foo'; 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | 4 | /packages/*/*.tgz 5 | /packages/*/lib/ 6 | *.tsbuildinfo 7 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "3.4.0", 3 | "packages": [ 4 | "packages/*" 5 | ], 6 | "version": "0.1.0" 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "noEmit": true 5 | } 6 | } -------------------------------------------------------------------------------- /packages/bar/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Foo } from 'typescript-composite-lerna-foo'; 2 | 3 | export function Foobar() { 4 | return Foo() + 'bar'; 5 | } -------------------------------------------------------------------------------- /packages/baz/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Foobar } from 'typescript-composite-lerna-bar'; 2 | 3 | function main() { 4 | console.log(Foobar() + 'baz'); 5 | } 6 | 7 | if (process.mainModule === module) { 8 | main(); 9 | } -------------------------------------------------------------------------------- /packages/tsconfig.project.json: -------------------------------------------------------------------------------- 1 | // GENERATED by scripts/update-package-tsconfig.ts 2 | { 3 | "files": [], 4 | "references": [ 5 | { 6 | "path": "./foo/tsconfig.package.json" 7 | }, 8 | { 9 | "path": "./bar/tsconfig.package.json" 10 | }, 11 | { 12 | "path": "./baz/tsconfig.package.json" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /packages/foo/tsconfig.package.json: -------------------------------------------------------------------------------- 1 | // GENERATED by scripts/update-package-tsconfig.ts 2 | { 3 | "extends": "../../tsconfig.base.json", 4 | "compilerOptions": { 5 | "outDir": "./lib", 6 | "rootDir": "./src", 7 | "composite": true 8 | }, 9 | "references": [], 10 | "include": [ 11 | "src" 12 | ], 13 | "exclude": [ 14 | "tests", 15 | "lib" 16 | ] 17 | } -------------------------------------------------------------------------------- /packages/foo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-composite-lerna-foo", 3 | "version": "0.1.0", 4 | "main": "./lib/index.js", 5 | "types": "./lib/index.d.ts", 6 | "files": [ 7 | "lib", 8 | "src" 9 | ], 10 | "scripts": { 11 | "build": "tsc -b ./tsconfig.package.json", 12 | "prepublish": "npm run build" 13 | }, 14 | "publishConfig": { 15 | "access": "public" 16 | } 17 | } -------------------------------------------------------------------------------- /packages/bar/tsconfig.package.json: -------------------------------------------------------------------------------- 1 | // GENERATED by scripts/update-package-tsconfig.ts 2 | { 3 | "extends": "../../tsconfig.base.json", 4 | "compilerOptions": { 5 | "outDir": "./lib", 6 | "rootDir": "./src", 7 | "composite": true 8 | }, 9 | "references": [ 10 | { 11 | "path": "../foo/tsconfig.package.json" 12 | } 13 | ], 14 | "include": [ 15 | "src" 16 | ], 17 | "exclude": [ 18 | "tests", 19 | "lib" 20 | ] 21 | } -------------------------------------------------------------------------------- /packages/bar/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-composite-lerna-bar", 3 | "version": "0.1.0", 4 | "main": "./lib/index.js", 5 | "types": "./lib/index.d.ts", 6 | "files": [ 7 | "lib", 8 | "src" 9 | ], 10 | "scripts": { 11 | "build": "tsc -b ./tsconfig.package.json", 12 | "prepublish": "npm run build" 13 | }, 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "dependencies": { 18 | "typescript-composite-lerna-foo": "0.1.0" 19 | } 20 | } -------------------------------------------------------------------------------- /packages/baz/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-composite-lerna-baz", 3 | "version": "0.1.0", 4 | "main": "./lib/index.js", 5 | "types": "./lib/index.d.ts", 6 | "files": [ 7 | "lib", 8 | "src" 9 | ], 10 | "scripts": { 11 | "build": "tsc -b ./tsconfig.package.json", 12 | "prepublish": "npm run build" 13 | }, 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "dependencies": { 18 | "typescript-composite-lerna-bar": "0.1.0" 19 | } 20 | } -------------------------------------------------------------------------------- /packages/baz/tsconfig.package.json: -------------------------------------------------------------------------------- 1 | // GENERATED by scripts/update-package-tsconfig.ts 2 | { 3 | "extends": "../../tsconfig.base.json", 4 | "compilerOptions": { 5 | "outDir": "./lib", 6 | "rootDir": "./src", 7 | "composite": true 8 | }, 9 | "references": [ 10 | { 11 | "path": "../foo/tsconfig.package.json" 12 | }, 13 | { 14 | "path": "../bar/tsconfig.package.json" 15 | } 16 | ], 17 | "include": [ 18 | "src" 19 | ], 20 | "exclude": [ 21 | "tests", 22 | "lib" 23 | ] 24 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-composite-lerna", 3 | "private": true, 4 | "license": "MIT", 5 | "scripts": { 6 | "bootstrap": "npm i && lerna bootstrap --ignore-scripts && npm run build", 7 | "build": "tsc --build packages/tsconfig.project.json", 8 | "watch": "tsc --build --watch packages/tsconfig.project.json", 9 | "update:tsconfig.json": "ts-node scripts/update-package-tsconfig.ts", 10 | "update:package.json": "ts-node scripts/update-package-json.ts" 11 | }, 12 | "devDependencies": { 13 | "@types/node": "^12.7.0", 14 | "lerna": "^3.16.4", 15 | "typescript": "^3.5.3", 16 | "ts-node": "^8.3.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitThis": true, 4 | "preserveConstEnums": true, 5 | "lib": [ 6 | "es5", 7 | "es6", 8 | "esnext.asynciterable" 9 | ], 10 | "target": "es2016", 11 | "module": "commonjs", 12 | "moduleResolution": "node", 13 | "allowJs": false, 14 | "esModuleInterop": true, 15 | "emitDecoratorMetadata": true, 16 | "experimentalDecorators": true, 17 | "downlevelIteration": true, 18 | "forceConsistentCasingInFileNames": true, 19 | "sourceMap": true, 20 | "declarationMap": true, 21 | "noImplicitAny": true, 22 | "declaration": true, 23 | "noFallthroughCasesInSwitch": true, 24 | "noImplicitReturns": true, 25 | "stripInternal": true, 26 | "pretty": true, 27 | "strictNullChecks": true, 28 | "noUnusedLocals": true 29 | } 30 | } -------------------------------------------------------------------------------- /scripts/update-package-json.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | 4 | const packagesRoot = path.join(__dirname, '..', 'packages'); 5 | 6 | const packages = fs.readdirSync(packagesRoot).filter( 7 | item => (fs.lstatSync(path.join(packagesRoot, item)).isDirectory()) 8 | ); 9 | 10 | packages.forEach(packageName => { 11 | const packageJSONPath = path.join(packagesRoot, packageName, 'package.json'); 12 | // const tsconfigPath = path.join(packagesRoot, packageName, 'tsconfig.json'); 13 | 14 | const packageJSONData = JSON.parse(fs.readFileSync(packageJSONPath).toString()); 15 | delete packageJSONData.scripts; 16 | packageJSONData.main = './lib/index.js'; 17 | packageJSONData.types = './lib/index.d.ts'; 18 | packageJSONData.files = ['lib', 'src']; 19 | packageJSONData.scripts = { 20 | 'build': 'tsc -b ./tsconfig.package.json', 21 | 'prepublish': 'npm run build', 22 | }; 23 | packageJSONData.publishConfig = { 24 | access: 'public', 25 | }; 26 | fs.writeFileSync(packageJSONPath, JSON.stringify(packageJSONData, null, ' ')); 27 | }); 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Typescript Composite Project with Lerna 2 | 3 | [TypeScript Composite Project(Project References)](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html#project-references) + [Lerna](https://github.com/lerna/lerna) 4 | 5 | 6 | ## Usage 7 | 8 | ### Initialization 9 | 10 | ``` 11 | git clone https://github.com/hanpama/typescript-composite-lerna 12 | cd typescript-composite-lerna 13 | npm run boostrap 14 | npm run watch 15 | ``` 16 | 17 | #### `npm run bootstrap` 18 | 19 | * installs the project dependencies (in `/package.json`) 20 | * links each packages 21 | * and builds the project with `npm run build` (`tsc --build packages/tsconfig.project.json`) 22 | 23 | #### `npm run watch` 24 | 25 | * run tsc project build mode with `--watch` flag (`tsc --build --watch packages/tsconfig.project.json`) 26 | 27 | You can watch the entire project with a single tsc running. 28 | 29 | ### Updating `tsconfig.package.json` 30 | 31 | ``` 32 | npm run update:tsconfig.json 33 | ``` 34 | 35 | Each package has its `tsconfig.package.json` file for individual build. 36 | To integrate those building process into project, we need `references` field listing its build dependencies. 37 | 38 | ```json 39 | // GENERATED by scripts/update-package-tsconfig.ts 40 | { 41 | "extends": "../../tsconfig.base.json", 42 | "compilerOptions": { 43 | "outDir": "./lib", 44 | "rootDir": "./src", 45 | "composite": true 46 | }, 47 | "references": [ 48 | { 49 | "path": "../foo/tsconfig.package.json" 50 | }, 51 | { 52 | "path": "../bar/tsconfig.package.json" 53 | } 54 | ], 55 | "include": [ 56 | "src" 57 | ], 58 | "exclude": [ 59 | "tests", 60 | "lib" 61 | ] 62 | } 63 | ``` 64 | 65 | `update:tsconfig.json` is a script which resolves and updates those dependencies. 66 | 67 | ## Running Example 68 | 69 | ```sh 70 | ts-node packages/baz/src/index.ts 71 | ``` 72 | -------------------------------------------------------------------------------- /scripts/update-package-tsconfig.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | 4 | 5 | const PACKAGE_TSCONFIG = 'tsconfig.package.json'; 6 | const PROJECT_TSCONFIG = 'tsconfig.project.json'; 7 | const TSCONFIG_COMMENT = `// GENERATED by update-package-tsconfig\n`; 8 | 9 | const packagesRoot = path.join(__dirname, '..', 'packages'); 10 | const packageDirectories = fs.readdirSync(packagesRoot).filter( 11 | item => (fs.lstatSync(path.join(packagesRoot, item)).isDirectory()) 12 | ); 13 | 14 | type DirectoryName = string; 15 | type PackageName = string; 16 | 17 | const packageJSONMap: Map = new Map(); 22 | 23 | const packageDirnameMap: Map = new Map(); 24 | 25 | packageDirectories.forEach(packageDirname => { 26 | const packageJSONPath = path.join(packagesRoot, packageDirname, 'package.json'); 27 | const packageJSONData = JSON.parse(fs.readFileSync(packageJSONPath).toString()); 28 | const packageName = packageJSONData.name; 29 | packageDirnameMap.set(packageName, packageDirname); 30 | packageJSONMap.set(packageName, packageJSONData); 31 | }); 32 | 33 | const internalDependencyMap: Map = new Map(); 34 | packageDirnameMap.forEach((_packageDirname, packageName) => { 35 | 36 | const { dependencies, devDependencies } = packageJSONMap.get(packageName)!; 37 | 38 | const internalDependencies = [ 39 | ...(dependencies ? Object.keys(dependencies) : []), 40 | ...(devDependencies ? Object.keys(devDependencies) : []), 41 | ].filter(dep => packageDirnameMap.has(dep)); 42 | 43 | internalDependencyMap.set(packageName, internalDependencies); 44 | }); 45 | 46 | function resolveInternalDependencies(dependencies: string[]): string[] { 47 | const childDeps = []; 48 | 49 | for (let idep of dependencies) { 50 | const deps = internalDependencyMap.get(idep)!; 51 | const res = resolveInternalDependencies(deps); 52 | for (let jdep of res) { 53 | childDeps.push(jdep); 54 | } 55 | } 56 | const resolved = childDeps.concat(dependencies); 57 | // remove all duplicated after the first appearance 58 | return resolved.filter((item, idx) => resolved.indexOf(item) === idx); 59 | } 60 | 61 | packageDirnameMap.forEach((packageDirname, packageName) => { 62 | 63 | const tsconfigPath = path.join(packagesRoot, packageDirname, PACKAGE_TSCONFIG); 64 | 65 | const internalDependencies = resolveInternalDependencies( 66 | internalDependencyMap.get(packageName)! 67 | ); 68 | 69 | const tsconfigData = { 70 | extends: '../../tsconfig.base.json', 71 | compilerOptions: { 72 | outDir: './lib', 73 | rootDir: './src', 74 | composite: true, 75 | }, 76 | references: internalDependencies.map(dep => { 77 | return { path: `../${packageDirnameMap.get(dep)}/${PACKAGE_TSCONFIG}` }; 78 | }), 79 | include: ['src'], 80 | exclude: ['tests', 'lib'], 81 | }; 82 | fs.writeFileSync(tsconfigPath, TSCONFIG_COMMENT + JSON.stringify(tsconfigData, null, ' ')); 83 | }); 84 | 85 | const projectLevelTsconfigPath = path.join(packagesRoot, PROJECT_TSCONFIG); 86 | 87 | const projectLevelTsconfigData = { 88 | files: [], 89 | references: resolveInternalDependencies(Array.from(packageDirnameMap.keys())).map( 90 | packageName => ({ path: `./${packageDirnameMap.get(packageName)}/${PACKAGE_TSCONFIG}` }) 91 | ), 92 | }; 93 | 94 | fs.writeFileSync(projectLevelTsconfigPath, TSCONFIG_COMMENT + JSON.stringify(projectLevelTsconfigData, null, ' ')); 95 | --------------------------------------------------------------------------------