webpack
打包時只能處理 js
文件,對於其餘類型的文件如 jsx
, css
, scss
, vue
, png
等文件,須要專門的東西處理一下再傳入 webpack
,這個東西就是 loader
。css
loader 用於對模塊的源代碼進行轉換。loader 可使你在 import 或"加載"模塊時預處理文件。所以,loader 相似於其餘構建工具中「任務(task)」,並提供了處理前端構建步驟的強大方法。loader 能夠將文件從不一樣的語言(如 TypeScript)轉換爲 JavaScript,或將內聯圖像轉換爲 data URL。loader 甚至容許你直接在 JavaScript 模塊中 import CSS文件!
在開發本身 loader
以前,咱們得知道 loader
是啥html
loader 是導出爲一個函數的 node 模塊。該函數在 loader 轉換資源的時候調用。給定的函數將調用 loader API,並經過 this 上下文訪問。
說白了,loader
就是一個函數,接收源模塊,而後處理一番,再導出去,給下一個 loader
或者 webpack
。前端
module.exports = function(source) { // handle source ... return handled source }
關於開發一個 loader
遵循的一些原則,你們能夠去看文檔,本文以一個處理 txt
文件的小例子來講明如何開發一個 loader。目錄結構以下vue
// webpack.config.js const path = require('path') module.exports = { mode: 'development', entry: __dirname + "/src/app.js", output: { path: __dirname + "/dist", filename: "bundle.js" }, module: { rules: [ { test: /\.txt$/, use: [ 'text-loader' ] } ] } }
在 loaders
文件中存放咱們的 txt-loader.js
node
// txt-loader.js module.exports = function(source) { console.log(source) }
源文件 name.txt
webpack
// name.txt hello [name]!
入口文件 app.js
web
// app.js const name = require('./name.txt') console.log(name)
先執行走一波,終端執行數組
./node_modules/.bin/webpack
確定會報錯,由於咱們的 loader
尚未寫完,可是源文件內容已經打印出來了瀏覽器
報錯信息也說,這個 loader
沒有返回 Buffer
或者 String
。sass
txt-loader
要作的事情就是將任何 .txt
文件中的 [name]
直接替換爲咱們想要的名字,而後返回包含默認導出文本的 JavaScript
模塊。
須要注意的是,咱們不能再 loader
裏面將這個名字寫死,而應該在使用 loader
的時候以配置的形式傳進去,咱們平時看到的 loader
通常都有個 options
選項,就是爲了傳些配置進去
{ test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: utils.assetsPath('img/[name].[hash:7].[ext]') } },
咱們給 txt-loader
加個配置選項
// webpack.config.js ... { test: /\.txt$/, use: { loader: path.resolve(__dirname, './src/loaders/txt-loader.js'), options: { name: 'Jay' } } } ...
那咱們在 txt-loader.js
怎麼接收配置呢?webpack
提供了一個 loader
工具庫
// txt-loader.js const loaderUtils = require('loader-utils') module.exports = function(source) { this.cacheable && this.cacheable() const options = loaderUtils.getOptions(this) || {} console.log(options) source = source.replace(/\[name\]/g, options.name) console.log(source) return source }
執行一下
發現咱們期待的結果打印出來了,可是仍是報錯了,報錯信息說還須要額外的 loader
去處理當前 loader
的結果。
有時咱們處理某種類型的文件須要多個 loader
,這些 loader
的執行順序和 use
數組中 loader
書寫順序是相反的,如解析 scss
文件時
{//處理.scss文件 test: /\.scss$/, use: [{ loader: "style-loader" // creates style nodes from JS strings }, { loader: "css-loader" // translates CSS into CommonJS }, { loader: "sass-loader" // compiles Sass to CSS }] },
上面例子 webpack
是先通過 sass-loader
,而後將結果傳入 css-loader
,最後再進入 style-loader
。
鏈路中間的 loader
返回什麼樣的結果都行,只要下一個接收的 loader
可以正常處理就行,可是最後一個調用 loader
的結果是須要傳入至 webpack
中,webpack
指望它返回 JS
代碼,以及可選的source map
。
注意:若是是處理順序排在最後一個的 loader,那麼它的返回值將最終交給 webpack 的 require,換句話說,它必定是一段可執行的 JS 腳本 (用字符串來存儲),更準確來講,是一個 node 模塊的 JS 腳本。
// 處理順序排在最後的 loader module.exports = function (source) { // 這個 loader 的功能是把源模塊轉化爲字符串交給 require 的調用方 return `module.exports = ${JSON.stringify(source)}` }
本例處理 txt
文件只有一個 txt-loader
,最終傳入至 webpack
中的是 hello Jay!
,不是個可執行的 JS 腳本。最終代碼以下
module.exports = function(source) { this.cacheable && this.cacheable() const options = loaderUtils.getOptions(this) || {} source = source.replace(/\[name\]/g, options.name) return `module.exports = ${JSON.stringify(source)}` }
將 dist
中生成的 bundle.js
文件放入瀏覽器控制檯中運行一下,能夠看到輸出 hello Jay!
咱們再看一個使用多個 loader
的例子,處理 html
文件並壓縮,解析 html
並使之成爲 JS
可執行的腳本的任務就交給現有的 html-loader
,壓縮的任務就我們本身來實現,就叫 html-optimize-loader
吧。
修改一下 webpack.config.js
// webpack.config.js ... module: { rules: [ { test: /\.txt$/, use: { loader: 'name-loader', options: { name: 'Jay' } } }, { test: /\.html$/, use: ['html-loader', { loader: 'html-optimize-loader', options: { comments: false } }] } ] }, resolveLoader: { // html-loader 在 'node_modules' modules: ['node_modules', path.resolve(__dirname, './src/loaders')] }, ...
這裏咱們改爲多個 loader
配置的模式,也在咱們新加的 html-optimize-loader
中加入了配置,壓縮時是否保留註釋。
在 src
中新建 test.html
文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <!-- 這裏是 main 內容 --> </body> </html>
入口文件 app.js 改爲 test.html
const html = require('./test.html') console.log(html)
在 loaders
文件中新建 html-optimize-loader.js
// hmtl-optimize-loader.js const Minimize = require('minimize') const loaderUtils = require('loader-utils') module.exports = function (source) { var callback = this.async() this.cacheable && this.cacheable() var options = loaderUtils.getOptions(this) || {} var minimize = new Minimize(options) console.log(source) console.log(minimize.parse(source)) return minimize.parse(source, callback) }
這裏 loader
咱們採用異步的方式,執行一下
發現 source
和壓縮後的 source
都打印出來了,這裏咱們直接將壓縮後 source
直接傳入 html-loader
中去處理了。你們能夠將 options
中的 comment
設成 true
,發現註釋就會保留了,最終生成的 bundle
文件也能夠丟進瀏覽器的控制檯跑一下。
就這樣咯,下一篇寫實現一個 webpack
plugin