1.0.6 • Published 4 years ago

cra-template-quickdva v1.0.6

Weekly downloads
3
License
MIT
Repository
github
Last release
4 years ago

你是否还在为了搭建一个企业级的react框架而发愁,是否有如下问题:

  • eslint要怎么配置才能兼容ts?
  • react项目要怎么样才能实现样式隔离?
  • 怎么才能让团队成员统一提交代码的格式?

如果你有以上问题,那么这就是你需要的实践方案!我们将一步步实现。

创建cra项目

这里我们使用typescript模板

npx create-react-app my-app --template typescript

使用rewrited

因为我们之后要修改webpack的配置,所以我们需要重写官方的默认配置,在这里我使用的是react-app-rewired,你也可以使用eject命令,改命令会把webpack的配置暴露出来你可以随意修改。

yarn add react-app-rewired -D
  /* package.json */

  "scripts": {
-   "start": "react-scripts start",
+   "start": "react-app-rewired start",
-   "build": "react-scripts build",
+   "build": "react-app-rewired build",
-   "test": "react-scripts test",
+   "test": "react-app-rewired test",
    "eject": "react-scripts eject"
}

在根目录下新建一个config-overrides.js文件,我们暂时不写内容

module.exports = {

}

引入eslint

  • 在根目录下创建.eslintrc.js文件

    module.exports = {
      root: true,
      parser: '@typescript-eslint/parser', // Specifies the ESLint parser
      plugins: ['react-app','@typescript-eslint'],
      extends: [
        'plugin:react-app/recommended',
        'eslint:recommended',
        'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin
      ],
      rules: {
        '@typescript-eslint/explicit-function-return-type': 0
      }
    };
  • 在根目录创建.env文件,设置EXTEND_ESLINT=true来扩展基本ESLint配置

    EXTEND_ESLINT=true
  • 要使eslintrc文件生效,你还需要删除package.json文件里的eslintConfig

    -  "eslintConfig": {
    -    "extends": "react-app"
    -  },
  • 此时再次启动项目会发现报如下错误,说明已经使用了我们自定义的eslint配置

    Failed to load plugin 'react-app' declared in '.eslintrc.js': Cannot find module 'eslint-plugin-react-app'
  • 安装所需依赖

    yarn add eslint-plugin-react-app typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin -D 
  • 然鹅我发现事情并不那么简单,启动后发现eslint报了个警告。我们在rules里不是配置了'@typescript-eslint/explicit-function-return-type': 0么?我明明照着官网配置的~

    ./src/App.tsx
      Line 5:1:  Missing return type on function  @typescript-eslint/explicit-function-return-type
    
    Search for the keywords to learn more about each warning.
    To ignore, add // eslint-disable-next-line to the line before.
  • 一番搜索,发现是create-react-appissue,issue的解决方案是修改node_modules/react-scripts/config/webpack.config.js文件下的cache改为false,试过之后的确可行,但是我不可能通知团队成员修改源码吧。一定还有其他的方案。

    use: [
                {
                  options: {
    -                cache: true,
    +                cache: false,
                    formatter: require.resolve('react-dev-utils/eslintFormatter'),
                    eslintPath: require.resolve('eslint'),
                    resolvePluginsRelativeTo: __dirname,
                    // @remove-on-eject-begin
                    ignore: isExtendingEslintConfig,
                    baseConfig: isExtendingEslintConfig
                      ? undefined
                      : {
                          extends: [require.resolve('eslint-config-react-app')],
                        },
                    useEslintrc: isExtendingEslintConfig,
                    // @remove-on-eject-end
                  },
  • 想了一下,既然使用了react-app-rewired我们就能覆盖webpack的配置。于是第二种解决方案就是覆盖webpack的默认配置,我们使用customize-cra来重写配置,暴力破解:

    yarn add customize-cra -D
    /*config-overrides.js*/
    
    const {
        override,
    } = require('customize-cra');
    const eslintConfig = require('./.eslintrc.js');
    
    const useEslintConfig = configRules => config => {
      const updatedRules = config.module.rules.map(rule => {
        // Only target rules that have defined a `useEslintrc` parameter in their options
        if (rule.use && rule.use.some(use => use.options && use.options.useEslintrc !== void 0)) {
          const ruleUse = rule.use[0];
          const baseOptions = ruleUse.options;
          const baseConfig = baseOptions.baseConfig || {};
          const newOptions = {
            useEslintrc: false,
            ignore: true,
            baseConfig: { ...baseConfig, ...configRules },
          };
          ruleUse.options = newOptions;
          return rule;
    
          // Rule not using eslint. Do not modify.
        } else {
          return rule;
        }
      });
    
      config.module.rules = updatedRules;
      return config;
    };
    module.exports = {
        webpack: override(
            useEslintConfig(eslintConfig),
        ),
    }

    这时候我们重启发现一切都是那么顺利☀️

加入prittier

团队协作少不了的代码格式化利器

  • 在根目录加入.prettierrc文件

    {
      "singleQuote": true,
      "trailingComma": "es5",
      "printWidth": 100,
      "tabWidth": 2,
      "endOfLine": "auto",
      "overrides": [
        {
          "files": ".prettierrc",
          "options": {
            "parser": "json"
          }
        },
        {
          "files": ".less",
          "options": {
            "parser": "css"
          }
        }
      ]
    }
  • eslint能校验到prettier

    安装依赖

    yarn add eslint-config-prettier eslint-plugin-prettier prettier -D

    修改.eslintrc.js添加prettier的配置

      plugins: [
        'react-app',
        '@typescript-eslint',
    +   'prettier'
      ],
      extends: [
        'plugin:react-app/recommended',
        'eslint:recommended',
        'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin
    +    'prettier',
    +    'prettier/@typescript-eslint',
    +		 'prettier/react',
    +    'plugin:prettier/recommended'
      ],

    修改App.tsx文件后重启我们可以看到prettier错误则代表配置成功了

加入editorConfig

加入editorConfig用于维护跨多个编辑器和IDE从事同一项目的多个团队成员的编码风格一致。

根目录加入.editorconfig文件:

# http://editorconfig.org
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

[Makefile]
indent_style = tab

加入webpackAlias

/*config-overrides.js*/

const {
    override,
+    addWebpackAlias,
} = require('customize-cra');
+ const path = require('path');
...
module.exports = {
    webpack: override(
        useEslintConfig(),
+        addWebpackAlias({
+          ['@']: path.resolve(__dirname, 'src'),
+          ['assets']: path.resolve(__dirname, 'src/assets'),
+          ['components']: path.resolve(__dirname, 'src/components'),
+        })
    ),
}

关于样式

引入全局样式文件

样式我使用less作为预处理语言,我们之前如果想要在每个less文件里使用一些全局的常量,只能把文件通过@import的方式引入。现在我们可以通过customize-craaddLessLoader来实现。

yarn add less-loader -D
/*config-overrides.js*/

const {
    override,
+    addLessLoader
} = require('customize-cra');
...
module.exports = {
    webpack: override(
        useEslintConfig(),
+        addLessLoader({
+        lessOptions: {
+           javascriptEnabled: true,
+           modifyVars: {
+             hack: `true; @import "~@/assets/styles/mixin.less";`,
+           },
+         },
+        }),
    ),
}

加入cssModule支持

/*config-overrides.js*/
module.exports = {
    webpack: override(
        useEslintConfig(),
        addLessLoader({
          javascriptEnabled: true,
          modifyVars: {
            hack: `true; @import "~@/assets/styles/mixin.less";`,
          },
+         localIdentName: '[local]_[hash:base64:5]',
+      		cssModules: true,
        }),
    ),
}

加入之后我们在App.tsx的同级新建一个文件App.module.less,注意的是,module文件必须要以module.less结尾

.app{
  background: red;
}

修改App.tsx文件

import React from 'react';
import styles from './App.module.less';

function App() {
  return <div className={styles.app}></div>;
}

export default App;

然后我们就可以看到样式名后面加了一个hash值,说明cssModule生效了

npm.io

加入stylelint校验

对于样式的格式,我们也可以使用stylelint来校验

在根目录添加.stylelintrc.json文件

{
  "extends": ["stylelint-config-standard"],
  "rules": {
  }
}

安装依赖

yarn add -D stylelint-config-standard

修改config-overrides.js文件加入StyleLintPlugin

/*config-overrides.js*/
const { 
	override, 
	addWebpackAlias, 
	addLessLoader, 
+	addWebpackPlugin 
} = require('customize-cra');
+ const StyleLintPlugin = require('stylelint-webpack-plugin');

module.exports = {
    webpack: override(
        useEslintConfig(),
+        addWebpackPlugin(
+          new StyleLintPlugin({
+            context: 'src',
+            configFile: path.resolve(__dirname, './.stylelintrc.json'),
+            files: '**/*.less',
+            failOnError: false,
+            quiet: true,
+            fix: true, // 修复不规范的样式代码
+          })
+        )
    ),
}

重启之后我们发现已经自动把less文件里的错误修复了,说明配置成功~

加入postcss

在这里,我们使用了customize-craaddPostcssPlugins,修改config-overrides`文件,加入如下代码:

/*config-overrides.js*/
const { 
	override, 
	addWebpackAlias, 
	addLessLoader, 
+	addPostcssPlugins, 
} = require('customize-cra');

module.exports = {
    webpack: override(
        useEslintConfig(),
+        addPostcssPlugins([
+          require('postcss-px-to-viewport')({
+            unitToConvert: 'px',
+            viewportWidth: 1920,
+            unitPrecision: 3,
+            propList: ['*', '!letter-spacing', '!font-size'],
+            viewportUnit: 'vw',
+            fontViewportUnit: 'vw',
+            selectorBlackList: [],
+            minPixelValue: 2,
+            mediaQuery: false,
+            replace: true,
+            exclude: [],
+            landscape: false,
+          }),
+        ])
    ),
}
yarn add postcss-px-to-viewport -S

这里我使用了postcss-px-to-viewport插件,px单位直接转换为vw,但是字号不转换。

加入BundleAnalyzerPlugin

/*config-overrides.js*/
+ const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');

module.exports = {
    webpack: override(
        useEslintConfig(),
+        addWebpackPlugin(new BundleAnalyzerPlugin()),
    ),
}

当然,这东西仅在分析的时候开启就好了。

加入SplitChunk 对模块进行分割

/*config-overrides.js*/
const { 
	override, 
	addWebpackAlias, 
	addLessLoader, 
+	setWebpackOptimizationSplitChunks, 
} = require('customize-cra');

module.exports = {
    webpack: override(
       useEslintConfig(),
+       setWebpackOptimizationSplitChunks({
+          chunks: 'all', //默认作用于异步chunk,值为all/initial/async
+          minSize: 30000, //默认值是30kb,代码块的最小尺寸
+          minChunks: 1, //被多少模块共享,在分割之前模块的被引用次数
+          maxAsyncRequests: 5, //按需加载最大并行请求数量
+          maxInitialRequests: 5, //一个入口的最大并行请求数量
+          name: true, //打包后的名称,默认是chunk的名字通过分隔符(默认是~)分隔开,如vendor~
+          automaticNameDelimiter: '~', //默认webpack将会使用入口名和代码块的名称生成命名,比如 'vendors~main.js'
+          cacheGroups: {
+            //设置缓存组用来抽取满足不同规则的chunk,下面以生成common为例
+            vendors: {
+              test: /node_modules/, //条件
+              priority: -10, //优先级,一个chunk很可能满足多个缓存组,会被抽取到优先级高的缓存组中,为了能够让自定义缓存组有更高的优先级(默认0),默认缓存组的priority属性为负值.
+            },
+            commons: {
+              minSize: 0, //最小提取字节数
+              minChunks: 2, //最少被几个chunk引用
+              priority: -20,
+              reuseExistingChunk: true, //    如果该chunk中引用了已经被抽取的chunk,直接引用该chunk,不会重复打包代码
+            },
+          },
+        })
    ),
}

对commit操作做拦截

在前面我们已经使用了eslint对代码进行了统一的校验,保证了开发人员在开发的时候规范代码。但是却无法保证错误的eslint校验代码提交到仓库中,那么我们就需要针对git提交之前做一些事情。这个时候husky就发挥作用了。作者的原话就是它就是看门狗形式的存在。

Husky can prevent bad git commit, git push and more 🐶 woof!

yarn add husky -D
/*package.json*/
+"husky": {
+  "hooks": {
+    "pre-commit": "npm run lint-staged",
+  }
+},

可以看到,上面的pre-commit运行了一个命令,那么lint-staged是什么呢,它主要的工作是对git暂存的文件执行一些linner。

yarn add lint-staged -D
/*package.json*/
+"lint-staged": {
+  "**/*.{ts,tsx}": [
+    "prettier --write",
+    "npm run eslint",
+    "git add"
+  ],
+  "**/*.less": "npm run stylelint"
+},

如上述代码,它会对git暂存的tstsx文件按顺序执行prettier格式化和eslint校验后才能进行add

有时候我们会发现,团队成员们提交的commit message千奇百怪,根本无法很好地定位到底哪次提交是修复bug,哪次提交是新功能,哪次提交修改了配置,所以这个时候我们需要一个统一的提交message的格式,那么commitlint就来了。

新增commitlint.config.js文件

module.exports = {
    extends: ['@commitlint/config-conventional'],
    rules: {
        'type-enum': [2, 'always', [
            "feat", "fix", "docs", "style", "refactor", "test", "chore"
        ]],
    }
};

修改package.json文件

/*package.json*/
  "husky": {
    "hooks": {
      "pre-commit": "npm run lint-staged",
+      "commit-msg": "commitlint -E  HUSKY_GIT_PARAMS"
    }
  },

此时你会发现,如果不按config的规则提交,那么就提交不上去。