1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        項目規(guī)范:讓你的代碼更上一層樓

        共 28509字,需瀏覽 58分鐘

         ·

        2023-06-20 10:33

            

        大廠技術(shù)  高級前端  Node進階

        點擊上方 程序員成長指北,關(guān)注公眾號

        回復(fù)1,加入高級Node交流群


        初始化項目

        • vscode 里下好插件:eslint,prettier,stylelint

        • 官網(wǎng)模版創(chuàng)建項目:pnpm create vite react-starter \--template react-swc-ts

        • 安裝依賴:pnpm i

        • 后面有可能遇到 ts 類型錯誤,可以提前安裝一個pnpm i @types/node \-D

        配置 npm 使用淘寶鏡像

        • 配置npmrc

        registry = "https://registry.npmmirror.com/"

        配置 node 版本限制提示

        • package.json 中配置

        "engines": { "node": ">=16.0.0" },

        配置 eslint 檢查代碼規(guī)范

        eslint 處理代碼規(guī)范,prettier 處理代碼風(fēng)格 eslint 選擇只檢查錯誤不處理風(fēng)格,這樣 eslint 就不會和 prettier 沖突 react 官網(wǎng)有提供一個 hook 的 eslint (eslint-plugin-react-hooks),用處不大就不使用了

        • 安裝:pnpm i eslint \-D

        • 生成配置文件:eslint \--init(如果沒eslint,可以全局安裝一個,然后使用npx eslint --init)

        - To check syntax and find problems  //這個選項是eslint默認選項,這樣就不會和pretter起風(fēng)格沖突
        - JavaScript modules (import/export)
        - React
        - YES
        - Browser
        - JSON
        - Yes
        - pnpm
        • 配置eslintrc.json->rules里配置不用手動引入 react,和配置不可以使用 any

        • 注意使用 React.FC 的時候如果報錯說沒有定義 props 類型,那需要引入一下 react

        "rules": {
         //不用手動引入react
         "react/react-in-jsx-scope""off",
         //使用any報錯
         "@typescript-eslint/no-explicit-any""error",
        }
        • 工作區(qū)配置.vscode>settings.json,配置后 vscode 保存時自動格式化代碼風(fēng)格

        比如寫了一個 var a = 100,會被自動格式化為 const a = 100

        {
         "editor.codeActionsOnSave": {
         // 每次保存的時候?qū)⒋a按照 eslint 格式進行修復(fù)
         "source.fixAll.eslint"true,
         //自動格式化
         "editor.formatOnSave"true
         }
        }
        • 配置.eslintignore,eslint 會自動過濾 node_modules

        dist

        • 掌握eslint格式化命令,后面使用 lint-staged 提交代碼的時候需要配置

          為什么上面有 vscode 自動 eslint 格式化,還需要命令行: 因為命令行能一次性爆出所有警告問題,便于找到位置修復(fù)

        npx eslint . --fix//用npx使用項目里的eslint,沒有的話也會去使用全局的eslint
        eslint . --fix //全部類型文件
        eslint . --ext .ts,.tsx --fix //--ext可以指定文件后綴名s

        eslintrc.json 里配置

        "env": {
         "browser"true,
         "es2021"true,
         "node"true // 因為比如配置vite的時候會使用到
        },

        配置 prettier 檢查代碼風(fēng)格

        prettier 格式化風(fēng)格,因為使用 tailwind,使用 tailwind 官方插件

        • 安裝:pnpm i prettier prettier-plugin-tailwindcss \-D

        • 配置.prettierrc.json

        注釋要刪掉,prettier 的配置文件 json 不支持注釋

        {
         "singleQuote"true// 單引號
         "semi"false// 分號
         "trailingComma""none"// 尾隨逗號
         "tabWidth"2// 兩個空格縮進
         "plugins": ["prettier-plugin-tailwindcss"//tailwind插件
        }
        • 配置.prettierignore
        dist
        pnpm-lock.yaml
        • 配置.vscode>settings.json,配置后 vscode 保存時自動格式化代碼風(fēng)格
        {
         "editor.codeActionsOnSave": {
           // 每次保存的時候?qū)⒋a按照 eslint 格式進行修復(fù)
           "source.fixAll.eslint"true
         },
         //自動格式化
         "editor.formatOnSave"true,
         //風(fēng)格用prettier
         "editor.defaultFormatter""esbenp.prettier-vscode"
        }
        • 掌握prettier命令行

        可以讓之前沒有格式化的錯誤一次性暴露出來

        npx prettier --write .//使用Prettier格式化所有文件

        配置 husky 使用 git hook

        記得要初始化一個 git 倉庫,husky 能執(zhí)行 git hook,在 commit 的時候?qū)ξ募M行操作

        • 安裝

        sudo pnpm dlx husky-init

        pnpm install

        npx husky add .husky/commit-msg 'npx \--no \-- commitlint \--edit "$1"',commit-msg 使用 commitlint

        npx husky add .husky/pre-commit "npm run lint-staged",pre-commit 使用 lint-staged

        配置 commitlint 檢查提交信息

        提交規(guī)范參考:www.conventionalcommits.org/en/v1.0.0/[1]

        • 安裝pnpm i @commitlint/cli @commitlint/config-conventional \-D

        • 配置.commitlintrc.json

        { extends: ['@commitlint/config-conventional'] }

        配置 lint-staged 增量式檢查

        • 安裝pnpm i \-D lint-staged

        • 配置package.json

        "scripts": {
         "dev""vite",
         "build""tsc && vite build",
         "preview""vite preview",
         "prepare""husky install",
         "lint-staged""npx lint-staged"//新增,對應(yīng)上面的husky命令
        },
        • 配置.lintstagedrc.json
        {
         "*.{ts,tsx,json}": ["prettier --write""eslint --fix"],
         "*.css": ["stylelint --fix""prettier --write"]
        }

        配置 vite(代理/別名/drop console 等)

        如果有兼容性考慮,需要使用 legacy 插件,vite 也有 vscode 插件,也可以下載使用

        • 一些方便開發(fā)的配置
        import { defineConfig } from 'vite'
        import react from '@vitejs/plugin-react-swc'
        import path from 'path'
        // https://vitejs.dev/config/
        export default defineConfig({
         esbuild: {
           drop: ['console''debugger']
         },
         css: {
           // 開css sourcemap方便找css
           devSourcemaptrue
         },
         plugins: [react()],
         server: {
           // 自動打開瀏覽器
           opentrue
           proxy: {
             '/api': {
               target'https://xxxxxx',
               changeOrigintrue,
               rewrite(path) => path.replace(/^\/api/'')
             }
           }
         },
         resolve: {
           // 配置別名
           alias: { '@': path.resolve(__dirname, './src') }
         },
         //打包路徑變?yōu)橄鄬β窂?用liveServer打開,便于本地測試打包后的文件
         base'./'
        })
        • 配置打包分析,用 legacy 處理兼容性

        pnpm i rollup-plugin-visualizer \-D

        pnpm i @vitejs/plugin-legacy \-D,實際遇到了再看官網(wǎng)用

        import { defineConfig } from 'vite'
        import react from '@vitejs/plugin-react-swc'
        import { visualizer } from 'rollup-plugin-visualizer'
        import legacy from '@vitejs/plugin-legacy'
        import path from 'path'
        // https://vitejs.dev/config/
        export default defineConfig({
         css: {
           // 開css sourcemap方便找css
           devSourcemaptrue
         },
         plugins: [
           react(),
           visualizer({
             openfalse // 打包完成后自動打開瀏覽器,顯示產(chǎn)物體積報告
           }),
           //考慮兼容性,實際遇到了再看官網(wǎng)用
           legacy({
             targets: ['ie >= 11'],
             additionalLegacyPolyfills: ['regenerator-runtime/runtime']
           })
         ],
         server: {
           // 自動打開瀏覽器
           opentrue
         },
         resolve: {
           // 配置別名
           alias: { '@': path.resolve(__dirname, './src') }
         },
         //打包路徑變?yōu)橄鄬β窂?用liveServer打開,便于本地測試打包后的文件
         base'./'
        })
        • 如果想手機上看網(wǎng)頁,可以pnpm dev \--host

        • 如果想刪除 console,可以按h去 help 幫助,再按c就可以 clear console

        配置 tsconfig

        • tsconfig.json 需要支持別名
        {
         "compilerOptions": {
           "target""ESNext",
           "useDefineForClassFields"true,
           "lib": ["DOM""DOM.Iterable""ESNext"],
           "allowJs"false,
           "skipLibCheck"true,
           "esModuleInterop"false,
           "allowSyntheticDefaultImports"true,
           "strict"true,
           "forceConsistentCasingInFileNames"true,
           "module""ESNext",
           "moduleResolution""Node",
           "resolveJsonModule"true,
           "isolatedModules"true,
           "noEmit"true,
           "jsx""react-jsx",
           "baseUrl""./",
           "paths": {
             "@/*": ["src/*"]
           }
          },
          "include": ["src"],
          "references": [{ "path""./tsconfig.node.json" }]
        }

        配置 router

        • 安裝:pnpm i react-router-dom

        • 配置router->index.ts

        import { lazy } from 'react'
        import { createBrowserRouter } from 'react-router-dom'
        const Home = lazy(() => import('@/pages/home'))
        const router = createBrowserRouter([
         {
           path'/',
           element<Home></Home>
         }
        ])
        export default router
        • 配置main.tsx
        import { RouterProvider } from 'react-router-dom'
        import ReactDOM from 'react-dom/client'
        import './global.css'
        import router from './router'
        ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
        <RouterProvider router={router} />
        )

        配置 zustand 狀態(tài)管理

        • 安裝pnpm i zustand

        • store->index.ts

        import { create } from 'zustand'
        interface appsState {
         nums: number
         setNumber(nums: number) => void
        }
        const useAppsStore = create<appsState>((set) => ({
         nums0,
         setNumber(num) => {
           return set(() => ({
             nums: num
           }))
         }
        }))
        export default useAppsStore
        • 使用方法
        import Button from '@/comps/custom-button'
        import useAppsStore from '@/store/app'
        const ZustandDemo: React.FC = () => {
         const { nums, setNumber } = useAppsStore()
         const handleNum = () => {
           setNumber(nums + 1)
         }
         return (
           <div className="p-10">
             <h1 className="my-10">數(shù)據(jù)/更新</h1>
               <Button click={handleNum}>點擊事件</Button>
             <h1 className="py-10">{nums}</h1>
           </div>

         )
        }
        export default ZustandDemo

        配置 antd

        • 新版本的 antd,直接下載就可以用,如果用到它的圖片再單獨下載pnpm i antd
        • 注意 antd5 版本的 css 兼容性不好,如果項目有兼容性要求,需要去單獨配置

        配置 Tailwind css

        pnpm i tailwindcss autoprefixer postcss

        tailwind.config.cjs

        // 打包后會有1kb的css用不到的,沒有影響
        // 用了antd組件關(guān)系也不大,antd5的樣式是按需的
        /** @type {import('tailwindcss').Config} */
        module.exports = {
         darkMode'class',
         content: ['./index.html''./src/**/*.{js,ts,jsx,tsx}'],
         theme: {
         extend: {
         // colors: {
         //   themeColor: '#ff4132',
         //   textColor: '#1a1a1a'
         // },
         // 如果寫自適應(yīng)布局,可以指定設(shè)計稿為1000px,然后只需要寫/10的數(shù)值
         // fontSize: {
         //   xs: '3.3vw',
         //   sm: '3.9vw'
         // }
         }
         },
         plugins: []
        }

        postcss.config.cjs

        module.exports = {
         plugins: {
         tailwindcss: {},
         autoprefixer: {}
         }
        }

        我喜歡新建一個 apply.css 引入到全局

        @tailwind base;
        @tailwind components;
        @tailwind utilities;
        .margin-center {
         @apply mx-auto my-0;
        }
        .flex-center {
         @apply flex justify-center items-center;
        }
        .absolute-center {
         @apply absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2;
        }

        封裝 fetch 請求

        這個封裝僅供參考,TS 類型有點小問題

        // 可以傳入這些配置
        interface BaseOptions {
         method?: string
         credentials?: RequestCredentials
         headers?: HeadersInit
         body?: string | null
        }
        // 請求方式
        type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD'
        // 第一層出參
        interface ResponseObject {
         ok: boolean
         error: boolean
         status: number
         contentType: string | null
         bodyText: string
         response: Response
        }
        // 請求頭類型
        type JSONHeader = {
         Accept: string
         'Content-Type': string
        }
        // 創(chuàng)建類
        class Request {
         private baseOptions: BaseOptions = {}
         // 根據(jù)傳入的 baseOptions 做為初始化參數(shù)
         constructor(options?: BaseOptions) {
           this.setBaseOptions(options || {})
         }
         public setBaseOptions(options: BaseOptions): BaseOptions {
           this.baseOptions = options
           return this.baseOptions
         }
         // 也提供獲取 baseOption 的方法
         public getBaseOptions(): BaseOptions {
           return this.baseOptions
         }
         // 核心請求 T 為入?yún)㈩愋?,ResponseObject 為出參類型
         public request<T>(
           method: HttpMethod,
           url: string,
           data?: T, //支持使用get的時候配置{key,value}的query參數(shù)
           options?: BaseOptions //這里也有個 base 的 method
         ): Promise<ResponseObject> {
           // 默認 baseOptions
           const defaults: BaseOptions = {
             method
             // credentials: 'same-origin'
           }
           // 收集最后要傳入的配置
           const settings: BaseOptions = Object.assign(
           {},
           defaults,
           this.baseOptions,
           options
         )
         // 如果 method 格式錯誤
         if (!settings.method || typeof settings.method !== 'string')
         throw Error('[fetch-json] HTTP method missing or invalid.')
         // 如果 url 格式錯誤
         if (typeof url !== 'string')
         throw Error('[fetch-json] URL must be a string.')
         // 支持大小寫
         const httpMethod = settings.method.trim().toUpperCase()
         // 如果是GET
         const isGetRequest = httpMethod === 'GET'
         // 請求頭
         const jsonHeaders: Partial<JSONHeader> = { Accept'application/json' }
         // 如果不是 get 設(shè)置請求頭
         if (!isGetRequest && data) jsonHeaders['Content-Type'] = 'application/json'
         // 收集最后的headers配置
         settings.headers = Object.assign({}, jsonHeaders, settings.headers)
         // 獲取query參數(shù)的key
         const paramKeys = isGetRequest && data ? Object.keys(data) : []
         // 獲取query參數(shù)的值
         const getValue = (key: keyof T) => (data ? data[key] : '')
         // 獲取query key=value
         const toPair = (key: string) =>
         key + '=' + encodeURIComponent(getValue(key as keyof T) as string)
         // 生成 key=value&key=value 的query參數(shù)
         const params = () => paramKeys.map(toPair).join('&')
         // 收集最后的 url 配置
         const requestUrl = !paramKeys.length
         ? url
         : url + (url.includes('?') ? '&' : '?') + params()
         // get沒有body
         settings.body = !isGetRequest && data ? JSON.stringify(data) : null
         // 做一層res.json()
         const toJson = (value: Response): Promise<ResponseObject> => {
         // 拿到第一次請求的值
         const response = value
         const contentType = response.headers.get('content-type')
         const isJson = !!contentType && /json|javascript/.test(contentType)
         const textToObj = (httpBody: string): ResponseObject => ({
           ok: response.ok,
           error: !response.ok,
           status: response.status,
           contentType: contentType,
           bodyText: httpBody,
           response: response
         })
         const errToObj = (error: Error): ResponseObject => ({
           okfalse,
           errortrue,
           status500,
           contentType: contentType,
           bodyText'Invalid JSON [' + error.toString() + ']',
           response: response
         })
         return isJson
           ? // 如果是json,用json()
           response.json().catch(errToObj)
           : response.text().then(textToObj)
         }
           // settings做一下序列化
           const settingsRequestInit: RequestInit = JSON.parse(
           JSON.stringify(settings)
         )
           // 最終請求fetch,再通過then就能取到第二層res
           return fetch(requestUrl, settingsRequestInit).then(toJson)
         }
           public get<T>(
           url: string,
           params?: T,
           options?: BaseOptions
         ): Promise<ResponseObject> {
           return this.request<T>('GET', url, params, options)
         }
         public post<T>(
           url: string,
           resource: T,
           options?: BaseOptions
         ): Promise<ResponseObject> {
           return this.request<T>('POST', url, resource, options)
         }
         public put<T>(
           url: string,
           resource: T,
           options?: BaseOptions
         ): Promise<ResponseObject> {
           return this.request<T>('PUT', url, resource, options)
         }
         public patch<T>(
           url: string,
           resource: T,
           options?: BaseOptions
         ): Promise<ResponseObject> {
           return this.request<T>('PATCH', url, resource, options)
         }
         public delete<T>(
           url: string,
           resource: T,
           options?: BaseOptions
         ): Promise<ResponseObject> {
           return this.request<T>('DELETE', url, resource, options)
         }
        }
        const request = new Request()
        export { request, Request }

        如果用 axios 請求

        request.ts

        import axios from 'axios'
        import { AxiosInstance } from 'axios'
        import { errorHandle, processData, successHandle } from './resInterceptions'
        import { defaultRequestInterception } from './reqInterceptions'
        const TIMEOUT = 5 * 1000
        class Request {
         instance: AxiosInstance
         constructor() {
           this.instance = axios.create()
           this.init()
         }
         private init() {
           this.setDefaultConfig()
           this.reqInterceptions()
           this.resInterceptions()
         }
         private setDefaultConfig() {
           this.instance.defaults.baseURL = import.meta.env.VITE_BASE_URL
           this.instance.defaults.timeout = TIMEOUT
         }
         private reqInterceptions() {
           this.instance.interceptors.request.use(defaultRequestInterception)
         }
         private resInterceptions() {
           this.instance.interceptors.response.use(processData)
           this.instance.interceptors.response.use(successHandle, errorHandle)
         }
        }
        export default new Request().instance

        reqInterceptions.ts

        import type { InternalAxiosRequestConfig } from 'axios'
        const defaultRequestInterception = (config: InternalAxiosRequestConfig) => {
         // TODO: 全局請求攔截器: 添加token
         return config
        }
        export { defaultRequestInterception }

        resInterceptions.ts

        import { AxiosError, AxiosResponse } from 'axios'
        import { checkStatus } from './checkStatus'
        const processData = (res: AxiosResponse) => {
        // TODO:統(tǒng)一處理數(shù)據(jù)結(jié)構(gòu)
        return res.data
        }
        const successHandle = (res: AxiosResponse) => {
        // TODO:處理一些成功回調(diào),例如請求進度條
        return res.data
        }
        const errorHandle = (err: AxiosError) => {
        if (err.status) checkStatus(err.status)
        else return Promise.reject(err)
        }
        export { processData, successHandle, errorHandle }

        checkStatus.ts

        export function checkStatus(status: number, msg?: string): void {
         let errMessage = ''
         switch (status) {
           case 400:
             errMessage = `${msg}`
             break
           case 401:
             break
           case 403:
             errMessage = ''
             break
           // 404請求不存在
           case 404:
             errMessage = ''
             break
           case 405:
             errMessage = ''
             break
           case 408:
             errMessage = ''
             break
           case 500:
             errMessage = ''
             break
           case 501:
             errMessage = ''
             break
           case 502:
             errMessage = ''
             break
           case 503:
             errMessage = ''
             break
           case 504:
             errMessage = ''
             break
           case 505:
             errMessage = ''
             break
           default:
         }
         if (errMessage) {
           // TODO:錯誤提示
           // createErrorModal({title: errMessage})
         }
        }`` 

        api.ts

        ```ts
        import request from '@/services/axios/request'
        import { ReqTitle } from './type'
        export const requestTitle = (): Promise<ReqTitle> => {
         return request.get('/api/一個獲取title的接口')
        }

        type.ts

        export type ReqTitle = {
         title: string
        }

        配置 mobx(可不用)

        • 安裝pnpm i mobx mobx-react-lite

        • 配置model->index.ts

        import { makeAutoObservable } from 'mobx'
        const store = makeAutoObservable({
          count1,
          setCount(count: number) => {
            store.count = count
          }
        })
        export default store
        • 使用方法舉個 ??
        import store from '@/model'
        import { Button } from 'antd'
        import { observer, useLocalObservable } from 'mobx-react-lite'
        const Home: React.FC = () => {
        const localStore = useLocalObservable(() => store)
        return (
        <div>
        <Button>Antd</Button>
        <h1>{localStore.count}</h1>
        </div>
        )
        }
        export default observer(Home)

        配置 changelog(可不用)

        pnpm i conventional-changelog-cli \-D

        第一次先執(zhí)行conventional-changelog \-**p** angular \-**i** CHANGELOG.md \-s \-r 0全部生成之前的提交信息

        配置個腳本,版本變化打 tag 的時候可以使用

        "scripts": {
         "changelog""conventional-changelog -p angular -i CHANGELOG.md -s"
        }

        配置 editorConfig 統(tǒng)一編輯器(可不用)

        editorConfig,可以同步編輯器差異,其實大部分工作 prettier 做了,需要下載 editorConfig vscode 插件 有編輯器差異的才配置一下,如果團隊都是 vscode 就沒必要了

        • 配置editorconfig
        #不再向上查找.editorconfig
        root = true
        # *表示全部文件
        [*]
        #編碼
        charset = utf-8
        #縮進方式
        indent_style = space
        #縮進空格數(shù)
        indent_size = 2
        #換行符lf
        end_of_line = lf

        配置 stylelint 檢查 CSS 規(guī)范(可不用)

        stylelint 處理 css 更專業(yè),但是用了 tailwind 之后用處不大了

        • 安裝:pnpm i \-D stylelint stylelint-config-standard

        • 配置.stylelintrc.json

        {
         "extends""stylelint-config-standard"
        }
        • 配置.vscode>settings.json,配置后 vscode 保存時自動格式化 css
        {
         "editor.codeActionsOnSave": {
           "source.fixAll.eslint"true// 每次保存的時候?qū)⒋a按照 eslint 格式進行修復(fù)
           "source.fixAll.stylelint"true //自動格式化stylelint
         },
         "editor.formatOnSave"true//自動格式化
         "editor.defaultFormatter""esbenp.prettier-vscode" //風(fēng)格用prettier
        }
        • 掌握stylelint命令行

        npx stylelint "**/*.css" --fix//格式化所有css,自動修復(fù)css

        下面是 h5 項目(可不用)

        配置vconsole(h5)

        • 安裝pnpm i vconsole \-D

        • main.tsx里新增

        import VConsole from 'vconsole'
        new VConsole({ theme: 'dark' })

        antd 換成 mobile antd(h5)

        • pnpm remove antd
        • pnpm add antd-mobile

        配置 postcss-px-to-viewport(廢棄)

        • 把藍湖設(shè)計稿尺寸固定為 1000px(100px我試過藍湖直接白屏了),然后你點出來的值比如是 77px,那你只需要寫 7.7vw 就實現(xiàn)了自適應(yīng)布局,就不再需要這個插件了

        • 安裝:pnpm i postcss-px-to-viewport \-D

        • 配置postcss.config.cjs

        module.exports = {
         plugins: {
         'postcss-px-to-viewport': {
           landscapefalse// 是否添加根據(jù) landscapeWidth 生成的媒體查詢條件 @media (orientation: landscape)
           landscapeUnit'vw'// 橫屏?xí)r使用的單位
           landscapeWidth568// 橫屏?xí)r使用的視口寬度
           unitToConvert'px'// 要轉(zhuǎn)化的單位
           viewportWidth750// UI設(shè)計稿的寬度
           unitPrecision5// 轉(zhuǎn)換后的精度,即小數(shù)點位數(shù)
           propList: ['*'], // 指定轉(zhuǎn)換的css屬性的單位,*代表全部css屬性的單位都進行轉(zhuǎn)換
           viewportUnit'vw'// 指定需要轉(zhuǎn)換成的視窗單位,默認vw
           fontViewportUnit'vw'// 指定字體需要轉(zhuǎn)換成的視窗單位,默認vw
           selectorBlackList: ['special'], // 指定不轉(zhuǎn)換為視窗單位的類名,
           minPixelValue1// 默認值1,小于或等于1px則不進行轉(zhuǎn)換
           mediaQuerytrue// 是否在媒體查詢的css代碼中也進行轉(zhuǎn)換,默認false
           replacetrue// 是否轉(zhuǎn)換后直接更換屬性值
           exclude: [/node_modules/// 設(shè)置忽略文件,用正則做目錄名匹配
          }
         }
        }

        參考資料

        [1]

        https://www.conventionalcommits.org/en/v1.0.0/

        關(guān)于本文

        作者:imber

        https://juejin.cn/post/7241875166887444541

        最后

           
        Node 社群


        我組建了一個氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對Node.js學(xué)習(xí)感興趣的話(后續(xù)有計劃也可以),我們可以一起進行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。

           “分享、點贊、在看” 支持一波

        瀏覽 38
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            欧美人一级婬片a免费播放 | 亚洲国产精品国自产拍av绿帽子 | 午夜成人激情视频 | 欧洲亚洲免费视频 | 欧美日韩人妻精品一区二区三区 | 欧美操逼影片 | 国内免费av在线 欧美老太做爱1级黄片儿 | 办公室护士毛片 | 周秀娜无删减床戏视频 | 好爽好紧军人h男男小说 |