如何開發webpack loader
關於webpack
作為近段時間風頭正盛的打包工具,webpack基本佔領了前端圈。相信你都不好意思說不知道webpack。 有興趣的同學可以參考下我很早之前的webpack簡介 . 確實webpack萬事萬物皆模組的思路真是極大的方便了我們的開發,將css,圖片等檔案都能打包的功能離不開形形色色的loader。 對於一個事情要知其然更要知其所以然,抱著這個心態我們一起來看下loader的相關知識及如何開發。
學習方法
對於一個新事物最好的學習方法,我認為是其官方文件。對於loader,將其官方文件看一遍,就知道如何開發最簡單的loader了。 只是其官方文件是英文的,我就順手翻譯了一下,一方面加深自己理解。另一方面為其他同學提供個參考。 我相信看完文件你就知道如何開發一個loader了。
什麼是loader
loader是一個對面暴露一個方法的node包.當遇到某些資源需要被轉換時呼叫該方法。
簡單情況
只有一個loader來處理某個檔案時,該loader被呼叫時只有一個引數,這個引數是該檔案的內容轉化之後的字串。
loader在function執行時可以通過this context來訪問laoder API 以便更高效的開發。
一個僅僅需要一個值的同步loader可以簡單的return 自己。其他情況下,loader可以通過this.callback(err, values...)返回一系列的值。error同樣傳遞給this.callback或者在loader中丟擲。
loader期望返回1-2個值,第一個是處理之後作為string或者buffer返回的js程式碼。第二個是SourceMap或者js 物件
複雜情況:
當多個loader被鏈式呼叫時,只有最後一個loader獲得資原始檔。 同時只有第一個loader被期望返回1-2個值(即上面提到的JavaScript和SourceMap)。 其他loader接收值由上一個loader傳遞。
換句話說,鏈式loader執行順序從右至左或者自下而上。 舉個栗子:下面這段程式碼的執行順序就是自下而上 foo-loader==>bar-loader
module: { loaders: [ { test: /\.js/, loaders: [ 'bar-loader', 'foo-loader' ] } ] }複製程式碼
注意:當前weboack只會在nodemodules資料夾下面搜尋你指定的loader
如果你的資料夾不在該目錄下需要在config下面增加一項配置: 即預設訪問node_modules,你的資料夾不在的話就需要手動在配置檔案里加上了。
resolveLoader: {
modules: ['node_modules', path.resolve(__dirname, 'loaders')]
}複製程式碼
溫馨提示
ps:經過自身實踐發現這樣寫是錯的,不需要通過path去解析,直接將檔案目錄寫入即可。 一般來說loader都會發布到npm上進行管理,這種狀況不用擔心,但是開發階段如果要自行測試,就面對這種情況了。 例如,我手寫的myloader在loaders下面,例子如下。
resolveLoader:{
modules: ['node_modules','loader']
}複製程式碼
Examples
就這麼簡單就是個普通的loader
module.exports = function(source,map){
this.cacheable && this.cacheable()
this.value = source
return '/*[email protected] xiaoxiangdaiyu*/'+JSON.stringify(source)
}複製程式碼
開發指南
loader需要遵循以下事項。 以下事項按優先順序排列,第一條具有最高優先順序。
一、單一任務
loaders可以被鏈式呼叫,為每一步建立一個loader而非一個loader做所有事情 也就是說,在非必要的狀況下沒有必要將他們轉換為js。
例如:通過查詢字串將一個字串模板轉化為html。 如果你寫了個loader做了所有事情那麼你違背了loader的第一條要求。 你應該為每一個task建立一個loader並且通過管道來使用它們
- jade-loader: 轉換模板為一個module
- apply-loader: 建立一個module並通過查詢引數來返回結果
- html-loade: 建立一個處理html並返回一個string的模組
二、建立moulde話的模組,即正常的模組
loader產出的module應該和遵循和普通的module一樣的設計原則。 舉個例子,下面這樣設計是不好的,沒有模組化,依賴全域性狀態
require("any-template-language-loader!./xyz.atl");
var html = anyTemplateLanguage.render("xyz");複製程式碼
三、儘量表明該loader是否可以快取
大部分loaders是cacheable,所以應該標明是否cacheable。 只需要在loader裡面呼叫即可
// Cacheable identity loader
module.exports = function(source) {
this.cacheable();
return source;
};複製程式碼
四、不要在執行和模組之間儲存狀態
- 一個loader相對於其他編譯後的模組應該是獨立的。 除非其可以自己處理這些狀態
- 一個loader相對於同一模組之前的編譯過程應該是獨立的。
五、標明依賴
如果該loader引用了其他資源(例如檔案系統), 必須宣告它們。這些資訊用來是快取的loader失效並且重新編譯它們
var path = require("path");
module.exports = function(source) {
this.cacheable();
var callback = this.async();
var headerPath = path.resolve("header.js");
this.addDependency(headerPath);
fs.readFile(headerPath, "utf-8", function(err, header) {
if(err) return callback(err);
callback(null, header + "\n" + source);
});
};複製程式碼
六、解析依賴
很多語言都提供了一些規範來宣告依賴,例如css中的 @import 和 url(...)。這些依賴應該被模組系統所解析。
下面是兩種解決方式:
- 1、將它們轉化成require
- 2、 用this.resolve方法來解析路徑
下面是兩個示例
- 1、css-loader: 將依賴轉化成require,即用require來替換@import和 url(...),解析對其他樣式檔案的依賴
- 2、less-loader: 不能像css-loader那樣做,因為所有的less檔案需要一起編譯來解析變數和mixins。因此其通過一個公共的路徑邏輯來擴充套件less編譯過程。這個公共的邏輯使用this.resolve來解析帶有module系統配置項的檔案。例如aliasing, custom module directories等。
如果語言僅僅接受相對urls(如css中url(file) 總是代表./file),使用~來說明成模組依賴.
url(file) -> require("./file")
url(~module) -> require("module")複製程式碼
七、抽離公共程式碼
extract common code 我感覺還是翻譯成上面的標題比較好。其實所有語言都遵循該思想,即封裝 不要寫出來很多每個模組都在使用的程式碼,在loader中建立一個runtime檔案,將公共程式碼放在其中
八、避免寫入絕對路徑
不要把絕對路徑寫入到模組程式碼中。它們將會破壞hash的過程當專案的根目錄發生改變的時候。應該使用loader-utils的 stringifyRequest方法來絕對路徑轉化為相對路徑。 例子:
var loaderUtils = require("loader-utils");
return "var runtime = require(" +
loaderUtils.stringifyRequest(this, "!" + require.resolve("module/runtime")) +
");";複製程式碼
九、使用peerDependencies來指明依賴的庫
使用peerDependency允許應用開發者去在package.json裡說明依賴的具體版本。這些依賴應該是相對開放的允許工具庫升級而不需要重新發布loader版本。簡而言之,對於peerDependency依賴的庫應該是鬆耦合的,當工具庫版本變化的時候不需要重新變更loader版本。
十、可程式設計物件作為查詢項
有些情況下,loader需要某些可程式設計的物件但是不能作為序列化的query引數被方法解析。例如less-loader通過具體的less-plugin提供了這種可能。這種情況下,loader應該允許擴充套件webpack的options物件去獲得具體的option。為了避免名字衝突,基於loader的名稱空間來命名是很必要的。
// webpack.config.js
module.exports = {
...
lessLoader: {
lessPlugins: [
new LessPluginCleanCSS({advanced: true})
]
}
};複製程式碼
結束語
至此,如何開發一個webpack loader 我相信大家已經知道了,如果還不太清楚的話,可以移步w-loader檢視。 另外,對於我這種英語渣渣來說,翻譯起來確實難度蠻大的。此處拋磚引玉,希望大家共同探討學習。
作者:瀟湘待雨 連結:https://juejin.im/post/59e6a5de518825469c7461da 來源:掘金 著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。