面试官:你使用webpack时手写过loader,分离过模块吗?

6,387 阅读3分钟

前言


大家好,之前出了一篇面试篇webpack入门,这篇文章继续介绍接下来更深入东西。

概览

  • 如何载入自己的loader
  • 使用loader-utils,schema-utils
  • 编写自己的loader
  • 扩展

如何载入自己的loader

讲道理大家都是直接import一个loader或者使用webpack内置的loader的。

如果调试自己的loader,应该如下写法:

//webpack.config.js
const path = require("path");
module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                use: [
                    {
                        loader: path.resolve("loader.js"),
                        options: {
                            test: 'apple'
                        }
                    }
                ]
            }
        ]
    }
};

其实和正常载入loader一样,只是这里指向的是本地文件的路径。

path.resolve('loader.js') 得出路径

/Users/kev1nzh/Desktop/my/webpack/loader.js

使用单个loader
在使用一个loader的时候,loader会接收你正则匹配的资源文件(如上,所有js文件)的字符串。

loader通过代码转化模块后,最后返回传递出去。
使用多个loader
  • 当使用多个loader的时候,从传入loader数组的最后一个开始反向传入资源文件字符串。

  • 最后一个loader接收最原始的资源文件字符串,转化后传入下一个lodaer。

  • 中间的loader接收上一个loader,转化后传入下一个。

  • 第一个loader最后接收转化,并传出所有loader处理完的资源文件字符串。

{
     test: /\.css$/,
     use: [
         {
             loader: 'css-loader'
         },
         {
             loader: 'style-loader'
         },
     ]
}
//style-loader接收所有css的文件,转化完再传给css-loader,转化完后再怼出来。

使用loader-utils,schema-utils

loader-utils, schema-utils是webpack的loader工具库,有很多便捷的方法可以调用。

const { getOptions,stringifyRequest, parseQuery  } = require("loader-utils");
const validateOptions = require("schema-utils");
const schema = {
    type: "object",
    properties: {
        test: {
            type: "string"
        }
    }
};
module.exports = function(source) {
    //getOptions 用于在loader里获取传入的options,返回的是对象值。
    const options = getOptions(this);
    
    // stringifyRequest转换路径,避免require()或impot时使用的绝对路径
    stringifyRequest(this, "./test.js"); //   Result =>  "\"./test.js\""
    
    //parseQuery获取query参数的,这个很简单就不说啦
    parseQuery('?name=kev&age=14') // Result => {name: 'kev', age: '14'}
    
    //验证参数的类型是否正确。
    validateOptions(schema, options, "loader");
};

编写自己的loader

总算到手写环节了!!!!

//webapck.config.js
const path = require("path");

module.exports = {
    entry: "./src",
    output: {
        path: path.resolve(__dirname, "dist"),
        filename: "package.js"
    },
    mode: "production",
    module: {
        rules: [
            {
                test: /\.js$/,
                use: [
                    {
                        loader: path.resolve("loader.js"),
                        options: {
                            work: '996',
                            sick: 'ICU',
                        }
                    }
                ]
            }
        ]
    }
};

首先载入工具库,为了后续使用。

第一步先验证options是否符合类型,

第二步获取参数,然后替换传入的资源文件字符串。

//loader.js
const { getOptions } = require("loader-utils");
const validateOptions = require("schema-utils");

const schema = {
    type: "object",
    properties: {
        work: {
            type: 'String'
        },
        sick: {
            type: 'String'
        }
    }
};

module.exports = function(source) {

    const options = getOptions(this);
    validateOptions(schema, options, 'loader');

    const  {work, sick} = options;
    source = source.replace(/\[work\]/g, work).replace(/\[sick\]/g, sick);

    return `export default ${JSON.stringify(source)}`;
};

展示下要转换的js。

// src/index.js
console.log('工作[work] 生病[sick] 加班不规范 亲人两行泪');

最后在命令行,webpack!!!!!


扩展

webpack是如何运行的?
const index = require('./index');
const console = require('./console');

//index.js
const axios = require('./scripts/debounce.js'');
const moment = require('moment');
// do something
  1. webpack会解析所有模块,如果模块中有依赖其他文件,那就继续解析依赖的模块。直到文件没有依赖为止。
  2. 解析结束后,webpack会把所有模块封装在一个函数里,并放入一个名为modules的数组里。
  3. 将modules传入一个自执行函数中,自执行函数包含一个installedModules对象,已经执行的代码模块会保存在此对象中。
  4. 最后自执行函数中加载函数(webpack__require)载入模块。

分离代码

// ./src/moment.js
const moment = require('moment');
console.log(moment().format('MMMM Do YYYY, h:mm:ss a'))

// ./index.js
const momentJs = require('./src/moment');
console.log(123);

如上代码,我们打包一下试试看。

两个文件有依赖关系,所以打包后,都会把moment模块打包进去。

SplitChunksPlugin

webpack4.x的分离代码方法,之前的CommonsChunkPlugin插件已被移除。 此模块开箱即用,默认情况下,它仅影响按需块,因为更改初始块会影响HTML文件应包含的脚本标记以运行项目。

webpack将根据以下条件自动拆分块:

  • 可以共享新块或来自该node_modules文件夹的模块
  • 新块将大于30kb(在min + gz之前)
  • 根据需要加载块时的最大并行请求数将小于或等于5
  • 初始页面加载时的最大并行请求数将小于或等于3
  • 当试图满足最后两个条件时,首选更大的块。

让我们看下代码如何实现!

module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'initial',   //选择哪些模块需要优化, 参数为 all、async、initial
      minSize: 30000,  // 要生成的块的最小数
      maxSize: 0,  //要生成的块的最大数
      minChunks: 2, // 分割前共享模块的最小块数
      maxAsyncRequests: 5, //按需加载时的最大并行请求数
      maxInitialRequests: 3, // 入口的最大并行请求数
      automaticNameDelimiter: '~',  //指定生成文件名当中的分隔符
      name: true, //拆分块的名称
      cacheGroups: { //缓存组
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  }
};


好了这篇需要讲的东西已经结束了。

面试系列第一篇: 面试官:你知道Callback Hell(回调地狱)吗?

面试系列第二篇: 面试官:react和vue有什么区别吗?

面试系列第三篇: 面试官:你了解es6的知识吗?

面试系列第四篇: 面试官:你了解Webpack吗?

如果您有收获或者疑问请在下方评论,求赞!谢谢观看到这里。