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依賴哦。

等等

結語

在這個項目的啓動階段,接觸到的自定義配置十分有限,後續隨着業務愈來愈複雜和使用場景愈來愈多,我會不斷實時更新所遇到的問題和解決方案,供你們參考,但願這篇文章對你能有稍許幫助。

相關文章
相關標籤/搜索