webpack loader—自己写一个按需加载插件

栏目: 编程工具 · 发布时间: 6年前

内容简介:在开发的工程中,线上环境需要引入一些统计和打印日志的js文件。但是对于开发环境,加速打包速度减少页面渲染时间很关键。我于是想根据开发环境,写一个简单的loader,按需加载一些资源。例如:在index.js中,用自定义函数envLoader添加资源index.js

在开发的工程中,线上环境需要引入一些统计和打印日志的js文件。但是对于开发环境,加速打包速度减少页面渲染时间很关键。我于是想根据开发环境,写一个简单的loader,按需加载一些资源。

例如:在index.js中,用自定义函数envLoader添加资源

index.js

//......

envLoader(
    '/vendor/log.js'
)

//......

复制代码

为了完成按需加载的功能。打算使用自定义的loader。 实现思路如下:

  1. 添加js loader 对index.js进行处理
  2. 解析envLoader函数
  3. 拿到传入的参数并根据环境判断是否加载。

结合官网的loader api了解webpack loader的工作原理。

将使用以下api

  • loader-utils
  • schema-utils
  • this.async
  • this.cacheable
  • getOptions
  • validateOptions
  • urlToRequest

开始撸一个自己的loader (^-^)V

Webpack Loader

loader 用于对模块的源代码进行转换。loader 可以使你在 import 或"加载"模块时预处理文件。因此,loader 类似于其他构建 工具 中“任务(task)”,并提供了处理前端构建步骤的强大方法。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。

一、基本用法

loader 是导出为一个函数的 node 模块。该函数在 loader 转换资源的时候调用。给定的函数将调用 loader API,并通过 this 上下文访问。

loader是一个node module,那么它的基本形式如下

module.exports = function(source) {
  return source;
};
复制代码
  • loader只能传入一个包含包含资源文件内容的字符串(source)
  • 如果是同步loader,可以用 return 或者 this.callback(err, value…) 将代码返回
  • 异步loader:在一个异步的模块中,回传时需要调用 Loader API 提供的回调方法 this.async 来获取 callback 函数:
module.exports = function(content, map, meta) {
  var callback = this.async();
  someAsyncOperation(content, function(err, result) {
    if (err) return callback(err);
    callback(null, result, map, meta);
  });
};
复制代码

二、在webpack中引入loader

官网上介绍了配给单个和多个loader的方法。

主要原理是 path.resolve 方法,给loader添加路径。也可以使用 resolveLoader.modules 统一配置多个loader的路径。

webpack会在这些目录中搜索loaders,我在项目中新建了loaders本地目录,并修改文件如下:

webpack.config.js

module.exports = {
    //...
    resolveLoader: {// 配置查找loader的目录
        modules: [
            'node_modules',
            path.resolve(__dirname, 'src', 'loaders')
        ]
    },        
  module: {
    rules:[
       {
           test: /\.js$/,
           use: [
               {
                   loader: 'env-loader',
                   options: {
                       env: process.env.NODE_ENV
                   }
               },
               {
                   loader:'babel-loader',
                   options: {
                       presets: ['env','es2015','react'],
                   }
               },
           ]
       }]
    },
    //...
};
复制代码

注意:loader的执行方式是从右到左,链式执行,上一个 Loader 的处理结果给下一个接着处理

在package.json中定义了根据环境打包的命令

"scripts": {
    "webpack": "cross-env NODE_ENV=development webpack-dev-server --open --mode development",
    "test": "cross-env NODE_ENV=test webpack --mode development",
    "dev": "cross-env NODE_ENV=dev webpack --mode development",
    "prd": "cross-env NODE_ENV=prd webpack --mode development",
    "boot":"cross-env NODE_ENV=boot webpack --mode development"
  },
复制代码

通过设置 NODE_ENV 来区分dev、prd环境。

Q1:怎么获取命令中设置的环境参数

A1: process.env 对象上可以获取到打包时定义的NODE_ENV,在webpack.config.js中引入env-loader的时候,可以将参数传递给 loader 的 options 选项。

webpack.config.js

{
   loader: 'env-loader',
   options: {
       env: process.env.NODE_ENV
   }
},
复制代码

三、使用loader工具库,解析loader传参

  • loader-utils 包。它提供了许多有用的工具,但最常用的一种工具是获取传递给 loader 的选项
  • schema-utils 包配合 loader-utils,用于保证 loader 选项,进行与 JSON Schema 结构一致的校验

在loader中使用loader-utils包的 getOptions 方法,拿到loader的option选项({env:'dev'}。用schema-utils 包配合 loader-utils,用于保证 loader 选项, 进行与 JSON Schema结构一致的校验 。在index.js中添加这两个包:

env-loader/index.js

const loaderUtils = require('loader-utils')
const validate = require('schema-utils');

let json = {
    "type": "object",
    "properties": {
        "content": {
            "type": "string"
        }
    }
}

module.exports = function(source) {
    this.cacheable();
    let callback = this.async();
    let options = loaderUtils.getOptions(this) //{env:'dev'}
    validate(json, options, "env-loader");
}
复制代码

四、使用esprima解析js节点

Esprima parser把js程序转换成描述程序语法结构的语法树(AST)。产生的语法树对于从程序转换到静态程序分析的各种用途都很有用。

之前写过一篇介绍AST的文章点击链接查看,这里就不详细展开。

使用方法:

esprima.parseScript(input, config, delegate)
esprima.parseModule(input, config, delegate)
复制代码
  • input入是表示要解析的程序的字符串
  • config是用于自定义解析行为的对象(可选)
  • delegate是为每个节点调用的回调函数(可选)

将source作为input参数,程序将会被解析成AST。

node返回每个节点对应的Syntax,meta是节点在程序中的具体位置。

esprima.parseModule(source, {}, async(node, meta)=> {
    console.log(node.meta)
    //....
})
复制代码

解析结果如下:

webpack loader—自己写一个按需加载插件

分析每个节点的Syntax是否满足判断条件,这里判断node的type类型和正在执行的函数callee的name==='envLoader'和type==='Identifier',对满足条件的节点进行处理。

function judgeType(node) {
    return (node.type === 'CallExpression')
        && (node.callee.name === 'envLoader')
        && (node.callee.type === 'Identifier')
}
if (judgeType(node)) {
    flag = true
    node.arguments.map(argument=>{
        entries.push({
            val: argument.value,
            start: meta.start.offset,
            end: meta.end.offset
        });
    })

}
复制代码

五、文件的路径处理

在节点分析中,拿到了自定义envLoader函数中传入的外部资源地址,接下来要再loader中。

在loader中一般使用 require() 或者 import 方法。这是因为webpack是在 将模块路径转换为模块id 之前计算散列的,所以我们必须避免绝对路径,以确保不同编译之间的哈希一致。

不要在模块代码中插入绝对路径,因为当项目根路径变化时,文件绝对路径也会变化。

loaderUtils.urlToRequest可以将一些资源URL转换为webpack模块请求。

//获取当前路径下的src文件夹
let downloadPath = path.resolve(process.cwd(), 'src')

if(env == 'prd'){
    //如果是prd环境
    //使用loaderUtils将请求转换为module
    const saveUrl = loaderUtils.urlToRequest(`${extName}`,downloadPath);// "path/to/module.js"
    //将转换好的module引入
    var replaceText = `import "${saveUrl}"`
}else{
    //其他环境
    var replaceText = 'function envLoad(){}'
}
    
//将envLoader函数替换
source = source.replace(transText, replaceText);
复制代码

六、测试

完成上面的步骤,已经开发完成了一个简单的loader,并且可以在本地运行。接下来让我们用一个简单的单元测试,来保证 loader 能够按照我们预期的方式正确运行。

我们将使用 Jest 框架。然后还需要安装 babel-jest 和允许我们使用 import / export 和 async / await 的一些预设环境(presets)。

6.1 安装依赖

npm install --save-dev jest babel-jest babel-preset-env
复制代码

.babelrc

{
  "presets": [[
    "env",
    {
      "targets": {
        "node": "4"
      }
    }
  ]]
}
复制代码

我们的 loader 将会处理 .js 文件,并且将任何实例中的

envLoader('xxx')
复制代码

在开发环境下替换成 function envLoad(){} ,在生产环境下替换成 import '路径/xxx.js'。

在test文件夹下新建example.js

envLoader(
    '/vendor/lodash.min.js'
)
复制代码

我们将会使用 Node.js API 和 memory-fs 去执行 webpack。

npm install --save-dev webpack memory-fss
复制代码

test/compiler.js

import path from 'path';
import webpack from 'webpack';
import memoryfs from 'memory-fs';

export default (fixture, options = {}) => {
    const compiler = webpack({
        context: __dirname,
        entry: `./${fixture}`,
        output: {
            path: path.resolve(__dirname),
            filename: 'bundle.js',
        },
        module: {
            rules: [{
                test: /\.js$/,
                use: {
                    loader: path.resolve(__dirname, '../src/loaders/env-loader'),
                    options: {
                        env: process.env.NODE_ENV
                    }
                }
            }]
        }
    });

    compiler.outputFileSystem = new memoryfs();

    return new Promise((resolve, reject) => {
        compiler.run((err, stats) => {
            if (err || stats.hasErrors()) reject(err);

            resolve(stats);
        });
    });
};
复制代码

最后,我们来编写测试,并且添加 npm script 运行它。

import compiler from './compiler.js';

test('envLoader to import', async () => {
    const stats = await compiler('example.js');
    const output = stats.toJson().modules[0].source;
    if(process.env.NODE_ENV == 'prd'){
        expect(output).toBe('import "/Users/yuan/Documents/yuanyuan/Project/env-loader/src/vendor/lodash.min.js"');
    }else{
        expect(output).toBe('function envLoad(){}');
    }
});
复制代码

package.json

{
  "scripts": {
    "test-boot": "cross-env NODE_ENV=boot jest",
    "test-prd": "cross-env NODE_ENV=prd jest"
  }
}
复制代码

分别运行两个script

webpack loader—自己写一个按需加载插件
webpack loader—自己写一个按需加载插件

各自验证成功~测试通过

env-loader地址 详细实现过程点这里!!


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

算法引论

算法引论

[美]Udi Manber / 黄林鹏、谢瑾奎、陆首博、等 / 电子工业出版社 / 2005-9-1 / 35.00元

本书是国际算法大师乌迪·曼博(Udi Manber)博士撰写的一本享有盛誉的著作。全书共分12章:第1章到第4章为介绍性内容,涉及数学归纳法、算法分析、数据结构等内容;第5章提出了与归纳证明进行类比的算法设计思想;第6章到第9章分别给出了4个领域的算法,如序列和集合的算法、图算法、几何算法、代数和数值算法;第10章涉及归约,也是第11章的序幕,而后者涉及NP完全问题;第12章则介绍了并行算法;最后......一起来看看 《算法引论》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

随机密码生成器
随机密码生成器

多种字符组合密码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具