Tech stacks:
- Angular 15+ (main framework)
- Nx (monorepo)
- ngRx (state management)
- cypress (e2e testing)
- jest (unit testing)
- prettier (code formatter)
- eslint (static code analysis)
- husky (git commit hook)
- SonarQube (code quality analysis)
- gitlab (CICD)
- strapi (CMS)
- lit element (UI web components)
- Kong (Gateway)
Workflow:
- Create new components with nx cli, or modify existing code (add tests if needed)
- Run unit test with jest, and e2e test with cypress
- Run sonar qube to check code quality
- Format the changes with prettier
- Commit code into appropriate feature branch (this will also trigger husky to check code format and eslint)
- See if the gitlab pipeline run success
- Create MR on gitlab
Configurations examples
1. Prettier
.prettierrc
{"singleQuote": true,"importOrderParserPlugins": ["typescript", "decorators-legacy"],"importOrder": ["^@angular/(.*)$", "^@my-project/(.*)$", "^[./]"],"importOrderSeparation": true,"importOrderSortSpecifiers": true}
2. Eslint
.eslintrc.json
{"root": true,"ignorePatterns": ["**/*"],"plugins": ["@nrwl/nx"],"overrides": [{"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],"rules": {"@typescript-eslint/no-explicit-any": "off","@nrwl/nx/enforce-module-boundaries": ["error",{"enforceBuildableLibDependency": true,"allow": [],"depConstraints": [{"sourceTag": "*","onlyDependOnLibsWithTags": ["*"]}]}]}},{"files": ["*.ts", "*.tsx"],"extends": ["plugin:@nrwl/nx/typescript"],"rules": {}},{"files": ["*.js", "*.jsx"],"extends": ["plugin:@nrwl/nx/javascript"],"rules": {}}]}
3. Jest
jest.config.ts
const { getJestProjects } = require('@nrwl/jest');export default {projects: [...getJestProjects(),'<rootDir>/apps/my-app','<rootDir>/libs/my-ui','<rootDir>/libs/helpers',],};
4. SonarQube
sonar-project.properties
# Required metadatasonar.projectKey=my.projectsonar.projectName=MY-PROJECTsonar.projectVersion=$(date +%Y-%m-%d)sonar.host.url=http://sonar.my.domain.com:8080# Comma-separated paths to directories with sources (required)sonar.sources=apps, libssonar.exclusions=**/*.spec.*, **/*.mock.*# Languagesonar.language=ts# Encoding of sources filessonar.sourceEncoding=UTF-8sonar.typescript.lcov.reportPaths=coverage/lcov.infosonar.typescript.tsconfigPath=tsconfig.base.jsonsonar.coverage.exclusions=**/*.spec.*sonar.tests.inclusions=**/*.spec.*sonar.ts.tslint.outputPath=tslint-output.jsonsonar.qualitygate.wait=true
5. TypeScript config
tsconfig.base.json
{"compileOnSave": false,"compilerOptions": {"rootDir": ".","sourceMap": true,"declaration": false,"moduleResolution": "node","emitDecoratorMetadata": true,"experimentalDecorators": true,"importHelpers": true,"target": "es2015","module": "esnext","lib": ["es2017", "dom", "ES2018.Promise"],"skipLibCheck": true,"skipDefaultLibCheck": true,"baseUrl": ".","noUncheckedIndexedAccess": true,"paths": {"@my-project/config": ["config"],"@my-project/e2e-utils/*": ["libs/e2e-utils/src/lib/*"],"@my-project/helpers": ["libs/helpers/src/index.ts"],"@my-project/shared/ui": ["libs/shared/ui/src/index.ts"],"@my-project/my-ui/*": ["libs/my-ui/src/lib/*"],}},"exclude": ["node_modules", "tmp"]}
6. Gitlab CICD
################################################### Workflow rules ###################################################include:- template: Workflow-rules.gitlab-ci.ymlimage: $DOCKER_REGISTERY/my-image-store/nodejs:20.14.0stages:- install- quality- test- build- deploy##########################[ install ]###################################yarn:install:stage: installtags:- cache_devvariables:GIT_DEPTH: 0CYPRESS_INSTALL_BINARY: '0'HUSKY_SKIP_INSTALL: '1'script:- |-if [ ! -d "node_modules" ]; thenyarn config set registry http://repo.my.domain.com/repository/npm-group/ -gyarn install --frozen-lockfileelseecho "node_modules has been retrieved from cache. No need to launch the yarn install."ficache:- key:files:- yarn.lockpaths:- node_modules/artifacts:paths:- node_modules/expire_in: 15 days##########################[ quality ]###################################yarn:lint:stage: qualitytags:- deploy_prodneeds:- job: yarn:installartifacts: truebefore_script:- '[ $CI_COMMIT_BRANCH = $CI_DEFAULT_BRANCH ] \&& export BASE_COMPARE_BRANCH=remotes/origin/master~1 \|| export BASE_COMPARE_BRANCH=remotes/origin/master'- git fetch originscript:- yarn run nx -- affected --target=lint --base=$BASE_COMPARE_BRANCH --parallel- yarn run nx -- format:check --parallel --verbose --base=$BASE_COMPARE_BRANCHallow_failure: falsewhen: alwayssonar:image: $DOCKER_REGISTERY/sonarsource/sonar-scanner-cli:lateststage: qualitytags:- deploy_prod- sonarscript:- sonar-scanner -Dproject.settings=./sonar-project.propertiesartifacts:when: on_successexpire_in: 15 daypaths:- .scannerwork/rules:- if: $CI_COMMIT_BRANCH == 'master'when: alwaysallow_failure: true##########################[ test ]###################################yarn:unit-test:extends: yarn:lintstage: testscript: yarn run nx -- affected --target=test --base=$BASE_COMPARE_BRANCH --parallelwhen: alwaysallow_failure: falseyarn:e2e-test-chrome:extends: yarn:lintstage: testscript:- yarn run nx -- affected --target=e2e --base=origin/master --parallelwhen: manual##########################[ build ]###################################b:dev:stage: builddependencies:- yarn:installtags:- cache_devneeds:- job: yarn:installartifacts: truebefore_script:- echo `date`script:- 'yarn run nx -- run-many --target=build --projects=my-project,my-other-project --configuration=dev --parallel'- 'yarn run nx -- run-many --target=server --projects=my-project,my-other-project --configuration=dev --parallel'after_script:- echo `date`- 'mkdir -p target'- 'tar -zcf target/package.tar.gz dist'- echo `date`cache:- key: nx-cache-$CI_COMMIT_BRANCHpaths:- tmp/nx-cacheartifacts:when: on_successexpire_in: 15 daypaths:- target/rules:- when: manualb:staging:extends: b:devscript:- 'yarn run nx -- run-many --target=build --projects=my-project,my-other-project --configuration=staging --parallel'- 'yarn run nx -- run-many --target=server --projects=my-project,my-other-project --configuration=staging --parallel'allow_failure: falseb:preprod:extends: b:devscript:- 'yarn run nx -- run-many --target=build --projects=my-project,my-other-project --configuration=preproduction --parallel'- 'yarn run nx -- run-many --target=server --projects=my-project,my-other-project --configuration=preproduction --parallel'allow_failure: falseb:prod:extends: b:devscript:- 'yarn run nx -- run-many --target=build --projects=my-project,my-other-project --configuration=production --parallel'- 'yarn run nx -- run-many --target=server --projects=my-project,my-other-project --configuration=production --parallel'rules:- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'when: always- when: never- allow_failure: false##########################[ deploy ]###################################d:dev:stage: deployvariables:GIT_STRATEGY: nonetags:- deploy_devenvironment:name: devurl: https://my.domain.com:4004needs:- job: b:devartifacts: truescript:- bash /tools/deploy-toolrules:- when: manualallow_failure: trued:staging:extends: d:devenvironment:name: stagingurl: https://my.domain.com:4004needs:- job: b:stagingartifacts: trued:preprod:extends: d:devtags:- deploy_prodenvironment:name: preprodneeds:- job: b:preprodartifacts: trued:prod:extends: d:devtags:- deploy_prodenvironment:name: prodneeds:- job: b:prodartifacts: truerules:- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'when: manual- when: never- allow_failure: false
7. Husky & other run scripts
{"name": "my-project","version": "1.0.0","license": "MIT","scripts": {"start:my-project": "ng serve --project my-project","e2e:my-project": "nx e2e my-project-e2e","affected:build": "nx affected:build","affected:e2e": "nx affected:e2e","affected:test": "nx affected:test","affected:lint": "nx affected:lint --parallel --uncommitted","affected:dep-graph": "nx affected:dep-graph","affected": "nx affected","format": "nx format:write","format:check": "nx format:check --parallel --uncommitted","update": "nx migrate latest","update:check": "ng update","dep-graph": "nx dep-graph",},"husky": {"hooks": {"pre-commit": "yarn run affected:lint && yarn run format:check"}},"private": true,"dependencies": {"@angular-devkit/core": "15.2.11","@angular-devkit/schematics": "15.2.11","@angular/animations": "15.2.10","@angular/cdk": "14.2.7","@angular/common": "15.2.10","@angular/compiler": "15.2.10","@angular/core": "15.2.10","@angular/forms": "15.2.10","@angular/material": "14.2.7","winston": "3.3.3","zone.js": "0.11.8"},"devDependencies": {"@angular-devkit/build-angular": "15.2.11","@angular-eslint/eslint-plugin": "14.0.4","@angular-eslint/eslint-plugin-template": "14.0.4","@angular-eslint/template-parser": "14.0.4","@angular/cli": "~15.2.11","typescript": "4.8.4","webpack": "^5.58.1","yarn": "1.22.19"}}
8. Kong
proxy.conf.json
{"/api": {"target": "https://apim-kong-staging.dev.mydomain.com:8080/api-path","secure": false,"logLevel": "debug","pathRewrite": {"^/api": ""},"headers": {"apiKey": "apikey..."}}}
angular.json > projects > my-project > architect > serve
"serve": {"builder": "@angular-devkit/build-angular:dev-server","options": {"browserTarget": "my-project:build","host": "my.local.domain.com","proxyConfig": "proxy.conf.json"},