Webpack loader 之 file-loader
npm install --save-dev file-loader
用法
預設情況下,生成的檔案的檔名就是檔案內容的 MD5 雜湊值並會保留所引用資源的原始副檔名。
import img from './webpack-logo.png'
webpack.config.js
module.exports = { module: { rules: [ { test: /\.(png|jpg|gif)$/, use: [ { loader: 'file-loader', options: {} } ] } ] } }
生成檔案 bd62c377ad80f89061ea5ad8829df35b.png
(預設的檔名為 [hash].[ext]),輸出到輸出目錄並返回 public URL。
"/public/path/bd62c377ad80f89061ea5ad8829df35b.png"
當然如果不想使用預設的檔名,我們也可以通過配置 options.name
選項來設定輸出的檔名命名規則,需要注意的是 name 選項支援的型別為: {String|Function}
:
- String 型別
webpack.config.js
{ loader: 'file-loader', options: { name: '[path][name].[ext]' } }
- Function 型別
webpack.config.js
{ loader: 'file-loader', options: { name (file) { if (env === 'development') { return '[path][name].[ext]' } return '[hash].[ext]' } } }
以上的示例中,我們使用了 [path]
, [name]
, [hash]
和 [ext]
佔位符,它們對應的含義是:
[<hashType>:hash:<digestType>:<length>]
其實除了以上常用的四個佔位符之外,還有支援 [N]
,N 是數值型別,表示當前檔名按照查詢引數 regExp
匹配後獲得到第 N 個匹配結果。介紹完 name 配置項,接下來我們來繼續介紹幾個常用的配置。
常用配置項
outputPath
outputPath 用於配置自定義 output
輸出目錄,支援 String|Function 型別,預設值為 ‘undefined’,用法如下:
webpack.config.js
{ loader: 'file-loader', options: { name: '[path][name].[ext]', outputPath: 'images/' } }
需要注意的是,outputPath 所設定的路徑,是相對於 webpack 的輸出目錄。
publicPath
publicPath 用於配置自定義 public
釋出目錄,支援 String|Function 型別,預設值為 __webpack_public__path__
,用法如下:
webpack.config.js
{ loader: 'file-loader', options: { name: '[path][name].[ext]', publicPath: 'assets/' } }
emitFile
emitFile 用於設定是否生成檔案,型別是 Boolean,預設值為 true。但我們可以通過將 emitFile 設定為 false 來禁用該預設行為。
webpack.config.js
{ loader: 'file-loader', options: { emitFile: false } }
outputPath vs publicPath
outputPath 僅僅告訴 webpack 結果儲存在哪裡,然而 publicPath 選項則被許多 webpack 的外掛用於在生產模式下更新內嵌到 css、html 檔案內的 url 值。例如:
loader 準則
編寫 loader 時應該遵循以下準則:
- 簡單易用 。
- 使用 鏈式 傳遞。
- 模組化 的輸出。
- 確保 無狀態 。
- 使用 loader utilities 。
- 記錄 loader 的依賴 。
- 解析 模組依賴關係 。
- 提取 通用程式碼 。
- 避免 絕對路徑 。
- 使用 peer dependencies 。
以上的準則按重要程度排序,但某些僅適用於某些場景。若想進一步瞭解自定義 loader,可以閱讀 ofollow,noindex">編寫一個 loader 這個文件。接下來,我們來基於上述的準則分析一下 file-loader 的原始碼。
file-loader 原始碼簡析
所謂 loader 只是一個匯出為函式物件的 JavaScript 模組。 loader runner 會呼叫這個函式,然後把上一個 loader 產生的結果或者資原始檔傳入進去。 函式的 this
上下文將由 webpack 填充,並且 loader runner 具有一些有用方法,可以使 loader 改變為非同步呼叫方式,或者獲取 query 引數 。
其實本文介紹的 file-loader 並不會對檔案的內容進行任何轉換,只是複製一份檔案內容,並根據相關的配置生成對應的檔名,所生成的檔名一般會帶上 hash 值,從而避免檔案重名導致衝突。接下來我們來簡單分析一下 file-loader 的部分原始碼。
匯入依賴模組
import path from 'path'; import loaderUtils from 'loader-utils'; import validateOptions from 'schema-utils'; import schema from './options.json';
獲取配置物件及驗證
export default function loader(content) { if (!this.emitFile) throw new Error('File Loader\n\nemitFile is required from module system'); const options = loaderUtils.getOptions(this) || {}; validateOptions(schema, options, 'File Loader'); }
以上程式碼中,emitFile 是由 loader 上下文提供的方法,用於輸出一個檔案,對應的函式簽名如下:
emitFile(name: string, content: Buffer|string, sourceMap: {...})
在呼叫 file-loader 時,如果發現 this.emitFile 無效,則會丟擲異常。接著 file-loader 會先呼叫 loaderUtils.getOptions() 方法,獲取當前 loader 對應的配置物件,然後基於已定義的 Schema,驗證配置物件的有效性。對應的 Schema 定義如下(不包含異常提示資訊):
{ "type": "object", "properties": { "name": {}, "regExp": {}, "context": { "type": "string" }, "publicPath": {}, "outputPath": {}, "useRelativePath": { "type": "boolean" }, "emitFile": { "type": "boolean" } }, "additionalProperties": true }
獲取 context 及生成檔名稱
const context = options.context //自定義檔案context // 從webpack 4開始,原先的this.options.context // 被改進為this.rootContext || this.rootContext || (this.options && this.options.context); const url = loaderUtils.interpolateName( this, options.name, // 預設為"[hash].[ext]" { context, content, regExp: options.regExp, });
loaderUtils 中的 interpolateName 方法,用於生成對應的檔名,該方法的簽名如下:
interpolateName(loaderContext, name, options);
其中 loaderContext 為 loader 的上下文物件,name 為檔名稱模板,options 為配置物件,支援 context,content 和 regExp 屬性。該方法的使用示例如下:
示例一:
// loaderContext.resourcePath = "/app/js/javascript.js"; let interpolatedName = loaderUtils.interpolateName( loaderContext, "js/[hash].script.[ext]", { content: "console.log('loaderUtils')" }); // => js/e353f4da4c3e380646d2b4d75c8a13ab.script.js
以上示例核心的處理流程如下:
示例二:
// loaderContext.resourcePath = "/app/js/page-home.js" loaderUtils.interpolateName( loaderContext, "script-[1].[ext]", { regExp: "page-(.*)\\.js", content: "console.log('loaderUtils')" }); // => script-home.js
處理 outputPath
let outputPath = url; if (options.outputPath) { if (typeof options.outputPath === 'function') { outputPath = options.outputPath(url); } else { outputPath = path.posix.join(options.outputPath, url); } }
處理 publicPath
// __webpack_require__.p = ""; let publicPath = `__webpack_public_path__ + ${JSON.stringify(outputPath)}`; if (options.publicPath) { if (typeof options.publicPath === 'function') { publicPath = options.publicPath(url); } else if (options.publicPath.endsWith('/')) { publicPath = options.publicPath + url; } else { publicPath = `${options.publicPath}/${url}`; } publicPath = JSON.stringify(publicPath); }
處理 emitFile
if (options.emitFile === undefined || options.emitFile) { // 把檔案輸出到指定的outputPath路徑 this.emitFile(outputPath, content); }
匯出最終路徑
return `module.exports = ${publicPath};`;