近幾年,前端各類框架工具層出不窮,從兩三年前仍是一個jQuery搞定全站,到以後requirejs/seajs,node,gulp/webpack,Angular/React/Vue,RN/weex的不斷涌現,徹底顛覆了原來的前端開發模式。css
那麼這些框架和工具給咱們到底帶來了什麼好處呢?其實我認爲最核心莫過於這兩點:模塊化開發、自動化工程。而本次前端重構所圍繞的核心問題就是自動化工程,將原有的gulp版本的項目利用webpack完全改造,順利消滅了既繁瑣又易錯的人工操做。html
咱們先來看下奇貨商城以前的開發流程:前端
從上圖能夠看出,咱們奇貨前端開發以前存在的一些痛點:node
前端在後端項目裏面修改vm文件聯調;webpack
開發聯調須要上傳靜態資源到測試域名CDN;es6
不一樣目錄下的資源還須要在CDN上傳網站上一級一級目錄的點開再上傳;web
上線前須要人工去替換vm文件裏的CDN路徑;express
上線前還須要人工去上傳靜態資源到正式域名CDN;npm
開發模式不支持es6轉義,致使低端安卓機沒法在本地進行前端調試;json
gulp-babel不徹底支持es6轉es5,致使部分低端安卓機出現各類莫名其妙的問題;
以上這些痛點,形成的重複性無用功,既浪費精力又着實讓人蛋疼,而通過此次的框架重構,只需一鍵操做,就可完成聯調和發佈的部署。省心省力還不會出錯。
先看一下改版後,奇貨商城的開發流程:
從上圖能夠看到,咱們通過改版後作到了:
vm文件自動生成
開發聯調直接讀取本地靜態資源
打包後全部資源在同一級目錄,一次性拖拽上傳(下個版本將實現前靜態資源自動上傳)
只需一行配置項,自動生成對應的線上CDN路徑
完美的babel-loader,es6語法也可在低端安卓機上輕鬆本地調試;
下面咱們看看如何實現。
下面是部分主要目錄結構:
├── build (全部的webpack配置項) │ ├── build.js │ ├── dev-client.js │ ├── dev-server.js │ ├── utils.js (★入口配置,生成文件配置,vm生成都靠這個文件) │ ├── webpack.base.conf.js (基礎配置) │ ├── webpack.dev.conf.js (開發模式配置) │ └── webpack.prod.conf.js (生成環境配置) ├── config (node環境變量,入口文件的配置) │ ├── dev.env.js │ ├── entry.js (頁面文件列表) │ ├── index.js (★主配置文件) │ ├── prod.env.js │ └── skinEntry.js (皮膚文件列表) ├── dist (打包後生成的文件夾,已所有轉成vm) │ ├── goods │ │ ├── detail.vm │ ├── index.vm │ └── static (打包後-靜態資源文件) │ ├── css │ ├── js │ └── skins (打包後皮膚文件夾) │ ├── default │ │ ├── default.1184b4d7.js │ │ ├── default.f07ae9df.css │ │ └── default.html │ ├── huotu │ └── pay ├── mock ├── package.json ├── routes ├── src (源文件) │ ├── js │ │ ├── components │ │ ├── goods │ │ │ ├── detail.js │ │ │ └── skins │ │ │ ├── default.js │ │ │ ├── huotu.js │ │ ├── index.js │ ├── less │ │ ├── components │ │ ├── goods │ │ │ ├── detail.less │ │ │ └── skins │ │ │ ├── default.less │ │ │ ├── huotu.less │ ├── index.less │ └── pages │ ├── components │ ├── goods │ │ ├── detail.html │ │ └── skins │ │ ├── default.html │ │ ├── huotu.html │ └── index.html ├── static │ └── images └── unit (公共庫) ├── common (業務組件) │ ├── js │ └── less ├── layout (公共頁面) │ ├── footer.html │ └── header.html └── lib (第三方組件)
以上是咱們奇商城的前端目錄結構。
webpack的一些必用的loader和plugin,例如less-loader, style-loader, file-loader, html-loader, 還有UglifyJsPlugin, ExtractTextPlugin, OptimizeCSSPlugin
等等,在這裏就不詳細展開了。
咱們重點說說如下幾點核心:
經過node腳原本調用webpack,而不是直接在命令行啓動webpack,會有這麼幾個用處:
經過node啓express作本地mock數據;
開發環境和生產環境的公共配置項,經過webpack-merge
模塊作抽離,方便維護;
能夠設置node環境變量,以區分不一樣環境中的打包配置,這點在後面還有一個大招;
這貨能夠說是整個構建過程裏,核心中的核心了。
自動生成vm、開發環境調用本地資源,以及皮膚文件的管理都有這個插件的功。部分代碼:
new HtmlWebpackPlugin({ filename: process.env.NODE_ENV === 'production' ? path + name + '.vm' : path + name + '.html', template: template, inject: false, chunks: [pathBuild + name, 'vendor', 'manifest'] })
經過判斷node環境變量,決定生成vm仍是本地html;
經過這個插件實現了js模塊打包,公共模塊提取,客戶端緩存&增量發佈,皮膚文件生成。部分代碼:
for (let i = 0; i < entry.length; i++) { let item = entry[i] let path = item.path let name = item.name let pathBuild = path.replace(/\//g, '-'); result[pathBuild + name] = './src/js/' + path + name + '.js' } for (let i = 0; i < skinEntry.length; i++) { let item = skinEntry[i] let path = item.path let name = item.name if (process.env.NODE_ENV === 'production') { result['../skins/' + path + name] = './src/js/goods/skins/' + name + '.js' } else { result['skins/' + path + name] = './src/js/goods/skins/' + name + '.js' } } Object.assign(result, { vendor: ['@unit/common/js/base', '@unit/common/js/util'] }) // 公共文件提取 new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', // 注意不要.js後綴 chunks: utils.computeChunks(entryConfig, '') }) // 避免修改業務代碼致使vendor的md5改變,保留文件緩存 new webpack.optimize.CommonsChunkPlugin({ name: 'manifest', chunks: ['vendor'] })
自動化部署是在打包服務器經過腳本實現的,先經過npm命令打包前端工程,而後將代碼copy到後端工程中,最後打包後端項目,再發布。
webpack官方文檔並無如何引入公共html文件的說明,這一點是在翻了N多資料後才發現的,最終的方案是:
去掉webpack.config.js文件中配置的全局html-loader,這樣html模版文件就不會被html-loader解析,咱們可使用ejs語法嵌入其餘html頁面和圖片資源。由於沒了全局的html-loader解析html文件,使用ejs語法嵌入的資源返回的是ejs代碼,還須要使用html-loader來解析成html代碼。
(html-loader!)表示引用html-loader這個加載器來解析
<%= require('html-loader!../layout/header.html') %>
可是這樣將全局html-loader去掉後,又碰到了下面的問題。
vm中有時須要直接引用後端的變量,如${cssUrl}
,就像這樣:
這時候webpack打包竟然就報錯了,報錯了:
出現這個問題的緣由應該是因爲HtmlWebpackPlugin這個插件引用的模版默認是ejs,當不使用全局html-loader的時候,模板文件實際上是以ejs解析的,而${cssUrl}
在ejs中也識別爲一個變量,固然就報錯了。
這過程當中,整個週末都在想這個問題,甚至已經開始考慮用gulp+webpack的方案了。。
又翻了不少資料,忽然想到既然是ejs模板,能夠嘗試了一些ejs去寫,而不是非要把這個模板以html的方式loader進來,而後就有了以下方法:
<link href="<%= '${cssUrl}' %>" rel="stylesheet">
這時候就被識別爲一個字符串了!成功解決。
上面的方法解決的其實也是挺醜的,由於本地開發的時候須要引用本地文件的,上線的時候又得傻乎乎地去一個個地方去替換:
<!-- <link href="<%= skinCss %>" rel="stylesheet"> --> <link href="/skins/pay/pay.css" rel="stylesheet">
而後立刻試了下,在模板文件中用ejs去讀node環境變量process.env.NODE_ENV
,果真能取到值,就有了下面這個相對完美的方案:
<% if (process.env.NODE_ENV === 'production') { skinCss = '${cssUrl}'; } else { skinCss = '/skins/pay/pay.css'; } %> <link href="<%= skinCss %>" rel="stylesheet">
其中production
就是利用node啓動webpack時配置的,在這裏派上了大用場。
到這裏,咱們奇貨商城已經實現了前端工程自動化,不再用一遍又一遍地去vm裏修改路徑,人工去記着改了哪些文件,要上傳哪些靜態資源。更加不用擔憂漏傳什麼資源文件而致使線上bug辣。:)