實現一個自動上傳資源到CDN的Webpack插件

導語:平常開發中咱們一般會把前端的靜態資源上傳到CDN,以提升訪問速度。本文將會帶領你們開發一個Webpack插件,實現把資源打包成zip,而後上傳到COS自動解壓的功能。javascript

需求分析

在開發一個功能以前,咱們先來拆解一下需求。通過分析,其實須要實現的邏輯就是如下幾點:css

  • Webpack構建完成以前,遍歷靜態資源,生成zip
  • 把zip上傳到COS(CDN)
  • 上傳到COS後自動解壓

由於咱們上傳的是一個.zip的文件,因此在上傳完成以後,須要對文件進行解壓。這裏會以COS觸發的方式觸發一個雲函數,用以處理zip文件的解壓。html

Webpack插件的基本結構

一個Webpack插件其實就是一個構造函數,原型上定義了一個apply方法,這個方法會在Webpack啓動時被調用。因此能夠在apply方法中設置鉤子函數,在特定的時機執行指定的邏輯。 下面就是一個Webpack插件的基本結構:前端

class UploadToCDN {
    apply (compiler) {
      compiler.hooks.somehook.tap('plugin-name', (compilation) => {
        // to do something
      })
    }
  }
複製代碼

compilercompilation

如上面的代碼所示,在開發Webpack插件時,必然會使用到compilercompilation這兩個對象。 compiler包含了咱們這次構建全部的配置信息,同時也提供了一些生命週期鉤子,讓咱們能夠在特定的時機執行相應的邏輯。 compilation能夠理解爲這次運行打包的上下文,全部打包過程當中產生的結果,都會放到這個對象中。它包含了當前的模塊資源、編譯生成資源、變化的文件、以及被跟蹤依賴的狀態信息等。當Webpack以開發模式運行時,每當檢測到一個變化,一次新的compilation將被建立。與compiler同樣,compilation對象也提供了不少事件回調供插件作擴展。 在UploadToCDN插件的開發中,咱們主要用到了compiler的兩個鉤子emitafterEmitemit會在輸出assetoutput目錄以前執行,因此咱們能夠在這個鉤子函數上對生成的靜態資源進行打包壓縮。而afterEmit會在輸出assetoutput目錄以後執行,此時zip文件已經生成,能夠進行上傳操做。java

因此咱們的插件大概是這樣子的:node

class UploadToCDN {
    constructor(options) {
      this.options = options
    }
    apply(compiler) {
      // 由於會有異步操做,因此這裏用tapAsync, 當邏輯執行完成時調用callback通知Webpack 
      compiler.hooks.emit.tapAsync(PLUGIN_NAME, (compilation, callback) => {
        // 在資源輸出到dist以前進行壓縮
      })
      compiler.hooks.afterEmit.tapAsync(PLUGIN_NAME, (compilation, callback) => {
        // 在資源輸出後,把dist上傳到cos
      })
    }
  }
複製代碼

插件核心代碼實現

  • 壓縮資源文件

compilation.assets這個對象上會有咱們本次構建的資源,因此咱們能夠經過遍歷獲取生成的靜態資源,同時也能夠經過給compilation.assets[filename]賦值輸出內容。這裏咱們會使用JSZip對文件進行壓縮。git

compiler.hooks.emit.tapAsync(PLUGIN_NAME, (compilation, callback) => {
    const folder = zip.folder(this.options.filename)
    // 遍歷資源,把資源放進zip包中
    for (let filename in compilation.assets) {
      const source = compilation.assets[filename].source()
      folder.file(filename, source)
    }
    zip.generateAsync({ type: 'nodebuffer' }).then((content) => {
      // 生成zip文件,並把文件輸出到指定目錄
      this.outputPath = path.join(compilation.options.output.path, `${this.options.filename}.zip`)
      const outputRelativePath = path.relative(compilation.options.output.path, this.outputPath)
      compilation.assets[outputRelativePath] = new RawSource(content)
      callback() // 通知Webpack該鉤子函數已經執行完畢
    })
  })
複製代碼
  • 上傳zip文件

由於我使用的是騰訊雲的對象存儲,因此這裏直接經過騰訊雲的SDK把壓縮包上傳到指定的Bucketgithub

compiler.hooks.afterEmit.tapAsync('UploadToCDN', (compilation, callback) => {
    cos.putObject({
      Bucket: 'your bucket', 
      Region: 'region', 
      Key: `${this.options.filename}.zip`,
      StorageClass: 'STANDARD',
      Body: fs.createReadStream(this.outputPath),
      onProgress: progressData => {
        console.log(JSON.stringify(progressData))
      }
    }, (err, data) => {
      if (err) {
        console.error('Upload to cdn fail.......!')
        console.err(err)
      }
      console.log('Upload to cdn success...!', data)
      callback()
    })
  })
複製代碼

配置雲函數

上面咱們上傳的是一個zip文件,上傳以後咱們還須要對zip包進行解壓,讓各個靜態資源處於正確的訪問路徑上。 具體作法是:markdown

  1. 建立兩個Bucket(staticupload)。static用於存放咱們真實會訪問到的靜態資源,而upload則專門存儲咱們剛剛上傳的zip文件。
  2. 選擇模板建立雲函數,函數模板選擇Nodejs解壓zip包。

3. 設置雲函數爲COS觸發 關於雲函數的代碼這裏就不貼出來了,由於都是基於模板生成的,基本不用本身手寫代碼。 解壓雲函數的邏輯 當有zip文件上傳到upload Bucket時,雲函數會被觸發,同時雲函數能拿到zip文件的下載地址。以後調用SDK的API下載zip,下載完成以後進行解壓,而後遍歷目錄,把資源上傳到另外一個Bucket static上。通過這些處理以後,咱們就能在static目錄上訪問到咱們剛剛打包上傳的js, html, css, image等等。app

通過以上一系列邏輯實現以後,上傳CDN插件總算完成了。具體源碼請查看:github.com/samzerf/upl…

相關文章
相關標籤/搜索