前端構(gòu)建 DevOps :腳手架篇 - H5 基礎(chǔ)腳手架

前言

H5 基礎(chǔ)腳手架:極速構(gòu)建項(xiàng)目

上一篇講到了快速構(gòu)建項(xiàng)目的通用 webpack 構(gòu)建鞭达,此篇將結(jié)合業(yè)務(wù)修改 H5 的腳手架

小聲 BB司忱,不是一定適合你的項(xiàng)目皇忿,具體項(xiàng)目具體對(duì)待,符合自身業(yè)務(wù)的才是最好的

資源添加版本號(hào)

看過之前博客的同學(xué)坦仍,應(yīng)該知道在創(chuàng)建版本的時(shí)候引入了版本號(hào)的概念鳍烁,在創(chuàng)建分支版本的時(shí)候,帶上版本號(hào)繁扎,創(chuàng)建的分支名為 feat/0.0.01幔荒,而我們發(fā)布的靜態(tài)資源也是帶了版本

image

之前有讓同學(xué)關(guān)注過 url 上面的版本號(hào)哈

改造 Webpack 路徑

const branch = childProcess.execSync('git rev-parse --abbrev-ref HEAD').toString().replace(/\s+/, '')
const version = branch.split('/')[1] // 獲取分支版本號(hào)

output: {
  publicPath: `./${version}`,
}

如上,我們先將分支版本號(hào)獲取梳玫,再修改資源引用路徑爹梁,即可完成資源版本的處理,如下圖所示提澎,h5 鏈接被修改成常規(guī) url姚垃,引用資源帶上了版本號(hào)

image

版本號(hào)的優(yōu)勢(shì)

  1. 可以快速定位 code 版本,針對(duì)性的修復(fù)
  2. 每個(gè)版本資源保存上在 cdn 上盼忌,快速回滾只需要刷新 html积糯,不必重新構(gòu)建發(fā)布

Webpack Plugin 開發(fā)

直接添加版本的方法是不是蠢出天際,so 我們隨便寫個(gè)插件玩玩好了

const HtmlWebpackPlugin = require('html-webpack-plugin');
const childProcess = require('child_process')
const branch = childProcess.execSync('git rev-parse --abbrev-ref HEAD').toString().replace(/\s+/, '')
const version = branch.split('/')[1]

class HotLoad {
  apply(compiler) { 
    compiler.hooks.beforeRun.tap('UpdateVersion', (compilation) => {
      compilation.options.output.publicPath = `./${version}/`
    })
  }
}

module.exports = HotLoad;

module.exports = {
  plugins: [
    new HotLoad() 
]}

如上谦纱,我們創(chuàng)建一個(gè) webpack plugin看成,通過監(jiān)聽 webpack hooks 在任務(wù)執(zhí)行之前修改對(duì)應(yīng)的資源路徑,通用性上升跨嘉。

高級(jí)定制化

CDN 資源引入

此外之前的博客我們還引入了 cdn 的概念川慌,我們可以將上述插件升級(jí),構(gòu)建的時(shí)候引入通用的 cdn 資源祠乃,減少構(gòu)建與加載時(shí)間窘游。

const scripts = [
  'https://cdn.bootcss.com/react-dom/16.9.0-rc.0/umd/react-dom.production.min.js',
  'https://cdn.bootcss.com/react/16.9.0/umd/react.production.min.js'
]


class HotLoad {
  apply(compiler) { 
    compiler.hooks.beforeRun.tap('UpdateVersion', (compilation) => {
      compilation.options.output.publicPath = `./${version}/`
    })
    
    compiler.hooks.compilation.tap('HotLoadPlugin', (compilation) => {
      HtmlWebpackPlugin.getHooks(compilation).alterAssetTags.tapAsync('HotLoadPlugin', (data, cb) => {
        scripts.forEach(src => [
          data.assetTags.scripts.unshift({
            tagName: 'script',
            voidTag: false,
            attributes: { src }
          })
        ])
        cb(null, data)
      })
    })
  }
}

上述我們借助了 HtmlWebpackPlugin 提供的 alterAssetTags hooks,主動(dòng)添加了 react 相關(guān)的第三方 cdn 鏈接跳纳,這樣在生產(chǎn)環(huán)境中忍饰,同域名下面的項(xiàng)目,可以復(fù)用資源寺庄。

通過緩存解決加載 js

對(duì)于長(zhǎng)期不會(huì)改變的靜態(tài)資源艾蓝,可以直接將資源緩存在本地,下次項(xiàng)目打開的時(shí)候可以直接從本地加載資源斗塘,提高二次開啟效率赢织。

首先,我們選擇 indexDB 來進(jìn)行緩存馍盟,因?yàn)?indexDB 較 stroage 來說于置,容量會(huì)更大,我們本身就需要緩存比較大的靜態(tài)資源所以需要更大容量的 indexDB 來支持

import scripts from './script.json';
import styles from './css.json';
import xhr from './utils/xhr'
import load from './utils/load'
import storage from './stroage/indexedDb'

const _storage = new storage()
const _load = new load()
const _xhr = new xhr()

class hotLoad {

  constructor(props) {
    this.loadScript(props)
    this.issafariBrowser = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
  }

  async loadScript(props = []) {
    const status = await _storage.init()
    let _scripts = scripts
    const expandScripts = props

    if (status) {
      for (let script of _scripts) {
        const { version, type = 'js', name, url } = script
        if (this.issafariBrowser) {
          await _load.loadJs({ url })
        } else {
          const value = await _storage.getCode({ name, version, type });
          if (!value) {
            const scriptCode = await _xhr.getCode(url || `${host}/${name}/${version}.js`)
            if (scriptCode) {
              await _load.loadJs({ code: scriptCode })
              await _storage.setCode({ scriptName: `${name}_${version}_${type}`, scriptCode: scriptCode })
            }
          } else {
            await _load.loadJs({ code: value })
          }
        }
      }

      for (let style of styles) {
        const { url, name, version, type = 'css' } = style
        if (this.issafariBrowser) {
          await _load.loadCSS({ url })
        } else {
          const value = await _storage.getCode({ name, version, type })
          if (!value) {
            const cssCode = await _xhr.getCode(url || `${host}/${name}/${version}.css`)
            _storage.setCode({ scriptName: `${name}_${version}_${type}`, scriptCode: cssCode })
            _load.loadCSS({ code: cssCode })
          } else {
            _load.loadCSS({ code: value })
          }
        }
      }
    } else {
      for (let script of _scripts) {
        const { version, name } = script
        const scriptCode = await _xhr.getCode(script.url || `${host}/${name}/${version}.js`)
        if (scriptCode) {
          await _load.loadJs({ code: scriptCode })
        }
      }
      for (let style of styles) {
        const { url, name, version } = style
        const cssCode = await _xhr.getCode(url || `${host}/${name}/${version}.css`)
        _load.loadCSS({ code: cssCode })
      }
    }

    for (let script of expandScripts) {
      const { url } = script
      await _load.loadJs({ url })
    }

  }
}

window.hotLoad = hotLoad

上述代碼是將第三方資源贞岭,通過 xhr 獲取之后八毯,使用 Blob + URL.createObjectURL 制造本地鏈接搓侄,使用 js 動(dòng)態(tài)添加到頁(yè)面中去。

class load {
  constructor() { }
  // 加載js
  loadJs({ url, code, callback }) {
    let oHead = document
      .getElementsByTagName('HEAD')
      .item(0);
    let script = document.createElement('script');
    script.type = "text/javascript";
    return new Promise(resolve => {
      if (url) {
        script.src = url
      } else {
        let blob = new Blob([code], { type: "application/javascript; charset=utf-8" });
        script.src = URL.createObjectURL(blob);
      }
      oHead.appendChild(script)
      if (script.readyState) {
        script.onreadystatechange = () => {
          if (script.readyState == "loaded" || script.readyState == "complete") {
            script.onreadystatechange = null;
            callback && callback();
            resolve(true)
          }
        }
      } else {
        script.onload = () => {
          callback && callback();
          resolve(true)
        }
      }
    })
  }

  // 加載css
  loadCSS({ url, code }) {
    let oHead = document
      .getElementsByTagName('HEAD')
      .item(0);
    let cssLink = document.createElement("link");
    cssLink.rel = "stylesheet"
    return new Promise(resolve => {
      if (url) {
        cssLink.href = url
      } else {
        let blob = new Blob([code], { type: "text/css; charset=utf-8" });
        cssLink.type = "text/css";
        cssLink.rel = "stylesheet";
        cssLink.rev = "stylesheet";
        cssLink.media = "screen";
        cssLink.href = URL.createObjectURL(blob);
      }
      oHead.appendChild(cssLink);
      resolve(true)
    })
  }
}

// 通過 xhr 拉取靜態(tài)資源
class xhr {
  constructor() {
    this.xhr;
    if (window.XMLHttpRequest) {
      this.xhr = new XMLHttpRequest();
    } else {
      this.xhr = new ActiveXObject('Microsoft.XMLHTTP');
    }
  }

  // 同步請(qǐng)求js
  getCode(url) {
    return new Promise(resolve => {
      this.xhr.open('get', url, true);
      this.xhr.send(null);
      this.xhr.onreadystatechange = () => {
        if (this.xhr.readyState == 4) {
          if (this.xhr.status >= 200 && this.xhr.status < 300 || this.xhr.status == 304) {
            resolve(this.xhr.responseText)
          }
        }
      }
    })
  }
}

弱網(wǎng)環(huán)境下的直接加載

image

弱網(wǎng)環(huán)境下的緩存加載

image

image

對(duì)比上圖话速,可以明顯看出讶踪,在網(wǎng)絡(luò)環(huán)境波動(dòng)的情況下,有緩存加持的網(wǎng)頁(yè)二次開啟的速度會(huì)明顯提效泊交,當(dāng)然在性能上乳讥,由于需要判斷第三方靜態(tài)資源版本以及從本地讀取資源,會(huì)消耗部分時(shí)間廓俭,可以針對(duì)業(yè)務(wù)自行取舍

優(yōu)劣勢(shì)對(duì)比

優(yōu)勢(shì)

  1. 統(tǒng)一接管項(xiàng)目的依賴云石,可以針對(duì)性的升級(jí)通用資源
  2. 資源有版本依賴概念,緩存在本地的時(shí)候研乒,可以快速切換版本
  3. 二次加載速度會(huì)上升
  4. 配合 Service Worker 有奇效

劣勢(shì)

  1. 統(tǒng)一升級(jí)的過程留晚,可能有引用項(xiàng)目存在不匹配造成程序崩潰的情況
  2. 其實(shí)強(qiáng)緩存所有共用靜態(tài) cdn 資源也是 ok 的,干嘛那么費(fèi)勁呢

上述的插件有沒有同學(xué)想要用的告嘲,需要的留言错维,我放到 github 上去

全系列博文目錄

后端模塊

  1. DevOps - Gitlab Api使用(已完成,點(diǎn)擊跳轉(zhuǎn))
  2. DevOps - 搭建 DevOps 基礎(chǔ)平臺(tái) 基礎(chǔ)平臺(tái)搭建上篇 | 基礎(chǔ)平臺(tái)搭建中篇 | 基礎(chǔ)平臺(tái)搭建下篇
  3. DevOps - Gitlab CI 流水線構(gòu)建
  4. DevOps - Jenkins 流水線構(gòu)建
  5. DevOps - Docker 使用
  6. DevOps - 發(fā)布任務(wù)流程設(shè)計(jì)
  7. DevOps - 代碼審查卡點(diǎn)
  8. DevOps - Node 服務(wù)質(zhì)量監(jiān)控

前端模塊

  1. DevOps - H5 基礎(chǔ)腳手架
  2. DevOps - React 項(xiàng)目開發(fā)

尾聲

此項(xiàng)目是從零開發(fā)橄唬,后續(xù)此系列博客會(huì)根據(jù)實(shí)際開發(fā)進(jìn)度推出(真 TMD 累)赋焕,項(xiàng)目完成之后,會(huì)開放部分源碼供各位同學(xué)參考仰楚。

為什么是開放部分源碼隆判,因?yàn)橛行I(yè)務(wù)是需要貼合實(shí)際項(xiàng)目針對(duì)性開發(fā)的,開放出去的公共模塊我寫的認(rèn)真點(diǎn)

為了寫個(gè)系列博客僧界,居然真擼完整個(gè)系統(tǒng)(不是一般的累)侨嘀,覺得不錯(cuò)的同學(xué)麻煩順手三連(點(diǎn)贊,關(guān)注捂襟,轉(zhuǎn)發(fā))咬腕。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市葬荷,隨后出現(xiàn)的幾起案子涨共,更是在濱河造成了極大的恐慌,老刑警劉巖宠漩,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件举反,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡扒吁,警方通過查閱死者的電腦和手機(jī)火鼻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人魁索,你說我怎么就攤上這事融撞。” “怎么了蛾默?”我有些...
    開封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵懦铺,是天一觀的道長(zhǎng)捉貌。 經(jīng)常有香客問我支鸡,道長(zhǎng),這世上最難降的妖魔是什么趁窃? 我笑而不...
    開封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任牧挣,我火速辦了婚禮,結(jié)果婚禮上醒陆,老公的妹妹穿的比我還像新娘瀑构。我一直安慰自己,他們只是感情好刨摩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開白布寺晌。 她就那樣靜靜地躺著,像睡著了一般澡刹。 火紅的嫁衣襯著肌膚如雪呻征。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天罢浇,我揣著相機(jī)與錄音陆赋,去河邊找鬼。 笑死嚷闭,一個(gè)胖子當(dāng)著我的面吹牛攒岛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播胞锰,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼灾锯,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了嗅榕?” 一聲冷哼從身側(cè)響起挠进,我...
    開封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎誊册,沒想到半個(gè)月后领突,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡案怯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年君旦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡金砍,死狀恐怖局蚀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情恕稠,我是刑警寧澤琅绅,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站鹅巍,受9級(jí)特大地震影響千扶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜骆捧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一澎羞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧敛苇,春花似錦妆绞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至来涨,卻和暖如春图焰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背扫夜。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工楞泼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人笤闯。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓堕阔,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親颗味。 傳聞我的和親對(duì)象是個(gè)殘疾皇子超陆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容