create-react-app建立项目的自定义配置指南

背景

在咱们使用create-react-app建立react项目的时候,虽然能够享受一键带来的方便,可是封装好的不少配置并不能知足多元化项目的需求。咱们知道,不一样团队team可能制定有不一样的eslint规则,或者不一样的项目结构等等情形。一键化的服务有一个通病,就是拓展起来比较麻烦,并且遇到沟坎的时候可能会使你立马处于举步维艰的地步,耗时耗力。javascript

最近,恰好接手一个项目遇到了这样的状况,因此把项目从建立到启动所遇到的一些自定义配置在这里作一下简单的总结。但愿对从此接手的项目开发过程有所助益,提高效率。css

开始

这里默认省略使用create-react-app(后面简称“CRA”)建立项目的过程,可自行搜网络查找相关资料学习。html

最初建立的项目结构是这样的: https://p1.ssl.qhimg.com/t01d970b68ef04d3735.pngjava

当前的react版本:16.13.1,当前的webpack版本:4.42.0,包管理工具:yarnnode

CRA项目构建基于的是react-scripts包,看不到任何关于eslint和webpack的配置,由于都在这个包里,这种设计的好处是,能够跟随react-scripts包的升级而升级,很是方便,项目也很是纯净,给开发者开箱即用的体验。react

可是若是不知足当前业务需求,须要自定义一些功能,那可能就没什么体验了。针对这种状况,咱们也有对应的策略,须要采起一些方法。目前主要方式有两种:webpack

  • 一种就是经过执行yarn eject。这里注意必须在项目没有改动以前执行才能够,并且是一次性的,不可逆的。package.json提供了这样一个命令,执行完会自动删除掉。
{
  ...
  "scripts": {
    "eject": "react-scripts eject"
  },
  ...
}
复制代码
  • 一种就是利用工具react-app-rewriedcustomize-cra来实现。在不透传CRA项目包里配置的前提下,覆盖和拓展webpack的配置项。这样比较简单干净,容易管理。

后面我会具体介绍。git

eject

执行完yarn eject,目录结构会变为下面所示: https://p1.ssl.qhimg.com/t01da0358df198b2ca0.pnggithub

咱们能够看到项目多了configscript两个文件夹。这里就是透传出来的CRA项目全部的webpack配置。web

package.json文件里也会变化,

"scripts": {
  "start": "node scripts/start.js",
  "build": "node scripts/build.js",
  "test": "node scripts/test.js",
  ...
},
...
"dependencies": {
  "@babel/core": "7.9.0",
  "@svgr/webpack": "4.3.3",
  "@testing-library/jest-dom": "^4.2.4",
  "@testing-library/react": "^9.3.2",
  "@testing-library/user-event": "^7.1.2",
  "@typescript-eslint/eslint-plugin": "^2.10.0",
  "@typescript-eslint/parser": "^2.10.0",
  "anujs": "^1.6.2",
  "babel-eslint": "10.1.0",
  "babel-jest": "^24.9.0",
  "babel-loader": "8.1.0",
  "babel-plugin-named-asset-import": "^0.3.6",
  "babel-preset-react-app": "^9.1.2",
  "camelcase": "^5.3.1",
  "case-sensitive-paths-webpack-plugin": "2.3.0",
  "css-loader": "3.4.2",
  "dotenv": "8.2.0",
  "dotenv-expand": "5.1.0",
  "eslint": "^6.6.0",
  "eslint-config-react-app": "^5.2.1",
  "eslint-loader": "3.0.3",
  "eslint-plugin-flowtype": "4.6.0",
  "eslint-plugin-import": "2.20.1",
  "eslint-plugin-jsx-a11y": "6.2.3",
  "eslint-plugin-react": "7.19.0",
  "eslint-plugin-react-hooks": "^1.6.1",
  "file-loader": "4.3.0",
  "fs-extra": "^8.1.0",
  "html-webpack-plugin": "4.0.0-beta.11",
  "identity-obj-proxy": "3.0.0",
  "jest": "24.9.0",
  "jest-environment-jsdom-fourteen": "1.0.1",
  "jest-resolve": "24.9.0",
  "jest-watch-typeahead": "0.4.2",
  "mini-css-extract-plugin": "0.9.0",
  "node-sass": "^4.14.1",
  "optimize-css-assets-webpack-plugin": "5.0.3",
  "pnp-webpack-plugin": "1.6.4",
  "postcss-flexbugs-fixes": "4.1.0",
  "postcss-loader": "3.0.0",
  "postcss-normalize": "8.0.1",
  "postcss-preset-env": "6.7.0",
  "postcss-safe-parser": "4.0.1",
  "react": "^16.13.1",
  "react-app-polyfill": "^1.0.6",
  "react-dev-utils": "^10.2.1",
  "react-dom": "^16.13.1",
  "resolve": "1.15.0",
  "resolve-url-loader": "3.1.1",
  "sass-loader": "8.0.2",
  "semver": "6.3.0",
  "style-loader": "0.23.1",
  "terser-webpack-plugin": "2.3.5",
  "ts-pnp": "1.1.6",
  "url-loader": "2.3.0",
  "webpack": "4.42.0",
  "webpack-dev-server": "3.11.0",
  "webpack-manifest-plugin": "2.2.0",
  "workbox-webpack-plugin": "4.3.1"
},
...
"jest": {
  "roots": [
    "<rootDir>/src"
....
.....
复制代码

此时,你只须要在config文件夹的webpack.config.js等文件里修改相关配置便可。(这里eject修改配置的方式,因为网络介绍已经不少,再也不赘述)

利用工具override配置

安装依赖

yarn add react-app-rewried customize-cra -D
复制代码

修改package.json

替换掉原来的react-scripts命令

{
...
  "scripts": {
  	"start": "react-app-rewired start",
    "build": "react-app-rewried build",
    "test": "react-app-rewired test --env=jsdom"
  }
...
}
复制代码

新建文件config-overrides.js

项目根目录新建文件config-overrides.js,通常选择在根目录建立,你也能够自定义目录,这里不作赘述。

格式
const {
  override,
  addWebpackAlias,
  addLessLoader,
  addDecoratorsLegacy,
  useEslintRc,
  ...
} = require('customize-cra');

const customize = () => (config) => {
	// 要自定义的配置内容
    ...
    return config;
}
...
module.exports = override(
  // 导入配置
  ....
);
复制代码

customize-craapi文档能够详细查看:github.com/arackaf/cus…,这里我只介绍下项目经常使用到的几个。

常规配置
// isEnvDevelopment和isEnvProduction用于项目当前环境判断
...
config.entry = multiConfigtools.allSitePath(isEnvDevelopment);
config.output.filename = isEnvProduction
  ? 'static/js/[name].[contenthash:8].js'
  : isEnvDevelopment && 'static/js/[name].bundle.js';
config.devtool = isEnvProduction ? false : 'source-map';
...
复制代码
plugin扩展
...
isEnvProduction &&
    config.plugins.push(
      new es3ifyPlugin(),
      ....
    );
...
复制代码
对于不须要的扩展,能够过滤掉
...
config.plugins = config.plugins.filter(
  (p) => p.constructor.name !== 'ManifestPlugin'
);
...
复制代码
添加别名
addWebpackAlias({
  'react': 'React',
  'views': path.resolve(__dirname, 'src/views'),
  '@': path.resolve(__dirname, 'src'),
  // '@': paths.appSrc
  ...
}),
复制代码
启用自定义eslint规则
// 开启才能使本地的.eslintrc.js文件生效
useEslintRc('.eslintrc.js'),
复制代码
引入less

默认支持了csspostcsssass,若是想使用less,必须单独添加。

yarn add less less-loader -D
复制代码

而后添加配置

addLessLoader({
  lessOptions: {
    javascriptEnabled: true,
    modifyVars: { '@primary-color': '#777' },
    ...
    localIdentName: '[local]-[hash:base64:8]'
    ...
  },
})
复制代码

自定义eslint配置

采用eject方式

新建.env文件,添加内容

EXTEND_ESLINT=true
复制代码

此时默认配置里就会启动根目录里的eslint配置。 这里我推荐使用结合prettier的方式,咱们知道prettier在代码的美化方面要比eslint更擅长。并且自动格式化的功能,更是开发过程当中的一把利器。

下面介绍下怎么使用,先执行命令安装

yarn add eslint-config-prettier eslint-plugin-prettier -D
复制代码

而后执行

eslint --init
复制代码

完以后,能够看到项目根目录多了一个.eslintrc.js的文件,接下来就在这个文件里来添加配置支持prettier

module.exports = {
	...
	extends: [
      'plugin:react/recommended', 
      'plugin:prettier/recommended',
    ],
    ...
    plugins: ['prettier'],
    ...
    rules: [
    	'prettier/prettier': 'error',
        ...
    ]
}
复制代码

配置完之后,咱们再新建.prettierrc,添加你须要约定的规则内容

{
  ...
  "singleQuote": true,
  "printWidth": 120,
  ...
}
复制代码

从新启动项目就生效了。

采用override工具配置方式

以前的内容里也提到了,此时也须要在项目根目录添加.eslintrc.js.prettierrc两个配置文件,而后在config-overrides.js里添加配置完成后启用便可。

// 开启才能使本地的.eslintrc.js文件生效
useEslintRc('.eslintrc.js'),
复制代码

添加提交时校验

咱们能够经过一些工具实现,在提交前prettier来优化代码,eslint校验代码。

husky

处理pre-commitpre-push等命令的工具

lint-staged

git暂存区代码,运行linter的工具

如何使用

  1. 安装依赖
yarn add lint-staged husky -D
复制代码
  1. package.json增长相关配置
{
  ...
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "src/*.{js,jsx,mjs,ts,tsx}": [
      "prettier --write",
      "eslint --cache --fix",
      "git add"
    ],
    "src/*.{css,scss,less,json,html,md,markdown}": [
      "prettier --write"
      "git add"
    ]
  }
  ...
}
复制代码
  1. 测试commit成功。

多页面导出配置

接手的这个项目须要打包导出不一样需求场景的页面,来提供给后端,此时CRA默认项目就没法知足了,那么怎么解决呢,下面介绍下如何实现

eject方式

  1. 项目结构(举例)
...
├── src
│   ├── components
│   ├── views
│       ├── demo
│       │   ├── demo.html
│       │   └── demo.js
│       └── index
│           ├── index.html
│           └── index.js
...
复制代码

分别添加不一样页面的htmljs的内容,这里不作赘述。 2. 修改config目录下paths.js文件 修改默认入口的路径

...
appHtml: resolveApp('src/views/index/index.html'),
appIndexJs: resolveModule(resolveApp, 'src/views/index/index'),
...
复制代码
  1. webpack配置

新建文件multi.config.js,添加内容

const path = require('path');
const glob = require('glob');
const paths = require('./config/paths');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const allSitePath = (isEnvDevelopment) => {
  let entryFiles = glob.sync(paths.appSrc + '/views/*');

  let map = {};
  entryFiles.forEach((item) => {
    let filename = item.substring(item.lastIndexOf('/') + 1);
    let filePath = `${item}/${filename}.js`;

    map[filename] = [
      isEnvDevelopment &&
        require.resolve('react-dev-utils/webpackHotDevClient'),
      filePath
    ].filter(Boolean);
  });

  return map;
}

const htmlPlugin = (isEnvProduction, isEnvDevelopment) => {
  let fileNameLists = Object.keys(
    allSitePath(isEnvDevelopment)
  );

  let arr = [];
  fileNameLists.forEach(item => {
    let filename = item.substring(item.lastIndexOf('/') + 1);

    arr.push(
      // Generates an `index.html` file with the <script> injected.
      new HtmlWebpackPlugin(
        Object.assign(
          {},
          {
            inject: true,
            filename: item + '.html',
            chunks: [item],
            template: path.resolve(paths.appSrc, `views/${filename}/${filename}.html`),
          },
          isEnvProduction
            ? {
                minify: {
                  removeComments: true,
                  collapseWhitespace: true,
                  removeRedundantAttributes: true,
                  useShortDoctype: true,
                  removeEmptyAttributes: true,
                  removeStyleLinkTypeAttributes: true,
                  keepClosingSlash: true,
                  minifyJS: true,
                  minifyCSS: true,
                  minifyURLs: true,
                },
              }
            : undefined
        )
      )
    );
  });

  return arr;
}

module.exports = {
  allSitePath,
  htmlPlugin
}
复制代码

webpack.config.js文件修改

...
const multiConfig = require('../multi.config');
...
entry: multiConfig.allSitePath(isEnvDevelopment),
...
...
filename: isEnvProduction
        ? 'static/js/[name].[contenthash:8].js'
        : isEnvDevelopment && 'static/js/[name].bundle.js',
...
...
plugins:[
	...multiConfig.htmlPlugin(isEnvProduction, isEnvDevelopment),
	...,
    ...,
]
...
...
// 注释掉 ManifestPlugin
new ManifestPlugin({s
  fileName: 'asset-manifest.json',
  ....
  ...
复制代码
  1. 执行

运行yarn build命令,能够看到build/两个html文件成功编译,启动项目经过不一样url查看不一样页面效果

override工具方式

这种方式更简单,只须要在config-overrides.js文件里作修改便可

...
...
const multiConfig = require('./multi.config');
const paths = require('react-scripts/config/paths');

paths.appHtml = `${paths.appSrc}/views/index/index.html`;
paths.appIndexJs = `${paths.appSrc}/views/index/index.js`;
...
...
// customize里添加
config.entry = multiConfig.allSitePath(isEnvDevelopment);
config.output.filename = isEnvProduction
  ? 'static/js/[name].[contenthash:8].js'
  : isEnvDevelopment && 'static/js/[name].bundle.js';
config.plugins = [
  ...multiConfig.htmlPlugin(isEnvProduction, isEnvDevelopment),
];
...
...
复制代码

运行项目,就能够看到编译多个页面成功。

其余

别名

alias: {
  ....
  '@': path.resolve(__dirname, '../src'),
  'react': 'anujs',
  ....
},
复制代码

省略路径后缀

extensions: ['.js', '.es', '.jsx', '.scss', 'less'],
复制代码

Error

........node_modules/neo-async/async.js:16
throw new Error('Callback was already called.');
...
Error: Callback was already called.
复制代码

采用eject方式,修改默认webpack配置来添加less支持的时候,遇到了这个报错仔细分析了下缘由,初步判断应该是执行顺序致使的问题。看了下webpack执行的原理,发现是loader加载的时候,是按照CRA项目默认配置的顺序执行,添加less-loader,放在了前面的位置,致使这个报错,因此less须要添加在sass配置以后,就能够顺利编译了。

这里强调一点,就是使用yarn eject方式来引入less配置的时候,最好是要注释掉sass的配置,否则会出现打包报错。因为项目安装sass依赖耗时比较长,网络情况不佳的状况下,下载失败的几率比较高。因此建议,直接用less取代掉sass,体验更高。记得删掉package.json里的sass依赖哦。

等等

结语

在这个项目的启动阶段,接触到的自定义配置十分有限,后续随着业务愈来愈复杂和使用场景愈来愈多,我会不断实时更新所遇到的问题和解决方案,供你们参考,但愿这篇文章对你能有稍许帮助。

相关文章
相关标签/搜索