美文网首页
前端构建 DevOps :脚手架篇 - H5 基础脚手架

前端构建 DevOps :脚手架篇 - H5 基础脚手架

作者: Cookieboty | 来源:发表于2020-11-20 16:18 被阅读0次

    前言

    H5 基础脚手架:极速构建项目

    上一篇讲到了快速构建项目的通用 webpack 构建,此篇将结合业务修改 H5 的脚手架

    小声 BB,不是一定适合你的项目,具体项目具体对待,符合自身业务的才是最好的

    资源添加版本号

    看过之前博客的同学,应该知道在创建版本的时候引入了版本号的概念,在创建分支版本的时候,带上版本号,创建的分支名为 feat/0.0.01,而我们发布的静态资源也是带了版本

    image

    之前有让同学关注过 url 上面的版本号哈

    改造 Webpack 路径

    const branch = childProcess.execSync('git rev-parse --abbrev-ref HEAD').toString().replace(/\s+/, '')
    const version = branch.split('/')[1] // 获取分支版本号
    
    output: {
      publicPath: `./${version}`,
    }
    

    如上,我们先将分支版本号获取,再修改资源引用路径,即可完成资源版本的处理,如下图所示,h5 链接被修改成常规 url,引用资源带上了版本号

    image

    版本号的优势

    1. 可以快速定位 code 版本,针对性的修复
    2. 每个版本资源保存上在 cdn 上,快速回滚只需要刷新 html,不必重新构建发布

    Webpack Plugin 开发

    直接添加版本的方法是不是蠢出天际,so 我们随便写个插件玩玩好了

    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() 
    ]}
    

    如上,我们创建一个 webpack plugin,通过监听 webpack hooks 在任务执行之前修改对应的资源路径,通用性上升。

    高级定制化

    CDN 资源引入

    此外之前的博客我们还引入了 cdn 的概念,我们可以将上述插件升级,构建的时候引入通用的 cdn 资源,减少构建与加载时间。

    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,主动添加了 react 相关的第三方 cdn 链接,这样在生产环境中,同域名下面的项目,可以复用资源

    通过缓存解决加载 js

    对于长期不会改变的静态资源,可以直接将资源缓存在本地,下次项目打开的时候可以直接从本地加载资源,提高二次开启效率。

    首先,我们选择 indexDB 来进行缓存,因为 indexDB 较 stroage 来说,容量会更大,我们本身就需要缓存比较大的静态资源所以需要更大容量的 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 动态添加到页面中去。

    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 拉取静态资源
    class xhr {
      constructor() {
        this.xhr;
        if (window.XMLHttpRequest) {
          this.xhr = new XMLHttpRequest();
        } else {
          this.xhr = new ActiveXObject('Microsoft.XMLHTTP');
        }
      }
    
      // 同步请求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)
              }
            }
          }
        })
      }
    }
    

    弱网环境下的直接加载

    image

    弱网环境下的缓存加载

    image
    image

    对比上图,可以明显看出,在网络环境波动的情况下,有缓存加持的网页二次开启的速度会明显提效,当然在性能上,由于需要判断第三方静态资源版本以及从本地读取资源,会消耗部分时间,可以针对业务自行取舍

    优劣势对比

    优势

    1. 统一接管项目的依赖,可以针对性的升级通用资源
    2. 资源有版本依赖概念,缓存在本地的时候,可以快速切换版本
    3. 二次加载速度会上升
    4. 配合 Service Worker 有奇效

    劣势

    1. 统一升级的过程,可能有引用项目存在不匹配造成程序崩溃的情况
    2. 其实强缓存所有共用静态 cdn 资源也是 ok 的,干嘛那么费劲呢

    上述的插件有没有同学想要用的,需要的留言,我放到 github 上去

    全系列博文目录

    后端模块

    1. DevOps - Gitlab Api使用(已完成,点击跳转)
    2. DevOps - 搭建 DevOps 基础平台 基础平台搭建上篇 | 基础平台搭建中篇 | 基础平台搭建下篇
    3. DevOps - Gitlab CI 流水线构建
    4. DevOps - Jenkins 流水线构建
    5. DevOps - Docker 使用
    6. DevOps - 发布任务流程设计
    7. DevOps - 代码审查卡点
    8. DevOps - Node 服务质量监控

    前端模块

    1. DevOps - H5 基础脚手架
    2. DevOps - React 项目开发

    尾声

    此项目是从零开发,后续此系列博客会根据实际开发进度推出(真 TMD 累),项目完成之后,会开放部分源码供各位同学参考。

    为什么是开放部分源码,因为有些业务是需要贴合实际项目针对性开发的,开放出去的公共模块我写的认真点

    为了写个系列博客,居然真撸完整个系统(不是一般的累),觉得不错的同学麻烦顺手三连(点赞,关注,转发)。

    相关文章

      网友评论

          本文标题:前端构建 DevOps :脚手架篇 - H5 基础脚手架

          本文链接:https://www.haomeiwen.com/subject/fvadiktx.html