在咱们使用create-react-app
建立react项目的时候,虽然能够享受一键带来的方便,可是封装好的不少配置并不能知足多元化项目的需求。咱们知道,不一样团队team可能制定有不一样的eslint规则,或者不一样的项目结构等等情形。一键化的服务有一个通病,就是拓展起来比较麻烦,并且遇到沟坎的时候可能会使你立马处于举步维艰的地步,耗时耗力。javascript
最近,恰好接手一个项目遇到了这样的状况,因此把项目从建立到启动所遇到的一些自定义配置在这里作一下简单的总结。但愿对从此接手的项目开发过程有所助益,提高效率。css
这里默认省略使用create-react-app
(后面简称“CRA”)建立项目的过程,可自行搜网络查找相关资料学习。html
最初建立的项目结构是这样的: java
当前的react版本:16.13.1
,当前的webpack版本:4.42.0
,包管理工具:yarn
node
CRA项目构建基于的是react-scripts
包,看不到任何关于eslint和webpack的配置,由于都在这个包里,这种设计的好处是,能够跟随react-scripts
包的升级而升级,很是方便,项目也很是纯净,给开发者开箱即用的体验。react
可是若是不知足当前业务需求,须要自定义一些功能,那可能就没什么体验了。针对这种状况,咱们也有对应的策略,须要采起一些方法。目前主要方式有两种:webpack
yarn eject
。这里注意必须在项目没有改动以前执行才能够,并且是一次性的,不可逆的。package.json
提供了这样一个命令,执行完会自动删除掉。{
...
"scripts": {
"eject": "react-scripts eject"
},
...
}
复制代码
react-app-rewried
和customize-cra
来实现。在不透传CRA项目包里配置的前提下,覆盖和拓展webpack的配置项。这样比较简单干净,容易管理。后面我会具体介绍。git
执行完yarn eject
,目录结构会变为下面所示: github
咱们能够看到项目多了config
和script
两个文件夹。这里就是透传出来的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修改配置的方式,因为网络介绍已经不少,再也不赘述)
yarn add react-app-rewried customize-cra -D
复制代码
替换掉原来的react-scripts
命令
{
...
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewried build",
"test": "react-app-rewired test --env=jsdom"
}
...
}
复制代码
项目根目录新建文件config-overrides.js
,通常选择在根目录建立,你也能够自定义目录,这里不作赘述。
const {
override,
addWebpackAlias,
addLessLoader,
addDecoratorsLegacy,
useEslintRc,
...
} = require('customize-cra');
const customize = () => (config) => {
// 要自定义的配置内容
...
return config;
}
...
module.exports = override(
// 导入配置
....
);
复制代码
customize-cra
的api
文档能够详细查看: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';
...
复制代码
...
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
...
}),
复制代码
// 开启才能使本地的.eslintrc.js文件生效
useEslintRc('.eslintrc.js'),
复制代码
默认支持了css
、postcss
和sass
,若是想使用less,必须单独添加。
yarn add less less-loader -D
复制代码
而后添加配置
addLessLoader({
lessOptions: {
javascriptEnabled: true,
modifyVars: { '@primary-color': '#777' },
...
localIdentName: '[local]-[hash:base64:8]'
...
},
})
复制代码
新建.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,
...
}
复制代码
从新启动项目就生效了。
以前的内容里也提到了,此时也须要在项目根目录添加.eslintrc.js
和.prettierrc
两个配置文件,而后在config-overrides.js
里添加配置完成后启用便可。
// 开启才能使本地的.eslintrc.js文件生效
useEslintRc('.eslintrc.js'),
复制代码
咱们能够经过一些工具实现,在提交前prettier
来优化代码,eslint
校验代码。
处理pre-commit
、pre-push
等命令的工具
对git
暂存区代码,运行linter
的工具
yarn add lint-staged husky -D
复制代码
{
...
"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"
]
}
...
}
复制代码
接手的这个项目须要打包导出不一样需求场景的页面,来提供给后端,此时CRA默认项目就没法知足了,那么怎么解决呢,下面介绍下如何实现
...
├── src
│ ├── components
│ ├── views
│ ├── demo
│ │ ├── demo.html
│ │ └── demo.js
│ └── index
│ ├── index.html
│ └── index.js
...
复制代码
分别添加不一样页面的html
和js
的内容,这里不作赘述。 2. 修改config目录下paths.js文件 修改默认入口的路径
...
appHtml: resolveApp('src/views/index/index.html'),
appIndexJs: resolveModule(resolveApp, 'src/views/index/index'),
...
复制代码
新建文件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',
....
...
复制代码
运行yarn build
命令,能够看到build/
两个html
文件成功编译,启动项目经过不一样url查看不一样页面效果
这种方式更简单,只须要在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'],
复制代码
........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
依赖哦。
等等
在这个项目的启动阶段,接触到的自定义配置十分有限,后续随着业务愈来愈复杂和使用场景愈来愈多,我会不断实时更新所遇到的问题和解决方案,供你们参考,但愿这篇文章对你能有稍许帮助。