Loader execution sequence

Default: right to left, bottom up

 {
    test:/\.js$/,
    use:['normal-loader1'.'normal-loader2']}Copy the code

Run normal-loader2 before run normal-loader1.

However, in additional cases, multiple rules can be configured for the same resource and the order in which they are executed can be controlled

Loader is divided into the following forms:

  • Front loader
{
    test:/\.js$/,
    enforce:'pre'.// Execute first
    use: ['pre-loader1'.'pre-loader2']}Copy the code
  • Ordinary loader
{
    test:/\.js$/,
    use:['normal-loader1'.'normal-loader2']}Copy the code
  • Inline loader

Inline loader is not defined in rules. If inline loader is used and you do not want to execute the pre-loader and normal-loader, you can use -! ban

symbol The results of
-! Disable the pre-loader and normal-loader command
! The normal-loader command is disabled
!!!!! Prohibit any loader other than inline-loader from executing this command
const xxx = require('-! inline-loader1! inline-loader2! ./index.js')
Copy the code
  • The rear loader
{
    test:/\.js$/,
    enforce:'post'.// Post Webpack is guaranteed to be executed after
    use: ['post-loader1'.'post-loader2']}Copy the code

The loaders are executed in the following order: pre -> normal -> inline -> POST

It works like this:

let request = `inline-loader1! inline-loader2! ./index.js`
let parts = request.replace(/ ^ -? ! +/.' ').split('! ')
// The last element is the resource to load
let resource = path.resolve(__dirname,'src',parts.pop())
// Loaders can be the directory where you customize the loader, or node_modules
let resolveLoader = (loader) = >path.resolve(__dirname,'loaders',loader)
let inlineLoaders = parts.map(resolveLoader)

let rules = options.module.rules
let preLoaders = [];
let postLoaders = [];
let normalLoaders = [];
for(let i=0; i<rules.length; i++){let rule = rules[i]
    // Check whether the file suffix is consistent
    if(rule.test.test(resource)){
       if(rule.enforce =='pre'){ preLoaders.push(... rule.use); }else if(rule.enforce == 'post'){ postLoaders.push(... rule.use); }else{ normalLoaders.push(... rule.use); }}}// Splice the full path
preLoaders = preLoaders.map(resolveLoader)
postLoaders = postLoaders.map(resolveLoader)
normalLoaders = normalLoaders.map(resolveLoader)
let loaders = [];
if(request.startsWith('!!!!! ')) {//noPrePostAutoLoaders
    loaders=[...inlineLoaders];
}else if(request.startsWith('-! ')) {//noPreAutoLoaders
    loaders=[...postLoaders,...inlineLoaders];
}else if(request.startsWith('! ')) {// Do not use normal loader
    loaders=[...postLoaders,...inlineLoaders,...preLoaders];
}else{
   // Inline-loader does not have prefixes that are added in order
  loaders=[...postLoaders,...inlineLoaders,...normalLoaders,...preLoaders];
}

runLoaders({
    resource,
    loaders,
    context: {name:'my'},
	readResource: fs.readFile.bind(fs)
}, function(err, result) {
    console.log(err);
    console.log(result.result,result.resourceBuffer.toString());
})

Copy the code
  • First get the resource path, if there is inline-loader cut out, concatenate the absolute path of loader, add tolineLoadersAn array of
  • Get the rules from the configuration file, iterate to add the same loader to the corresponding array, and concatenate the absolute path
  • Determine if there is a prefix in inline-loader and remove the corresponding loader. If there is no prefix, add it to the loader in orderloaders
  • loadersPass in the runLoaders execution, which is covered in the next section

The composition of the loader

Loader is composed of two parts: pitch and normal. In a true sense, Loader is actually a function. Pitch is another function hanging on the function object

In addition to the above mentioned how to configure loader to produce different execution order, there are pitch and normal execution order in loader

The left-most loader pitch will be executed first, then all pitches will be executed later, before loading resources (i.e. source code), and finally starting from the right-most Loader normal

The essence of loader is to take the resource file and process it, then send it to the next loader for processing, and finally return the result to Webpack.

Pitch does not work if there is no return value or if the pitch is not defined. If the pitch has a return value, it will intercept subsequent loader execution and return the result directly to the previous loader

Pitch itself is a function and it receives three arguments, namely remainingRequest previousRequest data

  • remainingRequestThe loading path of the remaining Loader
  • previousRequestPrevious loading path of loader

Style-loader is implemented through the pitch

normal.pitch = function(remainingRequest,previousRequest,data){
    let style =  `
     let style = document.createElement('style');
     style.innerHTML = require(${loaderUtils.stringifyRequest(this."!!!!!"+remainingRequest)}).toString();
     document.head.appendChild(style);
    `;  
    return style;
}
Copy the code

In general, style-loader is the left-most loader and the last loader to execute, because the loader to the right of style-loader usually returns a JS code string. Style-loader does not handle this JS string well. Therefore, the execution of subsequent loader is directly intercepted in pitch, and the processing results of remaining Loader are loaded through Webpack require. RemainingRequest + remainingRequest + remainingRequest + remainingRequest + remainingRequest + remainingRequest + remainingRequest + remainingRequest + remainingRequest No loader is executed except inline-loader.

loader-runner

Loader-runner is a separate library in Webpack, and all loader execution is dependent on loader-Runner. The implementation of Loader-Runner is explored below.

Loader-runner exposes a runLoaders function

function runLoaders(options,callback){
    let resource = options.resource || ' ';C :/ SRC /index.js? name=zhufeng#top
    let loaders = options.loaders || [];//loader an array of absolute paths
    let loaderContext = options.context||{};// This is an object that will be the context object when the Loader function executes
    let readResource = options.readResource||readFile;
    let loaderObjects = loaders.map(createLoaderObject);
    loaderContext.resource=resource;
    loaderContext.readResource = readResource;
    loaderContext.loaderIndex = 0;// It is a metric that can be modified to control which loader is currently executing
    loaderContext.loaders = loaderObjects;// Hold all loaders
    loaderContext.callback = null;
    loaderContext.async = null;// This is a function that changes loader execution from synchronous to asynchronous
    Object.defineProperty(loaderContext,'request', {get(){
            return loaderContext.loaders.map(l= >l.request).concat(loaderContext.resource).join('! ')}});Object.defineProperty(loaderContext,'remainingRequest', {get(){
            return loaderContext.loaders.slice(loaderContext.loaderIndex+1).concat(loaderContext.resource).join('! ')}});Object.defineProperty(loaderContext,'currentRequest', {get(){
            return loaderContext.loaders.slice(loaderContext.loaderIndex).concat(loaderContext.resource).join('! ')}});Object.defineProperty(loaderContext,'previousRequest', {get(){
            return loaderContext.loaders.slice(0,loaderContext.loaderIndex).join('! ')}});Object.defineProperty(loaderContext,'data', {get(){
            let loaderObj = loaderContext.loaders[loaderContext.loaderIndex];
            returnloaderObj.data; }});let processOptions = {
        resourceBuffer:null
    }
    iteratePitchingLoaders(processOptions,loaderContext,(err,result) = >{
        callback(err,{
            result,
            resourceBuffer:processOptions.resourceBuffer
        });
    });
}
Copy the code

Assign the properties of the passed configuration object to loaderContext. Of particular importance here is the implementation of the createLoaderObject function, which literally creates the Loader object around which all subsequent operations will be performed

function createLoaderObject(request){
    let loaderObj = {
        request,
        normal:null.// Loader itself
        pitch:null.// The pitch function itself
        raw:false.// Whether to convert to a string
        data: {},// Each loader has a custom data object to store some custom information
        pitchExecuted:false.// Whether the pitch function has been executed
        normalExecuted:false// Whether normal has already been executed
    }
    let normal = require(loaderObj.request);
    loaderObj.normal = normal;
    loaderObj.raw = normal.raw;
    let pitch = normal.pitch;
    loaderObj.pitch = pitch;
    return loaderObj;
}
Copy the code

The iteratePitchingLoaders function is then called

function iteratePitchingLoaders(processOptions,loaderContext,finalCallback){
    if(loaderContext.loaderIndex>=loaderContext.loaders.length){
        return processResource(processOptions,loaderContext,finalCallback);
    }
   let currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex];
   if(currentLoaderObject.pitchExecuted){
    loaderContext.loaderIndex++;
    return iteratePitchingLoaders(processOptions,loaderContext,finalCallback);
   }
   let pitchFunction = currentLoaderObject.pitch;
   currentLoaderObject.pitchExecuted =true;// Indicates that the pitch function has been executed
   if(! pitchFunction)// If the loader does not provide pitch methods
     return iteratePitchingLoaders(processOptions,loaderContext,finalCallback);
   runSyncOrAsync(pitchFunction,loaderContext,
    [loaderContext.remainingRequest,loaderContext.previousRequest,loaderContext.data]
    ,(err,... values) = >{
        if(values.length>0&&!!!!! values[0]){
            loaderContext.loaderIndex--;// Return to the previous loader and execute the normal method of the previous loader
            iterateNormalLoaders(processOptions,loaderContext,values,finalCallback);
        }else{ iteratePitchingLoaders(processOptions,loaderContext,finalCallback); }}); }Copy the code

First determines whether, if is executed to the most the right side of the loader’s Pitch load source resources, through loaderContext. LoaderIndex get the loader object, PitchExecuted is set to true to indicate that the Pitch has been executed, and the Pitch is called via runSyncOrAsync, If the return value is loaderContext. LoaderIndex — — to the execution of a loader before normal if there is no return value is the recursive call iteratePitchingLoaders execution of a loader pitch

function runSyncOrAsync(fn,context,args,callback){
  let isSync = true;// Whether to synchronize, the default is yes
  let isDone = false;// Whether fn has been executed. The default is false
  const innerCallback = context.callback = function(err,... values){
        isDone= true;
        isSync = false; callback(err,... values); } context.async =function(){
    isSync=false;// Set the synchronization flag to false, which means asynchronous
    return innerCallback;
  }
  let result = fn.apply(context,args);
  if(isSync){
    isDone =true;// Complete directly
    return callback(null,result);// Call the callback}}Copy the code

IterateNormalLoaders function

function iterateNormalLoaders(processOptions,loaderContext,args,finalCallback){
    if(loaderContext.loaderIndex<0) {// If the index is less than 0, all normal execution is complete
        return finalCallback(null,args);
    }
    let currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex];
   if(currentLoaderObject.normalExecuted){
    loaderContext.loaderIndex--;
    return iterateNormalLoaders(processOptions,loaderContext,args,finalCallback);
   }
   let normalFunction = currentLoaderObject.normal;
   currentLoaderObject.normalExecuted =true;// Indicates that the pitch function has been executed
   convertArgs(args,currentLoaderObject.raw);
   runSyncOrAsync(normalFunction,loaderContext,args,(err,... values) = >{
    if(err)finalCallback(err);
    iterateNormalLoaders(processOptions,loaderContext,values,finalCallback);
   });
}
Copy the code

If all loaders have finished calling finalCallback and returned the source code, Then there is loaderContext. LoaderIndex — — recursive call iterateNormalLoaders processed using normal function

conclusion

Loader execution is completed by loader-runner. The complete loader process starts from the pitch of the loader on the left to the pitch of the loader on the right to obtain resources. Normal from the far right to the far left