├── .cz-config.js ├── .deploy └── docker │ └── mongo-init.sh ├── .editorconfig ├── .env.example ├── .eslintrc.json ├── .gitignore ├── .htmlhintrc ├── .huskyrc ├── .lintstagedrc.js ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── .stylelintrc.json ├── .vscode ├── extensions.json └── settings.json ├── README.md ├── apps ├── .gitkeep ├── api │ ├── .eslintrc.json │ ├── jest.config.js │ ├── project.json │ ├── src │ │ ├── app │ │ │ ├── .gitkeep │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.spec.ts │ │ │ └── app.service.ts │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── config │ │ │ ├── database.config.ts │ │ │ ├── index.ts │ │ │ ├── load-config.ts │ │ │ └── validation-schema.ts │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ └── main.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ └── tsconfig.spec.json ├── web-e2e │ ├── .eslintrc.json │ ├── cypress.json │ ├── project.json │ ├── src │ │ ├── fixtures │ │ │ └── example.json │ │ ├── integration │ │ │ └── app.spec.ts │ │ ├── plugins │ │ │ └── index.js │ │ └── support │ │ │ ├── app.po.ts │ │ │ ├── commands.ts │ │ │ └── index.ts │ ├── tsconfig.e2e.json │ └── tsconfig.json └── web │ ├── .browserslistrc │ ├── .eslintrc.json │ ├── .stylelintrc.json │ ├── jest.config.js │ ├── project.json │ ├── proxy.conf.json │ ├── src │ ├── app │ │ ├── app.component.html │ │ ├── app.component.scss │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ └── app.module.ts │ ├── assets │ │ └── .gitkeep │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.scss │ └── test-setup.ts │ ├── tsconfig.app.json │ ├── tsconfig.editor.json │ ├── tsconfig.json │ └── tsconfig.spec.json ├── commit.scope.config.js ├── commitlint.config.js ├── decorate-angular-cli.js ├── docker-compose.yml ├── docs ├── README.md ├── images │ ├── commitizen.webp │ └── request-lifecycle.png └── post │ ├── 1.1-Nx安装环境.md │ ├── 1.2-项目工程化配置.md │ └── 1.3-Nest体系结构.md ├── jest.config.js ├── jest.preset.js ├── libs ├── .gitkeep └── api-interfaces │ ├── .babelrc │ ├── .eslintrc.json │ ├── README.md │ ├── project.json │ ├── src │ ├── index.ts │ └── lib │ │ └── api-interfaces.ts │ ├── tsconfig.json │ └── tsconfig.lib.json ├── nx.json ├── package-lock.json ├── package.json ├── tools ├── generators │ └── .gitkeep └── tsconfig.tools.json ├── tsconfig.base.json └── workspace.json /.cz-config.js: -------------------------------------------------------------------------------- 1 | const scopeEnum = require('./commit.scope.config'); 2 | module.exports = { 3 | // 自定义types 4 | types: [ 5 | { 6 | value: 'feat', 7 | name: '新增功能', 8 | }, 9 | { 10 | value: 'fix', 11 | name: 'bug修复', 12 | }, 13 | { 14 | value: 'docs', 15 | name: '文档更新', 16 | }, 17 | { 18 | value: 'style', 19 | name: '不影响程序逻辑的代码修改(修改空白字符,格式缩进,补全缺失的分号等,没有改变代码逻辑)', 20 | }, 21 | { 22 | value: 'refactor', 23 | name: '重构代码(既没有新增功能,也没有修复bug)', 24 | }, 25 | { 26 | value: 'perf', 27 | name: '改进性能、体验优化的代码更改', 28 | }, 29 | { 30 | value: 'test', 31 | name: '新增测试或更新现有测试用例', 32 | }, 33 | { 34 | value: 'build', 35 | name: '主要目的是修改项目构建系统(例如 glup, webpack, rollup,npm的配置等.xxx) 的提交', 36 | }, 37 | { 38 | value: 'ci', 39 | name: '主要目的是修改项目继续集成流程(例如 Travis, Jenkins, GitLab CI, Circle等) 的提交', 40 | }, 41 | { 42 | value: 'chore', 43 | name: '不属于以上类型的其他类型', 44 | }, 45 | { 46 | value: 'merge', 47 | name: '分支合并 Merge branch ? of ?', 48 | }, 49 | { 50 | value: 'revert', 51 | name: '回滚某个更早之前的提交', 52 | }, 53 | ], 54 | // 自定义scopes 55 | scopes: scopeEnum.map(item => ({ name: item.scope })), 56 | 57 | allowTicketNumber: false, 58 | isTicketNumberRequired: false, 59 | ticketNumberPrefix: 'TICKET-', 60 | ticketNumberRegExp: '\\d{1,5}', 61 | 62 | // override the messages, defaults are as follows 63 | messages: { 64 | type: '选择要提交的更改类型:', 65 | scope: '表示此更改的范围(可选):', 66 | // used if allowCustomScopes is true 67 | customScope: '选择此项可以更改自定义范围:', 68 | subject: '用简短的文字描述变更内容:', 69 | body: '提供更长的变更描述(可选)。使用“|”中断新行:', 70 | breaking: '列出任何中断更改(可选):', 71 | footer: '列出此更改所关闭的任何问题(可选)。例如:#31 #34。对应“package.json”文件里“bugs.url”', 72 | confirmCommit: '你确定要继续执行上面的提交吗?', 73 | }, 74 | 75 | allowCustomScopes: true, 76 | allowBreakingChanges: ['feat', 'fix'], 77 | // 跳过任何你想问的问题 78 | skipQuestions: ['body'], 79 | 80 | // limit subject length 81 | subjectLimit: 100, 82 | }; 83 | -------------------------------------------------------------------------------- /.deploy/docker/mongo-init.sh: -------------------------------------------------------------------------------- 1 | mongo -- "$MONGO_INITDB_DATABASE" < { 5 | const cwd = process.cwd(); 6 | const filesList = files.map((file) => path.relative(cwd, file)).join(','); 7 | return [ 8 | `npx nx affected:lint --parallel --fix --files=${filesList}`, 9 | `npx nx format:write --files=${filesList}`, 10 | `git add ${files.join(' ')}`, 11 | ]; 12 | }, 13 | '{apps,libs}/**/*.scss': (files) => { 14 | const cwd = process.cwd(); 15 | const filesList = files.map((file) => path.relative(cwd, file)).join(','); 16 | return [ 17 | `npx nx affected --target=stylelint --uncommitted --fix=true --files=${filesList}`, 18 | `npx nx format:write --files=${filesList}`, 19 | `git add ${files.join(' ')}`, 20 | ]; 21 | } 22 | }; -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npm.taobao.org/ 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 14.15.0 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | 3 | /dist 4 | /coverage 5 | /.deploy 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "printWidth": 140, 4 | "tabWidth": 2, 5 | "singleQuote": true, 6 | "trailingComma": "all", 7 | "semi": true, 8 | "overrides": [ 9 | { 10 | "files": "*.scss", 11 | "options": { 12 | "singleQuote": false 13 | } 14 | }, 15 | { 16 | "files": "*.yml", 17 | "options": { 18 | "useTabs": false, 19 | "tabWidth": 4 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "stylelint-config-standard", 4 | "stylelint-config-recommended-scss", 5 | "stylelint-config-idiomatic-order", 6 | "stylelint-config-prettier" 7 | ], 8 | "ignoreFiles": [ 9 | "node_modules/**", 10 | "dist/**" 11 | ], 12 | "rules": { 13 | "order/properties-alphabetical-order": null, 14 | "font-family-name-quotes": "always-where-recommended", 15 | "function-url-quotes": [ 16 | "always", 17 | { 18 | "except": [ 19 | "empty" 20 | ] 21 | } 22 | ], 23 | "selector-attribute-quotes": "always", 24 | "max-nesting-depth": 3, 25 | "selector-max-compound-selectors": 3, 26 | "selector-max-specificity": [ 27 | "0,3,2", 28 | { 29 | "ignoreSelectors": [ 30 | ":not", 31 | ":hover", 32 | ":focus", 33 | ":active", 34 | "/:host/", 35 | "/:host::ng-deep/", 36 | "/:host ::ng-deep/", 37 | "::ng-deep" 38 | ] 39 | } 40 | ], 41 | "declaration-no-important": true, 42 | "at-rule-no-vendor-prefix": true, 43 | "media-feature-name-no-vendor-prefix": true, 44 | "property-no-vendor-prefix": [ 45 | true, 46 | { 47 | "ignoreProperties": [ 48 | "appearance" 49 | ] 50 | } 51 | ], 52 | "selector-no-vendor-prefix": true, 53 | "value-no-vendor-prefix": true, 54 | "no-empty-source": null, 55 | "selector-class-pattern": "^([a-z][a-z0-9]*)(-[a-z0-9]+)*$", 56 | "selector-id-pattern": "j-[a-z]+", 57 | "selector-max-id": 0, 58 | "selector-no-qualifying-type": [ 59 | true, 60 | { 61 | "ignore": [ 62 | "attribute", 63 | "class", 64 | "id" 65 | ] 66 | } 67 | ], 68 | "selector-max-universal": 0, 69 | "selector-pseudo-element-no-unknown": [ 70 | true, 71 | { 72 | "ignorePseudoElements": [ 73 | "ng-deep" 74 | ] 75 | } 76 | ], 77 | "selector-pseudo-class-no-unknown": [ 78 | true, 79 | { 80 | "ignorePseudoClasses": [ 81 | "host" 82 | ] 83 | } 84 | ], 85 | "selector-type-no-unknown": [ 86 | true, 87 | { 88 | "ignoreTypes": [ 89 | "/^app-/", 90 | "/^router-/" 91 | ] 92 | } 93 | ], 94 | "unit-allowed-list": [ 95 | "px", 96 | "%", 97 | "em", 98 | "rem", 99 | "vw", 100 | "vh", 101 | "deg", 102 | "s", 103 | "ms" 104 | ] 105 | } 106 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "nrwl.angular-console", 4 | "angular.ng-template", 5 | "esbenp.prettier-vscode", 6 | "mikael.angular-beastcode", 7 | "streetsidesoftware.code-spell-checker", 8 | "eamodio.gitlens", 9 | "stylelint.vscode-stylelint", 10 | "mkaufman.htmlhint", 11 | "knisterpeter.vscode-commitizen", 12 | "wmaurer.change-case", 13 | "pkief.material-icon-theme", 14 | "oderwat.indent-rainbow", 15 | "coenraads.bracket-pair-colorizer-2", 16 | "quicktype.quicktype", 17 | "jasonnutter.search-node-modules", 18 | "humao.rest-client", 19 | "firsttris.vscode-jest-runner", 20 | "dbaeumer.vscode-eslint" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // 文件排序 3 | "explorer.sortOrder": "type", 4 | // Editor settings 5 | "editor.snippetSuggestions": "top", 6 | "editor.autoIndent": "full", 7 | "editor.codeLens": false, 8 | "editor.cursorBlinking": "solid", 9 | "editor.cursorSmoothCaretAnimation": true, 10 | "editor.cursorStyle": "line", 11 | "editor.fontSize": 14, 12 | "editor.fontLigatures": true, 13 | "editor.formatOnPaste": false, 14 | "editor.formatOnType": false, 15 | "editor.formatOnSave": true, 16 | "editor.letterSpacing": 0.5, 17 | "editor.lineHeight": 25, 18 | "editor.minimap.enabled": false, 19 | "editor.multiCursorModifier": "ctrlCmd", 20 | "editor.suggestSelection": "first", 21 | "editor.tabCompletion": "on", 22 | "editor.tabSize": 2, 23 | "editor.wordWrap": "on", 24 | "editor.codeActionsOnSave": { 25 | // For ESLint 26 | "source.fixAll.eslint": true, 27 | // For TSLint 28 | "source.fixAll.tslint": true, 29 | // For Stylelint 30 | "source.fixAll.stylelint": true, 31 | // import sort 32 | "source.organizeImports": true 33 | }, 34 | // File settings 35 | "files.associations": { 36 | "*.template": "plaintext" 37 | }, 38 | "files.trimTrailingWhitespace": true, 39 | // gitlens, git 可视化功能增强 40 | "gitlens.advanced.messages": { 41 | "suppressCommitHasNoPreviousCommitWarning": false, 42 | "suppressCommitNotFoundWarning": false, 43 | "suppressFileNotUnderSourceControlWarning": false, 44 | "suppressGitVersionWarning": false, 45 | "suppressLineUncommittedWarning": false, 46 | "suppressNoRepositoryWarning": false 47 | }, 48 | "eslint.validate": ["javascript", "typescript"], 49 | // markdown 50 | "markdownlint.config": { 51 | "MD033": { 52 | "allowed_elements": ["iframe", "details", "summary", "div", "hr", "br", "a", "img"] 53 | } 54 | }, 55 | // html 56 | "html.format.indentInnerHtml": true, 57 | "html.format.indentHandlebars": true, 58 | "html.format.endWithNewline": true, 59 | "html.format.extraLiners": "", 60 | // emmet 61 | "emmet.triggerExpansionOnTab": true, 62 | "emmet.showSuggestionsAsSnippets": true, 63 | // stylelint 64 | "stylelint.syntax": "scss", 65 | "stylelint.validate": ["sass", "scss"], 66 | "scss.validate": false, 67 | // typescript 68 | "typescript.tsdk": "node_modules\\typescript\\lib", 69 | // commitizen 70 | "commitizen.subjectLength": 150, 71 | // 以下配置大多是设置默认格式化程序 72 | "[html]": { 73 | "editor.defaultFormatter": "vscode.html-language-features" 74 | }, 75 | "[scss]": { 76 | "editor.defaultFormatter": "esbenp.prettier-vscode" 77 | }, 78 | "[typescript]": { 79 | "editor.defaultFormatter": "esbenp.prettier-vscode" 80 | }, 81 | "[javascript]": { 82 | "editor.defaultFormatter": "vscode.typescript-language-features" 83 | }, 84 | "[json]": { 85 | "editor.defaultFormatter": "vscode.json-language-features" 86 | }, 87 | "[markdown]": { 88 | "editor.defaultFormatter": "esbenp.prettier-vscode" 89 | }, 90 | "cSpell.words": ["jianshu", "Nestjs", "Typegoose", "Virtuals", "cnode", "dasherize", "jiayi", "mongodb", "nrwl", "stylelint", "typeof"] 91 | } 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jianshu 2 | 3 | A simple application demonstrating [Angular](https://github.com/angular/angular) (SPA, SSR) the basic usage of permissions with [NestJS](https://github.com/nestjs/nest) (JWT, Passport, Github, User, Group, Permission) based on [jianshu](https://www.jianshu.com) template. 4 | 5 | ## Related Technology 6 | 7 | - [@nrwl/nx](https://github.com/nrwl/nx) - Nx is an open platform with plugins for many modern tools and frameworks. 8 | - [NestJS](https://github.com/nestjs/nest) - a JS backend framework providing architecture out of the box with a syntax similar to Angular 9 | - [Angular](https://github.com/angular/angular) - a JS frontend framework created by Google 10 | - [Angular Universal](https://github.com/angular/universal) - a JS frontend framework created by Google 11 | - [Material-ui](https://material.angular.io/) - Material Design components for Angular 12 | - [TypeScript](https://github.com/Microsoft/TypeScript) - reactive extensions for JavaScript 13 | - [RxJS](https://github.com/Reactive-Extensions/RxJS) - superset of JS which compiles to JS, providing compile-time type checking 14 | - [MongoDB](https://github.com/mongodb/mongo) - a NoSQL database 15 | - [Mongoose](https://github.com/Automattic/mongoose) - MongoDB object modeling designed to work in an asynchronous environment 16 | - [TypeORM](https://github.com/typeorm/typeorm) - ORM for TypeScript and JavaScript (ES7, ES6, ES5). Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, WebSQL databases. 17 | - [Redis](https://redis.io) - Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache, and message broker. 18 | - [Ioredis](https://github.com/luin/ioredis) - A robust, performance-focused and full-featured Redis client for Node.js. 19 | - [Passport](https://github.com/jaredhanson/passport) - a popular library used to implement JavaScript authentication 20 | - [JWT](https://jwt.io) - JSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two parties. 21 | - [Docker](https://www.docker.com) - Complete flexibility to run any Docker commands. Ship Better Code, Faster. 22 | 23 | ## Features 24 | 25 | - Use the Mongoose(Typegoose) connect MongoDB 26 | - Use the Ioredis connect Redis 27 | - Use JWT for authentication 28 | - Support github authentication login 29 | - Super easy to install and start using the full-featured controllers and services 30 | - DB and service agnostic extendable CRUD controllers 31 | - Reach query parsing with filtering, pagination, sorting, relations, nested relations, cache, etc. 32 | - Framework agnostic package with query builder for a frontend usage 33 | - Query, path params and DTO validation included 34 | - Overriding controller methods with ease 35 | - Tiny config (including globally) 36 | - Additional helper decorators 37 | - Swagger documentation 38 | - Support Angular server rendering 39 | - Support Angular responsive presentation 40 | - Support Domain-Driven Design 41 | 42 | ## Running the project 43 | 44 | These instructions should be sufficient for one to get the project going on their local machine 45 | 46 | ### Installing core dependencies 47 | 48 | - make sure you have [node.js](https://nodejs.org/en/download/) installed version 12.13+ 49 | 50 | #### Local Installing DB dependencies 51 | 52 | - make sure you have [MongoDB](https://www.mongodb.com/) installed version 3.4+ 53 | - make sure you have [Redis](https://redis.io/download) installed version 3.2+ 54 | 55 | #### Docker Installing DB dependencies 56 | 57 | - make sure you have [Docker](https://www.docker.com/products/docker-desktop) 58 | 59 | ### Cloning the github repository 60 | 61 | To clone the project, run 62 | 63 | ```bash 64 | git clone https://github.com/jiayisheji/jianshu.git 65 | ``` 66 | 67 | ### Installing the dependencies 68 | 69 | To install the dependencies after you've cloned the project, go to its root folder and run 70 | 71 | ```bash 72 | cd jianshu && npm install 73 | ``` 74 | 75 | ### Setting environment variables 76 | 77 | ```bash 78 | cp .env.example .env 79 | ``` 80 | 81 | > Modify the corresponding values as required 82 | 83 | ### Starting the MongoDB and Redis 84 | 85 | Once you start the database application, you are ready to run the server 86 | 87 | Boot according to your system 88 | 89 | Notice that the server uses MongoDB and Redis so we need to have a MongoDB and Redis instance running so the server can connect to it。 90 | 91 | If you use Docker: 92 | 93 | ```bash 94 | docker-compose up -d 95 | ``` 96 | 97 | ### Alternative commands 98 | 99 | If you need to work on the frontend and backend parts at the same time, you can run 100 | 101 | ```bash 102 | npm start 103 | ``` 104 | 105 | Then, you can go to the Angular dev server at port 4200 and test server requests (to port 3000), we got a proxy to the backend 106 | 107 | If you only need to work on the frontend, you can run 108 | 109 | ```bash 110 | npm run start client 111 | ``` 112 | 113 | Alternatively, if you only need to work on the backend, you can run 114 | 115 | ```bash 116 | npm run start server 117 | ``` 118 | 119 | Keeping in mind that you need to have the Angular app built and a MongoDB and Redis connection established 120 | 121 | ## Documentation 122 | 123 | - [Angular Docs](https://angular.io/docs) 124 | - [NestJS Docs](https://docs.nestjs.com) 125 | - [Typescript Docs](http://www.typescriptlang.org/) 126 | - [Rxjs Docs](https://rxjs.dev/api) 127 | - [Mongoose Docs](https://mongoosejs.com/) 128 | - [Typegoose Docs](https://typegoose.github.io/typegoose/) 129 | - [Ioredis Docs](https://github.com/luin/ioredis/blob/master/API.md) 130 | - [Graphql Docs](https://graphql.org/) 131 | - [Passport Docs](http://www.passportjs.org/) 132 | - [JWT Docs](https://jwt.io/) 133 | - [Tutorial Docs](docs/README.md) 134 | 135 | ## Support 136 | 137 | Any support is welcome. At least you can give us a star :star: 138 | 139 | ## Browser Support 140 | 141 | | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [IE / Edge](http://godban.github.io/browsers-support-badges/)
IE / Edge | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | [Opera](http://godban.github.io/browsers-support-badges/)
Opera | 142 | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 143 | | last 2 versions | last 2 versions | IE11, Edge | last 2 versions | last 2 versions | 144 | 145 | ## License 146 | 147 | [MIT](LICENSE) 148 | 149 | -------------------------------------------------------------------------------- /apps/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiayisheji/jianshu/f3a73dda9e50c406fb8d89225c334ecb0aa2334a/apps/.gitkeep -------------------------------------------------------------------------------- /apps/api/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /apps/api/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'api', 3 | preset: '../../jest.preset.js', 4 | globals: { 5 | 'ts-jest': { 6 | tsconfig: '/tsconfig.spec.json', 7 | }, 8 | }, 9 | testEnvironment: 'node', 10 | transform: { 11 | '^.+\\.[tj]s$': 'ts-jest', 12 | }, 13 | moduleFileExtensions: ['ts', 'js', 'html'], 14 | coverageDirectory: '../../coverage/apps/api', 15 | }; 16 | -------------------------------------------------------------------------------- /apps/api/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "apps/api", 3 | "sourceRoot": "apps/api/src", 4 | "projectType": "application", 5 | "targets": { 6 | "build": { 7 | "executor": "@nrwl/node:build", 8 | "outputs": ["{options.outputPath}"], 9 | "options": { 10 | "outputPath": "dist/apps/api", 11 | "main": "apps/api/src/main.ts", 12 | "tsConfig": "apps/api/tsconfig.app.json", 13 | "assets": ["apps/api/src/assets"] 14 | }, 15 | "configurations": { 16 | "production": { 17 | "optimization": true, 18 | "extractLicenses": true, 19 | "inspect": false, 20 | "fileReplacements": [ 21 | { 22 | "replace": "apps/api/src/environments/environment.ts", 23 | "with": "apps/api/src/environments/environment.prod.ts" 24 | } 25 | ], 26 | "externalDependencies": "all", 27 | "generatePackageJson": true 28 | } 29 | } 30 | }, 31 | "serve": { 32 | "executor": "@nrwl/node:execute", 33 | "options": { 34 | "buildTarget": "api:build" 35 | } 36 | }, 37 | "lint": { 38 | "executor": "@nrwl/linter:eslint", 39 | "options": { 40 | "lintFilePatterns": ["apps/api/**/*.ts"] 41 | } 42 | }, 43 | "test": { 44 | "executor": "@nrwl/jest:jest", 45 | "outputs": ["coverage/apps/api"], 46 | "options": { 47 | "jestConfig": "apps/api/jest.config.js", 48 | "passWithNoTests": true 49 | } 50 | } 51 | }, 52 | "tags": [] 53 | } 54 | -------------------------------------------------------------------------------- /apps/api/src/app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiayisheji/jianshu/f3a73dda9e50c406fb8d89225c334ecb0aa2334a/apps/api/src/app/.gitkeep -------------------------------------------------------------------------------- /apps/api/src/app/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { AppController } from './app.controller'; 4 | import { AppService } from './app.service'; 5 | 6 | describe('AppController', () => { 7 | let app: TestingModule; 8 | 9 | beforeAll(async () => { 10 | app = await Test.createTestingModule({ 11 | controllers: [AppController], 12 | providers: [AppService], 13 | }).compile(); 14 | }); 15 | 16 | describe('getData', () => { 17 | it('should return "Welcome to api!"', () => { 18 | const appController = app.get(AppController); 19 | expect(appController.getData()).toEqual({ message: 'Welcome to api!' }); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /apps/api/src/app/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | 3 | import { Message } from '@jianshu/api-interfaces'; 4 | 5 | import { AppService } from './app.service'; 6 | 7 | @Controller() 8 | export class AppController { 9 | constructor(private readonly appService: AppService) {} 10 | 11 | @Get('hello') 12 | getData(): Message { 13 | return this.appService.getData(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /apps/api/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ConfigModule, ConfigService } from '@nestjs/config'; 3 | import { MongooseModule } from '@nestjs/mongoose'; 4 | import loadConfig from '../config/load-config'; 5 | import validationSchema from '../config/validation-schema'; 6 | import { AppController } from './app.controller'; 7 | import { AppService } from './app.service'; 8 | 9 | @Module({ 10 | imports: [ 11 | ConfigModule.forRoot({ 12 | isGlobal: true, 13 | envFilePath: '.env', 14 | load: loadConfig, 15 | validationSchema, 16 | }), 17 | MongooseModule.forRootAsync({ 18 | imports: [ConfigModule], 19 | useFactory: async (configService: ConfigService) => ({ 20 | uri: configService.get('database.uri'), 21 | useNewUrlParser: true, 22 | useUnifiedTopology: true, 23 | useCreateIndex: true, 24 | }), 25 | inject: [ConfigService], 26 | }), 27 | ], 28 | controllers: [AppController], 29 | providers: [AppService], 30 | }) 31 | export class AppModule {} 32 | -------------------------------------------------------------------------------- /apps/api/src/app/app.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test } from '@nestjs/testing'; 2 | 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppService', () => { 6 | let service: AppService; 7 | 8 | beforeAll(async () => { 9 | const app = await Test.createTestingModule({ 10 | providers: [AppService], 11 | }).compile(); 12 | 13 | service = app.get(AppService); 14 | }); 15 | 16 | describe('getData', () => { 17 | it('should return "Welcome to api!"', () => { 18 | expect(service.getData()).toEqual({ message: 'Welcome to api!' }); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /apps/api/src/app/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { Message } from '@jianshu/api-interfaces'; 3 | 4 | @Injectable() 5 | export class AppService { 6 | getData(): Message { 7 | return { message: 'Welcome to api!' }; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /apps/api/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiayisheji/jianshu/f3a73dda9e50c406fb8d89225c334ecb0aa2334a/apps/api/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/api/src/config/database.config.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from '@nestjs/config'; 2 | 3 | export default registerAs('database', () => { 4 | const host = 'localhost'; 5 | const port = 27017; 6 | const user = process.env.MONGO_USERNAME; 7 | const pass = process.env.MONGO_PASSWORD; 8 | const dbs = process.env.MONGO_DATABASE; 9 | 10 | return { 11 | host, 12 | port, 13 | user, 14 | pass, 15 | dbs, 16 | uri: `mongodb://${user}:${pass}@${host}:${port}/${dbs}`, 17 | }; 18 | }); 19 | -------------------------------------------------------------------------------- /apps/api/src/config/index.ts: -------------------------------------------------------------------------------- 1 | export * from './load-config'; 2 | export * from './validation-schema'; 3 | -------------------------------------------------------------------------------- /apps/api/src/config/load-config.ts: -------------------------------------------------------------------------------- 1 | import databaseConfig from './database.config'; 2 | 3 | export default [databaseConfig]; 4 | -------------------------------------------------------------------------------- /apps/api/src/config/validation-schema.ts: -------------------------------------------------------------------------------- 1 | import * as Joi from 'joi'; 2 | 3 | export default Joi.object({ 4 | MONGO_DATABASE: Joi.string().required(), 5 | MONGO_USERNAME: Joi.string().required(), 6 | MONGO_PASSWORD: Joi.string().required(), 7 | }); 8 | -------------------------------------------------------------------------------- /apps/api/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /apps/api/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: false, 3 | }; 4 | -------------------------------------------------------------------------------- /apps/api/src/main.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is not a production server yet! 3 | * This is only a minimal backend to get started. 4 | */ 5 | 6 | import { Logger } from '@nestjs/common'; 7 | import { NestFactory } from '@nestjs/core'; 8 | import { AppModule } from './app/app.module'; 9 | 10 | async function bootstrap() { 11 | const app = await NestFactory.create(AppModule); 12 | const globalPrefix = 'api'; 13 | app.setGlobalPrefix(globalPrefix); 14 | const port = process.env.PORT || 3333; 15 | await app.listen(port, () => { 16 | Logger.log('Listening at http://localhost:' + port + '/' + globalPrefix); 17 | }); 18 | } 19 | 20 | bootstrap(); 21 | -------------------------------------------------------------------------------- /apps/api/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["node"], 7 | "emitDecoratorMetadata": true, 8 | "target": "es2015" 9 | }, 10 | "exclude": ["**/*.spec.ts"], 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /apps/api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.app.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /apps/api/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": ["**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/web-e2e/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:cypress/recommended", "../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["src/plugins/index.js"], 11 | "rules": { 12 | "@typescript-eslint/no-var-requires": "off", 13 | "no-undef": "off" 14 | } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /apps/web-e2e/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileServerFolder": ".", 3 | "fixturesFolder": "./src/fixtures", 4 | "integrationFolder": "./src/integration", 5 | "modifyObstructiveCode": false, 6 | "pluginsFile": "./src/plugins/index", 7 | "supportFile": "./src/support/index.ts", 8 | "video": true, 9 | "videosFolder": "../../dist/cypress/apps/web-e2e/videos", 10 | "screenshotsFolder": "../../dist/cypress/apps/web-e2e/screenshots", 11 | "chromeWebSecurity": false 12 | } 13 | -------------------------------------------------------------------------------- /apps/web-e2e/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "apps/web-e2e", 3 | "sourceRoot": "apps/web-e2e/src", 4 | "projectType": "application", 5 | "targets": { 6 | "e2e": { 7 | "executor": "@nrwl/cypress:cypress", 8 | "options": { 9 | "cypressConfig": "apps/web-e2e/cypress.json", 10 | "tsConfig": "apps/web-e2e/tsconfig.e2e.json", 11 | "devServerTarget": "web:serve:development" 12 | }, 13 | "configurations": { 14 | "production": { 15 | "devServerTarget": "web:serve:production" 16 | } 17 | } 18 | }, 19 | "lint": { 20 | "executor": "@nrwl/linter:eslint", 21 | "options": { 22 | "lintFilePatterns": ["apps/web-e2e/**/*.{js,ts}"] 23 | } 24 | } 25 | }, 26 | "tags": [], 27 | "implicitDependencies": ["web"] 28 | } 29 | -------------------------------------------------------------------------------- /apps/web-e2e/src/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io" 4 | } 5 | -------------------------------------------------------------------------------- /apps/web-e2e/src/integration/app.spec.ts: -------------------------------------------------------------------------------- 1 | import { getGreeting } from '../support/app.po'; 2 | 3 | describe('web', () => { 4 | beforeEach(() => cy.visit('/')); 5 | 6 | it('should display welcome message', () => { 7 | // Custom command example, see `../support/commands.ts` file 8 | cy.login('my-email@something.com', 'myPassword'); 9 | 10 | // Function helper example, see `../support/app.po.ts` file 11 | getGreeting().contains('Welcome to web!'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /apps/web-e2e/src/plugins/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example plugins/index.js can be used to load plugins 3 | // 4 | // You can change the location of this file or turn off loading 5 | // the plugins file with the 'pluginsFile' configuration option. 6 | // 7 | // You can read more here: 8 | // https://on.cypress.io/plugins-guide 9 | // *********************************************************** 10 | 11 | // This function is called when a project is opened or re-opened (e.g. due to 12 | // the project's config changing) 13 | 14 | const { preprocessTypescript } = require('@nrwl/cypress/plugins/preprocessor'); 15 | 16 | module.exports = (on, config) => { 17 | // `on` is used to hook into various events Cypress emits 18 | // `config` is the resolved Cypress config 19 | 20 | // Preprocess Typescript file using Nx helper 21 | on('file:preprocessor', preprocessTypescript(config)); 22 | }; 23 | -------------------------------------------------------------------------------- /apps/web-e2e/src/support/app.po.ts: -------------------------------------------------------------------------------- 1 | export const getGreeting = () => cy.get('h1'); 2 | -------------------------------------------------------------------------------- /apps/web-e2e/src/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | 11 | // eslint-disable-next-line @typescript-eslint/no-namespace 12 | declare namespace Cypress { 13 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 14 | interface Chainable { 15 | login(email: string, password: string): void; 16 | } 17 | } 18 | // 19 | // -- This is a parent command -- 20 | Cypress.Commands.add('login', (email, password) => { 21 | console.log('Custom command example: Login', email, password); 22 | }); 23 | // 24 | // -- This is a child command -- 25 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 26 | // 27 | // 28 | // -- This is a dual command -- 29 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 30 | // 31 | // 32 | // -- This will overwrite an existing command -- 33 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 34 | -------------------------------------------------------------------------------- /apps/web-e2e/src/support/index.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | -------------------------------------------------------------------------------- /apps/web-e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "outDir": "../../dist/out-tsc", 6 | "allowJs": true, 7 | "types": ["cypress", "node"], 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true 12 | }, 13 | "include": ["src/**/*.ts", "src/**/*.js"], 14 | "angularCompilerOptions": { 15 | "strictInjectionParameters": true, 16 | "strictInputAccessModifiers": true, 17 | "strictTemplates": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /apps/web-e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.e2e.json" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /apps/web/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. 18 | -------------------------------------------------------------------------------- /apps/web/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "extends": [ 8 | "plugin:@nrwl/nx/angular", 9 | "plugin:@angular-eslint/template/process-inline-templates" 10 | ], 11 | "rules": { 12 | "@angular-eslint/directive-selector": [ 13 | "error", 14 | { 15 | "type": "attribute", 16 | "prefix": "jianshu", 17 | "style": "camelCase" 18 | } 19 | ], 20 | "@angular-eslint/component-selector": [ 21 | "error", 22 | { 23 | "type": "element", 24 | "prefix": "jianshu", 25 | "style": "kebab-case" 26 | } 27 | ] 28 | } 29 | }, 30 | { 31 | "files": ["*.html"], 32 | "extends": ["plugin:@nrwl/nx/angular-template"], 33 | "rules": {} 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /apps/web/.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.stylelintrc.json"] 3 | } 4 | -------------------------------------------------------------------------------- /apps/web/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'web', 3 | preset: '../../jest.preset.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsconfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | }, 10 | }, 11 | coverageDirectory: '../../coverage/apps/web', 12 | transform: { 13 | '^.+\\.(ts|js|html)$': 'jest-preset-angular', 14 | }, 15 | snapshotSerializers: [ 16 | 'jest-preset-angular/build/serializers/no-ng-attributes', 17 | 'jest-preset-angular/build/serializers/ng-snapshot', 18 | 'jest-preset-angular/build/serializers/html-comment', 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /apps/web/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectType": "application", 3 | "root": "apps/web", 4 | "sourceRoot": "apps/web/src", 5 | "prefix": "jianshu", 6 | "targets": { 7 | "build": { 8 | "executor": "@angular-devkit/build-angular:browser", 9 | "outputs": [ 10 | "{options.outputPath}" 11 | ], 12 | "options": { 13 | "outputPath": "dist/apps/web", 14 | "index": "apps/web/src/index.html", 15 | "main": "apps/web/src/main.ts", 16 | "polyfills": "apps/web/src/polyfills.ts", 17 | "tsConfig": "apps/web/tsconfig.app.json", 18 | "inlineStyleLanguage": "scss", 19 | "assets": [ 20 | "apps/web/src/favicon.ico", 21 | "apps/web/src/assets" 22 | ], 23 | "styles": [ 24 | "apps/web/src/styles.scss" 25 | ], 26 | "scripts": [] 27 | }, 28 | "configurations": { 29 | "production": { 30 | "budgets": [ 31 | { 32 | "type": "initial", 33 | "maximumWarning": "500kb", 34 | "maximumError": "1mb" 35 | }, 36 | { 37 | "type": "anyComponentStyle", 38 | "maximumWarning": "2kb", 39 | "maximumError": "4kb" 40 | } 41 | ], 42 | "fileReplacements": [ 43 | { 44 | "replace": "apps/web/src/environments/environment.ts", 45 | "with": "apps/web/src/environments/environment.prod.ts" 46 | } 47 | ], 48 | "outputHashing": "all" 49 | }, 50 | "development": { 51 | "buildOptimizer": false, 52 | "optimization": false, 53 | "vendorChunk": true, 54 | "extractLicenses": false, 55 | "sourceMap": true, 56 | "namedChunks": true 57 | } 58 | }, 59 | "defaultConfiguration": "production" 60 | }, 61 | "serve": { 62 | "executor": "@angular-devkit/build-angular:dev-server", 63 | "configurations": { 64 | "production": { 65 | "browserTarget": "web:build:production" 66 | }, 67 | "development": { 68 | "browserTarget": "web:build:development" 69 | } 70 | }, 71 | "defaultConfiguration": "development", 72 | "options": { 73 | "browserTarget": "web:build", 74 | "proxyConfig": "apps/web/proxy.conf.json" 75 | } 76 | }, 77 | "extract-i18n": { 78 | "executor": "@angular-devkit/build-angular:extract-i18n", 79 | "options": { 80 | "browserTarget": "web:build" 81 | } 82 | }, 83 | "lint": { 84 | "executor": "@nrwl/linter:eslint", 85 | "options": { 86 | "lintFilePatterns": [ 87 | "apps/web/src/**/*.ts", 88 | "apps/web/src/**/*.html" 89 | ] 90 | } 91 | }, 92 | "test": { 93 | "executor": "@nrwl/jest:jest", 94 | "outputs": [ 95 | "coverage/apps/web" 96 | ], 97 | "options": { 98 | "jestConfig": "apps/web/jest.config.js", 99 | "passWithNoTests": true 100 | } 101 | }, 102 | "stylelint": { 103 | "executor": "nx-stylelint:lint", 104 | "options": { 105 | "config": "apps/web/.stylelintrc.json", 106 | "lintFilePatterns": [ 107 | "apps/web/**/*.css", 108 | "apps/web/**/*.scss" 109 | ], 110 | "format": "json" 111 | } 112 | } 113 | }, 114 | "tags": [] 115 | } -------------------------------------------------------------------------------- /apps/web/proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api": { 3 | "target": "http://localhost:3333", 4 | "secure": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /apps/web/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Welcome to web!

3 | Nx - Smart, Extensible Build Framework 8 |
9 |
Message: {{ hello$ | async | json }}
10 | -------------------------------------------------------------------------------- /apps/web/src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Remove template code below 3 | */ 4 | :host { 5 | display: block; 6 | font-family: sans-serif; 7 | min-width: 300px; 8 | max-width: 600px; 9 | margin: 50px auto; 10 | } 11 | 12 | .gutter-left { 13 | margin-left: 9px; 14 | } 15 | 16 | .col-span-2 { 17 | grid-column: span 2; 18 | } 19 | 20 | .flex { 21 | display: flex; 22 | align-items: center; 23 | justify-content: center; 24 | } 25 | 26 | header { 27 | background-color: #143055; 28 | color: white; 29 | padding: 5px; 30 | border-radius: 3px; 31 | } 32 | 33 | main { 34 | padding: 0 36px; 35 | } 36 | 37 | p { 38 | text-align: center; 39 | } 40 | 41 | h1 { 42 | text-align: center; 43 | margin-left: 18px; 44 | font-size: 24px; 45 | } 46 | 47 | h2 { 48 | text-align: center; 49 | font-size: 20px; 50 | margin: 40px 0 10px 0; 51 | } 52 | 53 | .resources { 54 | text-align: center; 55 | list-style: none; 56 | padding: 0; 57 | display: grid; 58 | grid-gap: 9px; 59 | grid-template-columns: 1fr 1fr; 60 | } 61 | 62 | .resource { 63 | color: #0094ba; 64 | height: 36px; 65 | background-color: rgba(0, 0, 0, 0); 66 | border: 1px solid rgba(0, 0, 0, 0.12); 67 | border-radius: 4px; 68 | padding: 3px 9px; 69 | text-decoration: none; 70 | } 71 | 72 | .resource:hover { 73 | background-color: rgba(68, 138, 255, 0.04); 74 | } 75 | 76 | pre { 77 | padding: 9px; 78 | border-radius: 4px; 79 | background-color: black; 80 | color: #eee; 81 | } 82 | 83 | details { 84 | border-radius: 4px; 85 | color: #333; 86 | background-color: rgba(0, 0, 0, 0); 87 | border: 1px solid rgba(0, 0, 0, 0.12); 88 | padding: 3px 9px; 89 | margin-bottom: 9px; 90 | } 91 | 92 | summary { 93 | cursor: pointer; 94 | outline: none; 95 | height: 36px; 96 | line-height: 36px; 97 | } 98 | 99 | .github-star-container { 100 | margin-top: 12px; 101 | line-height: 20px; 102 | } 103 | 104 | .github-star-container a { 105 | display: flex; 106 | align-items: center; 107 | text-decoration: none; 108 | color: #333; 109 | } 110 | 111 | .github-star-badge { 112 | color: #24292e; 113 | display: flex; 114 | align-items: center; 115 | font-size: 12px; 116 | padding: 3px 10px; 117 | border: 1px solid rgba(27, 31, 35, 0.2); 118 | border-radius: 3px; 119 | background-image: linear-gradient(-180deg, #fafbfc, #eff3f6 90%); 120 | margin-left: 4px; 121 | font-weight: 600; 122 | } 123 | 124 | .github-star-badge:hover { 125 | background-image: linear-gradient(-180deg, #f0f3f6, #e6ebf1 90%); 126 | border-color: rgba(27, 31, 35, 0.35); 127 | background-position: -0.5em; 128 | } 129 | .github-star-badge .material-icons { 130 | height: 16px; 131 | width: 16px; 132 | margin-right: 4px; 133 | } 134 | -------------------------------------------------------------------------------- /apps/web/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { TestBed, async } from '@angular/core/testing'; 3 | import { HttpClientModule } from '@angular/common/http'; 4 | import { AppComponent } from './app.component'; 5 | 6 | describe('AppComponent', () => { 7 | beforeEach(async(() => { 8 | TestBed.configureTestingModule({ 9 | declarations: [AppComponent], 10 | imports: [HttpClientModule], 11 | }).compileComponents(); 12 | })); 13 | 14 | it('should create the app', () => { 15 | const fixture = TestBed.createComponent(AppComponent); 16 | const app = fixture.debugElement.componentInstance; 17 | expect(app).toBeTruthy(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /apps/web/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Message } from '@jianshu/api-interfaces'; 4 | 5 | @Component({ 6 | selector: 'jianshu-root', 7 | templateUrl: './app.component.html', 8 | styleUrls: ['./app.component.scss'], 9 | }) 10 | export class AppComponent { 11 | hello$ = this.http.get('/api/hello'); 12 | constructor(private http: HttpClient) {} 13 | } 14 | -------------------------------------------------------------------------------- /apps/web/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | 4 | import { AppComponent } from './app.component'; 5 | import { HttpClientModule } from '@angular/common/http'; 6 | 7 | @NgModule({ 8 | declarations: [AppComponent], 9 | imports: [BrowserModule, HttpClientModule], 10 | providers: [], 11 | bootstrap: [AppComponent], 12 | }) 13 | export class AppModule {} 14 | -------------------------------------------------------------------------------- /apps/web/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiayisheji/jianshu/f3a73dda9e50c406fb8d89225c334ecb0aa2334a/apps/web/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/web/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /apps/web/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /apps/web/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiayisheji/jianshu/f3a73dda9e50c406fb8d89225c334ecb0aa2334a/apps/web/src/favicon.ico -------------------------------------------------------------------------------- /apps/web/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Web 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /apps/web/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic() 12 | .bootstrapModule(AppModule) 13 | .catch((err) => console.error(err)); 14 | -------------------------------------------------------------------------------- /apps/web/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * IE11 requires the following for NgClass support on SVG elements 23 | */ 24 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 25 | 26 | /** 27 | * Web Animations `@angular/platform-browser/animations` 28 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 29 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 30 | */ 31 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 32 | 33 | /** 34 | * By default, zone.js will patch all possible macroTask and DomEvents 35 | * user can disable parts of macroTask/DomEvents patch by setting following flags 36 | * because those flags need to be set before `zone.js` being loaded, and webpack 37 | * will put import in the top of bundle, so user need to create a separate file 38 | * in this directory (for example: zone-flags.ts), and put the following flags 39 | * into that file, and then add the following code before importing zone.js. 40 | * import './zone-flags'; 41 | * 42 | * The flags allowed in zone-flags.ts are listed here. 43 | * 44 | * The following flags will work for all browsers. 45 | * 46 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 47 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 48 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 49 | * 50 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 51 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 52 | * 53 | * (window as any).__Zone_enable_cross_context_check = true; 54 | * 55 | */ 56 | 57 | /*************************************************************************************************** 58 | * Zone JS is required by default for Angular itself. 59 | */ 60 | import 'zone.js'; // Included with Angular CLI. 61 | 62 | /*************************************************************************************************** 63 | * APPLICATION IMPORTS 64 | */ 65 | -------------------------------------------------------------------------------- /apps/web/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /apps/web/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular/setup-jest'; 2 | -------------------------------------------------------------------------------- /apps/web/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": [] 6 | }, 7 | "files": ["src/main.ts", "src/polyfills.ts"], 8 | "include": ["src/**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/web/tsconfig.editor.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["**/*.ts"], 4 | "compilerOptions": { 5 | "types": ["jest", "node"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.app.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | }, 12 | { 13 | "path": "./tsconfig.editor.json" 14 | } 15 | ], 16 | "compilerOptions": { 17 | "forceConsistentCasingInFileNames": true, 18 | "strict": true, 19 | "noImplicitReturns": true, 20 | "noFallthroughCasesInSwitch": true 21 | }, 22 | "angularCompilerOptions": { 23 | "strictInjectionParameters": true, 24 | "strictInputAccessModifiers": true, 25 | "strictTemplates": true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/web/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "files": ["src/test-setup.ts"], 9 | "include": ["**/*.spec.ts", "**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /commit.scope.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * scope-enum 提交scope的枚举 3 | */ 4 | module.exports = [ 5 | { 6 | scope: 'api', 7 | name: '网站服务端', 8 | readme: '对应apps/api', 9 | remark: '', 10 | }, 11 | { 12 | scope: 'web', 13 | name: '网站前端页面', 14 | readme: '对应apps/web', 15 | remark: '', 16 | }, 17 | { 18 | scope: 'docs', 19 | name: '文档', 20 | readme: '对应docs', 21 | remark: '', 22 | }, 23 | { 24 | scope: 'libs', 25 | name: '库', 26 | readme: '对应libs', 27 | remark: '', 28 | }, 29 | { 30 | scope: 'deploy', 31 | name: '构建部署', 32 | readme: '对应.deploy', 33 | remark: '', 34 | }, 35 | { 36 | scope: 'scripts', 37 | name: '构建脚本', 38 | readme: '对应scripts', 39 | remark: '', 40 | }, 41 | { 42 | scope: 'tools', 43 | name: '工具箱', 44 | readme: '对应tools', 45 | remark: '', 46 | }, 47 | { 48 | scope: 'deps', 49 | name: '依赖管理', 50 | readme: 'package.json', 51 | remark: 'build(deps): bump nx from 0.2.3 to 0.3.1', 52 | }, 53 | ]; 54 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | const scopeEnum = require('./commit.scope.config'); 2 | 3 | module.exports = { 4 | extends: ['@commitlint/config-conventional'], 5 | rules: { 6 | 'header-max-length': [2, 'always', 100], 7 | 'scope-enum': [2, 'always', scopeEnum.map(item => item.scope)], 8 | /** 9 | * type-enum 提交的类型枚举 10 | * build: 主要目的是修改项目构建系统(例如 glup, webpack, rollup,npm的配置等.xxx) 的提交 11 | * chore: 不属于以上类型的其他类型 12 | * ci: 主要目的是修改项目继续集成流程(例如 Travis, Jenkins, GitLab CI, Circle等) 的提交 13 | * docs: 文档更新 14 | * feat: 新增功能 15 | * fix: bug 修复 16 | * merge: 分支合并 Merge branch ? of ? 17 | * perf: 性能, 体验优化 18 | * refactor: 重构代码(既没有新增功能, 也没有修复 bug) 19 | * release: 发布版本 20 | * revert: 回滚某个更早之前的提交 21 | * style: 不影响程序逻辑的代码修改(修改空白字符, 格式缩进, 补全缺失的分号等, 没有改变代码逻辑) 22 | * test: 新增测试用例或是更新现有测试 23 | */ 24 | 'type-enum': [ 25 | 2, 26 | 'always', 27 | ['build', 'chore', 'ci', 'docs', 'feat', 'fix', 'merge', 'perf', 'refactor', 'release', 'revert', 'style', 'test'], 28 | ], 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /decorate-angular-cli.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file decorates the Angular CLI with the Nx CLI to enable features such as computation caching 3 | * and faster execution of tasks. 4 | * 5 | * It does this by: 6 | * 7 | * - Patching the Angular CLI to warn you in case you accidentally use the undecorated ng command. 8 | * - Symlinking the ng to nx command, so all commands run through the Nx CLI 9 | * - Updating the package.json postinstall script to give you control over this script 10 | * 11 | * The Nx CLI decorates the Angular CLI, so the Nx CLI is fully compatible with it. 12 | * Every command you run should work the same when using the Nx CLI, except faster. 13 | * 14 | * Because of symlinking you can still type `ng build/test/lint` in the terminal. The ng command, in this case, 15 | * will point to nx, which will perform optimizations before invoking ng. So the Angular CLI is always invoked. 16 | * The Nx CLI simply does some optimizations before invoking the Angular CLI. 17 | * 18 | * To opt out of this patch: 19 | * - Replace occurrences of nx with ng in your package.json 20 | * - Remove the script from your postinstall script in your package.json 21 | * - Delete and reinstall your node_modules 22 | */ 23 | 24 | const fs = require('fs'); 25 | const os = require('os'); 26 | const cp = require('child_process'); 27 | const isWindows = os.platform() === 'win32'; 28 | let output; 29 | try { 30 | output = require('@nrwl/workspace').output; 31 | } catch (e) { 32 | console.warn('Angular CLI could not be decorated to enable computation caching. Please ensure @nrwl/workspace is installed.'); 33 | process.exit(0); 34 | } 35 | 36 | /** 37 | * Symlink of ng to nx, so you can keep using `ng build/test/lint` and still 38 | * invoke the Nx CLI and get the benefits of computation caching. 39 | */ 40 | function symlinkNgCLItoNxCLI() { 41 | try { 42 | const ngPath = './node_modules/.bin/ng'; 43 | const nxPath = './node_modules/.bin/nx'; 44 | if (isWindows) { 45 | /** 46 | * This is the most reliable way to create symlink-like behavior on Windows. 47 | * Such that it works in all shells and works with npx. 48 | */ 49 | ['', '.cmd', '.ps1'].forEach(ext => { 50 | if (fs.existsSync(nxPath + ext)) fs.writeFileSync(ngPath + ext, fs.readFileSync(nxPath + ext)); 51 | }); 52 | } else { 53 | // If unix-based, symlink 54 | cp.execSync(`ln -sf ./nx ${ngPath}`); 55 | } 56 | } 57 | catch(e) { 58 | output.error({ title: 'Unable to create a symlink from the Angular CLI to the Nx CLI:' + e.message }); 59 | throw e; 60 | } 61 | } 62 | 63 | try { 64 | symlinkNgCLItoNxCLI(); 65 | require('@nrwl/cli/lib/decorate-cli').decorateCli(); 66 | output.log({ title: 'Angular CLI has been decorated to enable computation caching.' }); 67 | } catch(e) { 68 | output.error({ title: 'Decoration of the Angular CLI did not complete successfully' }); 69 | } 70 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Run `docker-compose build` to build the images 2 | # Run `docker-compose up` to run the containers -d 后台启动 3 | # Run `docker-compose stop` 按依赖关系顺序停止服务 4 | # Run `docker-compose down` 按依赖关系顺序停止服务 5 | 6 | version: '3.8' 7 | services: 8 | db: 9 | image: mongo 10 | ports: 11 | - '27017:27017' 12 | command: [--auth] 13 | environment: 14 | MONGO_INITDB_ROOT_USERNAME: root 15 | MONGO_INITDB_ROOT_PASSWORD: root 16 | MONGO_INITDB_DATABASE: ${MONGO_DATABASE} 17 | MONGO_USERNAME: ${MONGO_USERNAME} 18 | MONGO_PASSWORD: ${MONGO_PASSWORD} 19 | volumes: 20 | - ./.docker/mongo_data:/data/db 21 | - ./.deploy/docker/mongo-init.sh:/docker-entrypoint-initdb.d/mongo-init.sh 22 | cache: 23 | image: redis 24 | volumes: 25 | - ./.docker/redis_data:/data 26 | ports: 27 | - '6379:6379' 28 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Nx + Nest.js + Angular 仿造一个全栈简书网站 2 | 3 | 本项目重点是仿造一个[简书](https://www.jianshu.com)网站,采用前后分离开发模式,前端使用最新版 [Angular](https://github.com/angular/angular),后端使用最新版 [Nestjs](https://github.com/nestjs/nest),使用开发 [Monorepos](https://trunkbaseddevelopment.com/monorepos/) 可扩展的开发工具 [Nx](https://github.com/nrwl/nx)。 4 | 5 | 项目使用 [Typescript](https://github.com/Microsoft/TypeScript) 作为主要编程语言,使用 [Rxjs](https://github.com/ReactiveX/rxjs) 配合 [Promise](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise)、[async](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/async_function)/[await](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/await) 异步操作。 6 | 7 | 项目使用 [Mongodb](https://www.mongodb.com) 作为主要数据库存储,使用 [Redis](https://redis.io) 作为缓存数据存储。 8 | 9 | 项目使用 [Passport](https://github.com/jaredhanson/passport) 作为主要身份认证,分别采用`local`、`jwt` 和第三方 `github` 认证策略。 10 | 11 | 项目使用 [Jest](https://github.com/facebook/jest) 作为单元测试,[Cypress](https://github.com/cypress-io/cypress) 作为 E2E 测试。 12 | 13 | 项目使用 [vs code](https://github.com/Microsoft/vscode) 作为主要编辑器开发。项目会推荐常用开发插件,如果在使用本项目时,请安装它们。 14 | 15 | 目录: 16 | 17 | 1. [Nx 安装环境](post/1.1-Nx安装环境.md) 18 | 2. [项目工程化配置](post/1.2-项目工程化配置.md) 19 | 3. [Nest 体系结构](post/1.3-Nest体系结构.md) 20 | 4. [使用 Mongoose 建立 MongoDB 数据库](post/使用Mongoose建立MongoDB数据库.md) 21 | 5. [使用 bcrypt、Passport、JWT 和 cookie 对用户进行身份验证](post/1.4-使用bcrypt-Passport-JWT和cookie对用户进行身份验证.md) 22 | 6. [使用 JWT 实现刷新令牌](post/1.5-使用JWT实现刷新令牌.md) 23 | -------------------------------------------------------------------------------- /docs/images/commitizen.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiayisheji/jianshu/f3a73dda9e50c406fb8d89225c334ecb0aa2334a/docs/images/commitizen.webp -------------------------------------------------------------------------------- /docs/images/request-lifecycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiayisheji/jianshu/f3a73dda9e50c406fb8d89225c334ecb0aa2334a/docs/images/request-lifecycle.png -------------------------------------------------------------------------------- /docs/post/1.1-Nx安装环境.md: -------------------------------------------------------------------------------- 1 | # Nx 安装环境 2 | 3 | 古人云:工欲善其事,必先利其器,这里给大家准备`windows`玩家必备的软件,[传送门](https://pan.baidu.com/s/1kQwJx4WpgMz1Lti0Y9moNg),口令:【9ofr】。 4 | 5 | 接下来需要很多命令行操作,我们需要一个强大的命令行工具,`windows` 已经推出了 [windows terminal](https://github.com/Microsoft/Terminal),不过现在命令不是很完善,这里推荐 [cmder](cmder.net)。关于 [如何安装和使用 Cmder](https://cn.bing.com/search?q=cmder&qs=n&form=QBRE&sp=-1&pq=cmder&sc=8-5&sk=&cvid=DA71E8C8A5914CAB9236A8C0F193C787)。 6 | 7 | 有了命令行工具需要一个不错的编辑器,这里推荐 [vs code](https://code.visualstudio.com/),点击下载安装。 8 | 9 | 推荐一些我常用的必备插件(ps: 插件不要太多,有些插件比较坑,烧内存和 cpu,注意辨别)和配置: 10 | 11 | 项目根目录下会有 `.vscode` 文件夹 12 | 13 | ```text 14 | .vscode/extensions.json // 必备插件 15 | .vscode/settings.json // 项目配置 16 | ``` 17 | 18 | ## 使用`nvm` 安装 `nodejs` 19 | 20 | 一般开发`nodejs`都需要安装`nodejs`环境,随着`nodejs`版本不断迭代,有种跟不上节奏,不同版本会有一些差异化,如果你现在开发环境是早期 nodejs 版本,那么你开心更新版本,可能就会哭了,会报各种懵逼的错误让你欲罢不能。那怎么办了,就有一个 `node` 版本管理的工具诞生 --- [nvm](https://github.com/creationix/nvm)。(ps:这个是 windows 用户用不了,必备软件里面 `nvm-setup` 就是 windows 用户专用)。关于 [如何安装和使用 nvm-windows](https://cn.bing.com/search?q=nvm-windows&qs=n&form=QBRE&sp=-1&pq=nvm-windows&sc=8-11&sk=&cvid=8F45D5194FCC45F098E949612179D489)。 21 | 22 | > **注意**:如果电脑已经安装过 `nodejs`,需要先卸载,请记住它的版本号,再安装 `nvm-windows`。 23 | 24 | 现在你应该安装完成了 `nvm-windows`,那么就可以开始安装 `nodejs` 版本: 25 | 26 | 例如:本项目需要安装 `v14.15` 以上版本 27 | 28 | ```bash 29 | nvm install v14.15 30 | ``` 31 | 32 | 安装过程可能会出现一个错误,下载`npm`错误: 33 | 34 | ```text 35 | Please visit https://github.com/npm/npm/releases/tag/v[npm版本] to download npm. 36 | ``` 37 | 38 | 要你去这个地址下载,其实是错误,这个指向是`npm-cli`v[npm 版本],我们需要手动去下载它 39 | 40 | ```text 41 | https://codeload.github.com/npm/cli/zip/v[npm版本] 42 | ``` 43 | 44 | 把它拷贝到你的 nvm 目录下,对应版本`nodejs`版本,解压到`node_modules`里,把文件夹重命名为`npm`。下载必备的软件里面的`npm`文件夹,把里面内容拷贝到对应版本`nodejs`版本即可。 45 | 46 | 另外一种就是借助淘宝源: 47 | 48 | 安装目录 `\nvm\settings.txt` 在这个文件后面跟上这 2 句 49 | 50 | ```text 51 | node_mirror: https://npm.taobao.org/mirrors/node/ 52 | npm_mirror: https://npm.taobao.org/mirrors/npm/ 53 | ``` 54 | 55 | 然后`nvm install`,按提示操作即可 56 | 57 | 切换当前版本并检查是否靠谱: 58 | 59 | ```bash 60 | nvm use v14.15 61 | node -v // 14.15.0 62 | npm -v // 6.14.8 63 | ``` 64 | 65 | **注意**: `nvm-windows`一定要安装好,检查靠谱,只会出现`npm -v`会报错,如果你按我的使用方式就不会出现问题。如果你在出问题了,可以联系我帮你看看。 66 | 67 | ## 使用`Nx` 构建项目 68 | 69 | ### 为什么要使用 `Nx` 70 | 71 | `Nx` 是一组针对 `Monorepos` 的可扩展开发工具,它可以帮助你像 `Google`、`Facebook` 和 `Microsoft` 那样进行开发。它对许多前端和后端技术提供一流的支持,因此其文档具有多种风格。 72 | 73 | #### 使用现代工具 74 | 75 | 使用 `Nx`,你可以将 `TypeScript`,`Cypress`,`Jest`,`Prettier`,`Storybook` 添加到你的开发工作流程中。 `Nx` 设置了这些工具,并允许你无缝使用它们。 这些创新工具具有很多优势,可以帮助你更好,更省心地工作。 76 | 77 | #### 构建全栈应用程序 78 | 79 | 使用`Nx`,你可以使用现代框架构建全栈应用程序。你可以在前端和后端之间共享代码。 你可以使用相同的 `build`/`test`/`serve` 命令来增强整个开发体验。 80 | 81 | #### 学习 Google,Facebook 和 Microsoft 一样开发实践 82 | 83 | 使用`Nx`,你可以整体开发多个全栈应用程序,并在同一工作区中共享它们之间的代码。 `Nx` 提供了高级工具,可帮助你扩展企业发展。 `Nx` 还有助于实施组织的标准和社区最佳实践。 84 | 85 | #### 不同技术栈组合,统一构建工具 86 | 87 | 使用`Nx`,你可以使用 `Angular`、`React`、`Web Components`、`Nestjs`、`Express`、`Nodejs` 技术做自由组合,构建你的项目技术栈。不用担心构建工具问题,`Nx` 帮你一键搞定。 88 | 89 | ### Monorepo vs Multrepo 90 | 91 | #### 什么是 `Monorepo` 92 | 93 | > 是将所有的模块统一的放在一个主干分支之中管理。不进行分库存储,当有特定的需要的时候进行分支,但是问题修改还是在主干上操作,并有专门人员合并到分支内容上,在特定需求完结的时候,分支也将会被废弃。如果想了解更多,[传送门](https://trunkbaseddevelopment.com/monorepos/)。 94 | > 优点: 1.统一的规范,语言,和 IDE 带来的是结构的完整稳定。 2.按照统一的标准进行开发,是哦程序稳定性更良好,更易解读。 3.标准化的开发流程,规避很多不必要的冲突与错误。 95 | > 缺点: 1.项目过大,难以管理。 2.由于统一的标准,不同的团队可能需要对于新的规范和 IDE 进行了解学习,需要时间。 3.修改和开发可能变得繁琐,减慢效率。 96 | 97 | #### 什么是 `Multrepo` 98 | 99 | > 将项目分化成为多个模块,并针对每一个模块单独的开辟一个 `repository` 来进行管理。 100 | > 优点: 1.团队自己的 IDE,语言,以及工作步调。 2.模块更小,更易维护。 3.开发效率更高。 101 | > 缺点: 1.合并困难,每个模块规范不同,同步和编译时这些问题将会集体爆发。 2.难以保证稳定性。 3.由于可能存在不同语言开发,或者不同 IDE 的情况,新建或者更新构建变得困难。 102 | 103 | 对于其他语言不熟,我们这里拿前端生态圈来说事,前端目前最火生态圈就是 `npm`,基本上成熟项目都是通过 `package.json` 来管理依赖,使用 `node_modules` 来存放项目依赖文件。那么 `node_modules` 就是一个 `Black hole`。随便一个项目就有几百兆依赖,你压根也不知道都安装了什么鬼东西。可怕,哈哈。 104 | 105 | 我有个 100G 的盘符,这个盘符下面我有个 `github` 文件夹,它是专门存放我从 `github` 拉取的一些别人和我自己开源项目地方,大概有 2-30 个左右,我如果想运行每个项目,那么都需要安装 `node_modules` ,加上公司项目也在这个盘符里,我现在只剩下 10g 左右了。 106 | 107 | 其实这种模式就是叫 `multrepo`,最大优点是安全,最大缺点依赖管理难于统一。 108 | 109 | `monorepo` 它是一种管理 `organisation` 代码的方式,在这种方式下会摒弃原先一个 `module` 一个 `repo` 的方式,取而代之的是把所有的 `modules` 都放在一个 `repo` 内来管理。 110 | 111 | 目前诸如 `Angular`, `Babel`, `Ember`, `Meteor`, `React`, `Jest`, `Vue3` 等等都采用了 `Monorepo` 这种方式来进行源码的管理。 112 | 113 | 其中 [Lerna](https://github.com/lerna/lerna) 它是基于 `monorepo` 理念在工具端的实现。 114 | 115 | > `Nx`也是推崇`monorepo`管理模式。 116 | 117 | ## 安装 Nx 118 | 119 | > `Nx`最开始是一个`Angular-cli`的扩展工具,学习它成本不高,只需要你按照下列方式安装就行了 120 | 121 | **重点提示**:`Nx` 不能解决 `Angular-cli` 打包编译慢的问题。可以通过 `ng build library` 来拆分应用。 122 | 123 | `Nx`的亮点: 124 | 125 | - 支持 `React` 构建 126 | - 支持 `React` 和 `Angular` 共存一个项目中 127 | - 支持选择测试工具,可以选择 `jest` 和 `cypress` 128 | - 支持前后端 `node` 项目一起构建 129 | - 构建多个`apps` 130 | - 构建多个`libraries` 131 | 132 | `Nx` 鼓励开发者在使用自定义 `library`,来构建项目,让你的 `App` 结构更清晰。 133 | 134 | 本项目使用 `Angular`、`Nestjs` 全栈开发。 135 | 136 | 你可能好奇,它们都有自己的 `CLI`,为什么不使用呢? 137 | 138 | - Angular-CLI:只支持 `Angular` 项目,支持 `monorepo` 模式,构建多个 `apps`,构建多个 `libraries` 139 | - Nest-CLI:支持 `Nestjs` 和 `Angular` 项目,支持 `monorepo` 模式,构建多个 `apps`,构建多个 `libraries` 140 | 141 | 它们都只支持一个配置文件,随着项目增大,配置 `.json` 体积庞大,维护管理困难。 142 | 143 | `Nx` 解决了这个一问题,`apps` 和 `libraries` 有一个独立 `project.json` 项目配置 ,`workspace.json` 配置文件,只做项目和 `Nx-CLI` 关系映射。 144 | 145 | > 目前版本 `nx@12.5.7` 默认不支持 `workspace.json`,需要手动迁移,后面会介绍如何快速迁移。 146 | 147 | `Nx` 创建工程叫工作区,可以使用以下三种方式安装: 148 | 149 | npx 150 | 151 | ```bash 152 | npx create-nx-workspace@latest 153 | ``` 154 | 155 | npm init 156 | 157 | ```bash 158 | npm init nx-workspace 159 | ``` 160 | 161 | yarn create 162 | 163 | ```bash 164 | yarn create nx-workspace 165 | ``` 166 | 167 | 我一般使用 `npx`。 168 | 169 | 运行上面安装命令行以后,就等着出现`npx: 166 安装成功,用时 42.249 秒` 170 | 171 | 填写工作区的名字: 172 | 173 | - Workspace name:`jianshu` 174 | 175 | 然后就会出现有奖知识问答环节:选择 `angular-nest` 176 | 177 | - What to create in the new workspace (Use arrow keys): 178 | 179 | - empty [an empty workspace] 180 | - web components [a workspace with a single app built using web components] 181 | - angular [a workspace with a single Angular application] 182 | - angular-nest [a workspace with a full stack application (Angular + Nest)] 183 | - react [a workspace with a single React application] 184 | - react-express [a workspace with a full stack application (React + Express)] 185 | - next.js [a workspace with a single Next.js application] 186 | 187 | > 随着不断迭代,以后选择创建工作区会更多。 188 | 189 | - Application name:web (这里是第一次创建 angular 项目的名字,) 190 | 191 | - Default stylesheet format:选择 `SASS` 192 | 193 | - CSS 194 | - SASS(.scss) [ ] 195 | - Stylus(.styl)[ ] 196 | - LESS [ ] 197 | 198 | 选择之后,出现 `Creating a sandbox with Nx...`,等待安装 `node_modules`,安装完成以后打开 `vs code` 就好提示安装插件,安装就好了,其中一个叫 `angular-console`,这是 `nx` 的 `vs code` 插件,后面生成文件,生成 lib,等全靠它。 199 | 200 | ### 目录结构 201 | 202 | > 使用脚本命令生成 `cmd命令`:`tree /f > tree.txt` 203 | 204 | ```tree 205 | root 206 | │ .editorconfig 207 | │ .gitignore 208 | │ .prettierignore 209 | │ .prettierrc 210 | │ angular.json 整个工作区应用和库配置 211 | │ jest.config.js 212 | │ LICENSE 213 | │ nx.json nx工作区应用和库关系配置 214 | │ package-lock.json 215 | │ package.json node模块包管理 216 | │ README.md 217 | │ tsconfig.json 218 | │ tslint.json 219 | | 220 | ├─.vscode vscode 配置 221 | │ extensions.json 222 | | 223 | ├─apps 应用 224 | │ ├─api 服务端 225 | │ ├─web 客户端 226 | │ └─web-e2e 客户端E2E测试 227 | | 228 | ├─libs 库 229 | | 230 | ├─node_modules node模块包 231 | | 232 | └─tools 自定义schematics工具包 233 | ``` 234 | 235 | ### 迁移 workspace.json 236 | 237 | 默认整个工作区配置 `angular.json`,把这个文件重命名为 `workspace.json`。 238 | 239 | ```json 240 | { 241 | "version": 2, 242 | ... 243 | } 244 | ``` 245 | 246 | 将文件里的 `version` 改为 `2`。然后运行命令: 247 | 248 | ```bash 249 | npm run nx generate @nrwl/workspace:convert-to-nx-project --all --no-interactive 250 | ``` 251 | 252 | ### 运行项目 253 | 254 | 使用 nx console 插件: 255 | 256 | 打开插件: 257 | 258 | 分 3 块: 259 | 260 | - schematics:项目生成器和运行命令 261 | - nx commands:nx 命令 262 | - projects:项目 263 | 264 | 运行项目可以使用 `schematics` 里对应的 `run` 或 `serve`,也可以使用 `projects` 中,找到对应的项目,鼠标移动到 `serve` 上,点击右边箭头即可。 265 | 266 | > 随着 `vs code` 不断升级,现在可以同时运行多个项目。 267 | 268 | ## 结语 269 | 270 | 在本文中,我们已经学习了使用 `Nx` 快速构建全栈工作区项目及迁移工作区和运行项目。 `schematics` 是可以帮助我们提升工作效率的工具。`NestJS` 和 `Angular` 有很多内置的 `schematics` ,可以随时使用。在本系列的后续部分中,我们将进一步研究 `schematics` ,请继续关注。 271 | -------------------------------------------------------------------------------- /docs/post/1.2-项目工程化配置.md: -------------------------------------------------------------------------------- 1 | # 项目工程化配置 2 | 3 | 上一篇我们已经工欲善其事,必先利其器,解决构建工具以及开发工具。 4 | 5 | 古语云:无规矩不成方圆。 6 | 7 | ## 团队编码规范约束 8 | 9 | 一千个程序员,就有一千种代码风格。 10 | 11 | 那什么是代码风格呢?从小的来说,有的开发喜欢以分号结尾,有的不喜欢带分号。有的喜欢使用空格缩进,有的喜欢使用 `Tab` 键。有的喜欢缩进空两个空格,有的喜欢四个空格。除了这些,还有一些关于代码的优化,如避免声明未使用,避免冗余的代码逻辑等。 12 | 13 | 如果团队项目代码风格混乱,对于接手项目的人,只有苦不堪言。 14 | 15 | 各大公司都有自己的团队规范。 16 | 17 | 我一般构建完项目,就开始准备团队规范和项目配置。 18 | 19 | 有时候你的错误是真正的错误。有时它只是草率、不一致或不清晰的编码风格。其中一些一开始可能看起来微不足道,但随着代码库的增长和老化,随着越来越多的人涉足其中并做一些丑陋的事情,它们明显变得重要起来。 20 | 21 | 统一编码规范不仅可以大幅提高代码可读性,甚至会提高代码质量。当我们设计了一套关于编码规范的规则时,需要工具去辅助检测,这就是 `Lint`。 22 | 23 | ## ESLint 与约束 24 | 25 | 随着 `tslint` 弃用,`eslint` 已经算统一 `js`、`ts` 和 `j(t)sx` 的 `lint` 规范。 26 | 27 | 规则集需要统一集中配置,`ESLint` 会默认读取配置文件 `.eslintrc(.json)` 来解析,而规则集在 `rules` 中进行配置。 28 | 29 | 本项目中 `@nrwl/nx` 对 `ESLint` 的 `rules` 进行了定制。有一个特殊的规则 `@nrwl/nx/enforce-module-boundaries`,专门来约束我们导入模块。 30 | 31 | Angular 也有根据 [Angular 风格指南](https://angular.cn/guide/styleguide) 专门定制的 `@angular-eslint` 库,来检查 `ts` 和 `html`。 32 | 33 | `ESLint` 规则很多,我们可以设计属于自己团队的一套编码规范。 34 | 35 | 很多优秀的团队,都根据最佳实践设定了特别优秀的编码规范,比如 `airbnb` 设定了一套约束特别强的规范。 36 | 37 | 推荐:[eslint-config-alloy](https://github.com/AlloyTeam/eslint-config-alloy) 38 | 39 | 一个良好的编码规范会带来解放强迫症的舒适感,但过于严格的代码风格有时也会使人烦躁。约束与自由的权衡,`ESLint` 在提供强有力约束时自然会牺牲一些开发上的便利性。 40 | 41 | 当不符合代码规范的第一时间,我们就要感知到它,及时反馈,快速纠正,不要等到编译时抛出 `Eslint`,编译很耗时。 42 | 43 | 在 `vs code` 里有 `eslint` 插件, 需要简单配置: 44 | 45 | ```json 46 | { 47 | "eslint.validate": ["javascript", "typescript"] 48 | } 49 | ``` 50 | 51 | 如果遇到不装 `eslint` 人怎么办,我们还需要提交代码适合进行效验。接下来 `Git` 部分讲解。 52 | 53 | ## Stylelint 与约束 54 | 55 | 这是关于 `CSS` 的 `lint` 检查。默认 `@nrwl/nx` 不提供。 56 | 57 | ```bash 58 | npm install -D stylelint stylelint-config-recommended-scss stylelint-config-standard stylelint-scss stylelint-config-prettier 59 | ``` 60 | 61 | > 本项目使用 `scss`,需要下载它对应插件。 62 | 63 | 在 `vs code` 里有 `stylelint`,需要简单配置: 64 | 65 | ```json 66 | { 67 | // stylelint 68 | "stylelint.syntax": "scss", 69 | "stylelint.validate": ["sass", "scss"], 70 | // 由于vscode自带css、less、scss的校验,为避免重复检查,可选择关闭vscode的校验 71 | "scss.validate": false 72 | } 73 | ``` 74 | 75 | > 配置只验证 `.scss` 文件 76 | 77 | 关于配置规则,前往[官网文档](https://stylelint.io/user-guide/rules/)。 78 | 79 | 有库和框架之后,大家对 `css` 书写就少了很多, 大部分代码被 UI 组件库代替. 80 | 81 | ### `css` 命名规范 82 | 83 | 关于 `css` 命名规范有很多: 84 | 85 | 例如: [BEM](http://getbem.com/) 86 | 87 | `stylelint` 默认几种常用规则: 88 | 89 | kebab case: 只允许在 `class` 和 `id` 中使用小写和中划线: 90 | 91 | ```json 92 | { 93 | "rules": { 94 | "selector-class-pattern": "^[a-z][a-z0-9-]+$", 95 | "selector-id-pattern": "^[a-z][a-z0-9-]+$" 96 | } 97 | } 98 | ``` 99 | 100 | snake case: 允许使用下划线: 101 | 102 | ```json 103 | { 104 | "rules": { 105 | "selector-class-pattern": "^[a-z][a-z0-9_-]+$", 106 | "selector-id-pattern": "^[a-z][a-z0-9_-]+$" 107 | } 108 | } 109 | ``` 110 | 111 | camel case: 驼峰大小写, 允许大写,只要不是第一个字符 112 | 113 | ```json 114 | { 115 | "rules": { 116 | "selector-class-pattern": "^[a-z][a-za-z0-9-]+$", 117 | "selector-id-pattern": "^[a-z][a-zA-Z0-9-]+$" 118 | } 119 | } 120 | ``` 121 | 122 | mixes case: 允许混合类型: kebab, snake, camel 123 | 124 | ```json 125 | { 126 | "rules": { 127 | "selector-class-pattern": "^[a-z][a-za-z0-9_-]+$", 128 | "selector-id-pattern": "^[a-z][a-zA-Z0-9_-]+$" 129 | } 130 | } 131 | ``` 132 | 133 | 我喜欢使用 `kebab case`,例如:`f-a`,`f-a-b` 134 | 135 | ### 禁用规则 136 | 137 | 因为 `CSS` 浏览器兼容性问题很多,一些浏览器私有前缀属性很多,特别是 `-webkit-`。 138 | 139 | 这样一来会出现很多 `error` 警告,所以我们需要禁用一些规则,配置里面没有那么灵活,需要手动来禁用某条规则。 140 | 141 | 禁用单行(当前一行生效) 142 | 143 | ```css 144 | a { 145 | padding-left: 20px !important; /* stylelint-disable-line declaration-no-important */ 146 | } 147 | ``` 148 | 149 | 禁用单行(下一行生效) 150 | 151 | ```css 152 | a { 153 | /* stylelint-disable-next-line declaration-no-important */ 154 | padding-left: 20px !important; 155 | } 156 | ``` 157 | 158 | 禁用全部(包含生效) 159 | 160 | ```css 161 | /* stylelint-disable */ 162 | a { 163 | padding-left: 20px !important; 164 | } 165 | /* stylelint-enable */ 166 | ``` 167 | 168 | 禁用全部(包含某条规则生效) 169 | 170 | ```css 171 | /* stylelint-disable declaration-no-important */ 172 | a { 173 | padding-left: 20px !important; 174 | } 175 | /* stylelint-enable */ 176 | ``` 177 | 178 | > 不要滥用禁用规则,慎用禁用规则,大面积使用,直接去掉 `stylelint` 更好 179 | 180 | ## htmlhint 与约束 181 | 182 | ```bash 183 | npm install -D htmlhint 184 | ``` 185 | 186 | `htmlhint` 相对于 `eslint` 和 `stylelint` 比较弱鸡。创建一个 `.htmlhintrc` 文件,把配置丢进去就好,总共 23 条规则,[官网文档](https://htmlhint.io/)。 187 | 188 | 运行检查 189 | 190 | ```bash 191 | # 执行全部html文件 192 | node_modules/.bin/htmlhint 193 | # 执行某个文件夹下html文件 194 | node_modules/.bin/htmlhint \"projects/web/**/*.html\" 195 | ``` 196 | 197 | > htmlhint 也可以自己写规则,不太推荐。 198 | 199 | ## 代码美化工具 200 | 201 | 目前有两款比较好用的代码美化工具:`prettier` 和 `Beautify`。 202 | 203 | 在 `vs code` 的插件市场里面可以看到它们两的差别,`prettier` 下载量比 `Beautify` 多,`prettier` 评分却比 `Beautify` 低 1 星。 204 | 205 | `@nrwl/nx` 默认内置了 `prettier` 配置,并且已经和 `eslint` 整合。 206 | 207 | 前面我们把 `stylelint` 和 `prettier` 整合了。 208 | 209 | `prettier` 配合 `vs code` 插件使用非常方便,规则的配置可以通过其提供的 [playground](https://prettier.io/playground/) 工具进行可视化的配置以及效果预览。 210 | 211 | > vs code 可以配置每次都会自动保存 `fix` 代码风格问题,默认选择 `"editor.defaultFormatter": "esbenp.prettier-vscode"` 即可 212 | 213 | ## Nx lint 214 | 215 | `@nrwl/nx` 默认提供 `nx workspace-lint`、`nx affected:lint`、`nx format:write` 命令。 216 | 217 | - `nx affected:lint`:功能和 `eslint` 类似 218 | - `nx format:write`:使用项目根目录下的 `prettier` 配置进行代码格式化。默认会自动执行 `eslint --fix` 219 | 220 | 那我们想检查 `css`,怎么办? 221 | 222 | `@nrwl/nx` 是个灵活的,可扩展的 `cli`。 223 | 224 | 我们想检查某个 `apps` 或 `libs`,比如 `web` 项目: 225 | 226 | ```bash 227 | npx stylelint apps/web/src/**/*.scss 228 | ``` 229 | 230 | `@nrwl/nx` 每个项目下都有 `targets` 集合,每个 `target` 都有 2 个特定且必须的属性:`executor` 和 `options`。 231 | 232 | - executor:执行器,执行 `target` 时调用。 233 | - options:配置项,执行 `target` 时调用。 234 | 235 | `@nrwl/nx` 为了更加灵活执行命令,特意提供了 `@nrwl/workspace:run-commands`。 236 | 237 | 然后我们给 `web` 配置一个 `target`: 238 | 239 | ```json 240 | "lint-style": { 241 | "executor": "@nrwl/workspace:run-commands", 242 | "options": { 243 | "command": "npx stylelint apps/web/src/**/*.scss" 244 | } 245 | }, 246 | ``` 247 | 248 | 那我们在 `package.json` 可以配置 2 个脚本: 249 | 250 | ```json 251 | { 252 | "scrips": { 253 | ... 254 | // 执行所有的项目 target=lint-style 的命令 255 | "lint-style": "nx run-many --target=lint-style --all", // 如果想要并行可以加上 `--parallel` 256 | "affected:lint-style": "nx affected --target=lint-style", // 配置这个命令,我们就可以愉快使用 `lint-staged` 257 | } 258 | } 259 | ``` 260 | 261 | 我们可以使用更方便的 `nx-stylelint` 插件来完成验证: 262 | 263 | ```bash 264 | npm i -D nx-stylelint 265 | ``` 266 | 267 | 然后使用 `nx-console` 交互界面配置 `nx-stylelint` 到当前应用,运行验证: 268 | 269 | ```bash 270 | nx affected --target=stylelint 271 | ``` 272 | 273 | > **注意**:`nx-stylelint` 只能验证 `css` 文件,不支持 `html` 和 `jsx` 内联验证。 274 | 275 | ## Git Hooks 276 | 277 | 团队合作中的编码规范有一点是,虽然自己有可能不舒服,但是不能让别人因为自己的代码而不舒服。 278 | 279 | `git` 自身包含许多 `hooks`,在 `commit`,`push` 等 `git` 事件前后触发执行。与 `pre-commit hook` 结合可以帮助校验 `Lint`,如果非通过代码规范则不允许提交。 280 | 281 | [husky](https://github.com/typicode/husky) 是一个使 `git hooks` 变得更简单的工具,通过简单的配置就能够极大增强 `hooks` 能力。 282 | 283 | ```bash 284 | npm install husky -D 285 | ``` 286 | 287 | 手动实装: 288 | 289 | ```bash 290 | npx husky install 291 | ``` 292 | 293 | 添加钩子 `.huskyrc`: 294 | 295 | ```json 296 | { 297 | "hooks": { 298 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS", 299 | "pre-commit": "lint-staged" 300 | } 301 | } 302 | ``` 303 | 304 | ## lint-staged 305 | 306 | 对暂存的 `git` 文件运行 `linter`,不要让屎代码溜进你的代码库! 307 | 308 | 很多同学选择在 `CI`(持续集成阶段)做 `Lint`,比如使用远程的 `Git Hooks` 来触发。但是从实际的经历来看,这种做法的反馈链条通常如下: 309 | 310 | ```text 311 | 代码提交 --> 发现问题(远程) --> 修复问题 --> 重新提交 --> 通过检查(远程) 312 | ``` 313 | 314 | 整个过程可能会浪费掉你不少时间,毕竟 `CI` 过程通常不仅是在做 `Lint` 工作。 315 | 316 | `lint-staged` 出现为了缩短 `Lint` 的反馈链条,把 `Lint` 挪到本地是最有效的办法。 317 | 318 | ```bash 319 | npm install -D lint-staged 320 | ``` 321 | 322 | 它的配置特别简单,`package.json` 里面写上就行: 323 | 324 | ```json 325 | "lint-staged": { 326 | "*.ts": "eslint --fix", 327 | "*.scss": "stylelint --fix", 328 | "*.html": "htmlhint --fix", 329 | } 330 | ``` 331 | 332 | 在 `@nrwl/nx` 我们需要这样去配置执行脚本 `.lintstagedrc.js`: 333 | 334 | ```js 335 | module.exports = { 336 | '{apps,libs}/**/*.{ts,json,md,html}': (files) => { 337 | const cwd = process.cwd(); 338 | const filesList = files.map((file) => path.relative(cwd, file)).join(','); 339 | return [ 340 | `npx nx affected:lint --parallel --fix --files=${filesList}`, 341 | `npx nx format:write --files=${filesList}`, 342 | `git add ${files.join(' ')}`, 343 | ]; 344 | }, 345 | '{apps,libs}/**/*.scss': (files) => { 346 | const cwd = process.cwd(); 347 | const filesList = files.map((file) => path.relative(cwd, file)).join(','); 348 | return [ 349 | `npx nx affected --target=stylelint --fix=true --files=${filesList}`, 350 | `npx nx format:write --files=${filesList}`, 351 | `git add ${files.join(' ')}`, 352 | ]; 353 | }, 354 | }; 355 | ``` 356 | 357 | ## commitlint 358 | 359 | `commit` 提交规范,这个最先是 [Angular](https://angular.io/) 团队在 [github](https://github.com/angular) 发起的,现在已经是最流行的 `commit` 提交规范。 360 | 361 | 提交规范格式 362 | 363 | ```text 364 | (): type 是强制性,scope 是可选的,subject 是一个简单标题 365 | 366 | 367 | 写详细描述 可选 368 | 369 |