内容简介:在开发的工程中,线上环境需要引入一些统计和打印日志的js文件。但是对于开发环境,加速打包速度减少页面渲染时间很关键。我于是想根据开发环境,写一个简单的loader,按需加载一些资源。例如:在index.js中,用自定义函数envLoader添加资源index.js
在开发的工程中,线上环境需要引入一些统计和打印日志的js文件。但是对于开发环境,加速打包速度减少页面渲染时间很关键。我于是想根据开发环境,写一个简单的loader,按需加载一些资源。
例如:在index.js中,用自定义函数envLoader添加资源
index.js
//...... envLoader( '/vendor/log.js' ) //...... 复制代码
为了完成按需加载的功能。打算使用自定义的loader。 实现思路如下:
- 添加js loader 对index.js进行处理
- 解析envLoader函数
- 拿到传入的参数并根据环境判断是否加载。
结合官网的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) //.... }) 复制代码
解析结果如下:
分析每个节点的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
各自验证成功~测试通过
env-loader地址 详细实现过程点这里!!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- CocoaPods 如何加载插件
- 使用 Webapck 优化 VS Code 插件加载性能
- Rust 实现动态库加载和基于此功能实现的插件管理
- shade插件解决打包后无法加载spring xsd文件办法
- 页面滚动无限加载jQuery插件jquery.infinitescroll.js使用说明
- 图片懒加载插件Vue-Lazyload@1.3.0存在bug及临时解决方法
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。