我正在为Pug文件编写一个Webpack加载器。它加载Pug文件中引用的所有依赖图像,使用file-loader
将它们复制到适当的目录,并用生成的文件的URL替换Pug文件中的require()
个调用。
我正在尝试添加功能以允许将数据URL插入到Pug文件中以代替文件URL。我现在正在使用this.loadModule()
来执行file-loader
。
此函数将回调作为参数。传递给此回调的其中一个args包含url-loader
(数据URL)的结果。所以,我需要这个写入应该输出的Pug文件。
问题是,整个加载程序在回调运行之前完成运行。因此,我对数据所做的任何事情都不会在最终文件中结束。我的程序的整体结构如下所示:
module.exports = function pugDepLoader(inputFile) {
/* Setting up various constants and variables. */
/* A while() loop that continually tests inputFile with a
regex to find require() statements.
It creates a copy of inputFile called outputFile,
which it iterates over again and again,
replacing require() statements with file paths
one at a time.
The call to this.loadModule() is within this while() loop. */
/* End of while() loop */
/* Return outputFile to Webpack, which is now a
string with the Pug file, but with the
require() statements replaced with
file paths.
This is meant to be fed to file-loader to
get written out to disc. */
}
在this.loadModule()
的回调运行一次之前,最后的return语句将被重复调用,正如我在调试语句中发现的那样。我需要能够在返回之前将回调中提供的数据URL提取到outputFile
,以便最终写入最终写入光盘的文件中。
完整的源代码如下:
module.exports = function pugDepLoader(inputFile) {
const fs = require('fs');
const path = require('path');
const loaderUtils = require('loader-utils');
var options = loaderUtils.getOptions(this);
//This option is required. It specifies
//the path to the root of the Pug file's
//dependencies relative to the location
//of the Pug files.
var contextualRoot;
if(!options || !options.hasOwnProperty('contextualRoot')) {
throw new Error('You must specify a contextual root');
}
else {
contextualRoot = options.contextualRoot;
//Ensure there is a trailing slash.
if(contextualRoot[contextualRoot.length-1] !== '/') {
contextualRoot = contextualRoot + '/';
}
}
//Determines whether paths should begin with
//a leading slash. Useful for Express.js
//compatibility.
if(!options.hasOwnProperty('absolute')) {
options.absolute = false;
}
//Set up regex to search for require() statements
const reqRE = new RegExp(/require\(/, 'g');
//outputFile will be returned containing the
//appropriately processed Pug
var outputFile = inputFile.slice();
//We need to execute reqRE once to kick things
//off, and we need to save it to a variable
//because we need information from it.
let regexResult = reqRE.exec(inputFile);
//regexResult will be null when there
//are no more matches to be found in
//the file.
while(regexResult != null) {
//pathStartIndex is the beginning of
//the path to the required file.
//We add 1 to skip the opening
//single quote.
let pathStartIndex = reqRE.lastIndex+1;
//We require the path to be wrapped in
//single quotes, so that we can easily
//be certain about where the require
//statement ends.
if(inputFile[reqRE.lastIndex] !== "'") {
console.log('FATAL ERROR: File path must be wrapped in single quotes!');
break;
}
//inputPath will hold the actual file path itself.
let inputPath = inputFile.slice(pathStartIndex, inputFile.indexOf("'", pathStartIndex));
//pathArray is used to split the
//path so we can easily extract
//the file name and path
//separately.
let pathArray = inputPath.split('/');
//Just the file name, with extension.
let fileName = pathArray.pop();
//outputPath will define what path should be
//written into the output Pug file.
//The user may optionally specify a
//custom output path.
let outputPath;
if(options.outputPath) {
outputPath = options.outputPath;
}
else {
outputPath = pathArray.join('/');
}
//Ensure a trailing slash.
if(outputPath[outputPath.length-1] !== '/') {
outputPath = (outputPath + '/');
}
//reqStart holds the index of the letter
//"r" in require(), so that we can remove
//the require() call and replace it
//with the file path.
let reqStart = inputFile.indexOf('require', regexResult.index);
//reqStmt is the require() statement in
//full. This will be used with replace()
//to replace the require() call with a
//file path in the output file.
let reqStmt = inputFile.slice(reqStart, inputFile.indexOf('"', pathStartIndex));
//The final file path, with file name.
//This will be written into the output
//Pug file in place of the require()
//calls.
let filePath = outputPath + fileName;
if(options.absolute && filePath[0] !== '/') {
if(filePath[0] === '.' && filePath[1] === '/') {
filePath = filePath.slice(1);
}
else {
filePath = '/' + filePath;
}
}
else if(!options.absolute && filePath[0] === '/') {
filePath = filePath.slice(1);
}
this.loadModule(contextualRoot + inputPath, (err, res, srcmap, module) => {
if(err) {
console.log(err);
}
if(new RegExp(/data:image/).test(res)) {
filePath = res.slice(res.indexOf('data:image'), res.lastIndexOf('"'));
}
});
//This takes care of require() calls
//inside of url() CSS functions,
//such as are used to declare
//background images inline.
//If the word "require" is
//preceeded by a single quote,
//it is within a url() function,
//and so we add appropriate closure
//for that function to the end of
//the path.
if(inputFile[reqStart-1] === "'") {
filePath = filePath + "');";
}
//Write the output.
outputFile = outputFile.replace(reqStmt, filePath);
//Run the next iteration of the regex search.
regexResult = reqRE.exec(inputFile);
}
//Return output as a string to Webpack.
return outputFile;
}