loader:是webpack中得一个重要概念,主要作用就是将一串代码转换成另一串能识别得代码,是webpack得模块加载器。
目录
- 简单得搭建一个环境
- loader得分类和执行顺序
- loader组成
- 实现babel-loader
- 实现一个loader 功能:在代码顶部加入 注释
- 实现图片模块loader
- style-loader css-loader less-loader 实现
1.简单得搭建一个环境
1.新建文件夹 wepack-loader
2.npm init -y 初始化项目
3.npm install webpack@4.0.0 webpack-cli@3.3.12 -D
4.新建src文件夹,里面新建一个index.js,放入内容 console.log(index.js)
5.稍微简单得写下配置文件,新建webpack.config.js
6.新建loader文件夹
loaderJs1,loaderJs2,loaderJs3 console得输出不一样改成loader1,2,3
function loader(source) {
console.log("loader1~~~~~~~~~");
return source;
}
module.exports = loader;
webpack.config.js
let path = require("path");
module.exports = {
mode: "development",
entry: {
home: "./src/index.js",
},
output: {
filename: "index.js",
path: path.resolve(__dirname, "dist"),
},
// 专门针对解析Loader得
resolveLoader: {
// 匹配下方Loader 先去node_modules找,没有再在loader下找
modules: ["node_modules", path.resolve(__dirname, "loader")],
// 别名
// alias: {
// loaderJs: path.resolve(__dirname, "loader", "loaderJs")
// }
},
module: {
// loader
rules: [
{
test: /\.js$/,
use: ["loaderJs2", "loaderJs1"],
},
],
},
};
npx webpack
针对上面代码分析,
resolveLoader是专门针对loader得配置,可以使用别名得方式,更方便点就是配置modules: 将rules中use得loader 优先匹配modules 找不到,再在根目录下loader文件夹中查找。也可以直接在use中写path.resolve(__dirname, “loader”, “loaderJs”),推荐配置modules
2.loader得分类和执行顺序
执行顺序由 右往左,由下到上。
由下到上验证:改变use得写法,可以单个独自写
loader得分类:顺序:pre 前置 +normal普通得+inline 行内loader+ post后置
通过 enforce设置
{
test: /\.js$/,
use: "loaderJs1",
},
{
test: /\.js$/,
use: "loaderJs2",
enforce: "pre",
},
{
test: /\.js$/,
use: "loaderJs3",
enforce: "post",
},
inline 行内loader,在loader文件夹下 新建loaderJs-inline.js, 里面跟其他loader一样,打印console.log(“loader-inline~~~~~~~~~”)
用法:在js中用
index.js,这里新建一个a.js
//require("-!loaderJs-inline!./a.js");
let str = require("loaderJs-inline!./a.js");
console.log("index.js");
npx webpack 验证顺序:pre 前置 +normal普通得+inline 行内loader+ post后置
require("-!loaderJs-inline!./a.js"); -! 不会让pre normal执行了 只执行它自己和post
require("!loaderJs-inline!./a.js");! 不会让normal执行了
require("!!loaderJs-inline!./a.js");!! 什么都不要
3.loader组成
默认两部分:pitch normal
use:[loader3,loader2,loader1];
loader先执行pitch方法,3=》2=》1顺序执行,然后获取资源,再返回 1,2,3
如果loader有返回值 就会直接返回,(阻断功能)
如果我们在我们得loaderJs1.js中加入picth 只执行了loaderJs2,loaderJs3.js
function loader(source) {
console.log("loader1~~~~~~~~~");
return source;
}
loader.pitch = function (params) {
return "1111";
};
module.exports = loader;
4.实现babel-loader
babel-loader
------------------es6转换----------------------
@babel/core
@babel/preset-env
安装这3个,然后配置解析js得规则
resolveLoader: {
// 匹配下方Loader 先去node_modules找,没有再在loader下找
modules: ["node_modules", path.resolve(__dirname, "loader")],
// 别名
// alias: {
// loaderJs: path.resolve(__dirname, "loader", "loaderJs")
// }
},
module:
rules: [
{
test: /\.js$/,
use: {
loader: "babel-loader",
options: {
// 插件库,预设
presets: [
"@babel/preset-env", //js es6语法转换
],
},
},
include: path.resolve(__dirname, "src"), //只在src下找
},
],
index.js中写个es6得语法
class A {
constructor(param) {
console.log("es6");
}
}
这里比较基础 不多说了,能编译成功! npx webpack
接下来我们将loader: “babel-loader”,改为 loader: “my-babel-loader”,在loader文件夹下新建my-babel-loader
my-babel-loader.js
// 用来处理es6得语法转换
let babel = require("@babel/core");
let loaderUtils = require("loader-utils"); //loader得工具类
function loader(source) {
//this 里面包含很多loader得内容包括有多少个Loader等 可以打印看下
// console.log(this);
console.log("babel-loader~~~~~~~~~");
// 利用工具类得方法获取 预设 presets
let options = loaderUtils.getOptions(this);
// 内部得 同步得变量,如果是异步 就放在异步回调里面
let cb = this.async();
// 转化
babel.transform(
source,
{
...options,
// 方便调试得时候 需要在webpack。config.js中设置de
sourceMap: true,
filename: this.resourcePath.split("/").pop(), //文件名 this.resourcePath完整得文件路径 ,我们这里可以取她得文件名
},
function (err, result) {
// 异步回调
cb(err, result.code, result.sourceMap); //异步
}
);
}
module.exports = loader;
webpack,config.js 配置下devtool:‘source-map’
npx webpack
5.实现一个loader 功能:在代码顶部加入 注释
配置文件
{
test: /\.js$/,
use: {
loader: "text-loader",
options: {
text: "一脸云",
filename: path.resolve(__dirname, "src", "text.js"),
},
},
include: path.resolve(__dirname, "src"), //只在src下找
},
新建loader中text-loader
npm i loader-utils schema-utils -D
let loaderUtils = require("loader-utils"); //loader得工具类
let { validate } = require("schema-utils"); //骨架校验,验证写得对不对 json 对象等
let fs = require("fs");
function loader(source) {
console.log("text-loader~~~~~~~~~");
// 不写默认得,是否缓存 this.cacheable(false) 不要缓存
this.cacheable && this.cacheable();
let options = loaderUtils.getOptions(this);
let schema = {
text: {
type: String,
},
filename: {
type: String,
},
};
validate(schema, options, "text-loader"); //第三个参数是报错信息
// 内部得 同步得变量,如果是异步 就放在异步回调里面
let cb = this.async();
if (options.filename) {
// 打包实时监听得时候,引入文件得内容修改时不会实时监听得,需要用这个方法将这个文件关联到我们得依赖关系中去
this.addDependency(options.filename);
fs.readFile(options.filename, "utf8", function (err, data) {
// 这里是异步
cb(err, `/**${data}**/${source}`);
});
} else {
cb(null, `/**${options.text}**/${source}`);
}
}
module.exports = loader;
npx webpack ,注意上面得addDependency 是将文件关联进依赖,这样监听打包得时候改变文件就能实时更新
6.实现图片模块loader
原本我们用得是url-loader和file-loader。 url-loader 会做判断limit大小,小于这个limit,会自已将二进制转化成base64输出,反之,交给file-loader去处理
下面我们手写下
配置文件
{
test: /\.jpg$/,
// use: {
// loader: "my-file-loader",
// },
// my-url-loade
use: {
loader: "my-url-loader", //第一步 让my-file-loader处理路径 第二步 转base64
options: {
limit: 1000 * 1024,
},
},
include: path.resolve(__dirname, "src"), //只在src下找
},
loader文件夹下新建my-url-loader.js
//第一步 让my-file-loader处理路径 第二步 转base64
let loaderUtils = require("loader-utils"); //loader得工具类
let mime = require("mime"); //获取图片类型得
function loader(source) {
// 获取参数
let { limit } = loaderUtils.getOptions(this);
console.log(source, limit);
if (limit && limit > source.length) {
return `module.exports="data:${mime.getType(
this.resourcePath
)};base64,${source.toString("base64")}"`; //这里有点不太明白这个二进制转base64得写法,不知道是不是固定这么写
} else {
return require("./my-file-loader").call(this, source);
}
}
// 图片返回得是二进制 ,将source代码转化成二进制
loader.raw = true;
module.exports = loader;
loader文件夹下新建my-file-loader.js
// 目的是根据图片生成一个md5 发射倒dist目录下,还会返回当前得图片路径
let babel = require("@babel/core");
let loaderUtils = require("loader-utils"); //loader得工具类
function loader(source) {
console.log("my-file-loader~~~~~~~~~", source);
// 获取文件名
let filename = loaderUtils.interpolateName(this, "[hash].[ext]", {
content: source,
});
// 发射文件
this.emitFile(filename, source);
return `module.exports="${filename}"`;
}
// 图片返回得是二进制 ,将source代码转化成二进制
loader.raw = true;
module.exports = loader;
效果,可以试下 改下limit 我试过了,还是可以得,这里就不单独截图了。
7.style-loader css-loader less-loader 实现
配置文件,css-loader 主要解决@import 背景图片等
resolveLoader 这里就不说了 上面统一配置了
{
test: /\.less$/,
// use: {
// loader: "my-file-loader",
// },
// my-url-loade
use: ["style-loader", "css-loader", "less-loader"],
include: path.resolve(__dirname, "src"), //只在src下找
},
在loader新建三个文件
less-loader: 这个文件就是将less 转成css
let less = require("less");
// less 转css 功能
function loader(source) {
let css = "";
// 可以在Node中调用编译器 '.class { width: (1 + 1) }' 输出 .class { width: 2 }
less.render(source, function (err, c) {
css = c.css;
});
return css;
}
module.exports = loader;
css-loader:处理@import 和 url 下面写得是简单得处理url,@import替换成require(“-!css-loader!./global.css”)来处理。
返回得是一个数组,数组中是js 代码片段(字符串),需要动态得执行js,style-loader无法将js字符串转化成css代码。下面截图是css-loader返回得js代码片段,为了获取CSS样式,我们会在 style-loader 中直接通过require来获取,可以通过pitch方法来实现。
// 转义@import 或者 url ('') 转移成require
function loader(source) {
// 匹配url
let reg = /url\((.+?)\)/g;
let pos = 0;
let current;
let arr = ["var list=[]"];
// reg.exec(source)这个返回结果打印我们可以知道 是一个数组 0和1 对应得是“url('./mmm.jpg')” “'./mmm.jpg'”
while ((current = reg.exec(source))) {
// 将代码分开 单独 将url('./mmm.jpg') 转成require
let [a, b] = current;
let last = reg.lastIndex - a.length;
arr.push(`list.push(${JSON.stringify(source.slice(pos, last))})`);
pos = reg.lastIndex;
// 将./mmm.jpg 转成require
arr.push(`list.push('url('+require(${b})+')')`);
}
// 将剩余代码放进去
arr.push(`list.push(${JSON.stringify(source.slice(pos))})`);
arr.push(`module.exports = list.join("")`);
console.log(arr.join("\r\n"));
return arr.join("\r\n");
}
module.exports = loader;
style-loader: 将css内联到html中。
let loaderUtils = require("loader-utils");
// css内联
function loader(source) {
console.log(source);
return loader;
}
// remainingRequest 剩余得请求 less-loader!css-loader!./index.less
loader.pitch = function (remainingRequest) {
// remainingRequest 打印发现是一个绝对路径需要处理
// loaderUtils.stringifyRequest 这个方法能将remainingRequest 处理成相对路径,通过行内loader处理好css文件后返回得就是css-loader中得数组代码片段,通过require得方式引入,就能拿到他得css代码片段
let str = `
let style=document.createElement('style');
style.innerHTML=require(${loaderUtils.stringifyRequest(
this,
"!!" + remainingRequest //行内loader去处理 css
)})
document.head.append(style)
`;
return str;
};
module.exports = loader;
上面说过了pitch这个方法,有返回值得话 ,将会被阻断。正常得顺序
style-loader-pitch=>css-loader-pitch=>less-loader-pitch=>less-loader=>css-loader=>style-loader;
现在在第一步style-loader-pitch有返回值,直接阻断,自己都不会执行,直接执行pitch中得内容,里面通过require得方式使用行内loader得处理方式重新链式处理css,!!是为了防止死循环,只执行这里得Loader得意思,最后成功,下面得url,是因为我配置了url-loader转成base64了
思考:我这里有个问题,为什么执行顺序 不能正常进行,一定要用style-loader中得pitch方法。
主要是还是css-loader这里返回得是js代码片段,因为这里处理css得代码片段,需要拼接字符串,最后再合并,直接输出这个字符串(尝试过,发现他得换行符/n会比较乱,最终也没找到实现得方法)有明白得可以给我留言哈。