手动实现一个Sass-loader

什么是Loader

一个loader可以看做是一个node模块,也可以看做一个loader就是一个函数 (loader会导出一个函数),众所周知webpack只能识别js文件,loaderwebpack中担任的角色就是翻译工作,它可以让其它非js的资源(source)可以在webpack中通过loader顺利加载。

Loader的方式

  • 单一职责,一个loader只做一件事
  • 调用方式,loader是从右向左执行,链式调用
  • 统一原则,loader输入和输出都字符串

来看一下案例

module.exports = () => {
    return 343
}

上面这种会报错,我们上面说过laoder的方式了,统一原则,输出输入必须是字符串。而我们上面代码则输出是数字,则报错。

loader导出尽量别使用箭头函数,loader内部属性都是靠this来获取的,如this.callback,this.sync

Webpack手写Loader

为什么要手写loader呢,假如有一些loader插件不满足我们的需求时,我们会采用手写loader来定制化我们功能。

开始

首先新建一个js文件

module.exports = function(source) {

}
  • 第一个参数:是当前要处理的内容

loader内置的方法

函数里面暴露了一些方法,this.query获取loader传过来的参数

module.exports = function(source) {
    console.log(this.query)
}

当然里面还可以引入一个库,来处理参数,该情况用于有时候我们传给loader的参数不是一个对象,可能是一个字符串。

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

webpack.config.js

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已实现完毕。如有帮助欢迎点赞+分享哦

欢迎关注我的公众号:前端娱乐圈
作者:蛙人
链接:https://juejin.cn/post/6989461400535973896
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容