create-react-app項(xiàng)目配置全解析
引言
create-react-app(以下簡稱cra)作為react官方提供的腳手架工具,是目前生成react項(xiàng)目一個(gè)非常常用和主流的工具。很多企業(yè)級的應(yīng)用搭建也是基于這個(gè)腳手架工具上二次開發(fā)。最近這段正好最近學(xué)習(xí)了webpack打包配置工程化的一些內(nèi)容,索性就以cra的配置為例,對這段時(shí)間的學(xué)習(xí)做一個(gè)總結(jié)。
準(zhǔn)備工作
首先,我們要用cra創(chuàng)建一個(gè)項(xiàng)目。這個(gè)沒啥好說,有手就行。
create-react-app cra-config-project
這樣初始化后創(chuàng)建出來項(xiàng)目的配置信息是隱藏在node_modules中的react-scripts中的。為了更直觀的看到配置信息和修改,使用
eject命令將配置彈射出來。
yarn eject
完成后,我們項(xiàng)目配置的目錄結(jié)構(gòu)變成這樣。

啟動(dòng)命令
打開package.json 文件,在scripts中看到以下三條命令
"scripts": {
"start": "node scripts/start.js",
"build": "node scripts/build.js",
"test": "node scripts/test.js"
},
很明顯,這分別是項(xiàng)目的啟動(dòng)開發(fā)環(huán)境,構(gòu)建,測試的命令。我們重點(diǎn)看一下scripts中開發(fā)和構(gòu)建的腳本。
start.js
在大概115行的位置,我們看到這樣一段代碼
const devServer = new WebpackDevServer(serverConfig, compiler);
// Launch WebpackDevServer.
devServer.startCallback(() => {
...
很明顯,這就是啟動(dòng)開發(fā)服務(wù)器的關(guān)鍵代碼。在開發(fā)環(huán)境的時(shí)候,我們通過webpack-dev-server來啟動(dòng)一個(gè)本地的服務(wù)器,然后把隨時(shí)構(gòu)建出來的項(xiàng)目放在這個(gè)服務(wù)器下面運(yùn)行。實(shí)例化這個(gè)devServer對象時(shí)候傳的第一個(gè)參數(shù)是服務(wù)器的配置項(xiàng),包括端口號,代理,靜態(tài)資源目錄等,具體見https://webpack.docschina.org/configuration/dev-server/;第二個(gè)參數(shù)是webpack的相關(guān)配置。如下所示:
compiler = webpack(config);
build.js
構(gòu)建腳本直接輸出打包結(jié)果,自然不再需要啟動(dòng)本地服務(wù)。因此在獲取了編譯結(jié)果后,直接運(yùn)行即可。因此在140行中
compiler.run((err, stats) => {
//...
}
在代碼中我們可以看到構(gòu)建時(shí),編譯過程通過promise封裝,對各種錯(cuò)誤情況進(jìn)行了處理。
目錄結(jié)構(gòu)
在看具體的配置之前,讓我們回到這張圖,看一下eject命令都彈射出了哪些配置放到了項(xiàng)目目錄中來。

比起初使?fàn)顟B(tài),現(xiàn)在的項(xiàng)目目錄中除了裝有啟動(dòng)腳本文件的目錄scripts外,另外增加的就是config目錄。打開config目錄,webpack.config.js和webpackDevServer.config.js赫然在目,根據(jù)這個(gè)文件名我們可以很明顯得知,這兩個(gè)文件一個(gè)是webpack的配置,一個(gè)是開發(fā)服務(wù)器devServer 的配置。接下來,我們就可以從這兩個(gè)文件按圖索驥,學(xué)習(xí)cra的基本配置了。
配置解析
weback.config.js
在webpack.config.js中,默認(rèn)導(dǎo)出了一個(gè)接受一個(gè)環(huán)境變量作為返回一個(gè)配置對象的方法。那傳這個(gè)環(huán)境變量的目的不言而喻,一定有很多配置開發(fā)和生產(chǎn)環(huán)境是不同的。接下來重頭戲來了,讓我們來一條一條地學(xué)習(xí)下react官方對react開發(fā)環(huán)境是怎么配置的吧。
1.entry
entry: paths.appIndexJs,
也就是 src目錄下的index.js,因?yàn)閏ra構(gòu)建的是單頁應(yīng)用,只有一個(gè)入口文件
2.output
output: {
path: paths.appBuild, // 打包后文件目錄 在config目錄中path.js中配置
pathinfo: isEnvDevelopment, // webpack 在 bundle 中是否引入「所包含模塊信息」的相關(guān)注釋 開發(fā)環(huán)境打開 生產(chǎn)環(huán)境關(guān)閉
filename: isEnvProduction
? 'static/js/[name].[contenthash:8].js'
: isEnvDevelopment && 'static/js/bundle.js',//打包后文件名,生產(chǎn)環(huán)境根據(jù)name放在不同文件,開發(fā)環(huán)境放在一個(gè)bundle.js文件中
chunkFilename: isEnvProduction
? 'static/js/[name].[contenthash:8].chunk.js'
: isEnvDevelopment && 'static/js/[name].chunk.js',//chunk文件名稱,生產(chǎn)環(huán)境和開發(fā)環(huán)境的區(qū)別是文件名中加上了hash
assetModuleFilename: 'static/media/[name].[hash][ext]',//打包后的靜態(tài)資源目錄和文件名規(guī)則,如不指定直接放在打包后的根目錄中
publicPath: paths.publicUrlOrPath,//打包后的文件部署的url地址
devtoolModuleFilenameTemplate: isEnvProduction
? info =>
path
.relative(paths.appSrc, info.absoluteResourcePath)
.replace(/\\/g, '/')
: isEnvDevelopment &&
(info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),//自定義source-map文件數(shù)組使用名稱
}
3.target
target: ['browserslist'],
構(gòu)建目標(biāo),從 browserslist-config 中推斷出平臺和 ES 特性 默認(rèn)是browserslist 如果browserslist不存在,為web(cra項(xiàng)目中browserslist在package.json中)
4.bail
bail: isEnvProduction,
錯(cuò)誤出現(xiàn)時(shí)是否立即退出,生產(chǎn)環(huán)境下打開
5.devtool
devtool: isEnvProduction
? shouldUseSourceMap
? 'source-map'
: false
: isEnvDevelopment && 'cheap-module-source-map',
生成sourceMap方式,cra配置為生產(chǎn)環(huán)境source-map,開發(fā)環(huán)境為cheap-module-source-map。這兩者的區(qū)別source-map調(diào)試時(shí)會(huì)顯示列信息。devtool的配置有很多種,具體見https://webpack.docschina.org/configuration/devtool/#root
6.cache
cache: {
type: 'filesystem',//緩存生成的 webpack 模塊和 chunk,來改善構(gòu)建速度 開發(fā)環(huán)境下默認(rèn)為type:'memory' 生產(chǎn)環(huán)境下關(guān)閉
version: createEnvironmentHash(env.raw),
cacheDirectory: paths.appWebpackCache,//緩存目錄
store: 'pack',
buildDependencies: {
defaultWebpack: ['webpack/lib/'],
config: [__filename],
tsconfig: [paths.appTsConfig, paths.appJsConfig].filter(f =>
fs.existsSync(f)
),
},
},
7.optimization 優(yōu)化項(xiàng)
optimization: {
minimize: isEnvProduction, //只在生產(chǎn)環(huán)境下開啟
minimizer: [
//js TerserPlugin開啟代碼壓縮
new TerserPlugin({
terserOptions: {
parse: {
ecma: 8,
},
compress: {
ecma: 5,
warnings: false,
comparisons: false,
inline: 2,
},
mangle: {
safari10: true,
},
keep_classnames: isEnvProductionProfile,
keep_fnames: isEnvProductionProfile,
output: {
ecma: 5,
comments: false,
ascii_only: true,
},
},
}),
//css 代碼壓縮
new CssMinimizerPlugin(),
],
},
8.resolve 解析
resolve: {
modules: ['node_modules', paths.appNodeModules].concat(
modules.additionalModulePaths || []
),//解析模塊時(shí)應(yīng)該搜索的目錄
extensions: paths.moduleFileExtensions
.map(ext => `.${ext}`)
.filter(ext => useTypeScript || !ext.includes('ts')),//如果有多個(gè)文件有相同的名字,但后綴名不同時(shí)webpack按順序解析這些后綴名,使用戶在引入模塊時(shí)不帶擴(kuò)展名
alias: {
'react-native': 'react-native-web',
...(isEnvProductionProfile && {
'react-dom$': 'react-dom/profiling',
'scheduler/tracing': 'scheduler/tracing-profiling',
}),
...(modules.webpackAliases || {}),
},//創(chuàng)建 import 或 require 的別名,來確保模塊引入變得更簡單??梢越outils之類的文件色之后
plugins: [
new ModuleScopePlugin(paths.appSrc, [
paths.appPackageJson,
reactRefreshRuntimeEntry,
reactRefreshWebpackPluginRuntimeEntry,
babelRuntimeEntry,
babelRuntimeEntryHelpers,
babelRuntimeRegenerator,
]),
],
},//應(yīng)該使用的額外的解析插件列表
9.performance
performance: false,
關(guān)閉了webpack本身的性能提示,cra本身提供了FileSizeReporter來計(jì)算和報(bào)告文件大小
10. module
終于進(jìn)入到我們這個(gè)比較重要的module配置項(xiàng),module配置決定了webpack如何解析非js的模塊,項(xiàng)目中的各種靜態(tài)資源,樣式文件,乃至于ts tsx jsx等loader配置都是在這個(gè)模塊中配置。
- source-map loader
shouldUseSourceMap && {
enforce: 'pre',
exclude: /@babel(?:\/|\\{1,2})runtime/,
test: /\.(js|mjs|jsx|ts|tsx|css)$/,
loader: require.resolve('source-map-loader'),
},
- 靜態(tài)資源loader
{
test: [/\.avif$/],
type: 'asset',
mimetype: 'image/avif',
parser: {
dataUrlCondition: {
maxSize: imageInlineSizeLimit,
},
},
},
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: imageInlineSizeLimit,
},
},
},
{
test: /\.svg$/,
use: [
{
loader: require.resolve('@svgr/webpack'),
options: {
prettier: false,
svgo: false,
svgoConfig: {
plugins: [{ removeViewBox: false }],
},
titleProp: true,
ref: true,
},
},
{
loader: require.resolve('file-loader'),
options: {
name: 'static/media/[name].[hash].[ext]',
},
},
],
issuer: {
and: [/\.(ts|tsx|js|jsx|md|mdx)$/],
},
},
對各種格式的圖片,svg文件的處理
- 樣式文件loader
{
test: cssRegex,
exclude: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: 'icss',
},
}),
sideEffects: true,
},
{
test: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: 'local',
getLocalIdent: getCSSModuleLocalIdent,
},
}),
},
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: 'icss',
},
},
'sass-loader'
),
sideEffects: true,
},
{
test: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: 'local',
getLocalIdent: getCSSModuleLocalIdent,
},
},
'sass-loader'
),
},
對樣式文件的處理, 主要是scss和css,cra 為什么沒有配置less文件loader呢?開發(fā)環(huán)境下直接將所有樣式注入head中style中,生產(chǎn)環(huán)境下結(jié)合下面要介紹的miniCssExtractPlugin插件抽出后放入不同css文件。另外,這里cra還對以.module.css 和 .module.sass后綴結(jié)尾的文件進(jìn)行了css module處理,如果開發(fā)者需要對樣式文件要用modules規(guī)則,可以將文件的后綴寫成這兩種。
11. 插件
- htmlWebpackPlugin
new HtmlWebpackPlugin(
Object.assign(
{},
{
inject: true,
template: paths.appHtml,
},
isEnvProduction
? {
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}
: undefined
)
),
沒啥好說的,地球人都知道的一個(gè)插件,把打包好的js文件注入到html中去,要注意的是在生產(chǎn)環(huán)境了開啟了移除注釋,合并空格一系列優(yōu)化配置
- InlineChunkHtmlPlugin
isEnvProduction &&
shouldInlineRuntimeChunk &&
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
這個(gè)插件輔助將一些chunk出來的模塊內(nèi)聯(lián)到html中,比如runtime的代碼,代碼量不大。生產(chǎn)環(huán)境下開啟
- InterpolateHtmlPlugin
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
HtmlWebpackPlugin的輔助插件,可以在html文件中加入變量
- ModuleNotFoundPlugin
- ReactRefreshWebpackPlugin
isEnvDevelopment &&
shouldUseReactRefresh &&
new ReactRefreshWebpackPlugin({
overlay: false,
}),
熱更新 react 組件,開發(fā)環(huán)境下開啟
- MiniCssExtractPlugin
isEnvProduction &&
new MiniCssExtractPlugin({
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
}),
抽離css文件插件,生產(chǎn)環(huán)境下開啟
- WebpackManifestPlugin
- ForkTsCheckerWebpackPlugin
useTypeScript &&
new ForkTsCheckerWebpackPlugin({
async: isEnvDevelopment,
typescript: {
typescriptPath: resolve.sync('typescript', {
basedir: paths.appNodeModules,
}),
configOverwrite: {
compilerOptions: {
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
skipLibCheck: true,
inlineSourceMap: false,
declarationMap: false,
noEmit: true,
incremental: true,
tsBuildInfoFile: paths.appTsBuildInfoFile,
},
},
context: paths.appPath,
diagnosticOptions: {
syntactic: true,
},
mode: 'write-references',
},
issue: {
include: [
{ file: '../**/src/**/*.{ts,tsx}' },
{ file: '**/src/**/*.{ts,tsx}' },
],
exclude: [
{ file: '**/src/**/__tests__/**' },
{ file: '**/src/**/?(*.){spec|test}.*' },
{ file: '**/src/setupProxy.*' },
{ file: '**/src/setupTests.*' },
],
},
logger: {
infrastructure: 'silent',
},
}),
強(qiáng)制ts類型檢查,如果項(xiàng)目使用了typescript編寫的話使用
- webpack.definePlugin
new webpack.DefinePlugin(env.stringified),
wepack內(nèi)置插件,在瀏覽器環(huán)境中定義環(huán)境變量
- webpack.ignorePlugin
new webpack.IgnorePlugin({
resourceRegExp: /^\.\/locale$/,
contextRegExp: /moment$/,
}),
wepack內(nèi)置插件,可以在打包時(shí)有選擇的忽略一些內(nèi)容,這里的配置是在打包moment的時(shí)候忽略moment的本地化內(nèi)容
isEnvDevelopment && new CaseSensitivePathsPlugin(),
解決為了解決mac系統(tǒng)中文件名大小寫不敏感導(dǎo)致的打包不報(bào)錯(cuò)的問題,詳見https://github.com/facebook/create-react-app/issues/240
結(jié)語
對于工程化經(jīng)驗(yàn)特別少的開發(fā)者來說,webpack的配置浩如煙海,宛如一本百科全書讓人望而興嘆。但是掌握webpack可以說是前端開發(fā)者進(jìn)階的必經(jīng)之路。在學(xué)習(xí)的過程中,可以自己多搞一些demo,多去嘗試和實(shí)踐,就會(huì)漸漸的對它熟悉起來。之后,筆者計(jì)劃對webpack打包的性能優(yōu)化從配置項(xiàng)的各個(gè)維度做一個(gè)總結(jié),請拭目以待。
以上就是create-react-app項(xiàng)目配置全解析的詳細(xì)內(nèi)容,更多關(guān)于create-react-app項(xiàng)目配置的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
在?React?中如何從狀態(tài)數(shù)組中刪除一個(gè)元素
這篇文章主要介紹了在?React?中從狀態(tài)數(shù)組中刪除一個(gè)元素,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03
react中關(guān)于Context/Provider/Consumer傳參的使用
這篇文章主要介紹了react中關(guān)于Context/Provider/Consumer傳參的使用,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09

