最近寫的文章不少,可是很是不愛發佈文章了,直到我最近用 notion 搭建了個人博客,從新找回了寫文章的初衷,近幾年一直在致力於後端,這個教程是我之前閒着沒事研究前端架構寫的,此次整理博客所有遷移到 notion 上去,順便整理合並,校對了一下,感興趣的朋友能夠預覽一下,專業的前端同窗能夠不用看了,基於 vue2.0 完成這個教程已是兩年前了,對於專業的大家可能有些過期javascript
個人 notion 線上 blog : niubility.mecss
遷移是個體力活,感興趣的同窗,我能夠出個教程,notion 可見便可得html
這個系列我所有采用了目前比較主流技術棧
這個系列文章我儘可能照顧前端的同窗和後端的同窗,不穿插講述。儘可能按照前端-後端-部署-運維來說,固然中途涉及到跨域、Rest、oAuth2.0這種先後協調的仍是沒法避免捎帶一筆。 前端
好比:vue
這種目錄閱讀可能對於只掌握 前端/後端 一種技術棧的同窗,或只想去看後端/前端/運維 的同窗看上去很是的難受、我會修改大綱避免這個問題,按部就班的來。java
如何從零打造一個先後分離的互聯網主流 WEB 項目、真心但願我能幫到大家。node
在校學習和幾年工做工做中不知不覺經歷了一半的 WEB 歷史演變、對近幾年的發展比較瞭解,結合經驗聊聊 WEB 發展歷史。
演變不易,但也是必然,由於爲人始終要進步。python
這是 1997 年 Apple 官網,那時的網站不如叫網頁,像一張浮誇的彩色報紙,那時是純粹的 HTML 時代,無論你是否是訪問這個網頁,每一個頁面都是在服務器上存在的。react
隨後技術性強一點的網站可能會經過 CGI Perl 運行一小段代碼與數據庫或文件系統進行交互。好比:jquery
這是1998 年的 Google ,爲了達到搜索條件,不可能用大量的人力去堆砌靜態頁面,因此使用這種方式「曲線救國」,可是 CGI 伸縮性不是太好:每一個請求分配一個新的進程,不太安全(直接使用文件系統或者環境變量),同時也沒提供一種結構化的方式去構造動態應用程序。
靜態網站是最受搜索引擎歡迎的網站,由於它相對固定,因此網站 SEO 很是好作,我猜想這也是爲何如今的文檔網站大部分都是靜態網頁的緣由之一吧。
很惋惜我沒能親眼看一看這樣的時代
2005 年左右,前後出現了 微軟的 ASP 和 Java Server Pages [JSP] 等技術,取代了 CGI ,加強了 WEB 與服務端的交互的安全性、用起來也更加簡單,但隨着各個公司WEB業務的複雜性,缺點也逐漸暴露出來:
一、技術單一,難以維護
JSP頁面由HTML代碼和嵌入其中的Java代碼所組成,用一個比較常見的 JSP 代碼段舉例:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %> <%@ page import="com.zifangsky.OnlineFriend.model.article.ShowByPage"%> <jsp:useBean id="showAllTitle" type="com.zifangsky.OnlineFriend.model.article.ShowByPage" scope="session"/>
JSP = HTML+Java
上面的代碼 HTML 中大量耦合了JAVA代碼,經過JSP編譯以後能夠在客戶端充當部分服務端的角色,這讓咱們難以搞清服務端的角色,以及增長調試的複雜度。業務稍微複雜一點,試想一下:HTML中摻雜了太多java代碼,不管是開發仍是維護都是一件痛苦的事情。
二、不不夠靈活
JSP與Java Servlet同樣,是在服務器端執行的,一般返回該客戶端的就是一個HTML文本。咱們每次的請求:獲取的數據、內容的加載,都是服務器爲咱們返回染完成以後的 DOM,這也就使得咱們開發網站的靈活度大打折扣,在這種狀況下,同年:Ajax火了。
爲何說 2005 年 Ajax 火了?由於 Ajax 技術並非 2005 年出現的,他的雛形是 1999 年。
1999年,微軟公司發佈IE5,第一次引入新功能:容許javascript腳本向服務器發起HTTP請求[這也就是今天萬惡的 ActiveX 原型]。這個功能當時並無引發注意,直到2004年Gmail發佈和2005年Google Map發佈,才引發普遍重視
Google作了什麼事兒?
在 2005 年,Google 經過其 Google Suggest 使 AJAX 變得流行起來,他大概是這樣的事情:
如今看來很常見的技術手段,當時迅速燃爆了技術圈,以此來實現:異步交互
這樣既能增長用戶的體驗,又能替代掉頁面部分的服務端代碼,今後, AJAX 成爲腳本發起 HTTP 通訊的代名詞,次年 W3C 也在 2006 年發佈了 AJAX 的國際標準
總結:
隨後各類 JSP ASP 的改良模板引擎、全新的交互方式也如雨後春筍通常涌現。而且以 JAVA 做爲服務端也出現瞭如 Struts 、 Spring、Hibernate 的老一代框架、採用後端 MVC 的方式讓構建 WEB 應用再一次更加健全, WEB 服務正在逐漸由石器時代走向文明時代。
時光啊不斷地飛逝,前端後端也出現了幾個潮流。
移動端
手機已經發展出了一些苗頭,網頁也區分了web和移動應用兩種模式,但移動端限制於當時手機行業的技術,發展較慢。
Jquery的出現
出現了很是流行的JavaScript庫:jquery,可以快速構建動態、美妙的web應用,完美的封裝了Ajax,讓開發者開發網頁變得優雅。
SPA的雛形
隨着文明時代 Ajax 正式提出,加上 CDN 開始大量用於靜態資源存儲,因而出現了 SPA (Single Page Application 單頁面應用),Backbone EmberJS AngularJS 這樣一批前端框架隨之出現,但以當時的配套技術來講,SPA 道路並很差走:例如 SEO 問題、SPA 過多的頁面、複雜場景下 VIEW 的綁定等,都沒有很好的處理。
Struts 、 Spring、Hibernate 通過幾年的發展、SSM這個今天被咱們說爛了的詞、當時幾乎成了當時 JAVA 服務端的 首要選型,我想這也是爲何不少公司、或外包公司依然維護這樣一套架構的主要緣由。
總結
這幾年的飛速發展,爲咱們節約了大量的經歷、下降了開發者和開發過程的門檻,極大提高了開發效率和迭代速度,我稱之爲工業時代
經歷
說出來你可能不信:大三快結束時實習求得的第一份工做,一我的斷斷續續開發七、8個月,就是鑽研這些自技術棧,獨立開發出一款web應用 微寶創業,慚愧的說:
項目架構從文明時代 -> 走到最後的工業時代!不斷的重構,不斷的上線 ,拼命的學習,我很感謝當時老闆對個人信任和同事對個人幫助。
時光啊他一刻不停,直到今天 -- 技術只能用爆炸來形容。
工業時代提出的 SPA 模型隨着 NODE 的興起、服務端、各類工具、容器的飛速發展、前端 MVC MVVM 模式逐漸清晰、前端涌現了至關一批優秀的開源項目:
包管理: npm yarn
打包:grunt gulp
模塊加載:RequireJS SeaJs
框架:VUE Angular React
hybrid :ionic weex react-native electron
預處理器:less sass
數據可視化:echarts hcharts
以及提高用戶體驗的動畫,讓咱們更有「面子」
甚至前端也能夠使用 Node 來構建本身簡單的服務端、正在逐漸擺脫「客戶端開發者」的角色
go
更適合面向服務器編程,之前你若是使用C或者C++作的那些事情,用Go來作很合適,例如、虛擬機處理、文件系統等,強如 docker Kubernetes(k8s)都是 GO 寫的
python
像一門生物語言,目前看來更容易處理算法、人工智能、網絡爬蟲、運維方向
java
一款20多年的語言,不斷的變強。涌現了不少高質量的庫,幾個有表明性的:
netty rebbitmq:輕鬆實現消息隊列
elasticSearch: 輕鬆實現搜索引擎
spring-boot: 面向配置,更加輕鬆的構建web服務端
spring-cloud、dubbo: 輕鬆構建微服務
以及即將迎來的 強悍的JAVA11
還有 持續集成 雲服務 devops 等運維相關
總結
go 和 python 的出現讓咱們服務端開發者能作更多的事情,好比自動化運維、寫中間件。逐漸偏向全棧方向發展。而 JAVA 20多年來的生態圈子發展,能幫助咱們寫出更健壯的服務。以及狠狠向咱們砸來的:人工智能、devops、雲服務等技術,令咱們眼花繚亂,開源成爲了一種潮流,技術分享成了每一個人都想作的事情,我稱之爲:技術爆炸的時代
經歷
我近兩年很煩惱:如何才能讓先後端更加優雅的通訊?
曾經使用多種後端模板引擎直到徹底摒棄,後到 node 作代理、渲染 + grunt 進行數處理,以後逐漸使用
vue + webpack ------> Rest API
這樣若是不得不用 NODE 也只會成爲 Rest 中的一員而不用通過 NODE 作繁瑣的通訊了。這種先後分離的方式達到了滿意的效果,前端沒必要再管後端的事情,後端?寫好本身的服務就行了。
先後分離,一直是一個至關泛泛的問題,先後分離到底好很差?沒有絕對的對,沒有絕對的錯,業界就這個問題已經激烈的探討幾年了.出現討論的點在於:分離固然是好的,可是以什麼樣的服務須要進行先後拆分?拆分到什麼粒度?先後端如何配合?
截圖時間: 2018-08-30 - Github
咱們隨意在 Github 輸入先後分離關鍵字,看下搜索的結果: 1K 的庫 11k 的 Issues 足以說明先後分離的趨勢,能夠想象激烈程度,業界比較有名的討論:Web 先後端分離的意義大嗎?,值得一提的是:前排對於這個問題討論比較深入的大部分都是全棧工程師。由於全棧對全局的瞭解相對比單純作前端、後端全局觀念更強一些,考慮的問題更多一些。
在公司的簡歷庫隨手截幾個局部的圖,近兩年面試過不少的 1-3 年 java 開發者,在篩選簡歷和麪試過程當中,也發現了幾個問題:至關多一部分 javaer 技術棧上老是多了那麼個 HTML ajax jquery bootstrap easyUi ,看起來很唐突,若是面試提到了前端技術棧,基本沒有能答的很好的,甚至有的人連 原型鏈 都不知道。這也是大部分人對全棧的誤解,其實我是不太感冒這樣的簡歷的,由於沒有什麼亮點,技術棧不是寫的越多越好,總結起來:他們對前端的掌握很基礎,勉強能勝任一些業務上的工做。那爲何這麼多人都掌握一些前端技術呢?我分析可能有三點:
由於我也維護過幾個月的敏感項目,深有體會,只寫服務端的人是沒法勝任這項工做的,
若是多數的 開發者 這樣的簡歷,能夠推測:如今的 IT 行業中先後端糅雜一塊兒的架構仍是存在、而且有一個量級,這致使他們不得不尋找一些懂得一點前端技術的人來開發項目,減小溝通的成本,加快項目的進度,這也就催生了不少所謂的 web 開發培訓機構。
你問我當年維護的開心嗎?一會告訴你。
先後端分離並非什麼新鮮事,處處都是先後端分離的實踐。然而一些歷史項目在從一體化 Web 設計轉向先後端分離的架構時,不可避免的會遇到各類各樣的問題。因爲層出不窮的問題,甚至會有團隊質疑,一體化好好的,爲何要搞先後端分離?說到底,仍是技術和思惟方式沒轉變過來。
一體化模式其實在上一開篇:縱觀歷史演變 中已經提到過了,不在贅述。
先後分離看起來應該是這樣的:
先後分離就是在架構層次上 構建項目或對現有的項目 客戶端 服務端 分離開,減小先後端代碼的耦合度,你們一致認同的先後端分離的例子就是SPA(Single-page application) ,全部用到的展示數據都是後端經過 JSON 但不只限於 JSON 的方式提供的,前端只管展示,提供更好更絢的交互,後端只管提供更健壯的高可用服務。
千萬不要有先寫項目,寫完再重構的想法,項目初期能一步到位最好,何須再去重構,而後不得已拋棄一些已經寫完的組件、庫浪費人力呢?
好的開發者是可與不可求的,若尋找一個 優秀的 full_stack 更是難,從校招進行培養也不太實際,招一個能力通常的程序員,技術驅動性比較差,甚至拖慢產品迭代。分離開來咱們就能夠專一於 前端、 服務端 領域去尋找專業的人才。
前端後端代碼大量耦合代碼看起來是這樣的:
看看簡單例子吧:
//(node端處理) if (is_weixin()) { init([ 'api', 'image', 'xxx', '...', ], function () { <%- doSomeGloble %> }); } else { } //接收node端一些數據 let blogs = <%- blogs %>; let users = <%- users %>;
這還不是 JAVA 模板 而是相對輕量、優雅的 NODE 的 EJS 渲染的,這還好,我見過更使人難受的代碼,常常爲了一個問題要回頭看 N 多代碼,這裏就不寫了。
那麼先後分離如何讓它們解耦變得更清晰?後面會結合服務端統一補充。
你說你搭建一個博客、 API 文檔系統 這種小項目,一我的就能夠開發。搞了一個先後分離,須要分離部署。又增長了 SEO 的複雜度,增長了開發的週期、增長了用戶部署的難度,何須呢?固然,若是隻是技術實踐的一種學習方式,仍是歡迎的。
前端妹子:哥,獲取所有博客調哪一個接口?哦,昨天不是發你文件了嗎
前端妹子:我找不到了😭
哦,等下吃晚飯發你啊
前端妹子:哥,產品提出根據手機殼自動換主題的需求,你有接口嗎?... 我看看...應該...沒有!可能須要 Python 的老哥支持!你去找他要吧
前端妹子:哥,你根據 TAG 獲取博客的接口寫完了沒?接口是啥?我好渲染數據啊沒等等......
這顯然是不規範的,咱們指望的是前端後端先約定一個接口協議,後端沒完成時,前端本身 mock 測試數據,前端找不到接口的時候,直接查看 API ,根據這個接口協議咱們先後端統一編程,那麼咱們如何處理呢?
如何下降溝通成本?
後端數據服務化,走統一的 REST 接口規範輸出,下降先後端接口定義的溝通成本。避免「口頭說明」的方式。
什麼是 RESTful API ?
因此RESTful API就是REST風格的API。 那麼在什麼場景下使用RESTful API呢?在當今的互聯網應用的前端展現媒介很豐富。有手機、有平板電腦還有PC以及其餘的展現媒介。那麼這些前端接收到的用戶請求統一由一個後臺來處理並返回給不一樣的前端確定是最科學和最經濟的方式,RESTful API就是一套協議來規範多種形式的前端和同一個後臺的交互方式。
我一般用 swagger + mock 平臺生成標準的 RESTful API,同時也支持擴展多個編程語言例如: Go Python
以 vue + webpack 的 SPA 爲例,沒有了後端模板返回的 HTML,前端渲染並不被搜索引擎的爬蟲接納。在往後實戰 SEO 以前先通俗渲染唄爬蟲識別的區別:
seo 本質是一個服務器向另外一個服務器發起請求,解析請求內容。但通常來講搜索引擎是不回去執行請求到的 js 的。也就是說,若是一個單頁應用,html 在服務器端尚未渲染部分數據數據,在瀏覽器才渲染出數據,而搜索引擎請求到的 html 是沒有渲染數據的。 這樣就很不利於內容被搜索引擎搜索到。 因此服務端渲染就是儘可能在服務器發送到瀏覽器前 頁面上就是有數據的。
以博客爲例簡單聊聊:
<div>我是正文1</div> <div>我是正文2</div> <div>我是正文3</div>
爬蟲直接抓到 html 解析 - 生成索引
@RequestMapping("/index") public String index(HttpServletRequest request,HttpServletResponse response){ return "welcome"; }
這裏就比較有意思了,好比咱們打開的網址是:
http://host:port/index
實際充當 Controller 的是 服務端,服務端直接返回渲染好的網頁給你,爬蟲拿到的也是同樣,因此 SEO 沒啥太大的問題。
let blogs = []; this.axios.get('/index, {}) .then(res => { blogs = res.data; }) .catch(err => { console.error(err); }); <!--前端模板渲染dom--> <div v-for="(item, index) in blogs" :key="item">
一樣咱們輸入http://host:port/index
注意:SPA 一般有本身的路由策略,這也就是前端 MVC MVVM 中的 第一個 M
一個典型的 SPA 站點
咱們輸入網址先到了這個頁面,而後再去異步請求服務器,再由前端頁面渲染,又是單頁面服務若是咱們不作任何處理,那麼你將被各大搜索引擎拋棄。
如何解決?
只要作 SEO 的產品就要作服務端渲染,若是你對 SEO 需求有,但要求並不高,僅部分頁面、能夠曲線救國
nodejs 出現以前有兩種解決方式,一是作一動一靜兩套頁面,服務器判斷請求來自蜘蛛就呈現靜態頁,不然呈現動態頁;二是服務器架設虛擬瀏覽器軟件,請求過來了先讓虛擬瀏覽器跑一遍,再將獲得的靜態頁面返回給客戶端。這兩種方式在大型項目上都有性能問題。
有了 nodejs 後主流作法是先後端同構方案,即一套代碼在瀏覽器端和 node 端均可以運行,從而能夠先在 node 端請求數據渲染模板,而後將渲染結果返回給瀏覽器最終呈現。感興趣能夠看看
Vue 的SSR方案
Angular 的SSR方案
如何更細緻的研究 SEO 之後再說
因爲採用先後端分離部署,天然不在一個端口,不在一個端口必然跨域,不過這對如今的技術手段來講徹底不是問題
開發模式
爲了更快更好的開發,dev 下通常採用 node 作代理層,解決跨域,幾乎無障礙開發,並且能夠輕鬆切換環境。
部署模式
部署通常不依託 node 進行部署,一般咱們發佈到 HTTP 服務器,與服務端進行通訊,一般使用 nginx 進行正向代理。
首先咱們構建前端架構須要對前端生態圈有一切瞭解,而且最好帶有必定的技術前瞻性,好的技術架構可能往後會方便的擴展,減小重構的次數,即便重構也不須要大動干戈,我一般選型技術棧會參考如下三點:
有了這樣的問題咱們能夠帶着問題去重點選型一些這寫問題技術方案比較成熟的技術棧。
這裏舉一個之前開發過程當中實際遇到的,當時爲了優化用戶體驗,節省開發效率 選型了一款 MVVM 輕量框架,惋惜當時沒有決定權, CTO 選型了 avalon
當時之因此沒有選擇 backbone ,主要是由於沒有成熟的中文文檔,考慮到團隊的流動性和上手性暫時沒作考慮,最終選擇了 司徒正美的 avalon 當時來講仍是比較前衛的,也有一些以去哪網爲首的大公司都在用。咱們當時用的時候 avalon2 剛出不久,直接用的 2.0,使用過程也出現了一點問題:文檔離散,這一塊那一塊,存在後置性,生態少,擴展性價比不高 ,有時候遇到匪夷所思的 bug 尋找緣由翻了幾遍 demo 、文檔 可能會找到答案,沒有重點標識。固然就當時來講確實是給咱們提高了部分開發效率,可是我可能當時更偏向 Angular 或 vue 的。由於他們有無以倫比的生態圈和各類問題的技術方案以以及完善的開發者文檔,值得一提的是 avalon 的做者是兼職維護的,若是全棧運營的話,我相信遠比如今更好,看一看 avalon 的源碼也會對本身有很多的提高。對於生產的技術選型要更加謹慎。
上文提到了生態系統,以我比較經常使用的 vue 來舉例,vue 發展至今僅官方爲咱們提供了以 vuex、 vue-router、 vue-loader、 vue-cli、 vuepress、 vue-devtools、 vue-ssr 爲首的 89 個開源項目,包括無數的 vue 相關的 UI 庫,vue 插件 甚至是近兩年淘寶提供的 Hybrid : weex 的支持
截止今天 github 開源的 與 vue 相關的項目多達 167,752 個,與 angular 相關的多達 416,811 個,與 react 相關的 多達 594,272 個。
統計時間 2018-09-01
我想有了這樣的生態支持,徹底能夠知足咱們中小項目的 95% 以上的需求,至於比較哪一個更強是沒有意義的 。
由於比較熟悉讓我斗膽私自選擇 vue 做爲咱們的 SPA 主架構
由於咱們上一章選型了 Vue,若是隻考慮前端咱們最初的想法:技術棧大概是這樣的:
經過 node 和 webpack 的支持 把 vue 組件 build 打包成傳統元素,發佈到 http 服務中,請求後端服務。
隨後多是這樣的:
隨着目前主流第三方庫的愈來愈多和技術的嚐鮮、客戶端的需求、或被動[不得不用]、或主動的去引用了 babel less sass *.loader 和 hybrid 等組件庫。
再後來的技術棧須要咱們根據真正踩坑以後纔會逐步完善
多是 polyfill 懶加載 xss protobuf 等 針對 瀏覽器兼容、速度優化、 SEO 、通訊協議 等具體問題。因此,前期能夠不用過多考慮,咱們只要知道:這個問題咱們是能夠解決的,可是如今能夠先不去考慮,有些同窗,太過於「完美主義」以致於想法不錯,但動起手來作了幾天就不作了,完美主義害死人。
WebPack 能夠看作是模塊打包機器,它能夠分析你的項目結構,找到 JavaScript 模塊以及其它的一些瀏覽器不能直接運行的拓展語言:Stylus、Scss、less、TypeScript、CoffeeScript 等,並將其轉換和打包爲合適的格式供瀏覽器使用。比較經常使用的還能夠經過 webpack-dev-server 進行開發模式的熱更新
WebPack 是一種模塊化開發的方案
當 webpack 處理應用程序時,它會遞歸地構建一個依賴關係圖(dependency graph),其中包含應用程序須要的每一個模塊,而後將全部這些模塊打包成一個或多個 bundle
webpack 經過 loader 能夠支持各類語言和預處理器編寫模塊,最後打包爲一個(或多個)瀏覽器可識別的 JavaScript css 文件
目前支持的 loader 列表
ECMAScript 6(簡稱ES6)是於2015年6月正式發佈的JavaScript語言的標準,正式名爲ECMAScript 2015(ES2015)。它的目標是使得JavaScript語言能夠用來編寫複雜的大型應用程序.
不少人老是搞不清楚 ES 這些東西,這裏大白話講講:
他們的前後順序是:ES五、ES6(ES2015)、ES七、ES8
在 2015 年 6 月 ES6 的第一個版本發佈, 正式名稱就是 《ECMAScript 2015 標準》(簡稱 ES2015)算是 2011 年 ECMAScript 5.1 以後的 6.0版本
2016 年 6 月,小幅修訂的《ECMAScript 2016 標準》(簡稱 ES2016)[由於改動小,其實他是 6.1 版本,但總有人願意叫它 ES7 ,不標準的]
2017 年 6 月發佈 的《ECMAScript 2017 標準》(簡稱 ES2017) [由於改動小,其實他是 6.2 版本,但總有人願意叫它 ES8 ,不標準的]
就像 Kubernetes 人們開他起了一個 K8S 的名字 (K 和 S 中間有 8 個單詞),他是不標準的
Babel、Traceur 是一個編譯JavaScript的平臺,它能夠編譯代碼幫你達到如下目的:
JavaScript.next-to-JavaScript-of-today compiler
今天就使用將來的 JavaScript
截止發佈日期 (2018-09-04) ,沒有一款徹底支持ES6的JavaScript代理(不管是瀏覽器環境仍是服務器環境),因此熱衷於使用語言最新特性的開發者須要將ES6代碼轉譯爲ES5代碼。
讓你能使用最新的JavaScript代碼(ES6,ES7...),而不用管新標準是否被當前使用的瀏覽器徹底支持;
ES7 做者徹底沒精力看 ,不過 Bable 逐漸替代了 Google 的 Traceur 成爲主流了,我是個俗人,因此我選 Bable
Sass 是否是違反了中國的廣告法了??
Sass 、Stylus 和 Less 之類的預處理器是對原生CSS的拓展,它們容許你使用相似於variables, nesting, mixins, inheritance等不存在於CSS中的特性來寫CSS,CSS預處理器能夠這些特殊類型的語句轉化爲瀏覽器可識別的CSS語句。
語言 | 實現 | 特性 | 賦值 | 縮進 |
---|---|---|---|---|
Sass | Ruby | 變量$開頭 | $var: value | 不須要 |
Less | JavaSript | 變量@開頭 | @var: value | 不須要 |
Stylus | NodeJs | 不能使用@開頭 | var:10 | 均可以 |
你如今可能都已經熟悉了,上文講 WebPack 講過: webpack 裏使用相關 loaders 進行配置就能夠使用了,如下是經常使用的CSS 處理loaders:
Less Loader
Sass Loader
Stylus Loader
本身去找:loader 列表
像:哪一種語言更好、使用的更多、更簡單 容易引發爭議的 博主不想討論,看本身喜愛
一個能夠使用使用: JavaScript, HTML 和 CSS 構建跨平臺的桌面應用的框架,也算 hybrid 的一種,主要場景是 PC 端,沒啥好說的。
值得一提的是 Visual Studio Code 、Atom、GIthub Desktop 都是基於此構建的,有時候按 CMD + option + i 有驚喜哦
任何不錯的開源項目都有 project-cli 腳手架、咱們用它生成每每能 快速配製出最佳的、理想的腳手架
我一般使用 cli 生成項目骨架再在之基礎上進行我的修改。
命令行界面(英語:command-line interface,縮寫:CLI)是在圖形用戶界面獲得普及以前使用最爲普遍的用戶界面,它一般不支持鼠標,用戶經過鍵盤輸入指令,計算機接收到指令後,予以執行。也有人稱之爲字符用戶界面
顧名思義 XXX-CLI 就是使用命令行生成的 XXX 程序。以前寫過一款 基於 nodeJs 製做個性 CLI 的教程
如何用node開發本身的cli工具併發布到NPM , 想詳細瞭解製做流程的能夠簡單看看。
截止 2018-09-02 vue-cli 最新版本爲 3.0
vue 中文生態很是完善,咱們直接去官網看看:
很遺憾, vue-cli-3 是 2018-08-11 出來的,而個人論壇早在以前就着手搭建了 cli-3 耽誤了我一些時間,後面也會提到
我這兩天不忙的時候就在考慮項目兼容 vli-3 可是後來廢了不少時間,效果依然不理想,我回滾了代碼宣佈放棄了。
鑑於使用 cli-3 並無對個人項目有性能上的提高,反而翻遍了個人不少成熟的基礎架構,爲時間成本考慮,我決定仍是使用 cli-2 進行開發,大致目錄結構都是同樣的。
安裝前應注意前提條件,避免浪費沒必要要的時間。
Vue CLI 須要 Node.js 8.9 或更高版本 (推薦 8.11.0+)。(若是你用的和我同樣 也是 cli-2 那麼不須要如此新的 nodeJs )你能夠使用 nvm 或 nvm-windows 在同一臺電腦中管理多個 Node 版本。
不將遠離了,官網比我講的好得多。
能夠使用 yarn 或 npm 來安裝
npm install -g @vue/cli # OR yarn global add @vue/cli
我用 NPM 來從新嘗試一次 (對 npm 速度表示不理想的 能夠嘗試淘寶的 CNPM 不要過分依賴cnpm):
localhost:~ Venda-GM$ sudo cnpm i @vue/cli -g
npm 中 install 能夠寫成 i , -g 放哪都行 ,--save 能夠寫成 -S , --save-dev 能夠寫成 -D
看到這個畫面,安裝完成了。
測試一下查看一下版本是否是正確,ok 建立項目:
vue create new-bee
Vue CLI 3 和舊版使用了相同的 vue 命令,因此 Vue CLI 2 (vue-cli) 被覆蓋了。若是你仍然須要使用舊版本的 vue init 功能,你能夠全局安裝一個橋接工具:
npm install -g @vue/cli-init
vue init
的運行效果將會跟 vue-cli@2.x
相同vue init webpack my-project #這樣來生成一個 *cli-2* 項目
vue init webpack new-bee
下面是我建立項目我所選的選項:
稍微講講下面三個:
推薦使用 運行時 + 編譯時,一般須要 webpack 編譯 .vue 模板。
並是不每一個人都適合的,有的要求過於嚴格,我本身有一套成熟的, 代碼在這裏 , 就用本身的了,固然能夠基於它作一些刪減。
若是你有一個好的socket終端代理,能夠用這個,不然能夠選擇 No 本身用 cnpm 執行
讓咱們來看看 vue-cli2 自動生成的項目目錄,我打上標籤,爲可能不太理解的同窗簡單描述一下
此次咱們重構的主要目的是規範、更適合多模塊多人協做、而不是爲了讓它看起來更復雜,本文的項目結構、esLint 改良、等都是通過項目小組反覆的推敲決定的,有必定的生產價值。
cli 生成的項目 src 下面直接就是源碼,可是爲了考慮之後使用 electron 咱們再用 renderer 包裹一下,規範一點。
能夠參考一下 electron-vue
tips: 上述截圖 github 樹形目錄的插件是 octotree 也能夠在谷歌商店直接搜索安裝,看源碼省去很多時間。
別忘了改一下 webpack 相關的路徑問題
加上 renderer 的路徑
app: './renderer/src/main.js'
@ 的路徑也要在 webpack 進行修改,不然會不找組件
須要改下 webpack alias [別名] 配置
改以後的樣子
alias: { '@': resolve('renderer/src'), }
在 組件目錄(components) 同級創建 容器(container) :容器裏面的各個模塊分離開,這樣能夠使項目模塊看起來更加清晰。若是十多人協做的項目又能很好地對工做區劃分,合理的創建路由,避免沒必要要的衝突。
以目前的 論壇項目爲例
在 /router 下爲 /container/blog 創建 blog.js
const Blog = () => import ( /* webpackChunkName: "blog" */ '@/container/blog/index') /* 全部container/blog目錄下的路徑都配置在此路由children下,避免混亂 */ let routes = [{ path: '/blog', name: 'blog', component: Blog, children: [{ path: 'blogdemo', component: Blog } ] }] export { routes } // 注意 /* webpackChunkName: "blog" */ //是爲了後面的路由懶加載,後面會講,不懂不要緊,如今能夠忽略
太單一,咱們不可能全部的路由都寫在裏面做爲 children ,看起來很是混亂,開發調試很難處理,多人協做還很容易引發衝突。
咱們嘗試將 blog.js 引過來
先將 blog.js export 的路由引過來,起一個別名防止衝突
import { routes as blogRoutes } from './blog'
由於可能有N多個路由模塊,咱們將 routes 拆分
自動生成的是這樣簡單的:
//直接導出路由 export default new Router({ routes: [ { path: '/', name: 'HelloWorld', component: HelloWorld } ] })
咱們拆分紅這樣:
//定義基礎路由 let route = [ { path: '/', name: 'HelloWorld', component: HelloWorld } ] //以此類推能夠方便的連接更多路由 route = route.concat(blogRoutes) //導出 export default new Router({ routes: route, linkActiveClass: 'active' })
回過頭來,咱們爲 blog/index.vue 添加一些內容,測試一下:
<template> <div class="Blog"> <h1>{{ msg }}</h1> </div> </template> <script> export default { name: "Blog", data() { return { msg: "Welcome to Your Blog Page" }; } }; </script>
首先
npm install
嫌慢能夠使用淘寶的 cnpm 我之前的文章講過
npm run dev
按照提示在瀏覽器輸入:http://localhost:8080/#/blog
vue-cli2 webpack 生成的項目是支持熱部署的,因此不少配置不須要本身從零開始配置,這也是我但願你們使用 CLI 的緣由,省去了一些時間。其餘 login 等一些模塊 按照這個模式寫就能夠了。
我的以爲 esLint 不管是在我的項目仍是團隊協做中,都是有價值的,它可讓本身、團隊的代碼風格標準化。如今esLint 甚至能夠預測你的代碼是否可能會有問題。建議能夠制定一些規則,開發時經過你的 IDE(集成環境) : idea 、WebStorm、 vscode、 之類的插件配合檢測,eslint 打包檢測編譯不經過的那種很是嚴格的初期仍是不要嘗試了。
我當時參考 airbnb 調整的配置,通過一年多的項目實戰逐漸調整,目前還算比較合理,esLint配置規則代碼在這裏。
你甚至能夠再 commit 裏看到本章按部就班的改造過程
使用了 vue 的你,發現 Vue 竟然不能發請求,因而你 Google 了下,發現能夠用 Vue-Resource。
你去問別人 Vue-Resource 怎麼樣,他說不要用 Vue-Resource,由於 Vue-Resource 官方已經中止維護了,你應該用 Axios、或者 fetch。可是咱們想擁抱 ES6 排除掉了 ES5的fetch(固然也有ES6-fetch),這裏咱們使用 Axios!
這裏呢也科普一下:何時依賴須要放到 dependencies、何時依賴須要放到 devDependencies:
devDependencies:顧名思義,僅在開發(dev)模式下如:webpack. 、.loader、eslint、babel、打包後部署時徹底用不到的、僅在開發須要 編譯、檢測、轉換 的放在這裏。
dependencies:例如:axios、chart、js-cookie、less、lodash、underscore等運行時的庫或工具類等相關依賴咱們要放在這裏
不過基本不用擔憂,官網都會提供 start 說明,可是咱們要大概明白意思,不要機械般的 copy。
2018-09-28 截圖 npmjs.com
"dependencies": { "axios": "^0.18.0" }
基於上一章內容,別忘了從新 npm i 下載一下
還記得咱們自動生成的 vue 主頁面腳本 main.js嗎?
咱們在 src/renderer/utils
創建一個 request.js
在這個請求腳本中,對 Axios 作一些必要的封裝,大概內容是用 攔截器 axios.interceptors 對請求和響應作些攔截,定義一下 API 的前綴,處理一些常見的 HTTP 狀態碼。
我儘量的爲你們寫了詳細的註釋。
// src/renderer/utils/request.js import axios from 'axios' //這裏通常指後端項目API的前綴,例如 /baidu/*/*/1.api /mi/*/*/2.api const BASE_API = "" export function axiosIntercept(Vue, router) { const axiosIntercept = axios.create({ baseURL: BASE_API }) //http request 攔截器 通常用來在請求前塞一些全局的配置、或開啓一些 css 加載動畫 axiosIntercept.interceptors.request.use( (config) => { // 判斷是否存在token,若是存在的話,則每一個http header都加上token // if (store.getters.accessToken) { // console.log(store.getters.accessToken) // config.headers.Authorization = `token ${store.getters.accessToken}`; // } //todo:加載動畫 //如有需求能夠處理一下 post 亦或改變post傳輸格式 if (config.method === 'post') { }; return config; }, function (err) { return Promise.reject(err); }); //http response 攔截器 通常用來根據一些後端協議特殊返回值作一些處理,例如:權限方面、404... 或關閉一些 css 加載動畫 axiosIntercept.interceptors.response.use(function (response) { // todo: 暫停加載動畫 return response; }, function (err) { //捕獲異常 if (err.response) { switch (err.response.status) { case 401: // do something 這裏咱們寫完後端作好約束再完善 } } return Promise.reject(err); }); return axiosIntercept; }
你們還記得咱們用 vue-cli 生成的 vue 主頁腳本 main.js 吧,這裏咱們須要對 Axios 和 Vue 作一個耦合。
// src/renderer/main.js import axios from 'axios' import { axiosIntercept } from './utils/request' // 將Axios擴展到Vue原型鏈中 Vue.prototype.$http = axiosIntercept(Vue)
這樣咱們在寫業務邏輯,直接在 Vue 的上下文中 使用 this.$http 來發送請求。既實現了攔截、又實現了狀態的共享。
知其然,知其因此然
節省代碼量,讓代碼更加易讀
擴展到原型鏈,使 Axios 運行時共享 Vue 原型鏈的內容,減小了不少指代 Vue 的臨時變量
傳統狀況
import axios from 'axios' new Vue({ data: { user: "" }, created: function () { //此時做用域在 Vue 上,緩存起來,要依賴此變量 let _this = this; axios.get("/user/getUserInfo/" + userName).then(res => { if (res.data.code === 200) { //此時做用域在axios上,拿不到vue綁定的值,只能藉助剛纔緩存的_this上下文 _this.data.user = res.data.user } }); } })
代理以後
new Vue({ data: { user: "" }, created: function () { // axios 成爲了 vue 的原型鏈一部分,共享vue狀態。 this.$http.get("/user/getUserInfo/" + userName).then(res => { if (res.data.code === 200) { //注意,axios回調,應該儘可能使用箭頭函數,能夠繼承父類上下文,不然相似閉包,仍是沒法共享變量、 // 更優雅了一些 this.data.user = res.data.user } }); } })
不懂 prototype 能夠翻翻我之前寫的文章
先簡單弄一下,爲先後分離打個小鋪墊
resolve: { extensions: ['.js', '.vue', '.json'], alias: { '@': resolve('src/renderer'), } },
爲了使用起來更加優雅,能夠爲每一個經常使用的目錄都創建別名
resolve: { extensions: ['.js', '.vue', '.json'], alias: { 'vue$': 'vue/dist/vue.esm.js', '@': resolve('src/renderer'), 'assets': resolve('src/renderer/assets'), 'components': resolve('src/renderer/components'), 'container': resolve('src/renderer/container'), 'utils': resolve('src/renderer/utils') } },
dev 是開發時啓動的指令
build 是預發佈時 webPack 打包的指令
假設筆者只是一個前端,一般呢,在開發調試過程中,沒法避免須要與後端的同窗進行 API 的對接,那也就不免會出現跨域問題。固然傳統 javaWeb 不須要跨域,(ip 域 端口 任何一個不一樣皆爲跨域) 在 DEV 模式調試中,咱們都是儘可能選擇前端環境規避跨域問題,而不會去額外搭建 nginx 或更改後端代碼。
跨域只是針對 JavaScript 的,由於開發者認爲瀏覽器上的腳本是不安全的。
既然咱們的 vue 項目是 node 全家桶,依靠 node、webPack 編譯 咱們直接配置 node 的 proxyTable 做爲開發的代理器,這樣最簡單,依次配置,團隊受益。
https://cnodejs.org/api
上邊cnode 的 API 是能夠隨意調用的,由於後端作了處理。
看看掘金的:
https://xiaoce-timeline-api-m...
請求一下,不出意外瀏覽器作了跨域報警。
哦,咱們適配一下 node 代理
官方例子在這:
https://vuejs-templates.githu...
擴展一下 proxyTable:
proxyTable: [{ //攔截全部v1開頭的xhr請求 context: ['/v1'], target: "https://xiaoce-timeline-api-ms.juejin.im", cookieDomainRewrite: { // 不用cookie }, changeOrigin: true,//重點,此處本地就會虛擬一個服務替咱們接受或轉發請求 secure: false }],
再次發送請求。
這樣,先後分離的項目能夠這樣藉助 swagger 測試接口,不須要騷擾任何人。實現本身的業務邏輯,簡單實現一點。
代碼:
// blog/index.vue <template> <div class="Blog"> <h1>{{ msg }}</h1> <div v-for="(blog,index) in blogList" v-bind:key="index"> <h3 > <a :href="`https://juejin.im/book/`+blog.id" > <span>{{blog.title}}</span> </a> </h3> </div> </div> </template> <script> export default { name: "Blog", data() { return { msg: "掘金小冊一覽", blogList: [] }; }, created() { this.getBlog(); }, methods: { getBlog() { this.$http.get("/v1/getListByLastTime?src=web&pageNum=1").then(res => { this.blogList = res.data.d; }); } } }; </script>
寫完代碼以後部署到線上,是不會在線上 clone 代碼以後 Npm run dev 的😆,那樣會有太多太多的垃圾依賴,爲用戶帶來了災難性的網絡請求,一般藉助webPack打包以後發佈到服務器的web服務當中。
運行
npm run build
打包目錄是以前配置的webpack
好了,不少人直接雙擊 index.html 是不行的。
Tip: built files are meant to be served over an HTTP server.
Opening index.html over file:// won't work.
須要 http 服務啓動,能夠扔到本地或服務器的 nginx、apache、tomcat等容器測試,我一般使用 python 啓動一個 http 服務來運行(腳本地址)、固然,本身 ide 支持 http 啓動也能夠。
生產當中,由於前端後端必然不一樣端口,避免跨域,一般使用 nginx 的正向/反向代理做爲跨域的手段。(並不是負載均衡,兩個概念)
server { listen 80; server_name localhost; location ^~ /v1 { proxy_pass https://xiaoce-timeline-api-ms.juejin.im;#代理。 proxy_set_header X-Real-IP $remote_addr;#轉發客戶端真實IP proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; } }
簡單配置一下就能夠了,很少講,前端同窗瞭解一下就能夠了,nginx 能幹的事情還有不少。
你是否爲了找某一個業務的接口頭痛
你是否還在使用全局搜索找本身的接口
你是否某一個接口不一樣組件重複寫了屢次
整理一下本身的接口吧,像上文的 router 同樣整齊的劃分吧。
/renderer 下創建一個 api 的文件夾
webpack.base.conf.js 添加一條 api 的別名,方便咱們往後大量調用
'api': resolve('src/renderer/api')
咱們創建 /renderer/api/juejin.js
import axios from 'axios' let Api = Function() Api.prototype = { getBlog(page, fn) { axios.get(`/v1/getListByLastTime?src=web&pageNum=${page}`).then(res => { // if (res.data.code === 200) { fn(res.data) // } }).error() } } export default new Api()
修改一下咱們剛纔 /blog/index.vue 的 Axios 請求:
import juejin from "@/api/juejin";
注掉以前離散的 axios ,使用從 api 中定義過的 XHR 請求數據。
getBlog() { // this.$http.get("/v1/getListByLastTime?src=web&pageNum=1").then(res => { // this.blogList = res.data.d; // }); juejin.getBlog("1", response => { this.blogList = response.d; }); }
本文爲方便講述重構去除了 Element、vux 庫,用了最近比較火的 bulma 輕量、快捷、易讀。
項目截屏
首先,似上圖,咱們思考把一個小型網站拆成三部分:頁頭(Header)、內容(Content)、頁腳(Footer) 這幾乎每一個網站內都必須有的,一般把萬年不變的:頁頭(Header)、頁腳(Footer) 製做成 Layout 方便通用。
再把內容(Content)根據業務進行拆分紅 組件(Components)
如上圖:Header 和 Content :Header其實沒有拆分的必要,沒有能夠重用的組件,而 Conntent 是必需要拆分的佈局元素。由於動態網站 Conntent 隨着內容的變化而變化,內容多,可重用的東西的機率越高,須要把能重用的東西提煉出來
一、節省代碼、提升代碼閱讀性
二、便於修改 (好比更新廣告)
接着咱們的 第二章上傳的源碼 開始,基於它繼續完善小網站佈局和組件化。
值得一提的是:本系列教程一章跟隨一章而且每一章能夠獨立運行,章與章之間完美銜接,沒有 「忽然出現」 的代碼段。不會給新手無從下手的感受,若是那你對代碼陌生,那你該認真翻翻往期文章了。你能夠基於上一章逐步寫代碼,也能夠下載本章簡單預覽代碼。
vim new-bee/index.html
<!-- font --> <link href="//cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"> <!-- css --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.0/css/bulma.min.css">
vim new-bee/src/renderer/components/layout
這個組件專門寫頭部的內容,最好配合柵格儘量寫出簡單的響應式組件
<template> <div id="bee-header" element-loading-text="正在努力請求github..." element-loading-background="rgba(0, 0, 0, 0.8)"> <!-- 遮罩 --> <div :class=" loading ? `modal is-active` : `modal` " style="background-color: #ffffff36"> < img src="https://img.actd.tw/images/2018/11/17/ez-4-05f4bba41fef" style="width: 300px" alt=""> </div> <div class="is-underline "> <div class="container"> <nav class="navbar "> <div class="navbar-brand"> <a class="navbar-item" > < img src="https://img.actd.tw/images/2018/11/17" alt="Bulma: a modern CSS framework based on Flexbox" width="92" height="28"> </a > <div class="login-before is-hidden-mobile" style="padding-top: 5px;"> <a class="navbar-item is-hidden-desktop" href=" " target="_blank"> <span class="icon" style="color: #333;"> <i class="fa fa-lg fa-github is-size-2"></i> </span> </a > </div> <div class="navbar-item is-hidden-desktop "> <div class="field has-addons" ><div class="control" ><input type="input" class="input" name="email" placeholder="搜索一下" required="required" style="height: 36.4px;width:130px"><input type="hidden" name="redirect" id="name" value="/fr/#thanks"></div><div class="control" ><input type="submit" class="button is-warning" value="GO"></div></div> </div> <div class="navbar-burger burger" data-target="navMenuDocumentation" > <span></span> <span></span> <span></span> </div> </div> <div id="navMenuDocumentation" class="navbar-menu"> <div class="navbar-start"> <div class="navbar-item has-dropdown is-hoverable"> <a class="navbar-link is-active"> 發現 </a > <div class="navbar-dropdown "> <a class="navbar-item " type="收藏集"> 收藏集 </a > <a class="navbar-item" type="徽章"> 徽章 </a > <a class="navbar-item " type="排名"> 排名 </a > <a class="navbar-item " type="職場生活"> 職場生活 </a > </div> </div> <a class="navbar-item " href="https://bulma.io/expo/"> <!--<span class="bd-emoji">⭐️</span>--> 專欄 </a > <a class="navbar-item " href="https://bulma.io/expo/"> <!--<span class="bd-emoji">⭐️</span>--> 聊天 <!-- 不少人不知道幹什麼。。。 --> </a > <a class="navbar-item " href="https://bulma.io/expo/"> <!--<span class="bd-emoji">⭐️</span>--> 面經 </a > <router-link class="navbar-item " to="/book"> <!--<span class="bd-emoji">❤️</span>--> 書籍 </router-link> </div> <div class="navbar-end"> <div class="login-before" style="padding-top: 5px;"> <!-- pc --> <a class="navbar-item is-hidden-desktop-only" href="https://github.com/pkwenda/my-bbs" target="_blank"> <span class="icon" style="color: #333;"> <i class="fa fa-lg fa-github is-size-2"></i> </span> </a > </div> <div class="navbar-item is-hidden-mobile "> <div class="field has-addons" ><div class="control" ><input type="input" class="input" name="email" placeholder="搜索一下" required="required" style="height: 36.4px;"><input type="hidden" name="redirect" id="name" value="/fr/#thanks"></div><div class="control" ><input type="submit" class="button is-warning" value="GO"></div></div> </div> <div class="navbar-item is-hidden-mobile "> <!--<span class="icon is-medium">--> <i class="iconfont icon-tixing"></i> <!--</span>--> </div> <div class="navbar-item has-dropdown is-hoverable"> <a class="is-hidden-mobile" target="_blank"> < img src="https://avatars2.githubusercontent.com/u/14212375?s=400&u=dc515636befebfda36501309d1cdc087ee31d500&v=4" class=" header-avatar img-circle " style="margin-top: 10px"> </a > <div class="navbar-dropdown "> <a class="navbar-item " type="收藏集"> 寫文章 </a > <a class="navbar-item" type="徽章"> 設置 </a > <a class="navbar-item " type="排名"> 退出 </a > </div> </div> <div class="login-before"> <div class="navbar-item"> <div class="field is-grouped"> <p class="control"> <a class="button is-warning" v-show="!isLogin" > <strong>登陸</strong> </a > </p > </div> </div> </div> </div> </div> </nav> </div> </div> </div> </template> <script> export default { name: "BeeHeader", data() { return { popupShow: false, isLogin: false, user: {}, loading: false, userInfo: {} }; }, created() {}, destroyed() {}, mounted() {}, methods: {} }; </script> <style scoped> .img-circle { -webkit-border-radius: 50%; -moz-border-radius: 50%; border-radius: 50%; } </style>
上文的比較熟悉的代碼是讓咱們的頭像變圓的代碼段
<style scoped> .img-circle { -webkit-border-radius: 50%; -moz-border-radius: 50%; border-radius: 50%; }
這裏我偷了個懶,恰好能夠說一說,對於如此通用的樣式,侷限在 .vue文件中,而且以 scoped 標示,宣判了它沒法複用的事實,任何模塊想用這個樣式,都須要複製一份,顯然是不規範的,咱們一般還會創建通用的 css 文件進行管理,大型項目 css 管理規範將更加嚴格、規範的樹級結構,具體就看 CTO 的想法了。
vim new-bee/src/renderer/components/HelloWorld.vue
<template> <div> <Header></Header> <!--<div class="container"> </div>--> </div> </template> <script> import Header from "@/components/layout/Header"; export default { name: "NewBeeIndex", components: { Header }, data() { return {}; }, destroyed() {}, mounted() {}, methods: {}, watch: {} }; </script>
缺點是要一個一個引入,但優勢是代碼可讀性高
vim new-bee/src/renderer/App.vue
<template> <div id="app"> <Header></Header> <!-- < img src="./assets/logo.png"> --> <router-view/> </div> </template> <script> import Header from "@/components/layout/Header"; export default { name: "App", components: { Header } }; </script> <style> </style>
基於 webpack 爸爸的熱部署,咱們無需刷新瀏覽器,webpack 偷偷用 ws 更新了咱們的內容。彷佛很完美,可是也許你們發現了一個問題,咱們經過瀏覽器渲染出來的 dom 就能夠看到::
咱們在主 APP 入口引入了頭部佈局, App.vue 是緊臨 <body> 元素的正文元素,而這個程序全部頁面、子路由所有都是 App.vue 入口的子集,說明全局引入佈局會存在以下問題:
一、這個項目全部的項目都必定會帶上 Header 組件渲染的內容
二、並且會影響在下期 《性能優化》中講的 webpack 按需加載的性能。
固然能夠再 Header 組件上書寫邏輯條件,過濾指定的路由,但會破壞項目的易讀性,難以維護我我的是比較推薦第一種:按需引入的方式。
vim new-bee/src/renderer/components/layout/Footer.vue
<template> <footer class="footer footer-light-medium " style="padding-bottom: 20px;padding-top: 20px;"> <div class="container"> <div class="columns"> <!-- Column --> <div class="column is-4"> <div class="mb-20"> < img class="small-footer-logo" src="https://img.actd.tw/images/2018/11/17/" alt=""> <div class="footer-description pt-10"> new bee 是一個爲開發者提供的專一於技術分享的開源社區,全部源碼都可在 github 上找到,但願對廣大開發者有所幫助。 </div> </div> <div> <span class="moto">喜歡項目能夠點贊支持 <a href="https://github.com/pkwenda/new-bee" target="_blank"> <span class="icon"><i class="fa fa-github"></i></span> </a >.</span> <div class="social-links mt-20"> </div> </div> </div> <!-- Column --> <div class="column is-6 is-offset-2"> <div class="columns"> <!-- Column --> <div class="column"> <ul class="footer-column"> <li class="column-header"> Links </li> <li class="column-item"><a href="https://github.com/pkwenda/new-bee">Home</a ></li> <li class="column-item"><a href="https://cssninja.io/themes">Blog</a ></li> <li class="column-item"><a href="https://github.com/pkwenda/new-bee/wiki">Wiki</a ></li> </ul> </div> <!-- Column --> <div class="column"> <ul class="footer-column"> <li class="column-header"> Ressources </li> <li class="column-item"><a href="https://cssninja.io/help">Help center</a ></li> <li class="column-item"><a href="https://cssninja.io/blog">Blog</a ></li> <li class="column-item"><a href="https://cssninja.io/help/rules">Rules</a ></li> </ul> </div> <!-- Column --> <div class="column"> <ul class="footer-column"> <li class="column-header"> Terms </li> <li class="column-item"><a href="https://cssninja.io/help/terms/licenses/personal">Personal</a ></li> <li class="column-item"><a href="https://cssninja.io/help/terms/licenses/developer">Developer</a ></li> <li class="column-item"><a href="https://cssninja.io/help/terms/service">Terms of Service</a ></li> </ul> </div> </div> </div> </div> </div> </footer> </template> <script> export default { name: "Footer", data() { return {}; }, created() {}, destroyed() {}, mounted() { }, methods: {} }; </script>
別忘了在 HelloWorld 引入一下
vim new-bee/src/renderer/components/layout/Content.vue
<template> <div class="container" style="height:700px"> <h1 >博客列表</h1> <article class="column is-3" v-for="blog in blogs" v-bind:key="blog"> <a class="bd-article-image is-bootstrap" > <span class="bd-article-overlay"></span> <span class="bd-article-icon"> <i class="fa fa-tag"></i> </span> <strong class="bd-star-icon" ><i class="fa fa-star"></i> <span style="font-size: 1rem"> {{blog.commendCount}}</span></strong> <strong class="bd-article-info"> <span> <time class="bd-article-date" datetime="2017-10-09T00:00:00+00:00"> {{blog.tag}} </time> <strong class="bd-article-title"> {{blog.title}} </strong> </span> </strong> </a> </article> </div> </template> <script> let article = { tag: "java", title: "java", commendCount: 0 }; export default { name: "Footer", data() { return { blogs: [ article, article, article, article, article, article, article, article ] }; }, created() {}, destroyed() {}, mounted() {}, methods: {} }; </script> <style scoped> .bd-article-image.is-bootstrap { background-color: #6f5499; } .bd-article-image { background-color: #00d1b2; display: block; height: 240px; margin-left: auto; margin-right: auto; position: relative; text-align: center; } .bd-star-icon { font-size: 19.2px; font-size: 1.2rem; color: #0a0a0a; opacity: 0.25; bottom: 10px; left: 30px; position: absolute; -webkit-box-align: center; -ms-flex-align: center; -webkit-align-items: center; align-items: center; display: -webkit-box; display: -ms-flexbox; display: -webkit-flex; display: flex; -webkit-box-pack: center; -ms-flex-pack: center; -webkit-justify-content: center; justify-content: center; } .bd-article-icon, .bd-article-info { bottom: 0; left: 0; position: absolute; right: 0; top: 0; -webkit-box-align: center; -ms-flex-align: center; align-items: center; display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-pack: center; -ms-flex-pack: center; justify-content: center; } .bd-article-info { padding: 20px; } a strong { color: currentColor; } .bd-article-date { color: rgba(0, 0, 0, 0.5); display: block; } .bd-article-title { color: white; display: block; font-size: 1.8rem; font-weight: 700; line-height: 1.25; padding: 0 20px; } .bd-article-icon { color: #0a0a0a; opacity: 0.25; } h1 { text-align: center; font-size: 30px; } .column.is-3, .column.is-3-tablet { -webkit-box-flex: 0; -ms-flex: none; flex: none; width: 25%; float: left; } </style>
還算看得過去,咱們繼續參照圖二
爲 Content 制定 AD(廣告) 組件。
vim new-bee/src/renderer/components/common/AD.vue
<template> <div class="ad"><h1>澳門XX賭場上線啦</h1></div> </template> <script> export default { name: "AD", data() { return {}; }, destroyed() {}, mounted() {}, methods: {}, watch: {} }; </script> <style scoped> .ad { width: 150px; height: 180px; background-color: #ececec; position: fixed; right: 30px; top: 80px; } </style>
... <AD></AD> ... import AD from "@/components/common/AD"; export default { name: "Content", components: { AD }, ... }
差很少完成了咱們初步的構思
咱們一般在本地開發,本地環境和線上也並不是徹底同樣,不少項目第一次上線幾乎都會遇到本地開發沒法復現的問題,多是字體、樣式的問題,也多是webpack 編譯的問題、甚至多是本地的奇葩環境。因此 本地完美運行 ≠ 線上完美運行,咱們須要 build 項目,模擬線上測試一下,看看是否能夠完美運行,有問題能夠方便及時做出調整。
爲了不本教程污染你們本地環境,推薦你們安裝一個docker,後期運維也會根據 docker 展開。
看到這個 Title :《準備docker》,沒接觸過的前端不要慫,裝一個,敢於跨出第一步,不學習就是等死「點擊這裏瞭解 docker」
雖然 tomcat nginx apache jboss jetty
等等等等均可以做爲 http 服務,本章以最多見的 nginx 展開講述:
docker 就是用更優雅的方法,作到了虛擬機的事情,而且作的更好,可編程管理集羣。docker 啓動容器,在容器內部運行你的環境,默認各個容器是互相隔離的,固然你能夠經過 link network 關聯容器,或者直接使用 docker-compose 編排,啓動容器的前提是鏡像,也相似與虛擬機的鏡像,想跑容器,先得下載「pull」鏡像。
也許不少人沒用過,沒用過也不講怎麼安裝了,本身去看官網吧中文官網、社區版下載、中國鏡像加速,windows 的話可能要開啓虛擬化,linux 推薦 ubuntu, 有篇文章這樣講:爲了性能請不要在 centos中運行 Docker , 查看翻譯點這裏),幾年前的文章了,如今怎麼樣有待考究。
docker images
能夠看到我已經有一些鏡像了「我已經刪除了nginx」
docker pull registry.docker-cn.com/library/nginx:latest
正常 docker pull nginx 便可,中間那段是中國鏡像源
ok,咱們成功 pull 下來了 Nginx 的鏡像。默認存儲的鏡像名爲: registry.docker-cn.com/library/nginx
進入咱們上一章源碼的目錄,build 一下進行發佈。
上一章源碼在這裏
npm run build
docker run --name nginx -d -p 8888:80 -v /new-bee/dist:/usr/share/nginx/html registry.docker-cn.com/library/nginx
CMD | 解釋 |
---|---|
-d | 守護進程運行 |
-p | 端口映射 8888 :80 docker80端口映射到本機「宿主機」 |
-v | 掛載宿主機的一個目錄 本機「宿主機」: docker容器 |
—name | 爲容器命名 |
http://localhost:8888/#/
固然初次嘗試 docker 你可能會有更多的疑問:
這些小白問題本章簡單講講,後面作自動運維的時候單獨展開講,能夠關注個人博客
咱們能夠經過 webpack 壓縮腳本文件,上傳到 http 服務器,瀏覽器瀏覽的時候,通過壓縮的HTTP應答報文是由瀏覽器解壓的,比起壓縮,解壓的速度是很是快的(只要數據正常,能夠解壓的話),因此不用擔憂瀏覽器用於解壓的時間會下降用戶體驗。事實上,瀏覽器解壓消耗的這點時間比起數據包由於網絡擁堵而耽誤的時間要少的多也可控的多。
在瀏覽器發給服務器的HTTP請求報文中,使用Accept-Encoding字段標明本身支持的壓縮格式,即本身能夠解壓哪幾種壓縮報文(gzip、zlib庫提供的deflate)。服務器回覆客戶端的HTTP應答報文中,使用Content-Encoding字段標明該應答報文使用哪一種壓縮方式。
像我這樣屌絲的服務器通常都買 1M 的,大的資源文件 hold 不住,一個動輒 400K 的 vendar 文件這很蛋疼,不上 gZIp 很難受。
打開 network 觀察一下:
它有 144K 這麼大
咱們就以 webpack 打包的核心 vendor 爲例,咱們發現,客戶端向服務端請求了 gZIp 資源 Accept-Encoding: gzip, deflate
,但惋惜服務端並無給咱們理想中的 response - Content-Encoding: gzip
的響應, 咱們須要排查一下緣由。
很遺憾沒有,只有一些壓縮文件和用於定位的 map 文件,看來首先咱們的打包就出現了問題。
你們還記得當初構建項目我發的這張圖嗎?
打開看看 build 命令執行了哪一個腳本?
打開 build.js 看看執行了哪些內容,難道是 vue-cli 沒有爲咱們配置好webpack gZip 相關的配置嗎?
咱們發現沒什麼特別的,發現一個
const webpackConfig = require('./webpack.prod.conf')
的依賴,大概就是字面意思(webpack生產配置)進去看看。
哦,咱們看到了,webpack 確實爲咱們配置了 gZip 相關配置。
但是發現這個配置被這個判斷包裹住了:
if (config.build.productionGzip) { }
追蹤下去
// Gzip off by default as many popular static hosts such as // Surge or Netlify already gzip all static assets for you. // Before setting to `true`, make sure to: // npm install --save-dev compression-webpack-plugin productionGzip: false,
咱們的所有疑惑都被揭開了,開發者經過註釋這樣告訴咱們他的理由,我簡單翻譯一下:
首先下載一下依賴:
vim package.json "devDependencies": { "compression-webpack-plugin": "^1.1.12", }
而後 productionGzip 改爲 true
npm run build
成功了,出現了 .zg 文件壓縮包,可是 gZip 是須要服務端的支持的,服務器經過客戶端請求的 Accept-Encoding
首部開判斷返回哪一種格式的腳本文件,而後由瀏覽器解壓,咱們拉下來的 nginx 鏡像,nginx 是不會爲咱們默認配置 gZIp 服務端壓縮的,咱們去查看一下吧。
docker exec -it nginx /bin/bash
或者
docker exec -it nginx "bash"
CMD | 解釋 |
---|---|
exec | 進入docker容器 |
-i | -i: 以交互模式運行容器,一般與 -t 同時使用; |
-t | -t: 爲容器從新分配一個僞輸入終端,一般與 -i 同時使用; |
-it | -it = -i -t |
「bash」 或 /bin/bash | /bin/bash的做用是由於docker後臺必須運行一個進程,不然容器就會退出 |
Linux whereis 命令用於查找文件。
該指令會在特定目錄中查找符合條件的文件。這些文件應屬於原始代碼、二進制文件,或是幫助文件。
該指令只能用於查找二進制文件、源代碼文件和man手冊頁,通常文件的定位需使用 locate 命令。
語法
whereis [-bfmsu][-B <目錄>...][-M <目錄>...][-S <目錄>...][文件...]
root@e0017cab245f:/# whereis nginx nginx: /usr/sbin/nginx /usr/lib/nginx /etc/nginx /usr/share/nginx
ps 中間件這麼多,誰記得住呢,記不住本身看看就好了不是嗎?
確實 gZip 真的沒開啓,被注掉了。
咱們打開 gZip 的註釋,而且防止服務端對 css 偷懶,咱們一步到位加上幾行經典配置。
gzip on; gzip_min_length 1k; gzip_buffers 4 16k; gzip_comp_level 6; gzip_types application/javascript text/plain application/x-javascript text/css application/xml text/javascript application/json; gzip_vary on;
nginx配置 代碼在這裏
就目前來看,你有兩種方法能夠選擇:
咱們基於第二點在 new-bee/ 目錄 同級 建立了一個目錄 nginx/ 建立一個同名的 nginx.conf 文件。
nginx配置 代碼在這裏
docker stop nginx
docker rm nginx
docker run --name nginx -d -p 8888:80 -v /new-bee/dist:/usr/share/nginx/html -v /nginx/nginx.conf:/etc/nginx/nginx.conf:ro registry.docker-cn.com/library/nginx
http://localhost:8888/#/
爲了不瀏覽器加載剛纔的 304 緩存,清除下瀏覽器緩存或進行隱身模式
已經奏效了。
只有 50K 左右,壓縮了 2/3 的大小,這對於大型項目來講,節省的不僅是 100K ,甚至是更多,webpack 或者說 gz 等壓縮算法,會將全部的大量重複的片斷單獨標記,因此重複的越多,壓縮的越多,這對於如今帶寬比金子貴的雲服務來講是十分重要的。
你們注意到,有些能用 CDN 的我選擇使用了 CDN,那麼 CDN 對於線上服務來講到底有多重要呢?
廢話先不說給你們上個對比圖 測試地址
能夠看到僅有幾個地方還算不錯,其他地方都是一塌糊塗
不用說了吧?不過還好,這部分咱們資金不足敗了也很正常,但你們可能也大概知道 CDN 的意義了,主要意義不是節省開源項目服務器帶寬,而是全國各個節點的訪問速度問題,也就解釋了:我部署的項目訪問速度還不錯,你這裏怎麼這麼慢,你網很差吧?CDN 來告訴你答案。
咱們仍是拿實戰的 bbs論壇 舉例子吧,查看網絡狀態:
使用 CDN 的幾點優點
客戶端的 cookie 是綁定服務端 域名 的, 看上圖,咱們須要 XHR 請求攜帶 cookie 訪問服務端獲取對應權限,但試想一下:每個 js、img、甚至是css 都攜帶垃圾的 cookie ,在大用戶量下,服務端承受着不該該屬於他的痛苦,這樣的消耗是特別應該避免的,咱們能夠隨便翻一翻任何一個成熟的網站,都會發現存在本身的 CDN 服務,這樣既優化了中國不一樣地區的訪問速度,同時也大大減少了服務端的開銷。
很長時間前經歷過公司前端 webpack 編譯特別慢的問題,dev 模式下咱們能夠注掉開發範圍外的 路由,可是 build 發佈的時候彷佛無法解決,使用了 Happypack 多線程打包仍是不如人意,查閱資料讀到了 這篇文章
咱們能夠把可以 externals 調的排除掉,而後使用 webpack 的 webpack.DllPlugin 生成依賴庫(這點很重要),大大減小便以速度,DllPlugin 本質上的作法和咱們手動分離這些第三方庫是同樣的,可是對於包極多的應用來講,自動化明顯加快了生產效率。
其實不少人都知道,可能剛入坑的同窗不太瞭解,無論是 npm maven 都有本身一套以來分析工具,固然也都來源於第三方,這裏爲你們介紹 npm 的以來分析工具: webpack-bundle-analyzer ,他會在瀏覽器生成一個報表,直觀的展現哪裏大,哪裏須要優化,以及預測 gZip 的大小,仍是以 實戰項目爲例:
按照官方指引的配置,下載依賴,package.json 文件指定下 build 的腳本:
"analyz": "NODE_ENV=production npm_config_report=true npm run buildProd",
運行一下:
npm run analyz
效果:
分析:
發現了問題,static/
靜態文件下 hightlight 文件比較大,有錢能夠考慮下 CDN,node_modules/
下 element-ui 餓了麼組件比較大,(我比較懶,全局導入的,能夠用哪一個引入哪一個避免全局打包問題)能夠優化,而後無聊的同窗沒事兒點點玩玩吧。
當打包構建應用時,Javascript 包會變得很是大,影響頁面加載。若是咱們能把不一樣路由對應的組件分割成不一樣的代碼塊,而後當路由被訪問的時候才加載對應組件,這樣就更加高效了。 Webpack 的代碼分割功能, 實現路由組件的懶加載.
官方說的挺詳細了,這裏就偷個懶不上代碼了,給你們提供一種經典處理方式,咱們不放在組件上,直接對路由進行拆分,具體能夠看 實戰項目路由 的路由拆分
會發現不少這種註釋:
const Blog = () => import(/* webpackChunkName: "blog" */ '@/container/blog/Blog')
那麼相似:
/* webpackChunkName: "blog" */
不是白寫的,他是配合 webpack 對項目各路由拆分的,咱們能夠看看 實際項目加載狀況 :
這個 blog.hash.js
不是咱們寫的,是 webpack 進行分割的,這樣相似 vue 這樣的單頁面架構,不會加載某模塊老是加載所有腳本,大大提高加載速度。
原本不想講的,簡單說說吧,經常使用的也就那幾種 svg 、base6四、 或使用fastdfs組件相似 CDN 的服務。
簡單來說 base64 會減小你的 http 請求數量,要知道 XHR 可不是省油的燈,他會帶來額外的處理請求和處理響應損耗,以表情爲例,動輒幾十個表情 http 請求彷佛太智障了一些,一般採用 base64 處理,減小了 http 請求數量,可是增大了圖片自己的體積,若是你用了webpack 且你的表情在本地,那麼 webpack 能夠幫你自動進行 base64 編碼哦。
用戶上傳的圖片能夠經過壓縮圖片大小或質量減小帶寬哦,一般使用 GM 對用戶上傳的有必要大鎖的圖片 壓縮成不一樣大小的,根據業務加載,好比頭像,默認確定不會請求原始圖片,今日頭條的正文,使用流量的狀況下也會默認加載小圖,這些都不是客戶端能作到的,須要服務端壓縮。
固然這些知識萬里長征的第一步,之後的優化之路茫茫多,能大概想起來的好比 :Lazy-Load(優化首屏體驗)、PWA(構建web APP)、服務端渲染(爲了SEO)、骨架屏(提高用戶體驗),後端和服務端文章還沒寫, 1.0 版本就放這些吧,期待第二版填坑,下篇開始後端。
SO - 努力吧!