loader
类型
- loader本身不具备类型,但(在webpack中)使用的时候可以通过enforce指定配置类型,默认normal
- 类型有四种
- post 后置
- inline 内联
- normal 正常
- pre 前置
- 我们可以根据这些来配置执行时机,比如eslint做前置,因为编译完再检查没什么意义
- 一般来说3个就够了,post、normal、pre
- 执行顺序为pre、normal、inline、post
- 这个类型实际上还是我们根据传入的参数,拼接成一个数组,然后执行,只是webpack帮我们排了
特殊符号记忆
-! noPreAuto 不要pre
! noAuth 不要normal
!! noPrePostAuto 不要前置后置normal
- 应用给inline的,这样就可以来筛选本次的配置了,放在inline前面
let request = `inline-loader1!inline-loader2!${entryFile}`;
let request = `!inline-loader1!inline-loader2!${entryFile}`;
let request = `-!inline-loader1!inline-loader2!${entryFile}`;
let request = `!!inline-loader1!inline-loader2!${entryFile}`;
使用
- 以runLoaders来模拟,按照分类然后拼接
- 执行node runnder.js
const { runLoaders } = require("loader-runner");
const path = require("path");
const fs = require("fs");
const entryFile = path.resolve(__dirname, "src/index.js");
let request = `!!inline-loader1!inline-loader2!${entryFile}`;
const rules = [
{
test: /\.js$/,
use: ["normal-loader1", "normal-loader2"],
},
{
test: /\.js$/,
enforce: "pre",
use: ["pre-loader1", "pre-loader2"],
},
{
test: /\.js$/,
enforce: "post",
use: ["post-loader1", "post-loader2"],
},
];
const parts = request.replace(/^-?!+/, "").split("!");
let resource = parts.pop();
let inlineLoaders = parts;
let preLoaders = [];
let postLoaders = [];
let normalLoaders = [];
for (let i = 0; i < rules.length; i++) {
let rule = rules[i];
if (resource.match(rule.test)) {
if (rule.enforce === "pre") {
preLoaders.push(...rule.use);
} else if (rule.enforce === "post") {
postLoaders.push(...rule.use);
} else {
normalLoaders.push(...rule.use);
}
}
}
let loaders = [];
if (request.startsWith("!!")) {
loaders = inlineLoaders;
} else if (request.startsWith("-!")) {
loaders = [...postLoaders, ...inlineLoaders, ...normalLoaders];
} else if (request.startsWith("!")) {
loaders = [...postLoaders, ...inlineLoaders, ...preLoaders];
} else {
loaders = [...postLoaders, ...inlineLoaders, ...normalLoaders, ...preLoaders];
}
loaders = loaders.map((loader) =>
path.resolve(__dirname, "loader-chain", loader)
);
runLoaders(
{
resource,
loaders,
readResource: fs.readFile,
},
(err, result) => {
if (err) {
console.log(`错误信息:${err}`);
} else {
console.log("结果", result.result[0].toString());
console.log(
"源文件",
result.resourceBuffer && result.resourceBuffer.toString()
);
}
}
);
pitch
- 以行内举例 a!b!c!source.js
- 目前理解到的执行顺序为
读文件->c->b->a
- 但其实前面还有一步,为pitch阶段,正确的执行顺序为
a(pitch)->b(pitch)->c(pitch)->读文件->c->b->a
- 一旦pitch有返回值,那么会跳过,后续阶段,将当前返回值,给到后边的loader,说起来有点绕,举例说明:
- 在b的pitch有返回值,那么执行顺序为
a(pitch)->b(pitch)->a 跳过了4步c(pitch)、读文件、c、b
- pitch会有两个形参
remainingRequest 还没走的loader路径和文件路径,以!间隔
request 已经走过的loader路径,以!间隔
将request的!!去掉,然后执行
现在给post-loader1.js里面加一个pitch
function loader(source) {
console.log("pre-loader1");
return source + " pre-loade1";
}
loader.pitch = () => {
console.log("post1-pitch");
return "跳不跳";
};
module.exports = loader;
post1->post2->inl1->inl2->nor1->nor2->pre1->pre2
读文件->pre2->pre1->nor2->nor1->inl2->inl1->post2->post1
post1-pitch
在第一个pitch处返回内容了,那么后续全部跳过了,webpack接收的内容为 "跳不跳",因为没有读文件,所以源文件为null
在webpack中使用自定义loader
- 3种方式
- 1.使用绝对路径的形式
- 2.自定义别名
- 3.指定查找目录
推荐
const path = require("path");
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: path.resolve(__dirname, "loaders/babel-loader.js"),
options: {
presets: ["@babel/preset-env"],
},
},
}
],
},
resolveLoader: {
alias: {
"babel-loader": path.resolve(__dirname, "loaders/babel-loader.js"),
},
},
resolveLoader: {
modules: [path.resolve("loaders"), "node_modules"],
},
实现babel-loader
const babel = require("@babel/core");
function loader(source) {
console.log("我自己的loader");
const options = this.getOptions();
const callback = this.async();
babel.transformAsync(source, options).then(({ code }) => {
callback(null, code);
});
}
module.exports = loader;
- 在使用过程中,我们需要使用sourcemap建立堆栈,因为源代码与打包后的代码差异过大,我们需要在开发时候通过打包后的产物定位源码
- 而且每到一个babel,就需要重新建立语法树,其实是没必要的
const babel = require("@babel/core");
function loader(source) {
const options = this.getOptions();
let babelOptions = {
...options,
ast: true,
sourceMaps: true,
};
const callback = this.async();
babel.transformAsync(source, babelOptions).then(({ code, ast, map }) => {
callback(null, code, ast, map);
});
}
module.exports = loader;
const babel = require("@babel/core");
function loader(source, ast, inputSourceMap) {
const options = this.getOptions();
let babelOptions = {
...options,
sourceMaps: true,
ast: true,
inputSourceMap,
};
const callback = this.async();
babel.transformFromAstAsync(ast, babelOptions).then(({ code }) => {
callback(null, code);
});
}
module.exports = loader;
...
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader2",
},
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
},
},
},
],
实现less-loader
const less = require("less");
function loader(source) {
let callback = this.async();
less.render(source, { filename: this.resource }, (err, output) => {
callback(err, `module.exports=${JSON.stringify(output.css)}`);
});
}
module.exports = loader;
实现style-loader
- 逻辑:
- 会先调用less-loader将代码编译成模块,模块导出的是默认编译好的css
- 然后由入口文件使用require引入
- 这样的话,会有一个问题,那就是style-loader接收的是一个
module.exports = css....,这样是不合理的,所以在pitch阶段执行
- 在pitch阶段执行,且返回,那么会跳过less-loader和读取less,但我们需要less-loader来解析文件
- 那么我们使用pitch的参数1(
未走路径),然后返回字符串,字符串会直接给到webpack
- 字符串中使用行间方法的!!,防止死循环,
- webpack识别到我们需要查找less,且标识了行间loader,那么这次就会读文件,然后走less-loader,然后返回
- 在这个过程中,我们把babel的概念去掉,将less理解成模块就行了,最终产出还是个模块,只不过是我们在style-loader中将它插入了
const path = require("path");
function loader() {}
loader.pitch = function (remainingRequest) {
const request =
remainingRequest
.split("!")
.map(
(requestAbsPath) =>
"./" + path.posix.relative(this.context, requestAbsPath)
)
.join("!");
let script = `
let styleCss = require(${JSON.stringify(request)});
let style = document.createElement('style');
style.innerHTML = styleCss;
document.head.appendChild(style);
`;
return script;
};
module.exports = loader;
使用
{
test: /\.less$/,
exclude: /node_modules/,
use: ["style-loader", "less-loader"],
},
结果
...
var __webpack_modules__ = {
"./loaders/less-loader.js!./src/index.less": (module) => {
module.exports =
"body {\n background-color: greenyellow;\n}\nbody div {\n color: red;\n}\n";
},
"./src/index.less": (
__unused_webpack_module,
__unused_webpack_exports,
require
) => {
let styleCss = require("./loaders/less-loader.js!./src/index.less");
let style = document.createElement("style");
style.innerHTML = styleCss;
document.head.appendChild(style);
},
};
...