Webpack自動化工程


近幾年,前端各種框架工具層出不窮,從兩三年前還是一個jQuery搞定全站,到之后requirejs/seajs,node,gulp/webpack,Angular/React/Vue,RN/weex的不斷涌現,完全顛覆了原來的前端開發模式。

那么這些框架和工具給我們到底帶來了什么好處呢?其實我認為最核心莫過於這兩點:模塊化開發自動化工程。而本次前端重構所圍繞的核心問題就是自動化工程,將原有的gulp版本的項目利用webpack徹底改造,順利消滅了既繁瑣又易錯的人工操作。

gulp版本的痛點

我們先來看下奇貨商城之前的開發流程:

a1

從上圖可以看出,我們奇貨前端開發之前存在的一些痛點:

  1. 前端在后端項目里面修改vm文件聯調;
  2. 開發聯調需要上傳靜態資源到測試域名CDN;
  3. 不同目錄下的資源還需要在CDN上傳網站上一級一級目錄的點開再上傳;
  4. 上線前需要人工去替換vm文件里的CDN路徑;
  5. 上線前還需要人工去上傳靜態資源到正式域名CDN;
  6. 開發模式不支持es6轉義,導致低端安卓機無法在本地進行前端調試;
  7. gulp-babel不完全支持es6轉es5,導致部分低端安卓機出現各種莫名其妙的問題;

以上這些痛點,造成的重復性無用功,既浪費精力又着實讓人蛋疼,而經過這次的框架重構,只需一鍵操作,就可完成聯調和發布的部署。省心省力還不會出錯。

如何利用webpack做自動化

先看一下改版后,奇貨商城的開發流程:

a2

從上圖可以看到,我們經過改版后做到了:

  1. vm文件自動生成
  2. 開發聯調直接讀取本地靜態資源
  3. 打包后所有資源在同一級目錄,一次性拖拽上傳(下個版本將實現前靜態資源自動上傳)
  4. 只需一行配置項,自動生成對應的線上CDN路徑
  5. 完美的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

通過node腳本來調用webpack,而不是直接在命令行啟動webpack,會有這么幾個用處:

  • 通過node啟express做本地mock數據;
  • 開發環境和生產環境的公共配置項,通過webpack-merge模塊做抽離,方便維護;
  • 可以設置node環境變量,以區分不同環境中的打包配置,這點在后面還有一個大招;

HtmlWebpackPlugin

這貨可以說是整個構建過程里,核心中的核心了。

自動生成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;

CommonsChunkPlugin

通過這個插件實現了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到后端工程中,最后打包后端項目,再發布。

a3

遇到的困難

公共文件的引入

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去掉后,又碰到了下面的問題。

jsp變量的引入

vm中有時需要直接引用后端的變量,如${cssUrl},就像這樣:

b1

這時候webpack打包居然就報錯了,報錯了:

b2

原因排查

出現這個問題的原因應該是由於HtmlWebpackPlugin這個插件引用的模版默認是ejs,當不使用全局html-loader的時候,模板文件其實是以ejs解析的,而${cssUrl}在ejs中也識別為一個變量,當然就報錯了。

解決方法

這過程中,整個周末都在想這個問題,甚至已經開始考慮用gulp+webpack的方案了。。

又翻了很多資料,突然想到既然是ejs模板,可以嘗試了一些ejs去寫,而不是非要把這個模板以html的方式loader進來,然后就有了如下方法:

<link href="<%= '${cssUrl}' %>" rel="stylesheet">  

這時候就被識別為一個字符串了!成功解決。

進一步探索,巧用node環境變量

上面的方法解決的其實也是挺丑的,因為本地開發的時候需要引用本地文件的,上線的時候又得傻乎乎地去一個個地方去替換:

<!-- <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時配置的,在這里派上了大用場。

End

到這里,我們奇貨商城已經實現了前端工程自動化,再也不用一遍又一遍地去vm里修改路徑,人工去記着改了哪些文件,要上傳哪些靜態資源。更加不用擔心漏傳什么資源文件而導致線上bug辣。😃


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM