├── .nvmrc ├── .eslintignore ├── .gitattributes ├── .yarnrc ├── .markdownlintignore ├── packages ├── foo │ ├── tsconfig.json │ ├── src │ │ ├── hello.ts │ │ └── index.ts │ ├── jest.config.js │ ├── tsconfig.build.json │ ├── test │ │ ├── unit │ │ │ └── index.test.ts │ │ └── integration │ │ │ └── index.test.ts │ └── package.json └── bar │ ├── jest.config.js │ ├── tsconfig.json │ ├── src │ └── index.ts │ ├── test │ └── unit │ │ └── index.test.ts │ ├── tsconfig.build.json │ └── package.json ├── .husky ├── pre-commit └── commit-msg ├── .markdownlint.json ├── lerna.json ├── .vscode ├── settings.json └── launch.json ├── .prettierrc.js ├── commitlint.config.js ├── .editorconfig ├── tsconfig.json ├── LICENSE ├── .github └── workflows │ ├── lint-markdown.yaml │ └── test.yaml ├── package.json ├── .eslintrc.js ├── .gitignore ├── jest.config.js └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 16 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | --install.prefer-offline true 2 | -------------------------------------------------------------------------------- /.markdownlintignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | .git 3 | -------------------------------------------------------------------------------- /packages/foo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /packages/foo/src/hello.ts: -------------------------------------------------------------------------------- 1 | export const hello = (to: string) => { 2 | console.log(`hello ${to}`) 3 | } 4 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "MD013": false, 4 | "MD042": false, 5 | "MD033": false 6 | } 7 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmClient": "yarn", 3 | "packages": ["packages/*"], 4 | "version": "1.0.0", 5 | "useWorkspaces": true 6 | } 7 | -------------------------------------------------------------------------------- /packages/bar/jest.config.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('../../jest.config.js') 2 | 3 | module.exports = { 4 | ...baseConfig, 5 | } 6 | -------------------------------------------------------------------------------- /packages/foo/jest.config.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('../../jest.config.js') 2 | 3 | module.exports = { 4 | ...baseConfig, 5 | } 6 | -------------------------------------------------------------------------------- /packages/bar/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [{ "path": "../foo/tsconfig.build.json", "prepend": false }] 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.insertFinalNewline": true, 3 | "files.trimFinalNewlines": true, 4 | "files.trimTrailingWhitespace": true, 5 | "editor.renderFinalNewline": true 6 | } 7 | -------------------------------------------------------------------------------- /packages/bar/src/index.ts: -------------------------------------------------------------------------------- 1 | import { doubleNumbers } from '@jjangga0214/foo' 2 | 3 | export const run = () => { 4 | const value = doubleNumbers([1, 2, 3]) 5 | return value 6 | } 7 | 8 | console.log(run()) 9 | -------------------------------------------------------------------------------- /packages/bar/test/unit/index.test.ts: -------------------------------------------------------------------------------- 1 | import { run } from '#bar' 2 | 3 | describe('index', () => { 4 | it('run', () => { 5 | expect.hasAssertions() 6 | expect(run()).toStrictEqual([2, 4, 6]) 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | semi: false, 4 | useTabs: false, 5 | tabWidth: 2, 6 | trailingComma: 'all', 7 | endOfLine: 'lf', 8 | // bracketSpacing: true 9 | // printWidth: 80 10 | } 11 | -------------------------------------------------------------------------------- /packages/foo/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./dist", 6 | "tsBuildInfoFile": "../../.build-cache/foo.tsbuildinfo" 7 | }, 8 | "include": ["src"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/foo/test/unit/index.test.ts: -------------------------------------------------------------------------------- 1 | import { doubleNumbers } from '#foo' 2 | 3 | describe('index', () => { 4 | it('doubleNumbers', () => { 5 | expect.hasAssertions() 6 | expect(doubleNumbers([1, 2, 3])).toStrictEqual([2, 4, 6]) 7 | expect(doubleNumbers([6, 2, 13])).toStrictEqual([12, 4, 26]) 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /packages/bar/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./dist", 6 | "tsBuildInfoFile": "../../.build-cache/bar.tsbuildinfo" 7 | }, 8 | "include": ["src"], 9 | "references": [{ "path": "../foo/tsconfig.build.json", "prepend": false }] 10 | } 11 | -------------------------------------------------------------------------------- /packages/foo/test/integration/index.test.ts: -------------------------------------------------------------------------------- 1 | import { doubleNumbers } from '#foo' 2 | 3 | describe('this is an unit test', () => { 4 | it('doubleNumbers', () => { 5 | expect.hasAssertions() 6 | expect(doubleNumbers([1, 2, 3])).toStrictEqual([2, 4, 6]) 7 | expect(doubleNumbers([6, 2, 13])).toStrictEqual([12, 4, 26]) 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | 'header-max-length': [0, 'always', 100], // corresponding to maxHeaderWidth of commitizen 5 | 'body-max-length': [0, 'always'], 6 | 'body-max-line-length': [0, 'always'], 7 | 'footer-max-length': [0, 'always'], 8 | 'footer-max-line-length': [0, 'always'], // Make sure there is never a max-line-length by disabling the rule 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | charset = utf-8 11 | 12 | # Indentation override for all JS under lib directory 13 | [*.{js,ts,json,yml,yaml}] 14 | indent_style = space 15 | indent_size = 2 16 | trim_trailing_whitespace = true 17 | 18 | [*.md] 19 | max_line_length = off 20 | -------------------------------------------------------------------------------- /packages/foo/src/index.ts: -------------------------------------------------------------------------------- 1 | /* The module name can be just './hello'. 2 | But '#foo/hello' is demonstration of "Path Mapping" of `tsconfig` 3 | and "Subpath imports"(defined in package.json's `imports` field) of node.js */ 4 | import { hello } from '#foo/hello' 5 | // import { hello } from './hello' => this will work, too 6 | // import { hello } from '@jjangga0214/foo/hello' // => this will not work for tsc, without additional configuration on tsconfig.json 7 | 8 | hello('world') 9 | 10 | export const doubleNumbers = (data: number[]) => data.map((i) => i * 2) 11 | -------------------------------------------------------------------------------- /packages/bar/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jjangga0214/bar", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "private": true, 6 | "main": "dist/index.js", 7 | "types": "dist/index.d.ts", 8 | "files": [ 9 | "dist" 10 | ], 11 | "imports": { 12 | "#bar/*": { 13 | "default": "./dist/*.js" 14 | } 15 | }, 16 | "scripts": { 17 | "build": "tsc --build tsconfig.build.json", 18 | "clean": "tsc --build tsconfig.build.json --clean && del-cli coverage *.log junit.xml dist && jest --clearCache", 19 | "test": "jest", 20 | "test:unit": "jest test/unit", 21 | "coverage": "jest --coverage", 22 | "coverage:show": "live-server coverage", 23 | "dev": "ts-node-dev -r tsconfig-paths/register src/index.ts", 24 | "start": "node dist/index.js" 25 | }, 26 | "dependencies": { 27 | "@jjangga0214/foo": "^1.0.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/foo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jjangga0214/foo", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "private": true, 6 | "main": "dist/index.js", 7 | "types": "dist/index.d.ts", 8 | "files": [ 9 | "dist" 10 | ], 11 | "imports": { 12 | "#foo/*": { 13 | "default": "./dist/*.js" 14 | } 15 | }, 16 | "scripts": { 17 | "build": "tsc --build tsconfig.build.json", 18 | "clean": "tsc --build tsconfig.build.json --clean && del-cli coverage *.log junit.xml dist && jest --clearCache", 19 | "test": "jest", 20 | "test:unit": "jest test/unit", 21 | "test:integration": "jest test/integration", 22 | "coverage": "jest --coverage", 23 | "coverage:show": "live-server coverage", 24 | "dev": "ts-node-dev -r tsconfig-paths/register src/index.ts", 25 | "start": "node dist/index.js" 26 | }, 27 | "dependencies": {}, 28 | "devDependencies": {} 29 | } 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "CommonJS", 5 | "moduleResolution": "Node", 6 | "rootDir": ".", 7 | "baseUrl": "packages", 8 | "paths": { 9 | "@jjangga0214/*": ["*/src"], 10 | "#foo/*": ["foo/src/*"], 11 | "#bar/*": ["bar/src/*"], 12 | "#*": ["*/src"] 13 | }, 14 | "strict": true, 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": true, 19 | "removeComments": true, 20 | "composite": true, 21 | "declaration": true, 22 | "declarationMap": true, 23 | "sourceMap": true, 24 | "esModuleInterop": true, 25 | "incremental": true, 26 | "resolveJsonModule": true 27 | }, 28 | "ts-node": { 29 | "transpileOnly": true, 30 | "transpiler": "ts-node/transpilers/swc-experimental", 31 | "require": ["tsconfig-paths/register"] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 jjangga0214 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 | -------------------------------------------------------------------------------- /.github/workflows/lint-markdown.yaml: -------------------------------------------------------------------------------- 1 | name: Lint markdown 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.md' 7 | - '.markdownlint*' 8 | - '!.github/workflows/**' 9 | - '.github/workflows/lint-markdown.yaml' # SYNC: edit this if the file name is changed. 10 | pull_request: 11 | branches: 12 | - master 13 | paths: 14 | - '**.md' 15 | - '.markdownlint*' 16 | - '!.github/workflows/**' 17 | - '.github/workflows/lint-markdown.yaml' # SYNC: edit this if the file name is changed. 18 | 19 | jobs: 20 | # [original source of job "check-if-to-skip"](https://github.com/srz-zumix/ci-skip/blob/master/.github/workflows/main.yml#L15) 21 | check-if-to-skip: 22 | runs-on: ubuntu-latest 23 | # skip commits with [ci skip] or [skip ci], except on an action triggered by push of a version tag 24 | if: "!( contains(github.event.head_commit.message, '[ci skip]') || contains(github.event.head_commit.message, '[skip ci]') )" 25 | steps: 26 | - name: Check [ci skip] or [skip ci] 27 | run: exit 0 28 | 29 | lint: 30 | needs: check-if-to-skip 31 | runs-on: ubuntu-latest 32 | 33 | steps: 34 | - uses: actions/checkout@v2 35 | 36 | - name: Read .nvmrc 37 | id: nvmrc 38 | run: echo "::set-output name=nvmrc::$(cat .nvmrc)" 39 | 40 | - name: Use Node.js 41 | uses: actions/setup-node@v2 42 | with: 43 | node-version: ${{ steps.nvmrc.outputs.nvmrc }} 44 | 45 | - name: Get yarn cache 46 | id: yarn-cache-dir 47 | run: echo "::set-output name=dir::$(yarn cache dir)" 48 | 49 | - uses: actions/cache@v2 50 | with: 51 | path: ${{ steps.yarn-cache-dir.outputs.dir }} 52 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 53 | restore-keys: | 54 | ${{ runner.os }}-yarn- 55 | 56 | - name: Install Dependencies 57 | run: yarn install --silent --frozen-lockfile 58 | 59 | - name: Lint 60 | run: yarn lint:md . 61 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Run via 'node '", 6 | "type": "node", 7 | "request": "launch", 8 | "cwd": ".", 9 | "runtimeExecutable": "node", 10 | "runtimeArgs": ["${input:filepath}"], 11 | "console": "integratedTerminal", 12 | "internalConsoleOptions": "neverOpen" 13 | }, 14 | { 15 | "name": "Run via 'yarn ts-node'", 16 | "request": "launch", 17 | "type": "node", 18 | "cwd": ".", 19 | "runtimeExecutable": "yarn", 20 | "runtimeArgs": ["ts-node", "${input:filepath}"], 21 | "console": "integratedTerminal", 22 | "internalConsoleOptions": "neverOpen" 23 | }, 24 | { 25 | "name": "Run via 'yarn workspace dev'", 26 | "type": "node", 27 | "request": "launch", 28 | "cwd": "${workspaceFolder}", 29 | "runtimeExecutable": "yarn", 30 | "runtimeArgs": ["workspace", "${input:projectPackage}", "dev"], 31 | "console": "integratedTerminal", 32 | "internalConsoleOptions": "neverOpen" 33 | }, 34 | { 35 | "name": "Run via 'yarn workspace test' --testNamePattern", 36 | "type": "node", 37 | "request": "launch", 38 | "cwd": ".", 39 | "runtimeExecutable": "yarn", 40 | "runtimeArgs": [ 41 | "workspace", 42 | "${input:projectPackage}", 43 | "test", 44 | "--testNamePattern", 45 | "${input:--testNamePattern}" 46 | ], 47 | "console": "integratedTerminal", 48 | "internalConsoleOptions": "neverOpen" 49 | } 50 | ], 51 | "inputs": [ 52 | { 53 | "id": "filepath", 54 | "type": "promptString", 55 | "description": "What is the file path relative from cwd?" 56 | }, 57 | { 58 | "id": "projectPackage", 59 | "type": "promptString", 60 | "description": "What is the package name?" 61 | }, 62 | { 63 | "id": "--testNamePattern", 64 | "type": "promptString", 65 | "description": "What is the argument for `--testNamePattern` option?" 66 | } 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jjangga0214/monorepo", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "private": true, 7 | "engines": { 8 | "node": ">=14.6.0" 9 | }, 10 | "workspaces": [ 11 | "packages/**" 12 | ], 13 | "scripts": { 14 | "build": "lerna run --parallel build", 15 | "build:fresh": "yarn clean && yarn build", 16 | "clean": "lerna run --parallel clean && del-cli .build-cache *.log coverage junit.xml", 17 | "test": "jest", 18 | "test:unit": "jest packages/*/test/unit", 19 | "test:integration": "jest packages/*/test/integration", 20 | "coverage": "jest --coverage", 21 | "coverage:unit": "yarn test:unit --coverage", 22 | "coverage:integration": "yarn test:integration --coverage", 23 | "coverage:show": "live-server coverage", 24 | "lint": "eslint --ext .js,.ts,.md", 25 | "lint:ci": "yarn lint . --format junit", 26 | "lint:md": "markdownlint --ignore node_modules --ignore .git", 27 | "format": "yarn lint --fix", 28 | "format:md": "yarn lint:md --fix", 29 | "husky-skip": "cross-env HUSKY_SKIP_HOOKS=1", 30 | "commit": "git cz", 31 | "prepare": "husky install" 32 | }, 33 | "lint-staged": { 34 | "*.{js,jsx,ts,tsx}": [ 35 | "eslint --fix" 36 | ], 37 | "*.md": [ 38 | "yarn format:md" 39 | ] 40 | }, 41 | "devDependencies": { 42 | "@commitlint/cli": "^13.1.0", 43 | "@commitlint/config-conventional": "^13.1.0", 44 | "@swc/core": "^1.2.66", 45 | "@swc/helpers": "^0.2.12", 46 | "@swc/jest": "^0.1.4", 47 | "@types/jest": "^26.0.24", 48 | "@types/node": "^16.4.2", 49 | "@typescript-eslint/eslint-plugin": "^4.29.3", 50 | "@typescript-eslint/parser": "^4.29.3", 51 | "commitizen": "^4.2.4", 52 | "cross-env": "^7.0.3", 53 | "cz-conventional-changelog": "^3.3.0", 54 | "del-cli": "^4.0.1", 55 | "eslint": "^7.31.0", 56 | "eslint-config-airbnb-base": "^14.2.1", 57 | "eslint-config-airbnb-typescript": "^14.0.0", 58 | "eslint-config-prettier": "^8.3.0", 59 | "eslint-import-resolver-typescript": "^2.4.0", 60 | "eslint-plugin-import": "^2.23.4", 61 | "eslint-plugin-jest": "^24.4.0", 62 | "eslint-plugin-markdown": "^2.2.0", 63 | "eslint-plugin-prettier": "^3.4.0", 64 | "husky": "^7.0.1", 65 | "jest": "^27.0.6", 66 | "jest-junit": "^12.2.0", 67 | "lerna": "^4.0.0", 68 | "lint-staged": "^11.1.1", 69 | "live-server": "^1.2.1", 70 | "markdownlint-cli": "^0.28.1", 71 | "prettier": "^2.3.2", 72 | "regenerator-runtime": "^0.13.9", 73 | "serve": "^12.0.0", 74 | "ts-jest": "^27.0.4", 75 | "ts-node": "^10.1.0", 76 | "ts-node-dev": "^1.1.8", 77 | "tsconfig-paths": "^3.10.1", 78 | "typescript": "^4.3.5" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const common = { 2 | env: { 3 | node: true, 4 | es6: true, 5 | 'jest/globals': true, 6 | }, 7 | plugins: ['prettier', 'jest', 'markdown'], 8 | extends: [ 9 | 'airbnb-base', 10 | 'plugin:jest/all', 11 | 'plugin:markdown/recommended', // REF: https://github.com/eslint/eslint-plugin-markdown/blob/main/lib/index.js 12 | 'prettier', 13 | ], 14 | rules: { 15 | 'prettier/prettier': 'error', 16 | 'jest/no-disabled-tests': 'warn', 17 | 'jest/no-focused-tests': 'error', 18 | 'jest/no-identical-title': 'error', 19 | 'jest/prefer-to-have-length': 'warn', 20 | 'jest/valid-expect': 'error', 21 | 'jest/expect-expect': 'off', 22 | 'jest/prefer-expect-assertions': 'off', 23 | 'jest/no-test-return-statement': 'off', 24 | 'import/prefer-default-export': 'off', 25 | 'import/extensions': 'off', 26 | 'no-console': 'off', 27 | 'no-iterator': 'off', 28 | 'no-restricted-syntax': 'off', 29 | 'no-await-in-loop': 'off', 30 | 'consistent-return': 'off', 31 | 'no-shadow': 'off', 32 | 'no-unused-vars': 'off', 33 | }, 34 | } 35 | 36 | module.exports = { 37 | // ...common, 38 | root: true, 39 | overrides: [ 40 | { 41 | /* 42 | eslint-plugin-markdown only finds javascript code block snippet. 43 | For specific spec, refer to https://github.com/eslint/eslint-plugin-markdown 44 | */ 45 | ...common, 46 | files: ['**/*.js'], 47 | }, 48 | { 49 | /* 50 | eslint-plugin-markdown only finds javascript code block snippet. 51 | For specific spec, refer to https://github.com/eslint/eslint-plugin-markdown 52 | */ 53 | files: ['**/*.md'], 54 | processor: 'markdown/markdown', 55 | }, 56 | { 57 | ...common, 58 | // In eslint-plugin-markdown v2, configuration for fenced code blocks is separate from the 59 | // containing Markdown file. Each code block has a virtual filename 60 | // appended to the Markdown file's path. 61 | files: ['**/*.md/*.js'], 62 | // Configuration for fenced code blocks goes with the override for 63 | // the code block's virtual filename, for example: 64 | parserOptions: { 65 | ecmaFeatures: { 66 | impliedStrict: true, 67 | }, 68 | }, 69 | rules: { 70 | ...common.rules, 71 | 'import/no-unresolved': 'off', 72 | 'import/no-extraneous-dependencies': 'off', 73 | }, 74 | }, 75 | { 76 | ...common, 77 | files: ['**/*.ts'], 78 | parser: '@typescript-eslint/parser', 79 | parserOptions: { 80 | project: './tsconfig.json', // REF: https://www.npmjs.com/package/eslint-config-airbnb-typescript 81 | }, 82 | env: common.env, 83 | plugins: [...common.plugins, '@typescript-eslint'], 84 | extends: [ 85 | ...common.extends, 86 | 'airbnb-typescript/base', // "base" does not include tsx rules. REF: https://www.npmjs.com/package/eslint-config-airbnb-typescript 87 | 'plugin:@typescript-eslint/recommended', 88 | 'plugin:import/errors', 89 | 'plugin:import/warnings', 90 | 'plugin:import/typescript', 91 | 'prettier', // Let prettier have high priority 92 | ], 93 | rules: { 94 | ...common.rules, 95 | '@typescript-eslint/explicit-function-return-type': 'off', 96 | '@typescript-eslint/explicit-module-boundary-types': 'off', 97 | }, 98 | settings: { 99 | 'import/resolver': { 100 | typescript: {}, 101 | }, 102 | }, 103 | }, 104 | ], 105 | } 106 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.js' 7 | - '**.jsx' 8 | - '**.ts' 9 | - '**.tsx' 10 | - '**/tsconfig.json' 11 | - '**/jest.config.*' 12 | - '**/package.json' 13 | - 'yarn.lock' 14 | - '**/eslint*' 15 | - '**/prettier*' 16 | - '.nvmrc' 17 | - '.yarnrc' 18 | - '.npmrc' 19 | - '!.github/workflows/**' 20 | - '.github/workflows/test.yaml' # SYNC: edit this if the file name is changed. 21 | # paths-ignore: 22 | # - '**.md' 23 | # - 'docs/**' 24 | # - '.markdownlint*' 25 | # - '.editorconfig' 26 | # - '.gitattributes' 27 | # - '.gitignore' 28 | # - 'commitlint.config.*' 29 | # - 'LICENSE' 30 | # - '.github/workflows/**' 31 | # - '!.github/workflows/test.yaml' # SYNC: edit this if the file name is changed. 32 | # - '**/.vscode' 33 | # - '**/.env.example' # dotenv file which is committed as an example 34 | pull_request: 35 | branches: 36 | - master 37 | paths: 38 | - '**.js' 39 | - '**.jsx' 40 | - '**.ts' 41 | - '**.tsx' 42 | - '**/tsconfig.json' 43 | - '**/jest.config.*' 44 | - '**/package.json' 45 | - 'yarn.lock' 46 | - '**/eslint*' 47 | - '**/prettier*' 48 | - '.nvmrc' 49 | - '.yarnrc' 50 | - '.npmrc' 51 | - '!.github/workflows/**' 52 | - '.github/workflows/test.yaml' # SYNC: edit this if the file name is changed. 53 | 54 | jobs: 55 | # [original source of job "check-if-to-skip"](https://github.com/srz-zumix/ci-skip/blob/master/.github/workflows/main.yml#L15) 56 | check-if-to-skip: 57 | runs-on: ubuntu-latest 58 | # skip commits with [ci skip] or [skip ci], except on an action triggered by push of a version tag 59 | if: "!( contains(github.event.head_commit.message, '[ci skip]') || contains(github.event.head_commit.message, '[skip ci]') )" 60 | steps: 61 | - name: Check [ci skip] or [skip ci] 62 | run: exit 0 63 | 64 | test: 65 | needs: check-if-to-skip 66 | runs-on: ubuntu-latest 67 | 68 | steps: 69 | - uses: actions/checkout@v2 70 | 71 | - name: Read .nvmrc 72 | id: nvmrc 73 | run: echo "::set-output name=nvmrc::$(cat .nvmrc)" 74 | 75 | - name: Use Node.js 76 | uses: actions/setup-node@v2 77 | with: 78 | node-version: ${{ steps.nvmrc.outputs.nvmrc }} 79 | 80 | - name: Get yarn cache 81 | id: yarn-cache-dir 82 | run: echo "::set-output name=dir::$(yarn cache dir)" 83 | 84 | - uses: actions/cache@v2 85 | with: 86 | path: ${{ steps.yarn-cache-dir.outputs.dir }} 87 | key: ${{ runner.os }}-yarn-${{ steps.nvmrc.outputs.nvmrc }}-${{ hashFiles('**/yarn.lock') }} 88 | restore-keys: | 89 | ${{ runner.os }}-yarn- 90 | 91 | - name: Install Dependencies 92 | run: yarn install --silent --frozen-lockfile 93 | 94 | - name: Test And Measure Coverage 95 | run: yarn jest --coverage --ci --reporters=jest-junit 96 | env: 97 | JEST_JUNIT_OUTPUT_DIR: ./coverage 98 | 99 | - name: Lint 100 | run: yarn lint . 101 | 102 | # e2etest: 103 | # needs: test 104 | # runs-on: ${{ matrix.os }} 105 | 106 | # strategy: 107 | # matrix: 108 | # node-version: [15, 16] 109 | # os: [ubuntu-latest, macOS-latest, windows-latest] 110 | 111 | # steps: 112 | # - uses: actions/checkout@v2 113 | 114 | # - name: Use Node.js ${{ matrix.node-version }} 115 | # uses: actions/setup-node@v2 116 | # with: 117 | # node-version: ${{ matrix.node-version }} 118 | 119 | # - name: Get yarn cache 120 | # id: yarn-cache-dir 121 | # run: echo "::set-output name=dir::$(yarn cache dir)" 122 | 123 | # - uses: actions/cache@v2 124 | # with: 125 | # path: ${{ steps.yarn-cache-dir.outputs.dir }} 126 | # key: ${{ runner.os }}-yarn-${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }} 127 | # restore-keys: | 128 | # ${{ runner.os }}-yarn- 129 | 130 | # - name: Install Dependencies 131 | # run: yarn install --silent --frozen-lockfile 132 | 133 | # - name: Test 134 | # run: yarn test:e2e . 135 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | reports 2 | junit.xml 3 | dist 4 | .build-cache 5 | # Created by https://www.gitignore.io/api/node,intellij+all,visualstudiocode 6 | # Edit at https://www.gitignore.io/?templates=node,intellij+all,visualstudiocode 7 | 8 | ### Intellij+all ### 9 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 10 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 11 | 12 | # User-specific stuff 13 | .idea/**/workspace.xml 14 | .idea/**/tasks.xml 15 | .idea/**/usage.statistics.xml 16 | .idea/**/dictionaries 17 | .idea/**/shelf 18 | 19 | # Generated files 20 | .idea/**/contentModel.xml 21 | 22 | # Sensitive or high-churn files 23 | .idea/**/dataSources/ 24 | .idea/**/dataSources.ids 25 | .idea/**/dataSources.local.xml 26 | .idea/**/sqlDataSources.xml 27 | .idea/**/dynamic.xml 28 | .idea/**/uiDesigner.xml 29 | .idea/**/dbnavigator.xml 30 | 31 | # Gradle 32 | .idea/**/gradle.xml 33 | .idea/**/libraries 34 | 35 | # Gradle and Maven with auto-import 36 | # When using Gradle or Maven with auto-import, you should exclude module files, 37 | # since they will be recreated, and may cause churn. Uncomment if using 38 | # auto-import. 39 | # .idea/modules.xml 40 | # .idea/*.iml 41 | # .idea/modules 42 | # *.iml 43 | # *.ipr 44 | 45 | # CMake 46 | cmake-build-*/ 47 | 48 | # Mongo Explorer plugin 49 | .idea/**/mongoSettings.xml 50 | 51 | # File-based project format 52 | *.iws 53 | 54 | # IntelliJ 55 | out/ 56 | 57 | # mpeltonen/sbt-idea plugin 58 | .idea_modules/ 59 | 60 | # JIRA plugin 61 | atlassian-ide-plugin.xml 62 | 63 | # Cursive Clojure plugin 64 | .idea/replstate.xml 65 | 66 | # Crashlytics plugin (for Android Studio and IntelliJ) 67 | com_crashlytics_export_strings.xml 68 | crashlytics.properties 69 | crashlytics-build.properties 70 | fabric.properties 71 | 72 | # Editor-based Rest Client 73 | .idea/httpRequests 74 | 75 | # Android studio 3.1+ serialized cache file 76 | .idea/caches/build_file_checksums.ser 77 | 78 | ### Intellij+all Patch ### 79 | # Ignores the whole .idea folder and all .iml files 80 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 81 | 82 | .idea/ 83 | 84 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 85 | 86 | *.iml 87 | modules.xml 88 | .idea/misc.xml 89 | *.ipr 90 | 91 | # Sonarlint plugin 92 | .idea/sonarlint 93 | 94 | ### Node ### 95 | # Logs 96 | logs 97 | *.log 98 | npm-debug.log* 99 | yarn-debug.log* 100 | yarn-error.log* 101 | lerna-debug.log* 102 | 103 | # Diagnostic reports (https://nodejs.org/api/report.html) 104 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 105 | 106 | # Runtime data 107 | pids 108 | *.pid 109 | *.seed 110 | *.pid.lock 111 | 112 | # Directory for instrumented libs generated by jscoverage/JSCover 113 | lib-cov 114 | 115 | # Coverage directory used by tools like istanbul 116 | coverage* 117 | *.lcov 118 | 119 | # nyc test coverage 120 | .nyc_output 121 | 122 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 123 | .grunt 124 | 125 | # Bower dependency directory (https://bower.io/) 126 | bower_components 127 | 128 | # node-waf configuration 129 | .lock-wscript 130 | 131 | # Compiled binary addons (https://nodejs.org/api/addons.html) 132 | build/Release 133 | 134 | # Dependency directories 135 | node_modules/ 136 | jspm_packages/ 137 | 138 | # TypeScript v1 declaration files 139 | typings/ 140 | 141 | # TypeScript cache 142 | *.tsbuildinfo 143 | 144 | # Optional npm cache directory 145 | .npm 146 | 147 | # Optional eslint cache 148 | .eslintcache 149 | 150 | # Optional REPL history 151 | .node_repl_history 152 | 153 | # Output of 'npm pack' 154 | *.tgz 155 | 156 | # Yarn Integrity file 157 | .yarn-integrity 158 | 159 | # dotenv environment variables file 160 | .env 161 | .env.test 162 | 163 | # parcel-bundler cache (https://parceljs.org/) 164 | .cache 165 | 166 | # next.js build output 167 | .next 168 | 169 | # nuxt.js build output 170 | .nuxt 171 | 172 | # vuepress build output 173 | .vuepress/dist 174 | 175 | # Serverless directories 176 | .serverless/ 177 | 178 | # FuseBox cache 179 | .fusebox/ 180 | 181 | # DynamoDB Local files 182 | .dynamodb/ 183 | 184 | ### VisualStudioCode ### 185 | .vscode/* 186 | !.vscode/settings.json 187 | !.vscode/tasks.json 188 | !.vscode/launch.json 189 | !.vscode/extensions.json 190 | 191 | ### VisualStudioCode Patch ### 192 | # Ignore all local history of files 193 | .history 194 | 195 | # End of https://www.gitignore.io/api/node,intellij+all,visualstudiocode 196 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const { pathsToModuleNameMapper } = require('ts-jest/utils') 2 | const { compilerOptions } = require('./tsconfig') 3 | 4 | // For a detailed explanation regarding each configuration property, visit: 5 | // https://jestjs.io/docs/en/configuration.html 6 | 7 | module.exports = { 8 | // All imported modules in your tests should be mocked automatically 9 | // automock: false, 10 | 11 | // Stop running tests after `n` failures 12 | // bail: 0, 13 | 14 | // Respect "browser" field in package.json when resolving modules 15 | // browser: false, 16 | 17 | // The directory where Jest should store its cached dependency information 18 | // cacheDirectory: "/tmp/jest_rs", 19 | 20 | // Automatically clear mock calls and instances between every test 21 | // clearMocks: false, 22 | 23 | // Indicates whether the coverage information should be collected while executing the test 24 | // collectCoverage: false, 25 | 26 | // An array of glob patterns indicating a set of files for which coverage information should be collected 27 | // collectCoverageFrom: null, 28 | 29 | // The directory where Jest should output its coverage files 30 | // coverageDirectory: null, 31 | 32 | // An array of regexp pattern strings used to skip coverage collection 33 | // coveragePathIgnorePatterns: [ 34 | // "/node_modules/" 35 | // ], 36 | 37 | // A list of reporter names that Jest uses when writing coverage reports 38 | coverageReporters: ['json', 'html'], 39 | 40 | // An object that configures minimum threshold enforcement for coverage results 41 | // coverageThreshold: null, 42 | 43 | // A path to a custom dependency extractor 44 | // dependencyExtractor: null, 45 | 46 | // Make calling deprecated APIs throw helpful error messages 47 | // errorOnDeprecated: false, 48 | 49 | // Force coverage collection from ignored files using an array of glob patterns 50 | // forceCoverageMatch: [], 51 | 52 | // A path to a module which exports an async function that is triggered once before all test suites 53 | // globalSetup: null, 54 | 55 | // A path to a module which exports an async function that is triggered once after all test suites 56 | // globalTeardown: null, 57 | 58 | // A set of global variables that need to be available in all test environments 59 | // globals: {}, 60 | 61 | // An array of directory names to be searched recursively up from the requiring module's location 62 | moduleDirectories: ['.', 'node_modules', 'src'], 63 | 64 | // An array of file extensions your modules use 65 | moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'], 66 | 67 | // A map from regular expressions to module names that allow to stub out resources with a single module 68 | // moduleNameMapper: { 69 | // '^~/(.*)$': '/src/$1', 70 | // '^@jjangga0214/(.*)$': '/../$1/src' 71 | // }, 72 | moduleNameMapper: { 73 | ...pathsToModuleNameMapper( 74 | compilerOptions.paths /* , { prefix: '/' }, */, 75 | ), 76 | }, 77 | 78 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 79 | // modulePathIgnorePatterns: [], 80 | 81 | // Activates notifications for test results 82 | // notify: false, 83 | 84 | // An enum that specifies notification mode. Requires { notify: true } 85 | // notifyMode: "failure-change", 86 | 87 | // A preset that is used as a base for Jest's configuration 88 | // preset: 'ts-jest', 89 | 90 | // Run tests from one or more projects 91 | // projects: null, 92 | 93 | // Use this configuration option to add custom reporters to Jest 94 | // reporters: ['default', 'jest-junit'], 95 | 96 | // Automatically reset mock state between every test 97 | // resetMocks: false, 98 | 99 | // Reset the module registry before running each individual test 100 | // resetModules: false, 101 | 102 | // A path to a custom resolver 103 | // resolver: null, 104 | 105 | // Automatically restore mock state between every test 106 | // restoreMocks: false, 107 | 108 | // The root directory that Jest should scan for tests and modules within 109 | // rootDir: null, 110 | 111 | // A list of paths to directories that Jest should use to search for files in 112 | // roots: [''], 113 | 114 | // Allows you to use a custom runner instead of Jest's default test runner 115 | // runner: "jest-runner", 116 | 117 | // The paths to modules that run some code to configure or set up the testing environment before each test 118 | // setupFiles: [], 119 | 120 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 121 | // setupFilesAfterEnv: [], 122 | 123 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 124 | // snapshotSerializers: [], 125 | 126 | // The test environment that will be used for testing 127 | testEnvironment: 'node', 128 | 129 | // Options that will be passed to the testEnvironment 130 | // testEnvironmentOptions: {}, 131 | 132 | // Adds a location field to test results 133 | // testLocationInResults: false, 134 | 135 | // The glob patterns Jest uses to detect test files 136 | // testMatch: [ 137 | // "**/__tests__/**/*.[jt]s?(x)", 138 | // "**/?(*.)+(spec|test).[tj]s?(x)" 139 | // ], 140 | 141 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 142 | // testPathIgnorePatterns: [ 143 | // "/node_modules/" 144 | // ], 145 | 146 | // The regexp pattern or array of patterns that Jest uses to detect test files 147 | // testRegex: [], 148 | 149 | // This option allows the use of a custom results processor 150 | // testResultsProcessor: null, 151 | 152 | // This option allows use of a custom test runner 153 | // testRunner: "jasmine2", 154 | 155 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href 156 | // testURL: "http://localhost", 157 | 158 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" 159 | // timers: "real", 160 | 161 | transform: { '^.+\\.(t|j)sx?$': ['@swc/jest'] }, 162 | // A map from regular expressions to paths to transformers 163 | // transform: { 164 | // '.(ts|tsx)': 'ts-jest', 165 | // }, 166 | 167 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 168 | // transformIgnorePatterns: ['/node_modules/'], 169 | 170 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 171 | // unmockedModulePathPatterns: undefined, 172 | 173 | // Indicates whether each individual test should be reported during the run 174 | // verbose: null, 175 | 176 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 177 | // watchPathIgnorePatterns: [], 178 | 179 | // Whether to use watchman for file crawling 180 | // watchman: true, 181 | } 182 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TypeScript + Yarn Workspace + Lerna + Jest Monorepo Boilerplate 2 | 3 | [![license](https://img.shields.io/badge/license-MIT-ff4081.svg?style=flat-square&labelColor=black)](./LICENSE) 4 | [![test](https://img.shields.io/badge/test-jest-7c4dff.svg?style=flat-square&labelColor=black)](./jest.config.js) 5 | [![code style:airbnb](https://img.shields.io/badge/code_style-airbnb-448aff.svg?style=flat-square&labelColor=black)](https://github.com/airbnb/javascript) 6 | [![code style:prettier](https://img.shields.io/badge/code_style-prettier-18ffff.svg?style=flat-square&labelColor=black)](https://prettier.io/) 7 | [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-ffab00.svg?style=flat-square&labelColor=black)](https://conventionalcommits.org) 8 | [![Commitizen friendly](https://img.shields.io/badge/Commitizen-cz_conventional_changelog-dd2c00.svg?style=flat-square&labelColor=black)](http://commitizen.github.io/cz-cli/) 9 | ![pr welcome](https://img.shields.io/badge/PRs-welcome-09FF33.svg?style=flat-square&labelColor=black) 10 | 11 | An example monorepo boilerplate for nodejs. 12 | 13 | This works well, and I've been carefully maintaining it. 14 | 15 | Just clone this! Then read and compare with README. 16 | 17 | You'd grasp how this repo works. 18 | 19 | Feel free to make issues for any questions or suggestions. 20 | 21 | ## Scenario 22 | 23 | This project has two packages, `foo`(`@jjangga0214/foo`) and `bar`(`@jjangga0214/bar`). `bar` depends on `foo`. 24 | 25 | ## Lerna + Yarn Workspace 26 | 27 | Lerna respects and and delegates monorepo management to yarn workspace, by `'useWorkspaces': true` in [lerna.json](lerna.json). But Lerna is still useful, as it provides utility commands for monorepo workflow (e.g. selective subpackge script execution, versioning, or package publishing). 28 | 29 | ## Module Aliases 30 | 31 | There're' some cases module alias becomes useful. 32 | 33 | 1. A package with a deep and wide directory tree: For example, let's say `foo/src/your/very/deep/module/index.ts` imports `../../../../another/deep/module/index`. In this case, absolute path from the root(e.g. alias `#foo` -> `foo/src`) like `#foo/another/deep/module/index` can be more concise and maintainable. 34 | 35 | 1. Dependency not located in node_modules: This can happen in monorepo. For instance, `@jjangga0214/bar` depends on `@jjangga0214/foo`, but the dependancy does not exist in node_modules, but in `packages/foo` directory. In this case, creating an alias(`@jjangga0214/foo` -> `packages/foo/src`(ts: Path Mapping)) is needed. 36 | 37 | (There is another case (e.g. "exporting" alias), but I'd like to omit them as not needed in this context.) 38 | 39 | ### Node.js: [**Subpath Imports**](https://nodejs.org/api/packages.html#packages_subpath_imports) 40 | 41 | There are several _3rd party_ solutions that resolves modules aliases. 42 | 43 | 1. Runtime mapper: [`module-alias`](https://www.npmjs.com/package/module-alias), etc. 44 | 1. Symlink: [`link-module-alias`](https://www.npmjs.com/package/link-module-alias), etc. 45 | 1. Transpiler/bundler: Babel plugins, Rollup, Webpack, etc. 46 | 1. Post-compile-processor: [`tsc-alias`](https://github.com/justkey007/tsc-alias), etc. 47 | 48 | However, from node v14.6.0 and v12.19.0, node introduced a new **native** support for it, named [**Subpath Imports**](https://nodejs.org/api/packages.html#packages_subpath_imports). 49 | It enables specifying alias path in package.json. 50 | It requires prefixing an alias by `#`. 51 | 52 | This repo uses **Subpath Import**. 53 | 54 | **`foo`'s package.json**: 55 | 56 | ```jsonc 57 | { 58 | "name": "@jjangga0214/foo", 59 | "imports": { 60 | "#foo/*": { 61 | "default": "./dist/*.js" 62 | } 63 | } 64 | } 65 | ``` 66 | 67 | There is `engines` restriction in package.json, as `subpath imports` is added from nodejs v14.6.0 and v12.19.0. 68 | 69 | ```jsonc 70 | // package.json 71 | { 72 | "engines": { 73 | "node": ">=14.6.0" 74 | } 75 | } 76 | ``` 77 | 78 | If you nodejs version does not fit in, you can consider _3rd party_ options. 79 | 80 | ### Typescript: [**Path Mapping**](https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping) 81 | 82 | Though we can avoid a runtime error(`Module Not Found`) for module alias resolution, compiling typescript is stil a differenct matter. 83 | Fot tsc, `tsconfig.json` has this path mappings configuration. 84 | 85 | ```jsonc 86 | { 87 | "baseUrl": "packages", 88 | "paths": { 89 | "@jjangga0214/*": ["*/src"], // e.g. `@jjangga0214/foo` -> `foo/src` 90 | "#foo/*": ["foo/src/*"], // e.g. `#foo/hello` -> `foo/src/hello.ts` 91 | "#bar/*": ["bar/src/*"], 92 | "#*": ["*/src"] // e.g. `#foo` -> `foo/src` 93 | } 94 | } 95 | ``` 96 | 97 | `@jjangga0214/foo` and `@jjangga0214/bar` are only used for cross-package references. For example, `bar` imports `@jjangga0214/foo` in its `src/index.ts`. 98 | 99 | However, `#foo` and `#bar` are only used for package's interal use. For example, `foo/src/index.ts` imports `#foo/hello`, which is same as `./hello`. 100 | 101 | Note that `bar` must NOT import `#foo` or `#foo/hello`, causing errors. I'm pretty sure there's no reason to do that as prefixing `#` is only for package's internal use, not for exporting in this scenario. 102 | 103 | But importing `@jjangga0214/foo/hello` in `bar` makes sense in some cases. For that, you should explicitly add additaional configuration like this. 104 | 105 | ```jsonc 106 | { 107 | "baseUrl": "packages", 108 | "paths": { 109 | "@jjangga0214/*": ["*/src"], 110 | + "@jjangga0214/foo/*": ["foo/src/*"], // => this one! 111 | // Other paths are ommitted for brevity 112 | } 113 | } 114 | ``` 115 | 116 | Be careful of `paths` orders for precedence. If the order changes, like the below, `#foo/hello` will be resolved to `foo/hello/src`, not `foo/src/hello`. 117 | 118 | ```jsonc 119 | { 120 | "baseUrl": "packages", 121 | "paths": { 122 | "#*": ["*/src"], 123 | "#foo/*": ["foo/src/*"], // => this will not work! 124 | "#bar/*": ["bar/src/*"] // => this will not work! 125 | } 126 | } 127 | ``` 128 | 129 | ## Dev 130 | 131 | [`ts-node-dev`](https://github.com/whitecolor/ts-node-dev) is used for `yarn dev`. 132 | You can replace it to [`ts-node`](https://github.com/TypeStrong/ts-node) if you don't need features of [`node-dev`](https://github.com/fgnass/node-dev). 133 | 134 | For `ts-node-dev`(or `ts-node`) to understand **Path Mapping**, [`tsconfig-paths`](https://github.com/dividab/tsconfig-paths) is used. 135 | 136 | **tsconfig.json**: 137 | 138 | ```jsonc 139 | { 140 | "ts-node": { 141 | "require": ["tsconfig-paths/register"] 142 | // Other options are ommitted for brevity 143 | } 144 | } 145 | ``` 146 | 147 | Until [wclr/ts-node-dev#286](https://github.com/wclr/ts-node-dev/issues/286) is resolved, `"ts-node"` field in **tsconfig.json** will be ignored by `ts-node-dev`. Thus it should be given by command options (e.g `-r` below.). This is not needed if you only use `ts-node`, not `ts-node-dev`. 148 | 149 | **Each packages' package.json**: 150 | 151 | ```jsonc 152 | { 153 | "scripts": { 154 | "dev": "ts-node-dev -r tsconfig-paths/register src/index.ts" 155 | } 156 | // Other options are ommitted for brevity. 157 | } 158 | ``` 159 | 160 | ### Extraneous Configuration 161 | 162 | In development environment, fast execution by rapid compilation is useful. 163 | `ts-node` is configured to use [`swc`](https://swc.rs/) internally. 164 | (Refer to the [official docs](https://typestrong.org/ts-node/docs/transpilers#bundled-swc-integration) -> That's why `@swc/core` and `@swc/helpers` are installed.) 165 | 166 | **tsconfig.json**: 167 | 168 | ```jsonc 169 | { 170 | "ts-node": { 171 | "transpileOnly": true, 172 | "transpiler": "ts-node/transpilers/swc-experimental" 173 | // Other options are ommitted for brevity 174 | } 175 | } 176 | ``` 177 | 178 | ## Typescript: tsconfig 179 | 180 | 1. VSCode only respects `tsconfig.json` (which can be multiple files) as of writing (until [microsoft/vscode#12463](https://github.com/microsoft/vscode/issues/12463) is resolved.). Other IDEs may have similar policy/restriction. In monorepo, as build-specific configurations (e.g. `include`, `exclude`, `rootDir`, etc) are package-specific, they should be seperated from the `tsconfig.json`. Otherwise, VSCode would not help you by its feature, like `type checking`, or `go to definition`, etc. For instance, it'd be inconvenient to work on `foo`'s API in `bar`'s code. To resolve this, build-specific configuration options are located in `tsconfig.build.json`. (But keep in note that compilation would always work flawlessly even if you only have `tsconfig.json` and let build-related options in there. The only problem in that case would be IDE support.) 181 | 182 | 1. `yarn build` in each package executes `tsc -b tsconfig.build.json`, not `tsc -p tsconfig.build.json`. This is to use typescript's [**Project References**](https://www.typescriptlang.org/docs/handbook/project-references.html) feature. For example, `yarn build` under `bar` builds itself and its dependancy, `foo` (More specifically, `foo` is compiled before `bar` is compiled). Look at `packages/bar/tsconfig.build.json`. It explicitly refers `../foo/tsconfig.build.json`. Thus, `tsc -b tsconfig.build.json` under `bar` will use `packages/foo/tsconfig.build.json` to build `foo`. And this fits well with [`--incremental`](https://www.typescriptlang.org/tsconfig/#incremental) option specified in `tsconfig.json`, as build cache can be reused if `foo` (or even `bar`) was already compiled before. 183 | 184 | 1. Each packages has their own `tsconfig.json`. That's because `ts-node-dev --project ../../tsconfig.json -r tsconfig-paths/register src/index.ts` would not find Paths Mapping, although `../../tsconfig.json` is given to `ts-node-dev` (env var `TS_NODE_PROJECT` wouldn't work, either). 185 | 186 | 1. **Path Mapping** should only be located in "project root `tsconfig.json`", even if certain some aliases are only for package's internal use. This is because `tsconfig-paths` does not fully respect **Project References** ([dividab/tsconfig-paths#153](https://github.com/dividab/tsconfig-paths/issues/153)). (If you do not use `tsconfig-paths`, this is not an issue.) 187 | 188 | ## Jest 189 | 190 | If you write test code in javascript, you can do what you used to do without additional configuration. 191 | However, if you write test code in typescript, there are several ways to execute test in general. 192 | 193 | You can consider `tsc`, `@babel/preset-typescript`, [`ts-jest`](https://github.com/kulshekhar/ts-jest), [`@swc/jest`](https://www.npmjs.com/package/@swc/jest), and so on. And there're pros/cons. 194 | 195 | - `tsc` and `@babel/preset-typescript` requires explict 2 steps (compilation + execution), while `ts-jest` and `@swc/jest` does not (compilation is done under the hood). 196 | 197 | - `@babel/preset-typescript` and `@swc/jest` do not type-check (do only transpilation), while `tsc` and `ts-jest` do. (Note that `@swc/jest` plans to implement type-check. Issue and status: [swc-project/swc#571](https://github.com/swc-project/swc/issues/571)) (Note: By `tsc`, you can turn off transpilation (to save time) but force type-check ([`--noEmit`](https://www.typescriptlang.org/tsconfig/#noEmit)) only when you want (e.g. git commit hook). By doing so, for instance, you can run test many times (e.g. test failure -> fix -> test failure -> fix -> test success) very fast without type-check (e.g. `@swc/jest`) on your local machine, and finally type-check before `git commit`, reducing total time cost. Since [microsoft/TypeScript#39122](https://github.com/microsoft/TypeScript/pull/39122), using `--incremental` and `--noEmit` simultaneously also became possible.) 198 | 199 | - `@swc/jest` is very fast, and `tsc` "can be" fast. 200 | - For example, `ts-jest` took 5.756 s while `@swc/jest` took 0.962 s for entire tests in this repo. 201 | - You can use incremental(`--incremental`) compilation if using `tsc`. 202 | - If it's possible to turn off `tsc`'s type-check, `tsc` can become "much" faster. This behaviour is not implemented yet (issue: [microsoft/TypeScript#29651](https://github.com/microsoft/TypeScript/issues/29651)). 203 | 204 | In this article, I'd like to introduce `ts-jest` and `@swc/jest`. 205 | In this repo, `@swc/jest` is preconfigured (as it is very fast of course). 206 | However, you can change it as you want. 207 | 208 | ### [`ts-jest`](https://github.com/kulshekhar/ts-jest) 209 | 210 | By `ts-jest/utils`, Jest respects **Path Mapping** automatically by reading `tsconfig.json` and `moduleNameMapper`(in `jest.config.js`), which are, in this repo, already configured as follows. See how `moduleNameMapper` is handeled in `jest.config.js` and refer to [docs](https://kulshekhar.github.io/ts-jest/user/config/#paths-mapping) for more details. 211 | 212 | **jest.config.js**: 213 | 214 | ```js 215 | const { pathsToModuleNameMapper } = require('ts-jest/utils') 216 | // Note that json import does not work if it contains comments, which tsc just ignores for tsconfig. 217 | const { compilerOptions } = require('./tsconfig') 218 | 219 | module.exports = { 220 | moduleNameMapper: { 221 | ...pathsToModuleNameMapper( 222 | compilerOptions.paths /* , { prefix: '/' }, */, 223 | ), 224 | }, 225 | // Other options are ommited for brevity. 226 | } 227 | ``` 228 | 229 | To use `ts-jest`, follow the steps below. 230 | 231 | **jest.config.js**: 232 | 233 | ```jsonc 234 | { 235 | // UNCOMMENT THE LINE BELOW TO ENABLE ts-jest 236 | // preset: 'ts-jest', 237 | // DELETE THE LINE BELOW TO DISABLE @swc/jest in favor of ts-jest 238 | "transform": { "^.+\\.(t|j)sx?$": ["@swc/jest"] } 239 | } 240 | ``` 241 | 242 | And 243 | 244 | ```shell 245 | yarn remove -W @swc/jest 246 | ``` 247 | 248 | ### [`@swc/jest`](https://www.npmjs.com/package/@swc/jest) 249 | 250 | [`swc`](https://swc.rs/) is very fast ts/js transpiler written in Rust, and `@swc/jest` uses it under the hood. 251 | 252 | Jest respects **Path Mapping** by reading `tsconfig.json` and `moduleNameMapper`(in `jest.config.js`), which are, in this repo, already configured. 253 | 254 | Do not remove(`yarn remove -W ts-jest`) ts-jest just because you use `@swc/jest`. 255 | Though `@swc/jest` replaces `ts-jest` completely, `ts-jest/utils` is used in jest.config.js. 256 | 257 | **jest.config.js**: 258 | 259 | ```js 260 | const { pathsToModuleNameMapper } = require('ts-jest/utils') 261 | // Note that json import does not work if it contains comments, which tsc just ignores for tsconfig. 262 | const { compilerOptions } = require('./tsconfig') 263 | 264 | module.exports = { 265 | moduleNameMapper: { 266 | ...pathsToModuleNameMapper( 267 | compilerOptions.paths /* , { prefix: '/' }, */, 268 | ), 269 | }, 270 | // Other options are ommited for brevity. 271 | } 272 | ``` 273 | 274 | If you want to configure `moduleNameMapper` manually, then you don't need `ts-jest`. 275 | 276 | #### Additional Dependencies 277 | 278 | Currently swc does not provide some features of babel plugins ([REF](https://swc.rs/docs/comparison-babel)). 279 | Thus additional dependencies might be needed. (You will be able to know what to install by reading error message if it appears.) 280 | 281 | A list of already installed packages in this repo is: 282 | 283 | - [`regenerator-runtime`](https://www.npmjs.com/package/regenerator-runtime): Needed when passing async callback to jest's `it` or `test` function. 284 | 285 | ## Linter and Formatter 286 | 287 | `eslint` and `prettier` is used along each other. You can run `yarn lint `. 288 | 289 | ### Airbnb Style 290 | 291 | This repo accepted airbnb style. [`eslint-config-airbnb-base`](https://www.npmjs.com/package/eslint-config-airbnb-base) and [`eslint-config-airbnb-typescript`](https://www.npmjs.com/package/eslint-config-airbnb-typescript) is configured. 292 | 293 | If you need `jsx` and `tsx` rules, you should install `eslint-config-airbnb` instead of `eslint-config-airbnb-base`. 294 | 295 | ```shell 296 | yarn add -D -W eslint-config-airbnb 297 | ``` 298 | 299 | And reconfigure .eslintrc.js as follows. 300 | 301 | **.eslintrc.js**: 302 | 303 | ```js 304 | // Other options are ommitted for brevity 305 | const common = { 306 | extends: [ 307 | 'airbnb-base', // "-base" does not include tsx rules. 308 | // 'airbnb' // Uncomment this line and remove the above line if tsx rules are needed. (Also install eslint-config-airbnb pacakge) 309 | ], 310 | } 311 | // Other options are ommitted for brevity 312 | module.exports = { 313 | overrides: [ 314 | { 315 | files: [ 316 | '**/*.ts', 317 | // '**/*.tsx' // Uncomment this line if tsx rules are needed. 318 | ], 319 | extends: [ 320 | 'airbnb-typescript/base', // "/base" does not include tsx rules. 321 | // 'airbnb-typescript' // Uncomment this line and remove the above line if tsx rules are needed. 322 | ], 323 | }, 324 | ], 325 | } 326 | ``` 327 | 328 | ### [`eslint-plugin-markdown`](https://github.com/eslint/eslint-plugin-markdown) 329 | 330 | It is not for markdown itself, but for javascript code block snippet appeared in markdown. 331 | 332 | ### [`eslint-plugin-jest`](https://github.com/jest-community/eslint-plugin-jest/issues) 333 | 334 | It is needed to lint jest code. 335 | 336 | ### Overrides 337 | 338 | By configuring `overrides` in `.eslintrc.js`, both of typescript and javascript files are able to be linted by `eslint`. (e.g. So typescript rules are not applied to `.js` files. Otherwise, it would cause errors.) 339 | 340 | ### [`markdownlint`](https://github.com/DavidAnson/markdownlint) and [`markdownlint-cli`](https://github.com/igorshubovych/markdownlint-cli#readme) 341 | 342 | `markdownlint-cli` uses `markdownlint` under the hood, and the cli respects `.markdownlintignore`. You can `yarn lint:md `. 343 | 344 | You can also install vscode extension [`vscode-markdownlint`](https://open-vsx.org/extension/DavidAnson/vscode-markdownlint). 345 | 346 | ### [`commitlint`](https://github.com/conventional-changelog/commitlint) 347 | 348 | It is used as commit message linter. This repo follows [**Conventional Commits**](https://www.conventionalcommits.org/) style for git commit message. [`@commitlint/config-conventional`](https://www.npmjs.com/package/@commitlint/config-conventional) is configured as preset, and `commitlint` is executed by `husky` for git's `commit-msg` hook. 349 | 350 | ## Git Hooks 351 | 352 | [`Husky`](https://typicode.github.io/husky/) executes [`lint-staged`](https://github.com/okonet/lint-staged) and [`commitlint`](https://github.com/conventional-changelog/commitlint) by git hooks. `lint-staged` makes sure staged files are to be formatted before committed. Refer to [`.husky/*`](./.husky) for details. 353 | 354 | ## Root commands 355 | 356 | Introducing some of commands specified in `package.json`. Refer to `package.json` for the full list. 357 | 358 | ```bash 359 | # remove compiled js folders, typescript build info, jest cache, *.log, and test coverage 360 | yarn clean 361 | 362 | # measure a single test coverage of entire packages 363 | yarn coverage 364 | 365 | # open web browser to show test coverage report. 366 | # run this AFTER running `yarn coverage`, 367 | # to make it sure there are reports before showing them. 368 | yarn coverage:show 369 | 370 | # lint code (ts, js, and js snippets on markdown) 371 | # e.g. yarn lint . 372 | yarn lint 373 | 374 | # lint markdown 375 | yarn lint:md 376 | 377 | # test entire packages 378 | yarn test 379 | 380 | # build entire packages in parallel 381 | yarn build 382 | ``` 383 | --------------------------------------------------------------------------------