前言
webpack 功能非常强大的前端工程化帮手,在纷杂的技术圈屹立不倒。能有如此地位少不了她的左膀右臂的帮忙----loader和plugin。loader可将非js代码转化成js模块化代码或者将内敛突降转换为dataurl,plugin可对js代码进行各种花式组合,双剑合璧天下无敌~
本文主要针对loader进行展开,最近集中梳理了一下;
四种类型
loader有四种类型:pre、normal、inline、post,类型与执行顺序息息相关;
其中的pre、normal、post是通过在webpack配置文件中的enforce
字段进行配置。enforce默认是normal。
module.exports = {
module: {
rules: [
// normal loader
{
test: /\.(css|less)$/,
use: ['normal1-loader', 'normal2-loader'],
},
// pre loader
{
test: /\.(css|less)$/,
use: ['pre1-loader', 'pre2-loader'],
enforce: 'pre',
},
// post loader
{
test: /\.(css|less)$/,
use: ['post1-loader', 'post2-loader'],
enforce: 'post',
},
],
},
};
另外一种 inline,顾名思义是在文件内部进行配置的
import XXXX from 'inline1-loader!inline2-loader!./index.js';
与plugin执行从左到右不同(从上到下)不同,由于webpack内部是使用reduceRight方式进行处理,大家常说loader是从右到左(从下到上)执行的。但是这回答比较片面;
两个阶段
loader在处理文件资源时分为两个阶段: pitch阶段和nomral阶段。有种js冒泡捕获事件的感觉。
每个loader的index文件,基本都是下面的结构。
其中导出的loader本身就是normal阶段执行的代码,导出的pitch属性中的内容是在pitch阶段执行的;
less-loader,均在normal阶段执行代码,没有pitch阶段
Pitching阶段的调用顺序: post > inline > normal > pre。
Normal阶段的调用顺序:pre > normal > inline > post。
在上面的代码示例中,我们执行的顺序其实是这样的:
loader流程图.png在normal阶段的最后一个loader处理完成后,一定会生成js的模块化代码并返回供webpack使用。如果在pitch阶段有loader返回了非undefined的值就会发生熔断,即直接调头将这个值传递到normal阶段的loader。
style-loader
不论是less-loader还是postcss-loader都是在normal阶段对代码进行处理,只有style-loader的normal空空如也,所有的逻辑都在pitch中。为什么这么设计呢?
首先style-loader的作用是将css代码插入到dom中。如果在normal而不是pitch阶段执行loader的话,当css-loader执行完毕,到style-loader执行时此时接收到的是个js字符串,无法完成将原始的css插入dom的工作。所以需要在style-loader里面去执行css-loader等,拿到处理过的css再插到dom中。
举个例子,在项目中用到 style-loader、css-loader、postcss-loader、less-loader,
以src/pages/collectlist/index.js
为例,其代码中引用用如下:
我们在style-loader的pitch中打印下:
// pitch中接收到的requst
module.exports.pitch = function (request) {
console.log(request)
}
request内容为
/项目/node_modules/`css-loader`/index.js??ref--6-1!/项目/node_modules/`postcss-loader`/src/index.js??postcss!/项目/node_modules/`less-loader`/dist/cjs.js??ref--6-3!/项目路径/src/pages/collectlist/index.less
// 后续使用均会转换成inline loader, 例如:
var content = require(" + loaderUtils.stringifyRequest(this, "!!" + request) + ")
双叹号的意思是禁用配置文件中所有的loader,
request
参数的含义是 剩余需要处理的loader,
插播,这里pitch支持接收三个参数,分别是
- remainingRequest:剩余需要处理的loader(不包括自身)
- previousRequest:已迭代过的loader(不包括自身)
- data: normal和pitch传递数据的纽带,默认是个空对象
再回到上述的例子,按照顺序将index.less进行处理转换,在编译后生成的入口js(pages/collectlist/index.js)中可以看到:
// EXTERNAL MODULE: ./src/pages/collectlist/index.less
var collectlist = __webpack_require__("Wehs");
...
// EXTERNAL MODULE: ./src/components/Head/index.js + 4 modules
var Head = __webpack_require__("rLZa");
...
// EXTERNAL MODULE: ./src/components/Center_slide/index.js
var Center_slide = __webpack_require__("0wbC");
...
// EXTERNAL MODULE: ./src/pages/collectlist/components/house_event/index.less
var house_event = __webpack_require__("Wn+z");
...
// 还有一些遍历的依赖项
// 比如:house_event/index.js中依赖的@/components/Collect/del
// EXTERNAL MODULE: ./src/components/Collect/del.js
var del = __webpack_require__("pePU");
...
//开始追collectlist/index.less的依赖,首先是style-loader
/***/"Wehs":
/***/ (function(module, exports, __webpack_require__) {
var content = __webpack_require__("W7or");
...
// 经postcss-loader &less-loader 处理过的
/***/"W7or":
/***/ (function(module, exports, __webpack_require__) {
exports = module.exports = __webpack_require__("I1BE")(false);
exports.push([module.i, "@charset \"utf-8\";\n\nhtml {\n overflow-y: scroll;\n -webkit-text-size-adjust: 100%;\n}\n.....close {\n display: none;\n}\n", ""]);
...
// css-loader 将转换过后的原生css导出模块,等待被引用
/***/"I1BE":
/***/ (function(module, exports, __webpack_require__) {
module.exports = function(useSourceMap){...}
function cssWithMappingToString(..){...}
...
// 开始追Head js的依赖
/***/"rLZa":
/***/ (function(module, exports, __webpack_require__) {
// EXTERNAL MODULE: ./src/components/Head/index.less
var components_Head = __webpack_require__("HBZh");
...
//style-loader
/***/"HBZh":
/***/ (function(module, exports, __webpack_require__) {
var content = __webpack_require__("ZbnD");
...
// 经postcss-loader &less-loader 处理过的
/***/"ZbnD"":
/***/ (function(module, exports, __webpack_require__) {
exports = module.exports = __webpack_require__("I1BE")(false);
exports.push([module.i, ".searchBar {\n background: #fff;\n height: 70px;\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.13);\n...z-index: 100;\n}\n", ""]);
...
/***/"0wbC":
/***/ (function(module, exports, __webpack_require__) {
var _index_less__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("Kyd0");
...
//style-loader
/***/"Kyd0":
/***/ (function(module, exports, __webpack_require__) {
var content = __webpack_require__("BZBj");
...
/***/ "BZBj":
/***/ (function(module, exports, __webpack_require__) {
exports = module.exports = __webpack_require__("I1BE")(false);
exports.push([module.i, "body .main-wrap,\nhtml .main-wrap {\n margin-top: 20px;\n ...height: 100%;\n}\n.nav dd {\n width: 100%;\n}\n", ""]);
...
loader也分有同步和异步
之分,在上面调试的过程中,我们能在 less-loader 和 postcss-loader中看到 :
//postcss-loader
const cb = this.async()
// less-loader
var loaderContext = this;
var done = loaderContext.async();
...
this.async()
就是异步的关键,比如:
const LoaderUtils = require("loader-utils");
module.exports = function(source){
const options = LoaderUtils.getOptions(this);
setTimeout(() => {
return source.replace( "webpack ', options.name);
},1000)
};
此时会报错,因为有settimeout异步操作导致无法正确返回,我们应该使用 this.async(),告诉webpack这个loader中有异步事件需要处理。
module.exports = function(source){
const options = LoaderUtils.getOptions(this);
const cb = this.async();
setTimeout(() => {
const res = source.replace( "webpack ', options.name);
cb(null, res)
},1000)
运行正常~ 完美~~~
网友评论