create-react-app 使用技巧及源碼分析

create-react-app 使用技巧及源碼分析 | 前端必備

現在大部分 react 項目都是基於 create-react-app 初始化的,對於普通的項目,使用默認的 webpack 配置完全夠用。今天講講 create-react-app 的使用技巧和源碼分析。

如何加速構建

代碼寫多了會發現 webpack 每次啟動都很慢,可以通過刪除配置、添加多線程處理來優化體驗。

去除 eslint-loader

eslint-loader 的功能是將 eslint 檢測的內容顯示到命令行,如果確保寫的代碼沒有問題,可以去除掉。去除之後 webpack-dev-server 開啟速度明顯提升。

create-react-app 使用技巧及源碼分析 | 前端必備

去除上面的代碼

使用 thread-loader 或者 happypack

thread-loader 會將後面的 loader 放置在一個 worker 池裡面運行,以達到多線程構建。每個 worker 都是一個單獨的有 600ms 限制的 node.js 進程。同時跨進程的數據交換也會被限制,請在高開銷的loader中使用。

{
test: /\\.(js|ts)x?$/i,
use: [
'thread-loader',
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
},
},
],
exclude: /node_modules/,
},

happypack 通過多線程併發加速構建過程,不過包作者現在很少維護了,推薦用 thread-loader。配置略微複雜,而且對複雜的 js less 配置不太友好。

**exports.plugins = [
new HappyPack({
id: 'jsx',
threads: 4,
loaders: [ 'babel-loader' ]
}),
new HappyPack({

id: 'styles',
threads: 2,
loaders: [ 'style-loader', 'css-loader', 'less-loader' ]
})
];
exports.module.rules = [
{
test: /\\.js$/,
use: 'happypack/loader?id=jsx'
},
{
test: /\\.less$/,
use: 'happypack/loader?id=styles'
},
]**

偷懶的話選擇 thread-loader 就好了,加一行代碼。

react-app-rewired 和 customize-cra

如果不想 eject 項目怎麼辦?

react-app-rewired 可以在不 eject 也不創建額外 react-scripts 的情況下修改 create-react-app 內置的 webpack 配置,然後你將擁有 create-react-app 的一切特性,且可以根據你的需要去配置 webpack 的 plugins, loaders 等,推薦配合 customize-cra 一起使用。

使用方法

yarn add react-app-rewired customize-cra -D

更改 package.json

"scripts": {
- "start": "react-scripts start",
+ "start": "react-app-rewired start",
- "build": "react-scripts build",
+ "build": "react-app-rewired build",

- "test": "react-scripts test --env=jsdom",
+ "test": "react-app-rewired test --env=jsdom",
"eject": "react-scripts eject"
}

在根目錄下創建 config-overrides.js 文件,然後改成你想要的的配置

const {
override,
addDecoratorsLegacy,
disableEsLint,
addBundleVisualizer,
addWebpackAlias,
adjustWorkbox
} = require("customize-cra");
const path = require("path");
module.exports = override(
// enable legacy decorators babel plugin
addDecoratorsLegacy(),
// disable eslint in webpack
disableEsLint(),
// add webpack bundle visualizer if BUNDLE_VISUALIZE flag is enabled
process.env.BUNDLE_VISUALIZE == 1 && addBundleVisualizer(),
// add an alias for "ag-grid-react" imports
addWebpackAlias({
["ag-grid-react$"]: path.resolve(__dirname, "src/shared/agGridWrapper.js")
}),
// adjust the underlying workbox
adjustWorkbox(wb =>
Object.assign(wb, {
skipWaiting: true,
exclude: (wb.exclude || []).concat("index.html")
})
)
);

定製化使用

添加 less

create-react-app 中默認開啟了 sass 預處理器編譯,想使用 less 需要手工添加。另外對於 css modules ,其內置的做法是對這類文件使用 /\\.module\\.(scss|sass)$/ 或者 /\\.module\\.css$/ 檢測。如果想要直接對 .css 或 .less 開啟 css modules ,需要針對 src 和 node_modules 寫兩套 loader 處理規則,原因是 node_modules 下的文件不需要開啟 css modules。

create-react-app 內置規則

create-react-app 使用技巧及源碼分析 | 前端必備

create-react-app 使用技巧及源碼分析 | 前端必備

安裝 less 相關依賴

yarn add less less-loader -D

對 src 文件夾下面的 less、css文件進行處理,並開啟 css modules,這裡要注意下,這個配置不針對 node_modules 下的樣式文件,node_modules 下的不需要開啟 css modules,否則會出問題。

// webpack.config.js
{
test: /\\.(le|c)ss$/i,
use: [
isProd
? {
loader: MiniCssExtractPlugin.loader,
}
: 'style-loader',
{
loader: 'css-loader',
options: {
modules: {
localIdentName: isProd ? '[hash:base64]' : '[path][name]__[local]--[hash:base64:5]',
},
},
},
{
loader: 'less-loader',
options: {
javascriptEnabled: true,
},
},
].filter(Boolean),
include: srcPath,
},

對 node_modules 的配置

{
test: /\\.(le|c)ss$/i,
use: [
isProd
? {

loader: MiniCssExtractPlugin.loader,
}
: 'style-loader',
'css-loader',
{ loader: 'less-loader', options: { javascriptEnabled: true } },
].filter(Boolean),
include: [/node_modules/],
},

源碼分析總結

terser-webpack-plugin

較新版本的腳手架中使用 terser-webpack-plugin 壓縮 js 代碼,uglifyjs 已經不再推薦使用。

terser 這樣介紹:uglify-es不再維護,並且 uglify-js 不支持 ES6+ 語法。

create-react-app 使用技巧及源碼分析 | 前端必備

簡單使用

const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
optimization: {
minimize: true,
minimizer: [new TerserPlugin()],
},
};

file-loader 置於末尾,文件會被自動編譯到 static/media 目錄下

在 webpack.config.js 中,file-loader 是在 oneOf 的數組中的最後一個,表示任何一個文件如果沒有命中前面的任何一個 loader,則歸 file-loader 接管了。exclude 表示除了這些它都會處理,默認會重命名文件,並複製到 build/static/media/ 目錄下。

另外代碼中也給出了提示,不要在 file-loader 後面添加 loader,因為所有的剩餘文件都會被 file-loader 處理掉

// "file" loader makes sure those assets get served by WebpackDevServer.
// When you `import` an asset, you get its (virtual) filename.
// In production, they would get copied to the `build` folder.
// This loader doesn't use a "test" so it will catch all modules
// that fall through the other loaders.
{
loader: require.resolve('file-loader'),
// Exclude `js` files to keep "css" loader working as it injects
// its runtime that would otherwise be processed through "file" loader.
// Also exclude `html` and `json` extensions so they get processed
// by webpacks internal loaders.
exclude: [/\\.(js|mjs|jsx|ts|tsx)$/, /\\.html$/, /\\.json$/],
options: {
name: 'static/media/[name].[hash:8].[ext]',
},

},
// ** STOP ** Are you adding a new loader?
// Make sure to add the new loader(s) before the "file" loader.

file-loader 和 url-loader 的區別

看看 use-loader 的配置,會發現 options.name 和 file-loader 一致,其實 file-loader 是 url-loader 的退化版本,也就是當文件大小是小於 10000 byte 時,url-loader 會將文件轉換成 base64 編碼插入到 html 中,不作為獨立的一個文件,這樣可以減少頁面發起的請求數,如果超過這個大小,則使用 file-loader 將文件重命名並複製到 build/static/media 目錄下。

// "url" loader works like "file" loader except that it embeds assets
// smaller than specified limit in bytes as data URLs to avoid requests.
// A missing `test` is equivalent to a match.
{
test: [/\\.bmp$/, /\\.gif$/, /\\.jpe?g$/, /\\.png$/],
loader: require.resolve('url-loader'),
options: {
limit: imageInlineSizeLimit, // 內置的是 10000 byte
name: 'static/media/[name].[hash:8].[ext]',
},
},

momentjs 冗餘多語言文件

如果有用到 ant design 或直接使用了 momentjs,默認會把多語言的包(node_modules/moment/local/*.js)全部打進來,導致包體積增加,但是我們不會用到這麼多語言,所以需要排除掉。

plugins:[
...,
new webpack.IgnorePlugin(/^\\.\\/locale$/, /moment$/),

]

env

create-react-app 可以通過 .env.xxx 來預置一些參數到 process.env 中。具體源碼在 react-scripts/config/env.js 中。

核心代碼

// 從以下文件中加載配置
const dotenvFiles = [
`${paths.dotenv}.${NODE_ENV}.local`, // 推薦本地使用
`${paths.dotenv}.${NODE_ENV}`, // .env.production 或者 .env.development
NODE_ENV !== 'test' && `${paths.dotenv}.local`, // 推薦本地使用
paths.dotenv, // .env
].filter(Boolean);
// 將 .env 文件的設置應用到 process.env 中
dotenvFiles.forEach(dotenvFile => {
if (fs.existsSync(dotenvFile)) {
require('dotenv-expand')(
require('dotenv').config({
path: dotenvFile,
})
);
}
});

以 REACT_APP_ 開頭的將會通過 new webpack.DefinePlugin(env.stringified) 注入到全局的 process.env 中。

// env.js
const REACT_APP = /^REACT_APP_/i;
function getClientEnvironment(publicUrl) {
const raw = Object.keys(process.env)
.filter(key => REACT_APP.test(key))
.reduce(
(env, key) => {
env[key] = process.env[key];
return env;

},
{
// 預置的兩個
NODE_ENV: process.env.NODE_ENV || 'development',
PUBLIC_URL: publicUrl,
}
);
// Stringify all values so we can feed into Webpack DefinePlugin
const stringified = {
'process.env': Object.keys(raw).reduce((env, key) => {
env[key] = JSON.stringify(raw[key]);
return env;
}, {}),
};
return { raw, stringified };
}

所以你可以愉快的設置一些變量

// .env
REACT_APP_IS_ENV_FILE=TRUE
// .env.development
REACT_APP_IS_DEV_ENV_FILE=TRUE
// .env.local
REACT_APP_IS_LOCAL_ENV_FILE=TRUE

瀏覽器中打印

create-react-app 使用技巧及源碼分析 | 前端必備

babel-preset-react-app

babel-preset-react-app 是 create-react-app 下面的一個包,它是 create-react-app 默認的 babel preset,可以用來處理 js 和 ts 。這樣就不需要額外的配置 ts-loader,另外它內置支持了 ts 的一些功能(裝飾器等),可以很方便的使用。

create-react-app 使用技巧及源碼分析 | 前端必備

create-react-app 使用技巧及源碼分析 | 前端必備

在我們的 webpack 配置中引入 babel-preset-react-app 可以簡化配置。


歡迎大家關注我的掘金和頭條號,算法、TypeScript、React 及其生態源碼定期講解。

"


分享到:


相關文章: