面試官:請手動實現一個Sass-loader

什麼是Loader

一個loader能夠看作是一個node模塊,也能夠看作一個loader就是一個函數 (loader會導出一個函數),衆所周知webpack只能識別js文件,loaderwebpack中擔任的角色就是翻譯工做,它可讓其它非js的資源(source)能夠在webpack中經過loader順利加載。javascript

Loader的方式css

  • 單一職責,一個loader只作一件事
  • 調用方式,loader是從右向左執行,鏈式調用
  • 統一原則,loader輸入和輸出都字符串

來看一下案例前端

module.exports = () => {
    return 343
}
複製代碼

上面這種會報錯,咱們上面說過laoder的方式了,統一原則,輸出輸入必須是字符串。而咱們上面代碼則輸出是數字,則報錯。java

loader導出儘可能別使用箭頭函數,loader內部屬性都是靠this來獲取的,如this.callback,this.syncnode

Webpack手寫Loader

爲何要手寫loader呢,假若有一些loader插件不知足咱們的需求時,咱們會採用手寫loader來定製化咱們功能。webpack

開始

首先新建一個js文件git

module.exports = function(source) {
    
}
複製代碼
  • 第一個參數:是當前要處理的內容

loader內置的方法

函數裏面暴露了一些方法,this.query獲取loader傳過來的參數github

module.exports = function(source) {
    console.log(this.query)
}
複製代碼

固然裏面還能夠引入一個庫,來處理參數,該狀況用於有時候咱們傳給loader的參數不是一個對象,多是一個字符串。web

module: {
    rules: [
        {
            test: /\.css/,
            use: [{
                loader: "testLoader",
                query: "前端娛樂圈"
            }]
        }
    ]
}
複製代碼
const loaderUtils = require('loader-utils')
module.exports = function(source) {
    console.log(loaderUtils.getOptions(this))
}
複製代碼

webpack.config.jsnpm

module: {
    rules: [
        {
            test: /\.css/,
            use: [
                "testLoader?name=前端娛樂圈"
            ]
        }
    ]
}
複製代碼

可使用上面loaderUtils內置庫獲取loader的參數。

webpack.config.js

module: {
    rules: [
        {
            test: /\.css/,
            use: [{
            	loader: "testLoader?name=前端娛樂圈",
            	options: {
            		name: "前端娛樂圈"
            	}
            	
            	// or
            	
            	query: {
            		name: "前端娛樂圈"
            	}
            }]
        }
    ]
}
複製代碼

上面這兩種傳參形式,若是options存在,行內參數拼接則無效。上面還寫了一種傳參形式,query也是能夠傳參的,那optionsquery有什麼區別的。這倆沒啥區別就是,querywebpack老版本以前的(2.5),options是最新支持的方式

loader異步

loader異步處理,假如說loader裏面須要處理一些邏輯操做,但這個操做是異步的,那麼loader就會編譯失敗,必須使用異步執行方法,來等待結果返回後,loader則纔回執行成功

module.exports = function(source) {
    setTimeout(() => {
        this.callback(1, source)
    }, 3000)
}
複製代碼

官方解釋:this.callback參數

this.callback(
  err: Error | null, // 錯誤信息
  content: string | Buffer, // 最終生成的源碼
  sourceMap?: SourceMap, // 對應的sourcemap
  meta?: any // 其餘額外的信息
);
複製代碼

還有一種方法是 this.async,async返回值也是一個callback因此這倆個是同樣的

module.exports = function(source) {
    const callback = this.async()
    setTimeout(() => {
        callback(1, source)
    }, 3000)
}
複製代碼

Loader起別名

resolveLoader - modules

咱們如今手寫的loader都仍是寫絕對路徑引入進來,那麼怎麼直接寫loader名呢,有兩種方法,咱們來看一下

module.exports = {
    resolveLoader: {
        modules: ["node_modules", "./loaders"]
    },
    module: {
        rules: [
            {
                test: /\.js/
                use: {
                	loader: "per-loader"
            	}
            }
        ]
    }
}
複製代碼

咱們能夠看到上面,咱們直接寫的per-loader,咱們是配置瞭解析loader路徑,會先去node_modules裏面查找,若是node_modules裏面沒有則會去loaders目錄下查找。而後咱們下面寫loader: per-loader注意:這裏的per-loader就是當前loader的文件名

resolveLoader - alias

這種方法直接起別名,把路徑引入過來就ok

module.exports = {
    resolveLoader: {
        "per-loader": path.resolve(__dirname, "./loaders/per-loader.js")
    },
    module: {
        rules: [
            {
                test: /\.js/
                use: {
                	loader: "per-loader"
            	}
            }
        ]
    }
}
複製代碼

實現一個sass-loader && style-loader

sass-loader

首先安裝一下node-sass插件,用於識別scss語法並編譯爲css

npm i node-sass
複製代碼

新建sassLoader.js文件,並引入node-sass插件

const nodeSass = require("node-sass");
const path = require("path")

let result = nodeSass.renderSync({
    file: path.resolve(__dirname, "../src/scss/index.scss"),
    outputStyle: 'expanded',
});
module.exports = function() {
    return result.css.toString()
}
複製代碼

上面採用node-sass官方配置,如異步解析.scss文件,上面對象中,file爲當前要解析的文件地址,outputStyle爲輸出風格包含:nested(嵌套)、expanded(展開)、compact(緊湊,不換行)、compressed(壓縮)。

導出result.css.toString, 這裏爲何要toString,若是不toString的話返回的是一個Buffer數據。由於這裏的返回值提供給下一個loader使用,爲了下一個loader(style-loader)更好的使用咱們這裏直接處理一下。

更多Api用法請參考node-sass

style-loader

新建styleLoader.js文件

module.exports = function(source) {
    const style = ` let style = document.createElement("style"); style.innerHTML = ${JSON.stringify(source)}; document.head.appendChild(style) `
    return style
}
複製代碼

上面導出的函數第一參數(source)就是咱們sassLoader的返回值,而後在字符串裏面寫上建立style元素邏輯代碼,並最終返回。注意這裏返回值必須是字符串上,剛開始咱們就說過了,輸入輸出都必須是字符串。

完整配置

index.js

console.log("前端娛樂圈")
import "./scss/index.scss"
複製代碼

webpack.config.js

const path = require("path");
module.exports = {
    mode: 'development',
    entry: {
        main: './src/index.js'
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    },
    resolveLoader: {
       alias: {
           "sassLoader": path.resolve(__dirname, "./loaders/sassLoader.js"),
           "styleLoader": path.resolve(__dirname, "./loaders/styleLoader.js")
       }
    },
    module: {
        rules: [
            {
                test: /\.scss/,
                use: ["styleLoader", "sassLoader"]
            }
        ]
    }
}
複製代碼

上面配置中咱們用到了解析loader路徑配置(起別名),loader是從右到左,從下到上解析執行。先是把.scss文件處理成css語法,而後在傳遞給styleLoader配置便可。以上一個簡單完整的loader已實現完畢。若有幫助歡迎點贊+分享哦

歡迎關注個人公衆號:前端娛樂圈

感謝

謝謝你讀完本篇文章,但願對你能有所幫助,若有問題歡迎各位指正。

我是蛙人(✿◡‿◡),若是以爲寫得能夠的話,請點個贊吧❤。

感興趣的小夥伴能夠加入 [ 前端娛樂圈交流羣 ] 歡迎你們一塊兒來交流討論

寫做不易,「點贊」+「在看」+「轉發」 謝謝支持❤

往期推薦

《什麼場景下使用Render函數,如何配置JSX》

《分享15個Webpack實用的插件!!!》

《手把手教你寫一個Vue組件發佈到npm且可外鏈引入使用》

《分享12個Webpack中經常使用的Loader》

《聊聊什麼是CommonJs和Es Module及它們的區別》

《這些工做中用到的JavaScript小技巧你都知道嗎?》

《【建議收藏】分享一些工做中經常使用的Git命令及特殊問題場景怎麼解決》

相關文章
相關標籤/搜索