vue、react對於開發單頁應用來講帶來了很好的用戶的體驗,可是一樣有缺點,好比首頁加載慢,白屏或SEO等問題的產生。爲何會出現這種狀況呢?咱們以前開發單頁應用是這樣開發的,好比首頁 index.html頁面或許是這樣的:javascript
<!DOCTYPE html> <html> <head> <title>webpack+vue項目架構</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0"> </head> <body> <div id="app"> </div> </body> </html>
在理解以前,咱們仍是和以前同樣,先把咱們的整個項目架構是一個什麼樣的,來簡單的介紹下,方便有個簡單的理解:css
### 目錄結構以下: demo1 # 工程名 | |--- dist # 打包後生成的目錄文件 | |--- node_modules # 全部的依賴包 | |--- app | | |---index | | | |-- views # 存放全部vue頁面文件 | | | | |-- home.vue | | | | |-- index.vue | | | | |-- java.vue | | | | |-- node.vue | | | |-- components # 存放vue公用的組件 | | | |-- js # 存放js文件的 | | | |-- app.js # vue入口配置文件 | | | |-- router.js # 路由配置文件 | |--- views | | |-- index.html # html文件 | |--- webpack.config.js # webpack配置文件 | |--- .gitignore | |--- README.md | |--- package.json | |--- .babelrc # babel轉碼文件
而後咱們會經過webpack打包,將單頁應用中的入口文件打包到一個js文件中去,以下配置:html
module.exports = { // 入口文件 entry: { main: './app/index/app.js' }, output: { filename: process.env.NODE_ENV === 'production' ? '[name].[contenthash].js' : 'bundle.js', // 將輸出的文件都放在dist目錄下 path: path.resolve(__dirname, 'dist') }, plugins: [ new HtmlWebpackPlugin({ hash: true, //爲了開發中js有緩存效果,因此加入hash,這樣能夠有效避免緩存JS。 template: './views/index.html' // 模版文件 }), new ClearWebpackPlugin(['dist']), new ExtractTextPlugin("style.css"), ] };
基本的配置代碼如上所示,它會把 我項目中 /views/index.html 當作模板頁面,而後會把css文件樣式和js文件會自動打包到index.html文件中,以下打包後的文件代碼以下:前端
dist/index.html 源碼以下:vue
<!DOCTYPE html> <html> <head> <title>webpack+vue項目架構</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0"> <link href="style.css?26aab5a9debce5432af2" rel="stylesheet"></head> <body> <div id="app"> </div> <script type="text/javascript" src="bundle.js?26aab5a9debce5432af2"></script></body> </html>
如上打包的主頁index.html文件,咱們能夠看到,頁面上只用一個 div, 而後有一個樣式文件,和一個js文件。並無其餘的。
那麼如上代碼,咱們很容易想到會出現以下幾種缺點:第一:SEO不友好,也就是說,我經過百度或google搜索引擎搜索不到我網站的主頁到,第二是:很容易出現白屏狀況,爲何呢?由於我頁面中的全部的內容都是經過 bundle.js這個動態加載進行,那麼瀏覽器在加載及解析這段時間內,頁面會一直是空白的。也就是咱們說的白屏。固然對於咱們首頁來說,加載也是很是慢的。所以爲了解決這個問題,webpack中的有個插件 prerender-spa-plugin 能夠解決上面這些問題。java
咱們再使用這個插件以前,咱們來理解下咱們以前是怎麼樣進行SEO優化的呢?咱們很早很早以前,咱們是使用 velocity 語法來編寫頁面,而後寫完後把該頁面部署到開發那邊去,那麼這樣就要來回折騰了,而且咱們前端開發成本也很是高。node
那麼第二種方式是使用SSR技術(服務器端渲染),好比Nuxt.js,最主要的思想是,是經過Node.js完成渲染邏輯,而後會將html視圖直接返回給客戶端。這樣的方式也能夠解決SEO的問題。可是對於開發成本仍是比較大。好比須要考慮Node.js環境中的內存泄露,運行速度,併發壓力等問題。而且開發成本增長,研發週期變長等這些問題。所以咱們在webpack中有一種插件能將seo,首屏加載的問題能夠解決掉了。
2. 如何使用 prerender-spa-plugin ?react
1. 安裝命令以下:webpack
npm install prerender-spa-plugin --save-dev
2. 在咱們的webpack.config.js 須要配置以下:git
const PrerenderSPAPlugin = require('prerender-spa-plugin'); const Renderer = PrerenderSPAPlugin.PuppeteerRenderer; var config = { // 入口文件 entry: { main: './app/index/app.js' }, output: { filename: process.env.NODE_ENV === 'production' ? '[name].[contenthash].js' : 'bundle.js', // 將輸出的文件都放在dist目錄下 path: path.resolve(__dirname, 'dist') }, module: { // .... }, resolve: { extensions: ['*', '.js', '.json', '.vue', '.styl'] }, devtool: 'cheap-module-eval-source-map', devServer: { // .... }, plugins: [ new HtmlWebpackPlugin({ hash: true, //爲了開發中js有緩存效果,因此加入hash,這樣能夠有效避免緩存JS。 template: './views/index.html' // 模版文件 }), new ClearWebpackPlugin(['dist']), new ExtractTextPlugin("style.css"), // ..... ] } // 這裏判斷 若是是 正式環境打包的話,就使用該插件 if (process.env.NODE_ENV === 'production') { config.plugins.push( new PrerenderSPAPlugin({ staticDir: path.join(__dirname, '/dist'), // 列出須要預渲染的路由 routes: [ '/home' ], renderer: new Renderer({ inject: { foo: 'bar' }, // 監聽到自定事件時捕獲 renderAfterDocumentEvent: 'render-event' }) }) ) } module.exports = config;
下面咱們來理解下PrerenderSPAPlugin中的幾個配置的含義:
staticDir:指的是預渲染輸出的頁面地址。
routes: 指的是須要預渲染的路由地址。
renderer:指的是所採用的渲染引擎是什麼。目前用的是 V3.4.0 版本支持 PuppeteerRenderer。
inject:指的是預渲染過程當中能拿到的值。是否須要渲染這部分代碼,能夠經過該值進行判斷。好比以下代碼:
isshowRender() { if(window.__PRERENDER_INJECTED && window.__PRERENDER_INJECTED.foo =='bar') return; this.message = '我是測試預加載攔截'; }
上面執行的方法中的代碼是不會被渲染的。
renderAfterDocumentEvent的含義是監聽 document.dispatchEvent 事件,決定何時開始預渲染。
3. 所以在咱們的app.js 代碼要改爲以下:
import Vue from 'vue'; import Index from './views/index'; // 引入路由 import router from './router'; new Vue({ el: '#app', router: router, render: h => h(Index), mounted() { document.dispatchEvent(new Event('render-event')) } });
在實例化 new Vue的時候 在mounted生命週期中加上代碼:document.dispatchEvent(new Event('render-event')),觸發render-event事件進行渲染。
4. 路由配置(router.js):
import Vue from 'vue'; import VueRouter from 'vue-router'; // 引入組件 import home from './views/home'; import path from 'path'; // 告訴 vue 使用 vueRouter Vue.use(VueRouter); const routes = [ { path: '/home', name: 'home', component: resolve => require(['./views/home'], resolve), // 子路由 children: [ { path: 'java', name: 'java', component: resolve => require(['./views/java'], resolve) }, { path: 'node', name: 'node', component: resolve => require(['./views/node'], resolve) } ] }, { path: '*', // 其餘沒有的頁面都重定向到 home頁面去 redirect: '/home' } ] var router = new VueRouter({ mode: 'history', // 訪問路徑不帶井號 須要使用 history模式 // base: path.resolve(__dirname, '/app/index'), // 配置單頁應用的基路徑 routes: routes }); export default router;
如上路由配置聽說要改爲 模式變成 mode: 'history'這個模式,該插件纔會渲染。
當咱們認爲配置一切成功的時候,咱們在項目的根目錄中運行 npm run build 的時候,發現打包報錯了,以下提示:
Chromium revision is not downloaded. Run "npm install" or "yarn install" 提示這樣的信息,咱們能夠根據這個信息去百度搜索下,看看是什麼錯誤,百度結果後,他們的意思是須要咱們安裝 puppeteer 這個插件,可是安裝這個插件也是有條件的。咱們須要使用國內Chromium源.以下命令:
npm install -g cnpm --registry=https://registry.npm.taobao.org cnpm i puppeteer
以下圖所示:
一切安裝完成後,咱們再運行 npm run build 後能夠看到在咱們的dist目錄下 會多生成一個 home文件夾了,該文件夾有一個 index.html文件,以下所示:
生成完成後,咱們查看下 dist/home/index.html頁面變成以下:
<!DOCTYPE html><html><head> <title>webpack+vue項目架構</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0"> <link href="style.css?e8ed6db9e5869aa22ba2" rel="stylesheet"><script charset="utf-8" src="1.465ef3291c29aadf53df.js"></script></head> <body> <div id="app"><header><li class="router-link-exact-active router-link-active">Home</li> <a href="/home/java" class="">java</a> <a href="/home/node" class="">node</a></header> <div class="home-container"><h1>歡迎來到Home</h1> <p>我是Home組件</p> <!----></div></div> <script type="text/javascript" src="main.5aea82d6c5e9feeac132.js?e8ed6db9e5869aa22ba2"></script> </body></html>
如上代碼,能夠看到,咱們的路由 /home 確實全部的靜態頁面都渲染了。所以實現SEO,頁面不會白屏的問題已經解決了。
github源碼查看