在日常的開發過程當中,若是沒有手動地配置或者優化打包後尺寸,那麼用戶打開網站時,首屏加載會很慢,幾秒後纔出現內容,大大增長了用戶的等待時間。css
爲了解決這個問題,咱們須要從打包這個環節進行優化。常見的優化打包工具webpack
,咱們從流行的React
和Vue
庫着手,嘗試着優化它們。html
首先,使用create-react-app
腳手架建立一個React
應用。react
若是沒有,首先須要從全局中安裝腳手架,命令以下:webpack
npm install create-react-app -g
複製代碼
接着新建一個文件夾,並在終端編寫命令爲:web
create-react-app webpack-optimiation-react
複製代碼
模板建立完畢以後,它目錄結構以下:npm
.
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── README.md
├── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── serviceWorker.js
│ └── setupTests.js
├── tree.txt
└── yarn.lock
複製代碼
模板文件建好後,咱們在不一樣的庫中添加相同的代碼,而後使用router
使他們可以正常運轉。json
// src/Home.js
import React from 'react';
export default () => <h1>Home</>; 複製代碼
// src/About.js
import React from 'react';
export default () => <h1>About</>; 複製代碼
// src/Concat.js
import React from 'react';
export default () => <h1>Concat</h1>;
複製代碼
而後使用npm
包管理器,添加react-router-dom
。後端
npm install react-router-dom -D
複製代碼
以上的代碼寫好後,在src/index.js
中添加:跨域
import React, { lazy, Suspense } from 'react';
import { Switch, BrowserRouter as Router, Link, Route } from 'react-router-dom';
const Home = lazy(() => import('./Home'));
const Concat = lazy(() => import('./Concat'));
const About = lazy(() => import('./About'));
const NavBar = () => (
<div> <Link to='/'>Home</Link> <Link to='/about'>About</Link> <Link to='/concat'>Concat</Link> </div>
);
function App() {
return (
<Router className='App'>
<>
<NavBar />
<Suspense fallback={<div>loading...</div>}>
<Switch>
<Route path='/' exact component={Home} />
<Route path='/about' component={About} />
<Route path='/concat' component={Concat} />
</Switch>
</Suspense>
</>
</Router>
);
}
export default App;
複製代碼
編寫完成以後,使用npm run start
,瀏覽器會自動打開,並顯示:瀏覽器
OK,運行正常。
咱們打包試試,在終端輸入命令:
npm run build
複製代碼
這時就會出現一個build
文件夾,這就是打包後的結果,能夠給後端部署了。
build
以後的文件大小竟然有600k
之多,爲了查看具體那些包體積大,就須要配置webpack
。
可是create-react-app
並不暴露webpack
配置文件,須要輸入命令才能看到webapck
配置文件:
npm run eject
複製代碼
它的結構目錄以下:
.
├── config
│ ├── env.js
│ ├── jest
│ │ ├── cssTransform.js
│ │ └── fileTransform.js
│ ├── modules.js
│ ├── paths.js
│ ├── pnpTs.js
│ ├── webpack.config.js
│ └── webpackDevServer.config.js
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── README.md
├── scripts
│ ├── build.js
│ ├── start.js
│ └── test.js
├── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── serviceWorker.js
│ └── setupTests.js
├── tree.txt
└── yarn.lock
複製代碼
爲了解決上面打包600kb
的問題,首先須要分析,那些地方打包後的尺寸過大。咱們須要webpack-bundle-analyzer
這個插件才能可視化地看到那些包尺寸較大。
安裝方法:
npm install webpack-bundle-analyzer -D
複製代碼
而後在config/webpack.config.js
寫入代碼:
首先導入這個包,而後再添加插件:
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
plugins:[
...
isEnvProduction && new BundleAnalyzerPlugin(),
// isEnvProduction這個變量是指,在是否在生產環境
...
]
複製代碼
可視化分析出來的結果以下:
從上圖咱們能夠發現,打包後最大的包是react-dom
。
那麼咱們就着手優化它吧,從兩個方面考慮:
減小服務器端的壓力方法
減小尺寸的方法
減小服務器壓力的方法有:
使用AggressiveSplittingPlugin
插件
使用prefetch&preload
方法
使用gzip
方法
減小尺寸的方法有:
使用ModuleConcatenationPlugin
插件
使用uglifyjs
插件
使用exterals
選項
那麼如今試着用AggressiveSplittingPlugin
優化打包後的代碼吧。
直接在config/webpack.config.js
的plugins
中添加代碼:
plugins: [
new webpack.optimize.AggressiveSplittingPlugin({
minSize: 3000,
maxSize: 5000,
chunkOverhead: 0,
entryChunkMultiplicator: 1
})
];
複製代碼
build
文件夾中出現了很是多的小文件。這個插件是的超過必定體積會分割文件。有利於減小服務器的請求壓力。AggressiveSplittingPlugin
能夠將bundle
拆分紅更小的chunk
,直到各個chunk
的大小達到option
設置的 maxSize
。它經過目錄結構將模塊組織在一塊兒。
它記錄了在webpack Records
裏的分離點,並嘗試按照它開始的方式還原分離。這確保了在更改應用程序後,舊的分離點(和 chunk)是可再使用的,由於它們可能早已在客戶端的緩存中。所以強烈推薦使用Records
。
第二項是預加載的能力,和預請求能力。咱們先來看看使用preload
會怎麼樣?
個人html-webpack-plugin
版本是4.0.0-beta.11
,爲了正確安裝這個 preload 插件,必需要將它的版本設置爲3.0.0-beta.3
,否則它會報如下的錯誤:
Plugin could not be registered at 'html-webpack-plugin-before-html-processing'. Hook was not found.
複製代碼
使用npm
管理器安裝這個版本:
npm install preload-webpack-plugin@3.0.0-beta.3
複製代碼
接着在webpack.config.js
中添加:
const PreloadWebpackPlugin = require('preload-webpack-plugin');
// ...
plugins: [
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
)
),
// ...
new PreloadWebpackPlugin()
];
// ...
複製代碼
打開調試臺,若是的你節點出現了這樣的樣子,那麼說明你成功了。
後面有一個ref=preload
就是預加載。
preload
有能作什麼?舉個例子,若是一個網頁中使用了許多的字體文件,那麼用戶在瀏覽的時候就會等待字體加載,出現白屏的現象。
若是使用了preload
的話,那麼瀏覽器不會每次請求一個頁面,到指定頁面中從新加載,而是預先加載字體文件,到指定頁面中不用再次加載。
如今,咱們嘗試使用prefetch
,是什麼結果。
在config/webpack.config.js
修改:
// ...
plugins: [
// ...
new PreloadWebpackPlugin({
rel: 'prefetch'
})
// ...
];
// ...
複製代碼
這時,preload
就變成了prefetch
,以下圖:
若是你看到了ref=prefetch
的話,那麼表明預處理成功。
preload
會首先優先加載,而且會佔用HTTP
併發數,也就是剛進入頁面就會請求。而prefetch
會瀏覽器出空閒期時,再請求文件。preload
能夠跨域請求,prefetch
不會。gzip
可以爲咱們減小存儲空間,以及減小傳輸的時間。
咱們須要安裝一下插件:
npm install -D compression-webpack-plugin
複製代碼
在config/webpack.config.js
中添加:
//...
plugins: [
//...
new CompressionWebpackPlugin({
filename: '[path].gz[query]',
algorithm: 'gzip',
test: /\.(js|css)/,
threshold: 1024,
minRatio: 0.8
})
//...
];
//...
複製代碼
build
完成以後,打開瀏覽器。若是你看到這樣的header
的話,那麼表明開啓gzip
成功。
如今,咱們開始減小尺寸的優化。
使用ModuleConcatenationPlugin
能夠提升代碼的執行速度和預編譯:
在config/webpack.config.js
中添加:
// ...
plugins: [
// ...
new webpack.optimize.ModuleConcatenationPlugin()
];
// ...
複製代碼
build
後,尺寸比之前集減小接近10kb
,並且網頁的打開速度也提升很多。
相比以前打開網頁的速度,提升了50ms
之多。再加上懶加載,並不須要提早加載全部頁面,因此首屏渲染速度提升了許多。
接着,咱們上一個大殺器,UglifyJS
插件,它最大程度壓縮和醜化代碼。
安裝它的命令行:
npm install -D uglifyjs-webpack-plugin
複製代碼
導入到config/webpack.config.js
中,並使用:
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
// ...
optimization: {
minimizer:{
new UglifyJsPlugin(),
}
}
// ...
複製代碼
能夠看見,壓縮後的代碼有多180kb
。
externals
這個webpack
選項是在打包過程當中剔除掉。這樣就有效地減小了依賴,使用第三方CDN
減小HTTP壓力和請求的壓力。
使用externals
只須要在webpack.config.js
中添加一下的代碼:
// ...
externals:{
'react': 'React',
'react-dom': 'ReactDOM',
'react-router-dom': 'ReactRouterDOM'
}
// ...
複製代碼
接着在index.html
中添加CDN
。
<script src="https://cdn.bootcss.com/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdn.bootcss.com/react-router-dom/5.1.2/react-router-dom.min.js"></script>
<script src="https://cdn.bootcss.com/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
複製代碼