文章目录
babel-loader 转译
- 通过babel的核心包进行转译,babel-loader主要利用了webpack的打包流程对匹配的js文件进行转译
// index.js
// 引入基础库等一系列操作
let babel;
try {
babel = require("@babel/core");
} catch (err) {
if (err.code === "MODULE_NOT_FOUND") {
err.message += "\n babel-loader@9 requires Babel 7.12+ (the package '@babel/core'). " + "If you'd like to use Babel 6.x ('babel-core'), you should install 'babel-loader@7'.";
}
throw err;
}
// Since we've got the reverse bridge package at @babel/core@6.x, give
// people useful feedback if they try to use it alongside babel-loader.
if (/^6\./.test(babel.version)) {
throw new Error("\n babel-loader@9 will not work with the '@babel/core@6' bridge package. " + "If you want to use Babel 6.x, install 'babel-loader@7'.");
}
const {
version
} = require("../package.json");
const cache = require("./cache");
const transform = require("./transform");
const injectCaller = require("./injectCaller");
const schema = require("./schema");
const {
isAbsolute
} = require("path");
const validateOptions = require("schema-utils").validate;
// ...
导出loader
- 通过工厂函数,导出给webpack使用的loader,并且导出的loader是一个异步loader,采用的是Promise风格
// 通过一个工厂模式
module.exports = makeLoader();
module.exports.custom = makeLoader;
function makeLoader(callback) {
const overrides = callback ? callback(babel) : undefined;
return function (source, inputSourceMap) {
// Make the loader async
const callback = this.async();
// 这里的调用格式是promise风格
loader.call(this, source, inputSourceMap, overrides).then(args => callback(null, ...args), err => callback(err));
};
}
loader
async function loader(source, inputSourceMap, overrides) {
// loader提供的API,获取当前的文件名
const filename = this.resourcePath;
// 获取开发者传递的参数
let loaderOptions = this.getOptions();
// 校验开发者传递的option格式
validateOptions(schema, loaderOptions, {
name: "Babel loader"
});
// ... 一系列合并、注入、校验参数等处理
// 最终处理完成的参数配置
const config = await babel.loadPartialConfigAsync(injectCaller(programmaticOptions, this.target));
if (config) {
// ...参数的一些转换
const {
cacheDirectory = null,
cacheIdentifier = JSON.stringify({
options,
"@babel/core": transform.version,
"@babel/loader": version
}),
cacheCompression = true,
metadataSubscribers = []
} = loaderOptions;
let result;
// 如果开发者配置了cacheDirectory缓存babel处理结果
if (cacheDirectory) {
// 写入缓存位置,实际调用通过 handleCache ,读取缓存中结果
result = await cache({
source,
options,
transform,
cacheDirectory,
cacheIdentifier,
cacheCompression
});
} else {
// 如果不开启缓存,每次进行编译,核心就是通过babel.transform进行转译
result = await transform(source, options);
}
// 通过this.addDependency监听依赖文件,依赖文件变化后重新执行loader
config.files.forEach(configFile => this.addDependency(configFile));
if (result) {
if (overrides && overrides.result) {
result = await overrides.result.call(this, result, {
source,
map: inputSourceMap,
customOptions,
config,
options
});
}
const {
code,
map,
metadata,
externalDependencies
} = result;
// 通过this.addDependency添加依赖,当依赖项改变时重新执行babel-loader
externalDependencies == null ? void 0 : externalDependencies.forEach(dep => this.addDependency(dep));
// 开发者传入的loader的this上下文中的钩子name,会在转译完成后触发
metadataSubscribers.forEach(subscriber => {
subscribe(subscriber, metadata, this);
});
// 返回转译后的代码和sourceMap
return [code, map];
}
}
// If the file was ignored, pass through the original content.
// 如果文件设置了忽略,那么直接直接返回源文件内容
return [source, inputSourceMap];
}
缓存loader结果
// ...
if (cacheDirectory) {
// 写入缓存位置,实际调用通过 handleCache ,读取缓存中结果
result = await cache({
source,
options,
transform,
cacheDirectory,
cacheIdentifier,
cacheCompression
});
} else {
// 如果不开启缓存,每次进行编译,核心就是通过babel.transform进行转译
result = await transform(source, options);
}
// ...
cache
- 通过find-cache-dir库,查找babel默认的存储路径,否则使用操作系统的默认临时文件路径
const os = require("os");
const path = require("path");
const zlib = require("zlib");
const crypto = require("crypto");
const findCacheDir = require("find-cache-dir");
const {
promisify
} = require("util");
const {
readFile,
writeFile,
mkdir
} = require("fs/promises");
const transform = require("./transform");
// ...
module.exports = async function (params) {
let directory;
if (typeof params.cacheDirectory === "string") {
directory = params.cacheDirectory;
} else {
if (defaultCacheDirectory === null) {
//默认缓存位置在 node_modules/.cache/babel-loader'
//os.tmpdir() 指定操作系统临时文件的默认目录的路径
defaultCacheDirectory = findCacheDir({
name: "babel-loader"
}) || os.tmpdir();
}
directory = defaultCacheDirectory;
}
return await handleCache(directory, params);
};
handleCache
- 返回cache结果,会将文件进行hash避免冲突
// 读文件操作
const read = async function (filename, compress) {
const data = await readFile(filename + (compress ? ".gz" : ""));
const content = compress ? await gunzip(data) : data;
return JSON.parse(content.toString());
};
// 写文件操作
const write = async function (filename, compress, result) {
const content = JSON.stringify(result);
const data = compress ? await gzip(content) : content;
return await writeFile(filename + (compress ? ".gz" : ""), data);
};
// 对文件进行hash操作
const filename = function (source, identifier, options) {
const hash = crypto.createHash(hashType);
const contents = JSON.stringify({
source,
options,
identifier
});
hash.update(contents);
return hash.digest("hex") + ".json";
};
const handleCache = async function (directory, params) {
const {
source,
options = {},
cacheIdentifier,
cacheDirectory,
cacheCompression
} = params;
// 获取缓存文件绝对路径
const file = path.join(directory, filename(source, cacheIdentifier, options));
try {
// 读取缓存位置内容 "/node_modules/.cache/babel-loader/c1d0c71205535b96755f65b2a39f16601016d58d8a61022ba4ad0c91bf41714e.json" 如果开启了压缩会添加.gz后缀
// 如果有返回值,直接返回,不再执行后续逻辑
return await read(file, cacheCompression);
} catch (err) {}
const fallback = typeof cacheDirectory !== "string" && directory !== os.tmpdir();
try {
// "/node_modules/.cache/babel-loader"
// 首次创建缓存文件夹
await mkdir(directory, {
recursive: true
});
} catch (err) {
if (fallback) {
return handleCache(os.tmpdir(), params);
}
throw err;
}
// 通过 babel 进行转译
const result = await transform(source, options);
if (!result.externalDependencies.length) {
try {
// 将内容写入缓存位置 "/node_modules/.cache/babel-loader/c1d0c71205535b96755f65b2a39f16601016d58d8a61022ba4ad0c91bf41714e.json"
await write(file, cacheCompression, result);
} catch (err) {
if (fallback) {
// Fallback to tmpdir if node_modules folder not writable
// 如果出错写入操作系统的临时文件位置
return handleCache(os.tmpdir(), params);
}
throw err;
}
}
return result;
};