mirror of https://github.com/webpack/webpack.git
				
				
				
			Merge pull request #10017 from webpack/feat-getOptions-util-for-loader
feat: getOptions util for loader
This commit is contained in:
		
						commit
						bd08639607
					
				|  | @ -5,7 +5,10 @@ | ||||||
| 
 | 
 | ||||||
| "use strict"; | "use strict"; | ||||||
| 
 | 
 | ||||||
|  | const parseJson = require("json-parse-better-errors"); | ||||||
| const { getContext, runLoaders } = require("loader-runner"); | const { getContext, runLoaders } = require("loader-runner"); | ||||||
|  | const querystring = require("querystring"); | ||||||
|  | const validateOptions = require("schema-utils"); | ||||||
| const { SyncHook } = require("tapable"); | const { SyncHook } = require("tapable"); | ||||||
| const { | const { | ||||||
| 	CachedSource, | 	CachedSource, | ||||||
|  | @ -49,6 +52,13 @@ const makeSerializable = require("./util/makeSerializable"); | ||||||
| /** @typedef {import("./util/Hash")} Hash */ | /** @typedef {import("./util/Hash")} Hash */ | ||||||
| /** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ | /** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * @typedef {Object} LoaderItem | ||||||
|  |  * @property {string} loader | ||||||
|  |  * @property {any} options | ||||||
|  |  * @property {string?} ident | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * @param {string} context absolute context path |  * @param {string} context absolute context path | ||||||
|  * @param {string} source a source path |  * @param {string} source a source path | ||||||
|  | @ -166,7 +176,7 @@ class NormalModule extends Module { | ||||||
| 	 * @param {string} options.request request string | 	 * @param {string} options.request request string | ||||||
| 	 * @param {string} options.userRequest request intented by user (without loaders from config) | 	 * @param {string} options.userRequest request intented by user (without loaders from config) | ||||||
| 	 * @param {string} options.rawRequest request without resolving | 	 * @param {string} options.rawRequest request without resolving | ||||||
| 	 * @param {TODO[]} options.loaders list of loaders | 	 * @param {LoaderItem[]} options.loaders list of loaders | ||||||
| 	 * @param {string} options.resource path + query of the real resource | 	 * @param {string} options.resource path + query of the real resource | ||||||
| 	 * @param {string | undefined} options.matchResource path + query of the matched resource (virtuel | 	 * @param {string | undefined} options.matchResource path + query of the matched resource (virtuel | ||||||
| 	 * @param {Parser} options.parser the parser used | 	 * @param {Parser} options.parser the parser used | ||||||
|  | @ -204,7 +214,7 @@ class NormalModule extends Module { | ||||||
| 		this.resource = resource; | 		this.resource = resource; | ||||||
| 		/** @type {string | undefined} */ | 		/** @type {string | undefined} */ | ||||||
| 		this.matchResource = matchResource; | 		this.matchResource = matchResource; | ||||||
| 		/** @type {TODO[]} */ | 		/** @type {LoaderItem[]} */ | ||||||
| 		this.loaders = loaders; | 		this.loaders = loaders; | ||||||
| 		if (resolveOptions !== undefined) { | 		if (resolveOptions !== undefined) { | ||||||
| 			// already declared in super class
 | 			// already declared in super class
 | ||||||
|  | @ -350,6 +360,44 @@ class NormalModule extends Module { | ||||||
| 		}; | 		}; | ||||||
| 		const loaderContext = { | 		const loaderContext = { | ||||||
| 			version: 2, | 			version: 2, | ||||||
|  | 			getOptions: schema => { | ||||||
|  | 				const loader = this.getCurrentLoader(loaderContext); | ||||||
|  | 
 | ||||||
|  | 				let { options } = loader; | ||||||
|  | 
 | ||||||
|  | 				if (typeof options === "string") { | ||||||
|  | 					if (options.substr(0, 1) === "{" && options.substr(-1) === "}") { | ||||||
|  | 						try { | ||||||
|  | 							options = parseJson(options); | ||||||
|  | 						} catch (e) { | ||||||
|  | 							throw new Error(`Cannot parse string options: ${e.message}`); | ||||||
|  | 						} | ||||||
|  | 					} else { | ||||||
|  | 						options = querystring.parse(options, "&", "=", { | ||||||
|  | 							maxKeys: 0 | ||||||
|  | 						}); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if (options === null || options === undefined) { | ||||||
|  | 					options = {}; | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if (schema) { | ||||||
|  | 					let name = "Loader"; | ||||||
|  | 					let baseDataPath = "options"; | ||||||
|  | 					let match; | ||||||
|  | 					if (schema.title && (match = /^(.+) (.+)$/.exec(schema.title))) { | ||||||
|  | 						[, name, baseDataPath] = match; | ||||||
|  | 					} | ||||||
|  | 					validateOptions(schema, options, { | ||||||
|  | 						name, | ||||||
|  | 						baseDataPath | ||||||
|  | 					}); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				return options; | ||||||
|  | 			}, | ||||||
| 			emitWarning: warning => { | 			emitWarning: warning => { | ||||||
| 				if (!(warning instanceof Error)) { | 				if (!(warning instanceof Error)) { | ||||||
| 					warning = new NonErrorEmittedError(warning); | 					warning = new NonErrorEmittedError(warning); | ||||||
|  |  | ||||||
|  | @ -0,0 +1,9 @@ | ||||||
|  | module.exports = [ | ||||||
|  | 	{ code: /DEP_WEBPACK_RULE_LOADER_OPTIONS_STRING/ }, | ||||||
|  | 	{ code: /DEP_WEBPACK_RULE_LOADER_OPTIONS_STRING/ }, | ||||||
|  | 	{ code: /DEP_WEBPACK_RULE_LOADER_OPTIONS_STRING/ }, | ||||||
|  | 	{ code: /DEP_WEBPACK_RULE_LOADER_OPTIONS_STRING/ }, | ||||||
|  | 	{ code: /DEP_WEBPACK_RULE_LOADER_OPTIONS_STRING/ }, | ||||||
|  | 	{ code: /DEP_WEBPACK_RULE_LOADER_OPTIONS_STRING/ }, | ||||||
|  | 	{ code: /DEP_WEBPACK_RULE_LOADER_OPTIONS_STRING/ } | ||||||
|  | ]; | ||||||
|  | @ -0,0 +1,12 @@ | ||||||
|  | module.exports = [ | ||||||
|  | 	[ | ||||||
|  | 		/\.\/loader-1\.js/, | ||||||
|  | 		/Loader has been/, | ||||||
|  | 		/options\.arg6\.bar\.baz should be a string/ | ||||||
|  | 	], | ||||||
|  | 	[ | ||||||
|  | 		/\.\/loader-2\.js/, | ||||||
|  | 		/Custom Loader Name has been/, | ||||||
|  | 		/configuration\.arg should be true/ | ||||||
|  | 	] | ||||||
|  | ]; | ||||||
|  | @ -0,0 +1,51 @@ | ||||||
|  | it("should get options", function() { | ||||||
|  | 	expect(require("./a")).toStrictEqual({ | ||||||
|  | 		arg: true, | ||||||
|  | 		arg1: null, | ||||||
|  | 		arg3: 1234567890, | ||||||
|  | 		arg4: "string", | ||||||
|  | 		arg5: [1, 2, 3], | ||||||
|  | 		arg6: { foo: "value", bar: { baz: "other-value" } } | ||||||
|  | 	}); | ||||||
|  | 	expect(require("./b")).toStrictEqual({ | ||||||
|  | 		arg: true, | ||||||
|  | 		arg1: null, | ||||||
|  | 		arg3: 1234567890, | ||||||
|  | 		arg4: "string", | ||||||
|  | 		arg5: [1, 2, 3], | ||||||
|  | 		arg6: { foo: "value", bar: { baz: "other-value" } } | ||||||
|  | 	}); | ||||||
|  | 	expect(require("./c")).toStrictEqual({ | ||||||
|  | 		arg: true, | ||||||
|  | 		arg1: null, | ||||||
|  | 		arg3: 1234567890, | ||||||
|  | 		arg4: "string", | ||||||
|  | 		arg5: [1, 2, 3], | ||||||
|  | 		arg6: { foo: "value", bar: { baz: "other-value" } } | ||||||
|  | 	}); | ||||||
|  | 	expect(require("./d")).toStrictEqual({ | ||||||
|  | 		arg4: "text" | ||||||
|  | 	}); | ||||||
|  | 	expect(require("./e")).toStrictEqual({}); | ||||||
|  | 	expect(require("./f")).toStrictEqual({ | ||||||
|  | 		delicious: "", | ||||||
|  | 		name: "cheesecake", | ||||||
|  | 		slices: "8", | ||||||
|  | 		warm: "false" | ||||||
|  | 	}); | ||||||
|  | 	expect(require("./g")).toStrictEqual({ | ||||||
|  | 		"=": "=" | ||||||
|  | 	}); | ||||||
|  | 	expect(require("./h")).toStrictEqual({ | ||||||
|  | 		foo: "bar" | ||||||
|  | 	}); | ||||||
|  | 	expect(require("./i")).toStrictEqual({ | ||||||
|  | 		foo: "bar" | ||||||
|  | 	}); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const never = false; | ||||||
|  | if (never) { | ||||||
|  | 	require("./error1"); | ||||||
|  | 	require("./error2"); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,11 @@ | ||||||
|  | const schema = require("./loader-1.options"); | ||||||
|  | 
 | ||||||
|  | module.exports = function() { | ||||||
|  | 	const options = this.getOptions(schema); | ||||||
|  | 
 | ||||||
|  | 	const json = JSON.stringify(options) | ||||||
|  | 		.replace(/\u2028/g, "\\u2028") | ||||||
|  | 		.replace(/\u2029/g, "\\u2029"); | ||||||
|  | 
 | ||||||
|  | 	return `module.exports = ${json}`; | ||||||
|  | }; | ||||||
|  | @ -0,0 +1,43 @@ | ||||||
|  | { | ||||||
|  |   "additionalProperties": false, | ||||||
|  |   "properties": { | ||||||
|  |     "arg": { | ||||||
|  |       "type": "boolean" | ||||||
|  |     }, | ||||||
|  |     "arg1": { | ||||||
|  |       "type": "null" | ||||||
|  |     }, | ||||||
|  |     "arg2": {}, | ||||||
|  |     "arg3": { | ||||||
|  |       "type": "number" | ||||||
|  |     }, | ||||||
|  |     "arg4": { | ||||||
|  |       "type": "string" | ||||||
|  |     }, | ||||||
|  |     "arg5": { | ||||||
|  |       "type": "array", | ||||||
|  |       "items": { | ||||||
|  |         "type": "number" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "arg6": { | ||||||
|  |       "type": "object", | ||||||
|  |       "properties": { | ||||||
|  |         "foo": { | ||||||
|  |           "type": "string" | ||||||
|  |         }, | ||||||
|  |         "bar": { | ||||||
|  |           "type": "object", | ||||||
|  |           "properties": { | ||||||
|  |             "baz": { | ||||||
|  |               "type": "string" | ||||||
|  |             } | ||||||
|  |           }, | ||||||
|  |           "additionalProperties": false | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       "additionalProperties": false | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   "type": "object" | ||||||
|  | } | ||||||
|  | @ -0,0 +1,11 @@ | ||||||
|  | const schema = require("./loader-2.options"); | ||||||
|  | 
 | ||||||
|  | module.exports = function() { | ||||||
|  | 	const options = this.getOptions(schema); | ||||||
|  | 
 | ||||||
|  | 	const json = JSON.stringify(options) | ||||||
|  | 		.replace(/\u2028/g, "\\u2028") | ||||||
|  | 		.replace(/\u2029/g, "\\u2029"); | ||||||
|  | 
 | ||||||
|  | 	return `module.exports = ${json}`; | ||||||
|  | }; | ||||||
|  | @ -0,0 +1,10 @@ | ||||||
|  | { | ||||||
|  |   "title": "Custom Loader Name configuration", | ||||||
|  |   "additionalProperties": false, | ||||||
|  |   "properties": { | ||||||
|  |     "arg": { | ||||||
|  |       "enum": [true] | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   "type": "object" | ||||||
|  | } | ||||||
|  | @ -0,0 +1,9 @@ | ||||||
|  | module.exports = function() { | ||||||
|  | 	const options = this.getOptions(); | ||||||
|  | 
 | ||||||
|  | 	const json = JSON.stringify(options) | ||||||
|  | 		.replace(/\u2028/g, '\\u2028') | ||||||
|  | 		.replace(/\u2029/g, '\\u2029'); | ||||||
|  | 
 | ||||||
|  | 	return `module.exports = ${json}`; | ||||||
|  | }; | ||||||
|  | @ -0,0 +1,92 @@ | ||||||
|  | module.exports = { | ||||||
|  | 	mode: "none", | ||||||
|  | 	module: { | ||||||
|  | 		rules: [ | ||||||
|  | 			{ | ||||||
|  | 				test: /a\.js$/, | ||||||
|  | 				loader: "./loader", | ||||||
|  | 				options: { | ||||||
|  | 					arg: true, | ||||||
|  | 					arg1: null, | ||||||
|  | 					arg2: undefined, | ||||||
|  | 					arg3: 1234567890, | ||||||
|  | 					arg4: "string", | ||||||
|  | 					arg5: [1, 2, 3], | ||||||
|  | 					arg6: { foo: "value", bar: { baz: "other-value" } } | ||||||
|  | 				} | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				test: /b\.js$/, | ||||||
|  | 				loader: "./loader-1", | ||||||
|  | 				options: { | ||||||
|  | 					arg: true, | ||||||
|  | 					arg1: null, | ||||||
|  | 					arg2: undefined, | ||||||
|  | 					arg3: 1234567890, | ||||||
|  | 					arg4: "string", | ||||||
|  | 					arg5: [1, 2, 3], | ||||||
|  | 					arg6: { foo: "value", bar: { baz: "other-value" } } | ||||||
|  | 				} | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				test: /c\.js$/, | ||||||
|  | 				loader: "./loader-1", | ||||||
|  | 				options: JSON.stringify({ | ||||||
|  | 					arg: true, | ||||||
|  | 					arg1: null, | ||||||
|  | 					arg2: undefined, | ||||||
|  | 					arg3: 1234567890, | ||||||
|  | 					arg4: "string", | ||||||
|  | 					arg5: [1, 2, 3], | ||||||
|  | 					arg6: { foo: "value", bar: { baz: "other-value" } } | ||||||
|  | 				}) | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				test: /d\.js$/, | ||||||
|  | 				loader: "./loader-1", | ||||||
|  | 				options: "arg4=text" | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				test: /d\.js$/, | ||||||
|  | 				loader: "./loader", | ||||||
|  | 				options: "" | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				test: /f\.js$/, | ||||||
|  | 				loader: "./loader", | ||||||
|  | 				options: "name=cheesecake&slices=8&delicious&warm=false" | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				test: /g\.js$/, | ||||||
|  | 				loader: "./loader", | ||||||
|  | 				options: "%3d=%3D" | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				test: /h\.js$/, | ||||||
|  | 				loader: "./loader", | ||||||
|  | 				options: "foo=bar" | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				test: /i\.js$/, | ||||||
|  | 				loader: "./loader", | ||||||
|  | 				options: `${JSON.stringify({ | ||||||
|  | 					foo: "bar" | ||||||
|  | 				})}` | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				test: /error1\.js$/, | ||||||
|  | 				loader: "./loader-1", | ||||||
|  | 				options: { | ||||||
|  | 					arg6: { foo: "value", bar: { baz: 42 } } | ||||||
|  | 				} | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				test: /error2\.js$/, | ||||||
|  | 				loader: "./loader-2", | ||||||
|  | 				options: { | ||||||
|  | 					arg: false | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		] | ||||||
|  | 	} | ||||||
|  | }; | ||||||
		Loading…
	
		Reference in New Issue