關於React首屏白屏問題
阿新 • • 發佈:2018-12-16
主要從下面幾個地方優化:
- 在HTML內實現Loading狀態或者骨架屏
- 去掉外聯 css
- 快取基礎框架
- 使用動態 polyfill
- 使用 SplitChunksPlugin 拆分公共程式碼
- 正確地使用 Webpack 4.0 的 Tree Shaking
- 使用動態 import,切分頁面程式碼,減小首屏 JS 體積
- 編譯到 ES2015+,提高程式碼執行效率,減小體積
- 使用 lazyload 和 placeholder 提升載入體驗
------------------------------在HTML內實現Loading狀態或者骨架屏------------------------ 1. 普通的處理方式,自定義Loading介面 (可以載入SVG檔案) 2. 使用html-webpack-plugin自動插入loading var HtmlWebpackPlugin = require('html-webpack-plugin'); var path = require('path'); // 讀取寫好的 loading 態的 html 和 css var loading = { html: fs.readFileSync(path.join(__dirname, './loading.html')), css: '<style>' + fs.readFileSync(path.join(__dirname, './loading.css')) + '</style>' } var webpackConfig = { entry: 'index.js', output: { path: path.resolve(__dirname, './dist'), filename: 'index_bundle.js' }, plugins: [ new HtmlWebpackPlugin({ filename: 'xxxx.html', template: 'template.html', loading: loading }) ] }; 然後在模板中引用即可: <!DOCTYPE html> <html lang="en"> <head> <%= htmlWebpackPlugin.options.loading.css %> </head> <body> <div id="root"> <%= htmlWebpackPlugin.options.loading.html %> </div> </body> </html> 3.使用prerender-spa-plugin渲染首屏(參考: https://zhuanlan.zhihu.com/p/31954853) 在一些比較大型的專案中,Loading 可能本身就是一個 React/Vue 元件,在不做伺服器端渲染的情況下,想把一個已經元件化的 Loading 直接寫入 html 檔案中會很複雜,不過依然有解決辦法。 prerender-spa-plugin 是一個可以幫你在構建時就生成頁面首屏 html 的一個 webpack 外掛,原理大致如下: 1).指定 dist 目錄和要渲染的路徑 2).外掛在 dist 目錄中開啟一個靜態伺服器,並且使用無頭瀏覽器(puppeteer)訪問對應的路徑,執行JS,抓取對應路徑的 html。 3).把抓到的內容寫入 html,這樣即使沒有做伺服器端渲染,也能達到跟伺服器端渲染幾乎相同的作用(不考慮動態資料的話) plugins: [ new PrerenderSpaPlugin( path.join(__dirname, 'dist'), [ '/', '/products/1', '/products/2', '/products/3'] ) ]
------------------------------去掉外聯CSS------------------------
實際上,webpack 預設就是沒有外鏈 css 的,你什麼都不需要做就可以了。當然如果你的專案之前配置了 extract-text-webpack-plugin 或者 mini-css-extract-plugin 來生成獨立的 css 檔案,直接去掉即可。
------------------------------快取基礎框架-------------------------------------------------- HTTP為我們提供了很好幾種快取的解決方案,不妨總結一下: 1. expires expires: Thu, 16 May 2019 03:05:59 GMT 在 http 頭中設定一個過期時間,在這個過期時間之前,瀏覽器的請求都不會發出,而是自動從快取中讀取檔案,除非快取被清空,或者強制重新整理。缺陷在於,伺服器時間和使用者端時間可能存在不一致,所以 HTTP/1.1 加入了 cache-control 頭來改進這個問題。 2. cache-control cache-control: max-age=31536000 設定過期的時間長度(秒),在這個時間範圍內,瀏覽器請求都會直接讀快取。當 expires 和 cache-control 都存在時,expires 的優先順序更高。 3. last-modified / if-modified-since 這是一組請求/相應頭 響應頭: last-modified: Wed, 16 May 2018 02:57:16 GMT 請求頭: if-modified-since: Wed, 16 May 2018 05:55:38 GMT 伺服器端返回資源時,如果頭部帶上了 last-modified,那麼資源下次請求時就會把值加入到請求頭 if-modified-since中,伺服器可以對比這個值,確定資源是否發生變化,如果沒有發生變化,則返回 304。 4. etag / if-none-match 這也是一組請求/相應頭 響應頭: etag: "D5FC8B85A045FF720547BC36FC872550" 請求頭: if-none-match: "D5FC8B85A045FF720547BC36FC872550" 原理類似,伺服器端返回資源時,如果頭部帶上了 etag,那麼資源下次請求時就會把值加入到請求頭 if-none-match 中,伺服器可以對比這個值,確定資源是否發生變化,如果沒有發生變化,則返回 304。 上面四種快取的優先順序:cache-control > expires > etag > last-modified
------------------------------使用動態Polyfill----------------------------------------------
------------------------------使用SplitChunksPlugin拆分公共程式碼------------------------ Webpack 4 拋棄了原有的 CommonChunksPlugin,換成了更為先進的 SplitChunksPlugin,用於提取公用程式碼。 它們的區別就在於,CommonChunksPlugin 會找到多數模組中都共有的東西,並且把它提取出來(common.js),也就意味著如果你載入了 common.js,那麼裡面可能會存在一些當前模組不需要的東西。 而 SplitChunksPlugin 採用了完全不同的 heuristics 方法,它會根據模組之間的依賴關係,自動打包出很多很多(而不是單個)通用模組,可以保證載入進來的程式碼一定是會被依賴到的。 下面的配置物件代表了splitChunksPlugin的預設行為: splitChunks: { chunks: "async", minSize: 30000, minChunks: 1, maxAsyncRequests: 5, maxInitialRequests: 3, automaticNameDelimiter: '~', name: true, cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 }, default: { minChunks: 2, priority: -20, reuseExistingChunk: true } } } 參考:https://segmentfault.com/a/1190000015938570
------------------------------正確地使用Webpack 4.0的Tree Shaking-------------------------
去掉用不到的CSS, 使用purifycss-webpack
const path = require('path');
const glob = require('glob');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const PurifyCSSPlugin = require('purifycss-webpack');
module.exports = {
entry: {...},
output: {...},
module: {
rules: [
{
test: /\.css$/,
loader: ExtractTextPlugin.extract({
fallbackLoader: 'style-loader',
loader: 'css-loader'
})
}
]
},
plugins: [
new ExtractTextPlugin('[name].[contenthash].css'),
// Make sure this is after ExtractTextPlugin!
new PurifyCSSPlugin({
// Give paths to parse for rules. These should be absolute!
paths: glob.sync(path.join(__dirname, 'app/*.html')),
})
]
};
參考: https://www.npmjs.com/package/purifycss-webpack
----------------------------使用動態import切分頁面程式碼,減小首屏JS體積------------------------
import: 遵循元件化思想, 減少當前JS程式碼的體積,好處就是程式碼易讀,減少渲染,按需載入
--------------------編譯到ES2015+,提高程式碼執行效率,減小體積---------------------------------
遵循ES6語法
------------------------------使用Lazyload和placeholder提升載入體驗------------------------
1. placeholder: 簡單的處理方式, 給標籤設定預設值, 或者說是佔位符
2. Lazyload:懶載入
import React from 'react';
import ReactDOM from 'react-dom';
import LazyLoad from 'react-lazyload';
import MyComponent from './MyComponent';
const App = () => {
return (
<div className="list">
<LazyLoad height={200}>
<img src="tiger.jpg" /> /*
Lazy loading images is supported out of box,
no extra config needed, set `height` for better
experience
*/
</LazyLoad>
<LazyLoad height={200} once >
/* Once this component is loaded, LazyLoad will
not care about it anymore, set this to `true`
if you're concerned about improving performance */
<MyComponent />
</LazyLoad>
<LazyLoad height={200} offset={100}>
/* This component will be loaded when it's top
edge is 100px from viewport. It's useful to
make user ignorant about lazy load effect. */
<MyComponent />
</LazyLoad>
<LazyLoad>
<MyComponent />
</LazyLoad>
</div>
);
};
ReactDOM.render(<App />, document.body);
參考: https://www.npmjs.com/package/react-lazyload
歡迎糾錯