前言
Create React App是创建 React 应用的一个常用脚手架,它将一些复杂工具(比如webpack)的配置封装了起来,可以让使用者不关心这些工具的具体配置,因此在使用 create-react-app
生成项目时,不会有 webpack
的配置项。
如果开发者要导出 webpack
,必须使用 react-script eject
。但这是一个单向操作,eject
后,就无法恢复了。个人觉得这种操作十分不便利,远没有vue 自带vue.config.js 的配置这么方便可行。推荐使用react-app-rewired
和customize-cra
的插件组合,再通过 config-overrides.js 实现对 webpack 的自由配置。
项目使用 eject
如下所示,执行 create-react-app xx(项目名称)
初始化一个react 项目之后,在package.json
中可以看到如下脚本命令。
js
{
...
"scripts": {
"eject": "react-scripts eject"
},
...
}
执行命令:npm run eject
后,会将封装在 CRA 中的配置全部反编译
到当前项目,这样用户就可以完全取得 webpack 文件的控制权,可以随心所欲修改wabpack配置。如下所示。CRA与其他脚手架不同的地方在于可以通过升级其中的react-scripts
包来升级 CRA 的特性。但如果我们使用了eject
命令,就再也享受不到 CRA 升级带来的好处了,原因如下:
react-scripts 已经是以文件的形式存在于项目中,而不是以包的形式,所以无法对其升级。
js
config
├── env.js
├── jest
│ ├── cssTransform.js
│ └── fileTransform.js
├── paths.js
├── polyfills.js
├── webpack.config.dev.js // 开发环境配置
├── webpack.config.prod.js // 生产环境配置
└── webpackDevServer.config.js
项目使用react-app-rewired
react-app-rewired
是react社区开源的一个修改 CRA 配置的工具。纯 react-app-rewired
的方式自定义配置,可以参考 Extended Configuration Options 文档。这次我们使用customize-cra
协助react-app-rewired
自定义配置。 customize-cra
提供了一些api通常就可以满足大部分的开发需求。
在 CRA 创建的项目中安装了react-app-rewired
后,可以通过创建一个config-overrides.js
文件来对 webpack 配置进行扩展。具体操作如下:
- 安装 react-app-rewired customize-cra:
js
yarn add react-app-rewired customize-cra -D
- 修改package.json文件,替换其中的
react-scripts
:
js
......
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},
......
- 在项目根目录新建
config-overrides.js
js
const { override } = require('customize-cra');
module.exports = {
......
};
1. 常规开发配置(打包路径、gzip压缩、端口配置等)
通过从 customize-cra
中引入 override
,然后在 override() 中自定义打包配置相关函数,可以实现自定义的 webpack 配置。
js
const { override } = require("customize-cra");
const CompressionWebpackPlugin = require('compression-webpack-plugin');
const path = require("path");
// 修改端口号:
process.env.PORT = 8000;
// 常规配置
const addCustomize = () => (config) => {
if (process.env.NODE_ENV === 'production') {
// 配置打包后的文件位置(默认为build, 修改为dist)
const paths = require("react-scripts/config/paths");
paths.appBuild = path.join(path.dirname(paths.appBuild), "dist");
config.output.path = path.join(path.dirname(config.output.path), "dist");
// 添加js打包gzip配置
config.plugins.push(
new CompressionWebpackPlugin({
test: /.js$|.css$/,
threshold: 1024,
}),
)
}
if (config.resolve) {
config.resolve.extensions.push(".jsx");
}
return config;
};
module.exports = override(
addCustomize()
)
2. 通过postcss-pxtorem 插件实现移动端适配
实现移动端自适应布局的最简单方式就是通过 lib-flexible
和 postcss-pxtorem
插件实现。
- 安装插件:
js
yarn add lib-flexible postcss-pxtorem
- 项目主入口引入
lib-flexible
,该插件可以获取当前h5屏幕尺寸来设置跟元素的 font-size大小, 实现响应式的屏幕尺寸适配。
js
// 在 index.js 文件中引入
import 'lib-flexible';
- 再配置
postcss-pxtorem
插件,该插件的作用是可以把 px 尺寸转成 rem 尺寸,方便我们在写样式的时候,可以直接写 px 就可以了。在当前config-overrides.js 中配置目前发现一个问题。几乎网上全都是通过以下方法来解决适配问题:
js
const { override, addPostcssPlugins } = require("customize-cra");
module.exports = override(
......
addPostcssPlugins([require('postcss-pxtorem')({
rootValue: 37.5,
propList: ['*'],
minPixelValue: 2
})]),
)
但是本人在配置的时候始终不生效,最后不得已通过执行 npm run eject
后在修改webpack 配置,发现还是不生效,目前网上千篇一律的这种做法,也没有附带解决方案,感受到了极大的困扰。然后发现可以通过在pacakge.json 中的直接添加 postcss
配置实现,算是可以顺利解决这个问题。
js
...
"postcss": {
"plugins": {
"autoprefixer": {},
"postcss-pxtorem": {
"rootValue": 37.5,
"selectorBlackList": [],
"propList": [
"*"
]
}
}
},
...
3. 引入less 和 antd插件的按需加载
需要支持less 编写样式,需要安装依赖:less
、less-loader
、babel-plugin-import
; 再通过引入 antd
插件,具体的配置如下:
js
const { override, fixBabelImports, addLessLoader } = require("customize-cra");
module.exports = override(
// 针对antd实现按需打包: 根据import来打包(使用babel-plugin-import)
fixBabelImports("import", {
libraryName: "antd",
libraryDirectory: "es",
style: true, // 自动打包相关的样式
}),
// 使用less-loader对源码中的less的变量进行重新指定
addLessLoader({
javascriptEnabled: true,
modifyVars: { "@primary-color": "#1DA57A" }, // 自定义antd的主题
lessOptions: {
javascriptEnabled: true,
localIdentName: '[local]--[hash:base64:5]',
},
})
)
执行代码后发现奇怪的报错:
PostCSS Loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'plugins'. These properties are valid:
可以通过再加上如下配置解决:
js
adjustStyleLoaders(({ use: [, , postcss] }) => {
const postcssOptions = postcss.options;
postcss.options = { postcssOptions };
}),
4. 添加别名
配置别名后,可以在组件路径引入的地方统一通过别名处理, 例如组件引入: import Name from '@/pages/name'
, 例如文件路径引入:<img src={require("@/imgs/pa-icon.png")} alt="" />
等,采用别名的好处在于清晰文件路径,同时加快webpack构建的速度。
js
const { override, addWebpackAlias } = require("customize-cra");
module.exports = override(
// 配置路径别名
addWebpackAlias({
"@": path.resolve(__dirname, "./src")
}),
)
5. sourceMap控制
js
const { override, addWebpackAlias } = require("customize-cra");
const closedMap = () => config => {
config.devtool = config.mode === 'development' ? 'cheap-module-source-map' : false
return config
}
module.exports = override(
// sourceMap控制
closedMap(),
)
6. 添加 addWebpackExternals 排除打包插件,用来配置cdn地址
通过cdn引入能大大减少打包体积的大小,提升首页加载时间,可以通过以下方式实现:
js
const {override, addWebpackExternals} = require('customize-cra')
module.exports = override(
// 排除打包依赖插件,配置cdn引入
addWebpackExternals({
react: 'React',
'react-dom': 'ReactDOM',
'react-router-dom': 'ReactRouterDOM',
'redux': 'Redux',
'axios': 'axios'
})
)
同时在public 的index.html 文件中引入cdn地址:
js
<!-- react相关 -->
<script crossorigin src="https://unpkg.com/react@17.0.2/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/react-router-dom/5.3.0/react-router-dom.min.js"></script>
<script src="https://unpkg.com/redux@4.0.1/dist/redux.js"></script>
<!-- axios -->
<script type="text/javascript" src="https://unpkg.com/axios/dist/axios.min.js"></script>
7. addWebpackModuleRule 处理图片
addWebpackModuleRule 相当于webpack里Module的rules,通过规则匹配来处理。如下所示,通过加载插件url-loader
来处理图片,可以将 20kb 以下的图片转成base64, 超出则不处理,将文件输出到目录static/imgs/
下。
js
const {override, addWebpackModuleRule} = require('customize-cra')
module.exports = override(
addWebpackModuleRule({
test: /\.(png|jpg|gif|jpeg|svg)$/i,
use: [{
loader: 'url-loader',
options: {
limit: 20 * 1024,
esModule: false,
outputPath: `static/imgs/`,
},
},],
}),
)