webpack-loader是怎樣煉成的

目錄

囉嗦兩句

學習這件事從學習動機上來看,能夠分紅兩種狀況:主動學習和被動學習。主動學習就是,某天你瀏覽網頁的時候,看到一個酷到沒朋友的效果,趕忙打開開發者工具,看看用了什麼 css 屬性,用了什麼庫或者框架實現的,這是主動學習。css

還有一種是被動學習。就拿我來講,以前用 mpvue 寫小程序的時候,頁面的 json 配置都是寫在 main.js 裏面的,loader 會從 main.js 解析出對應的代碼塊,而後爲我生成對應的配置文件。可是前兩天,當我又初始化一個新項目的時候(使用的是 mpvue-loader1.1.4),這個好用的特性竟然消失了,我須要在目錄下本身手動建一個 json 文件寫頁面配置。vue

人有這麼一種本性,從很差的體驗切換到好的體驗很快,可是再切回去就很難受😢。因此,這回只有硬着頭皮寫個 webpack loader 來回歸原來的體驗了。實現的功能很簡單,就是從新實現 mpvue 原有的功能,從 js 文件中解析出配置項的內容,並生成一個json文件到對應的文件夾中。
webpack

loader 是幹什麼的

無圖言卵,先上個圖:
1539876855690.jpges6

把 webpack 想像成一個工廠,loader 就是一個個身懷絕技的流水線工人,有的會處理 svg,有的會壓縮 css 或者圖片,有的會處理 less,有的會將 es6 轉換爲 es5。他們在 webpack 的調度下 (確切的說是 loader-runer),層次分明的完成本身工做後,把本身處理的結果交給下一個工人,直到最後由 webpack 將他們的勞動成果生成 dist 目錄下的文件。web

因此一個 loader 用一個函數來表示,應該是這樣的:正則表達式

module.exports = function(content, map, meta) {
  return content;
};

上面咱們就定義了一個什麼都不幹還拿工資的 loader,它就是拿到內容後原樣交給下一個 loader 同窗。可是,它如今其實還只是一個函數 -- 由於它尚未混入 webpack loader 內部啊,如今咱們幫他打入 webpack 的 loader 內部:json

// webpack.config.js

...
module: {
  rules: [
    {
      test: /\.js$/,
      include: [resolve('src'), resolve('test')],
      use: [
        {
          loader: path.resolve('path/to/my-loader.js'), // 本 loader😊
          options: {a: 1}
        },
        ... 其餘 loader
      ]
    }
  ]
}

做爲一個什麼都不作的 loader,它在 rules 下面使用 /\.js$/ 這個正則表達式,告 (hu) 訴 (you) webpack 它能夠處理 js 文件, 還經過 includes 字段,說明了它的業務範圍只負責 src 和 test 目錄下的 js 文件。小程序

如今回到上面的圖,大部分 loader 仍是實實在在辦事的。有的能夠處理文本文件,如 css 預處理,進去的是 less 語法的文件,出來的是 css 語法的文件;有的能夠處理二進制文件,好比將較小的圖片變成 base64 字符串。還有的 loader 買一送二,好比 mpvue-loader, 輸入的是 vue 文件,可是會輸出 wxss,wxml,js 三個文件。可是,這些工做僅靠 loader 本身是辦不到的,它須要和 webpack 溝通。也就是說,幹活是須要工具的,這個工具就是 loader 的上下文 (context)。
api

loader 的工具箱 --context

根據 官方文檔 的解釋,loader context 表示在 loader 內使用 this 能夠訪問的一些方法或屬性。仍是在上面的那個啥都不幹 loader 上說明:框架

const path = require('path')
module.exports = function(content){
  console.log('resource', this.resource) // 文件路徑帶 query
  console.log('query', this.query)// 對應配置中的 options {a: 1}
  console.log('resourcePath', this.resourcePath)// 文件路徑
  this.emitFile('main.json', JSON.stringify({hello: 'world'}))// dist目錄下生成一個 json 文件
  this.emitWarning('這個 loader 啥都不幹')// 會觸發一個警告⚠️
  // this.emitError('這個 loader 啥都不幹')// 會致使本次編譯過程失敗
  return content
}

正如上面的例子那樣,有了上下文提供的工具包,loader 就能夠幹更多的事情而不僅是對 content 進行處理,好比:

  • 經過 this.emitError 向 webpack 拋出一個錯誤,中斷本次編譯
  • 經過 this.emitFile 生成一個新的文件,emitFile接受的第一個參數是相對於dist目錄的文件路徑
  • 經過 this.resource 得到資源路徑

此外還有不少工具,你們能夠看文檔瞭解。

loader 實戰

把這些瞭解清楚以後,咱們就能夠實現以前想要的功能了:從全部文件名爲 main.js 的文件中提取 export default 的內容,並在同級目錄下生成一個 json 文件,以下所示:

// 生成前 src 目錄

page
|-main.js

// 生成後 dist 目錄
page
|-main.js
|-main.json

所有代碼以下,解釋見註釋:

module.exports = function(source){

  if(/main\.js$/.test(this.resource)){// 只處理 main.js 文件
    let jsonPath = this.resource.replace(/.+src\//, '') +'on'// 生成 json 文件相對於 dist 目錄的路徑
    let re = /export\s+?default\s*(\{[\s\S]+\})/m; // 解析出文件中的 export default 代碼塊
    if (re.test(source)) {
      let config = eval('a=' + re.exec(source)[1]); // 將配置轉成對象
      console.log(config)
      this.emitFile(jsonPath, JSON.stringify(config)); // 寫到文件中
    }
  }
  return source;
}

這就是本文的所有內容了,今天參加了同窗的婚禮,見了幾個老同窗,包括還在船廠🚢 打拼的同窗。吃飯的時候聊着你們的現狀,真的很開心,沒有想象中的侷促和無話可說。轉眼畢業都三年了,時間過的真 tm 快。(完)

相關文章
相關標籤/搜索