.6-浅析webpack源码之validateSchema模块
Posted QH-Jimmy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了.6-浅析webpack源码之validateSchema模块相关的知识,希望对你有一定的参考价值。
validateSchema模块
首先来看错误检测:
const webpackOptionsValidationErrors = validateSchema(webpackOptionsSchema, options); if(webpackOptionsValidationErrors.length) { throw new WebpackOptionsValidationError(webpackOptionsValidationErrors); }
可以注意到,这里传了两个参数,其实第一个参数来源于一个JSON文件:
const webpackOptionsSchema = require("../schemas/webpackOptionsSchema.json");
这个JSON文件非常大,可以观察一下部分内容:
{
"plugins": {
"description": "Add additional plugins to the compiler.",
"type": "array"
},
"resolve": {
"description": "Options for the resolver",
"anyOf": [
{
"$ref": "#/definitions/resolve"
}
]
},
"required": [
"entry"
],
"type": "object"
}
从描述可以猜测,这里的key对应options中的key,value就是检测方式。
比如说entry放到required代表是必须的,plugins的type为array代表这个键必须是一个数组,而$ref代表一个路径映射,即#/definnitions/resolve。
在JSON文件的开头有一个definitions,其中resolve对应的内容如下:
{
"definitions": {
"resolve": {
"additionalProperties": false,
"properties": {
"alias": {
// ...
}
// ...
},
"type": "object"
}
}
}
简单明了,就不多解释了。
下面进入validateSchema模块,流程如图:
内部代码简化如下:
const Ajv = require("ajv"); const ajv = new Ajv({ errorDataPath: "configuration", allErrors: true, verbose: true }); require("ajv-keywords")(ajv, ["instanceof"]); require("../schemas/ajv.absolutePath")(ajv); function validateSchema(schema, options) { // 仍然是多配置与单配置 if(Array.isArray(options)) { /*...*/ } else { return validateObject(schema, options); } } function validateObject(schema, options) { // 转换JSON文件 const validate = ajv.compile(schema); // 检测配置对象 const valid = validate(options); // 返回错误对象 return valid ? [] : filterErrors(validate.errors); } function filterErrors(errors) { /*...*/ } module.exports = validateSchema;
这里引入的ajv模块是一个工具,就像之前的JSON5一样,作用是将一个JSON配置文件转换成一个对象,用于检测对象的合法性。
在github上,该工具的Getting started如图所示:
简直跟源码中的使用过程一模一样,所以有兴趣的可以自己去看看教程学一下。
由于JSON十分巨大,所以编译后的对象也十分巨大,这里根据vue脚手架中的配置,只看常规的参数是如何进行检测的,比如说devServer、devtool、entry、module、output、plugins、resolve,测试代码如下:
var Ajv = require(\'ajv\'); const ajv = new Ajv({ errorDataPath: "configuration", allErrors: true, verbose: true }); // 简化后的JSON文件 const json = require(\'./tmp.json\'); const validate = ajv.compile(json);
打包后尝试获取生成的validate函数,整理后源码如下:
(function(self, RULES, formats, root, refVal, defaults, customRules, co, equal, ucs2length, ValidationError) { var refVal0 = refVal[0]; // ... var refVal13 = refVal[13]; var validate = (function(data, dataPath, parentData, parentDataProperty, rootData) { \'use strict\'; // 负责收集错误信息 var vErrors = null; // 负责对错误进行计数 var errors = 0; if (rootData === undefined) rootData = data; // 这是根级对象 if ((data && typeof data === "object" && !Array.isArray(data))) { var errs__0 = errors; var valid1 = true; for (var key0 in data) { /*...*/ } // 在这里进行检测 // 每出现一个错误errors+1并记录vErrors中 var data1 = data.devServer; if (data1 !== undefined) { /*...*/ } var data1 = data.devtool; if (data1 !== undefined) { /*...*/ } var data1 = data.entry; if (data1 === undefined) { /*...*/ } else { /*...*/ } var data1 = data.module; if (data1 !== undefined) { /*...*/ } var data1 = data.output; if (data1 !== undefined) { /*...*/ } var data1 = data.plugins; if (data1 !== undefined) { /*...*/ } var data1 = data.resolve; if (data1 !== undefined) { /*...*/ } } else { /*...*/ } validate.errors = vErrors; // 判断是否产生错误 return errors === 0; }); return validate; })
调用validate(options),options在函数中就相当于那个data,validate会依次从data中取出需要校验的key,按照JSON文件中的规则进行判断。
这里有一个比较麻烦的点,就是顶部的refVal,由于这是一个内部的IIFE,所以从这里看不出refVal数组如何定义的。凭借我的聪明才智,还是从源码中获取到了对应的定义:
打开后,其实是一堆validate函数,所以就不展开看了。
其实validate并不只有这么多,其中还可以分为特殊校验器和公共校验器,其中公共校验器不会针对特殊的键来进行校验,在这里可以先列出来。(友情警告:千万不要点开!!!)
refVal1
if ((data && typeof data === "object" && !Array.isArray(data))) { if (Object.keys(data).length < 1) { var err = { keyword: \'minProperties\', dataPath: (dataPath || \'\') + "", schemaPath: \'#/oneOf/0/minProperties\', params: { limit: 1 }, message: \'should NOT have less than 1 properties\', schema: 1, parentSchema: validate.schema.oneOf[0], data: data }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } var errs__1 = errors; var valid2 = true; for (var key1 in data) { var data1 = data[key1]; var errs_2 = errors; var errs__2 = errors; var prevValid2 = false; var valid2 = false; var errs_3 = errors; if (typeof data1 === "string") { if (ucs2length(data1) < 1) { var err = { keyword: \'minLength\', dataPath: (dataPath || \'\') + \'[\\\'\' + key1 + \'\\\']\', schemaPath: \'#/oneOf/0/additionalProperties/oneOf/0/minLength\', params: { limit: 1 }, message: \'should NOT be shorter than 1 characters\', schema: 1, parentSchema: validate.schema.oneOf[0].additionalProperties.oneOf[0], data: data1 }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } } else { var err = { keyword: \'type\', dataPath: (dataPath || \'\') + \'[\\\'\' + key1 + \'\\\']\', schemaPath: \'#/oneOf/0/additionalProperties/oneOf/0/type\', params: { type: \'string\' }, message: \'should be string\', schema: validate.schema.oneOf[0].additionalProperties.oneOf[0].type, parentSchema: validate.schema.oneOf[0].additionalProperties.oneOf[0], data: data1 }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } var valid3 = errors === errs_3; if (valid3) valid2 = prevValid2 = true; var errs_3 = errors; var errs__3 = errors; var valid3 = false; var errs_4 = errors; var errs_5 = errors; if (Array.isArray(data1)) { if (data1.length < 1) { var err = { keyword: \'minItems\', dataPath: (dataPath || \'\') + \'[\\\'\' + key1 + \'\\\']\', schemaPath: \'#/definitions/common.nonEmptyArrayOfUniqueStringValues/minItems\', params: { limit: 1 }, message: \'should NOT have less than 1 items\', schema: 1, parentSchema: refVal2, data: data1 }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } var valid5 = true; if (data1.length > 1) { var i = data1.length, j; outer: for (; i--;) { for (j = i; j--;) { if (equal(data1[i], data1[j])) { valid5 = false; break outer; } } } } if (!valid5) { var err = { keyword: \'uniqueItems\', dataPath: (dataPath || \'\') + \'[\\\'\' + key1 + \'\\\']\', schemaPath: \'#/definitions/common.nonEmptyArrayOfUniqueStringValues/uniqueItems\', params: { i: i, j: j }, message: \'should NOT have duplicate items (items ## \' + j + \' and \' + i + \' are identical)\', schema: true, parentSchema: refVal2, data: data1 }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } var errs__5 = errors; var valid5; for (var i5 = 0; i5 < data1.length; i5++) { var data2 = data1[i5]; var errs_6 = errors; if (typeof data2 === "string") { if (ucs2length(data2) < 1) { var err = { keyword: \'minLength\', dataPath: (dataPath || \'\') + \'[\\\'\' + key1 + \'\\\'][\' + i5 + \']\', schemaPath: \'#/definitions/common.nonEmptyArrayOfUniqueStringValues/items/minLength\', params: { limit: 1 }, message: \'should NOT be shorter than 1 characters\', schema: 1, parentSchema: refVal2.items, data: data2 }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } } else { var err = { keyword: \'type\', dataPath: (dataPath || \'\') + \'[\\\'\' + key1 + \'\\\'][\' + i5 + \']\', schemaPath: \'#/definitions/common.nonEmptyArrayOfUniqueStringValues/items/type\', params: { type: \'string\' }, message: \'should be string\', schema: refVal2.items.type, parentSchema: refVal2.items, data: data2 }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } var valid6 = errors === errs_6; } } else { var err = { keyword: \'type\', dataPath: (dataPath || \'\') + \'[\\\'\' + key1 + \'\\\']\', schemaPath: \'#/definitions/common.nonEmptyArrayOfUniqueStringValues/type\', params: { type: \'array\' }, message: \'should be array\', schema: refVal2.type, parentSchema: refVal2, data: data1 }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } var valid5 = errors === errs_5; var valid4 = errors === errs_4; valid3 = valid3 || valid4; if (!valid3) { var err = { keyword: \'anyOf\', dataPath: (dataPath || \'\') + \'[\\\'\' + key1 + \'\\\']\', schemaPath: \'#/oneOf/0/additionalProperties/oneOf/1/anyOf\', params: {}, message: \'should match some schema in anyOf\', schema: validate.schema.oneOf[0].additionalProperties.oneOf[1].anyOf, parentSchema: validate.schema.oneOf[0].additionalProperties.oneOf[1], data: data1 }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } else { errors = errs__3; if (vErrors !== null) { if (errs__3) vErrors.length = errs__3; else vErrors = null; } } var valid3 = errors === errs_3; if (valid3 && prevValid2) valid2 = false; else { if (valid3) valid2 = prevValid2 = true; } if (!valid2) { var err = { keyword: \'oneOf\', dataPath: (dataPath || \'\') + \'[\\\'\' + key1 + \'\\\']\', schemaPath: \'#/oneOf/0/additionalProperties/oneOf\', params: {}, message: \'should match exactly one schema in oneOf\', schema: validate.schema.oneOf[0].additionalProperties.oneOf, parentSchema: validate.schema.oneOf[0].additionalProperties, data: data1 }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } else { errors = errs__2; if (vErrors !== null) { if (errs__2) vErrors.length = errs__2; else vErrors = null; } } var valid2 = errors === errs_2; } } else { var err = { keyword: \'type\', dataPath: (dataPath || \'\') + "", schemaPath: \'#/oneOf/0/type\', params: { type: \'object\' }, message: \'should be object\', schema: validate.schema.oneOf[0].type, parentSchema: validate.schema.oneOf[0], data: data }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } var valid1 = errors === errs_1; if (valid1) valid0 = prevValid0 = true; var errs_1 = errors; if (typeof data === "string") { if (ucs2length(data) < 1) { var err = { keyword: \'minLength\', dataPath: (dataPath || \'\') + "", schemaPath: \'#/oneOf/1/minLength\', params: { limit: 1 }, message: \'should NOT be shorter than 1 characters\', schema: 1, parentSchema: validate.schema.oneOf[1], data: data }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } } else { var err = { keyword: \'type\', dataPath: (dataPath || \'\') + "", schemaPath: \'#/oneOf/1/type\', params: { type: \'string\' }, message: \'should be string\', schema: validate.schema.oneOf[1].type, parentSchema: validate.schema.oneOf[1], data: data }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } var valid1 = errors === errs_1; if (valid1 && prevValid0) valid0 = false; else { if (valid1) valid0 = prevValid0 = true; var errs_1 = errors; var errs__1 = errors; var valid1 = false; var errs_2 = errors; var errs_3 = errors; if (Array.isArray(data)) { if (data.length < 1) { var err = { keyword: \'minItems\', dataPath: (dataPath || \'\') + "", schemaPath: \'#/definitions/common.nonEmptyArrayOfUniqueStringValues/minItems\', params: { limit: 1 }, message: \'should NOT have less than 1 items\', schema: 1, parentSchema: refVal[2], data: data }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } var valid3 = true; if (data.length > 1) { var i = data.length, j; outer: for (; i--;) { for (j = i; j--;) { if (equal(data[i], data[j])) { valid3 = false; break outer; } } } } if (!valid3) { var err = { keyword: \'uniqueItems\', dataPath: (dataPath || \'\') + "", schemaPath: \'#/definitions/common.nonEmptyArrayOfUniqueStringValues/uniqueItems\', params: { i: i, j: j }, message: \'should NOT have duplicate items (items ## \' + j + \' and \' + i + \' are identical)\', schema: true, parentSchema: refVal[2], data: data }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } var errs__3 = errors; var valid3; for (var i3 = 0; i3 < data.length; i3++) { var data1 = data[i3]; var errs_4 = errors; if (typeof data1 === "string") { if (ucs2length(data1) < 1) { var err = { keyword: \'minLength\', dataPath: (dataPath || \'\') + \'[\' + i3 + \']\', schemaPath: \'#/definitions/common.nonEmptyArrayOfUniqueStringValues/items/minLength\', params: { limit: 1 }, message: \'should NOT be shorter than 1 characters\', schema: 1, parentSchema: refVal[2].items, data: data1 }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } } else { var err = { keyword: \'type\', dataPath: (dataPath || \'\') + \'[\' + i3 + \']\', schemaPath: \'#/definitions/common.nonEmptyArrayOfUniqueStringValues/items/type\', params: { type: \'string\' }, message: \'should be string\', schema: refVal[2].items.type, parentSchema: refVal[2].items, data: data1 }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } var valid4 = errors === errs_4; } } else { var err = { keyword: \'type\', dataPath: (dataPath || \'\') + "", schemaPath: \'#/definitions/common.nonEmptyArrayOfUniqueStringValues/type\', params: { type: \'array\' }, message: \'should be array\', schema: refVal[2].type, parentSchema: refVal[2], data: data }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } var valid3 = errors === errs_3; var valid2 = errors === errs_2; valid1 = valid1 || valid2; if (!valid1) { var err = { keyword: \'anyOf\', dataPath: (dataPath || \'\') + "", schemaPath: \'#/oneOf/2/anyOf\', params: {}, message: \'should match some schema in anyOf\', schema: validate.schema.oneOf[2].anyOf, parentSchema: validate.schema.oneOf[2], data: data }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } else { errors = errs__1; if (vErrors !== null) { if (errs__1) vErrors.length = errs__1; else vErrors = null; } } var valid1 = errors === errs_1; if (valid1 && prevValid0) valid0 = false; else { if (valid1) valid0 = prevValid0 = true; var valid1 = true; if (valid1 && prevValid0) valid0 = false; else { if (valid1) valid0 = prevValid0 = true; } } } if (!valid0) { var err = { keyword: \'oneOf\', dataPath: (dataPath || \'\') + "", schemaPath: \'#/oneOf\', params: {}, message: \'should match exactly one schema in oneOf\', schema: validate.schema.oneOf, parentSchema: validate.schema, data: data }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } else { errors = errs__0; if (vErrors !== null) { if (errs__0) vErrors.length = errs__0; else vErrors = null; } }
规则:
1.数据可以为字符串、对象、数组
2.字符串必须为非空字符串
3.数组必须是非空数组,元素必须是非空字符串且不能重复
4.如果是对象则至少要有2个键,键的规则满足2、3
refVal3
if ((data && typeof data === "object" && !Array.isArray(data))) { var errs__0 = errors; var valid1 = true; for (var key0 in data) { var isAdditional0 = !(false || validate.schema.properties[key0]); if (isAdditional0) { valid1 = false; var err = { keyword: \'additionalProperties\', dataPath: (dataPath || \'\') + "", schemaPath: \'#/additionalProperties\', params: { additionalProperty: \'\' + key0 + \'\' }, message: \'should NOT have additional properties\', schema: false, parentSchema: validate.schema, data: data }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } } var data1 = data.exprContextCritical; if (data1 !== undefined) { var errs_1 = errors; if (typeof data1 !== "boolean") { var err = { keyword: \'type\', dataPath: (dataPath || \'\') + \'.exprContextCritical\', schemaPath: \'#/properties/exprContextCritical/type\', params: { type: \'boolean\' }, message: \'should be boolean\', schema: validate.schema.properties.exprContextCritical.type, parentSchema: validate.schema.properties.exprContextCritical, data: data1 }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } var valid1 = errors === errs_1; } var data1 = data.exprContextRecursive; if (data1 !== undefined) { var errs_1 = errors; if (typeof data1 !== "boolean") { var err = { keyword: \'type\', dataPath: (dataPath || \'\') + \'.exprContextRecursive\', schemaPath: \'#/properties/exprContextRecursive/type\', params: { type: \'boolean\' }, message: \'should be boolean\', schema: validate.schema.properties.exprContextRecursive.type, parentSchema: validate.schema.properties.exprContextRecursive, data: data1 }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } var valid1 = errors === errs_1; } if (data.exprContextRegExp !== undefined) { var errs_1 = errors; var valid1 = errors === errs_1; } var data1 = data.exprContextRequest; if (data1 !== undefined) { var errs_1 = errors; if (typeof data1 !== "string") { var err = { keyword: \'type\', dataPath: (dataPath || \'\') + \'.exprContextRequest\', schemaPath: \'#/properties/exprContextRequest/type\', params: { type: \'string\' }, message: \'should be string\', schema: validate.schema.properties.exprContextRequest.type, parentSchema: validate.schema.properties.exprContextRequest, data: data1 }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } var valid1 = errors === errs_1; } var data1 = data.loaders; if (data1 !== undefined) { var errs_1 = errors; var errs__1 = errors; var valid1 = false; var errs_2 = errors; if (!refVal4(data1, (dataPath || \'\') + \'.loaders\', data, \'loaders\', rootData)) { if (vErrors === null) vErrors = refVal4.errors; else vErrors = vErrors.concat(refVal4.errors); errors = vErrors.length; } var valid2 = errors === errs_2; valid1 = valid1 || valid2; if (!valid1) { var err = { keyword: \'anyOf\', dataPath: (dataPath || \'\') + \'.loaders\', schemaPath: \'#/properties/loaders/anyOf\', params: {}, message: \'should match some schema in anyOf\', schema: validate.schema.properties.loaders.anyOf, parentSchema: validate.schema.properties.loaders, data: data1 }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } else { errors = errs__1; if (vErrors !== null) { if (errs__1) vErrors.length = errs__1; else vErrors = null; } } var valid1 = errors === errs_1; } if (data.noParse !== undefined) { var errs_1 = errors; var valid1 = errors === errs_1; } if (data.rules !== undefined) { var errs_1 = errors; var errs_2 = errors; if (!refVal[4](data.rules, (dataPath || \'\') + \'.rules\', data, \'rules\', rootData)) { if (vErrors === null) vErrors = refVal[4].errors; else vErrors = vErrors.concat(refVal[4].errors); errors = vErrors.length; } var valid2 = errors === errs_2; var valid1 = errors === errs_1; } var data1 = data.unknownContextCritical; if (data1 !== undefined) { var errs_1 = errors; if (typeof data1 !== "boolean") { var err = { keyword: \'type\', dataPath: (dataPath || \'\') + \'.unknownContextCritical\', schemaPath: \'#/properties/unknownContextCritical/type\', params: { type: \'boolean\' }, message: \'should be boolean\', schema: validate.schema.properties.unknownContextCritical.type, parentSchema: validate.schema.properties.unknownContextCritical, data: data1 }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } var valid1 = errors === errs_1; } var data1 = data.unknownContextRecursive; if (data1 !== undefined) { var errs_1 = errors; if (typeof data1 !== "boolean") { var err = { keyword: \'type\', dataPath: (dataPath || \'\') + \'.unknownContextRecursive\', schemaPath: \'#/properties/unknownContextRecursive/type\', params: { type: \'boolean\' }, message: \'should be boolean\', schema: validate.schema.properties.unknownContextRecursive.type, parentSchema: validate.schema.properties.unknownContextRecursive, data: data1 }; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } var valid1 = errors === errs_1; }以上是关于.6-浅析webpack源码之validateSchema模块的主要内容,如果未能解决你的问题,请参考以下文章
.7-浅析webpack源码之WebpackOptionsDefaulter模块