面试官:请手动实现一个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命令及特殊问题场景怎么解决》

相关文章
相关标签/搜索