From dfe2608c0f0b3b816df816da63bb532b5609515a Mon Sep 17 00:00:00 2001 From: Johannes Ewald Date: Sat, 18 Feb 2017 15:41:54 +0100 Subject: [PATCH 1/3] Replace parseQuery() with getOptions() #56 --- lib/getOptions.js | 13 +++++ lib/index.js | 4 +- lib/parseQuery.js | 17 ++---- test/getOptions.test.js | 112 ++++++++++++++++++++++++++++++++++++++++ test/parseQuery.test.js | 38 -------------- 5 files changed, 130 insertions(+), 54 deletions(-) create mode 100644 lib/getOptions.js create mode 100644 test/getOptions.test.js delete mode 100644 test/parseQuery.test.js diff --git a/lib/getOptions.js b/lib/getOptions.js new file mode 100644 index 0000000..7bac4ca --- /dev/null +++ b/lib/getOptions.js @@ -0,0 +1,13 @@ +"use strict"; + +const parseQuery = require("./parseQuery"); + +function getOptions(loaderContext) { + const query = loaderContext.query; + if(typeof query === "string") { + return parseQuery(loaderContext.query); + } + return query; +} + +module.exports = getOptions; diff --git a/lib/index.js b/lib/index.js index 2d0e642..4a88f15 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,6 +1,6 @@ "use strict"; -const parseQuery = require("./parseQuery"); +const getOptions = require("./getOptions"); const getLoaderConfig = require("./getLoaderConfig"); const stringifyRequest = require("./stringifyRequest"); const getRemainingRequest = require("./getRemainingRequest"); @@ -11,7 +11,7 @@ const parseString = require("./parseString"); const getHashDigest = require("./getHashDigest"); const interpolateName = require("./interpolateName"); -exports.parseQuery = parseQuery; +exports.getOptions = getOptions; exports.getLoaderConfig = getLoaderConfig; exports.stringifyRequest = stringifyRequest; exports.getRemainingRequest = getRemainingRequest; diff --git a/lib/parseQuery.js b/lib/parseQuery.js index 20e2bd2..b586739 100644 --- a/lib/parseQuery.js +++ b/lib/parseQuery.js @@ -1,14 +1,7 @@ "use strict"; const JSON5 = require("json5"); -const util = require("util"); -const os = require("os"); -const parseQueryDeprecationWarning = util.deprecate(() => {}, - "loaderUtils.parseQuery() received a non-string value which can be problematic, " + - "see https://github.com/webpack/loader-utils/issues/56" + os.EOL + - "parseQuery() will be replaced with getOptions() in the next major version of loader-utils." -); const specialValues = { "null": null, "true": true, @@ -16,18 +9,14 @@ const specialValues = { }; function parseQuery(query) { - if(!query) return {}; - if(typeof query !== "string") { - parseQueryDeprecationWarning(); - return query; + if(query.substr(0, 1) !== "?") { + throw new Error("A valid query string passed to parseQuery should begin with '?'"); } - if(query.substr(0, 1) !== "?") - throw new Error("a valid query string passed to parseQuery should begin with '?'"); query = query.substr(1); if(query.substr(0, 1) === "{" && query.substr(-1) === "}") { return JSON5.parse(query); } - const queryArgs = query.split(/[,\&]/g); + const queryArgs = query.split(/[,&]/g); const result = {}; queryArgs.forEach(arg => { const idx = arg.indexOf("="); diff --git a/test/getOptions.test.js b/test/getOptions.test.js new file mode 100644 index 0000000..120f269 --- /dev/null +++ b/test/getOptions.test.js @@ -0,0 +1,112 @@ +"use strict"; + +const assert = require("assert"); +const loaderUtils = require("../lib"); + +describe("getOptions()", () => { + describe("when loaderContext.query is a string", () => { + [{ + it: "should parse query params", + query: "?name=cheesecake&slices=8&delicious&warm=false", + expected: { + delicious: true, + name: "cheesecake", + slices: "8", // numbers are still strings with query params + warm: false + } + }, + { + it: "should parse query params with arrays", + query: "?ingredients[]=flour&ingredients[]=sugar", + expected: { + ingredients: ["flour", "sugar"] + } + }, + { + it: "should parse query params in JSON format", + query: "?" + JSON.stringify({ + delicious: true, + name: "cheesecake", + slices: 8, + warm: false + }), + expected: { + delicious: true, + name: "cheesecake", + slices: 8, + warm: false + } + }, + { + it: "should use decodeURIComponent", + query: "?%3d", + expected: { "=": true } + }, + { + it: "should recognize params starting with + as boolean params with the value true", + query: "?+%3d", + expected: { "=": true } + }, + { + it: "should recognize params starting with - as boolean params with the value false", + query: "?-%3d", + expected: { "=": false } + }, + { + it: "should not confuse regular equal signs and encoded equal signs", + query: "?%3d=%3D", + expected: { "=": "=" } + }].forEach(test => { + it(test.it, () => { + assert.deepEqual( + loaderUtils.getOptions({ + query: test.query + }), + test.expected + ); + }); + }); + describe("and the query string does not start with ?", () => { + it("should throw an error", () => { + assert.throws( + () => loaderUtils.getOptions({ query: "a" }), + "A valid query string passed to parseQuery should begin with '?'" + ); + }); + }); + }); + describe("when loaderContext.query is an object", () => { + it("should just return the object", () => { + const query = {}; + assert.strictEqual( + loaderUtils.getOptions({ + query + }), + query + ); + }); + }); + describe("when loaderContext.query is anything else", () => { + it("should just return it", () => { + const query = []; + assert.strictEqual( + loaderUtils.getOptions({ + query + }), + query + ); + assert.strictEqual( + loaderUtils.getOptions({ + query: undefined + }), + undefined + ); + assert.strictEqual( + loaderUtils.getOptions({ + query: null + }), + null + ); + }); + }); +}); diff --git a/test/parseQuery.test.js b/test/parseQuery.test.js deleted file mode 100644 index 2ed0098..0000000 --- a/test/parseQuery.test.js +++ /dev/null @@ -1,38 +0,0 @@ -"use strict"; - -const assert = require("assert"); -const loaderUtils = require("../"); - -describe("parseQuery()", () => { - [ - [ - "?sweet=true&name=cheesecake&slices=8&delicious&warm=false", - { "sweet": true,"name": "cheesecake","slices": "8","delicious": true,"warm": false } - ], - [ - "?%3d", - { "=": true } - ], - [ - "?+%3d", - { "=": true } - ], - [ - "?-%3d", - { "=": false } - ], - [ - "?%3d=%3D", - { "=": "=" } - ], - [ - { obj: "test" }, - { obj: "test" } - ] - ].forEach(test => { - it("should parse " + test[0], () => { - const parsed = loaderUtils.parseQuery(test[0]); - assert.deepEqual(parsed, test[1]); - }); - }); -}); From a78426b10a2444c71302f4c96d2156ff07d9eaca Mon Sep 17 00:00:00 2001 From: Johannes Ewald Date: Sat, 18 Feb 2017 15:42:59 +0100 Subject: [PATCH 2/3] Remove getLoaderConfig() in favor of getOptions() --- lib/getLoaderConfig.js | 17 ----------------- lib/index.js | 2 -- test/getLoaderConfig.test.js | 23 ----------------------- 3 files changed, 42 deletions(-) delete mode 100644 lib/getLoaderConfig.js delete mode 100644 test/getLoaderConfig.test.js diff --git a/lib/getLoaderConfig.js b/lib/getLoaderConfig.js deleted file mode 100644 index bc70cee..0000000 --- a/lib/getLoaderConfig.js +++ /dev/null @@ -1,17 +0,0 @@ -"use strict"; - -const parseQuery = require("./parseQuery"); - -function getLoaderConfig(loaderContext, defaultConfigKey) { - const query = parseQuery(loaderContext.query); - const configKey = query.config || defaultConfigKey; - if(configKey) { - const config = loaderContext.options[configKey] || {}; - delete query.config; - return Object.assign({}, config, query); - } - - return query; -} - -module.exports = getLoaderConfig; diff --git a/lib/index.js b/lib/index.js index 4a88f15..0ca780b 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,7 +1,6 @@ "use strict"; const getOptions = require("./getOptions"); -const getLoaderConfig = require("./getLoaderConfig"); const stringifyRequest = require("./stringifyRequest"); const getRemainingRequest = require("./getRemainingRequest"); const getCurrentRequest = require("./getCurrentRequest"); @@ -12,7 +11,6 @@ const getHashDigest = require("./getHashDigest"); const interpolateName = require("./interpolateName"); exports.getOptions = getOptions; -exports.getLoaderConfig = getLoaderConfig; exports.stringifyRequest = stringifyRequest; exports.getRemainingRequest = getRemainingRequest; exports.getCurrentRequest = getCurrentRequest; diff --git a/test/getLoaderConfig.test.js b/test/getLoaderConfig.test.js deleted file mode 100644 index 5164012..0000000 --- a/test/getLoaderConfig.test.js +++ /dev/null @@ -1,23 +0,0 @@ -"use strict"; - -const assert = require("assert"); -const loaderUtils = require("../"); - -describe("getLoaderConfig()", () => { - it("should merge loaderContext.query and loaderContext.options.testLoader", () => { - const config = loaderUtils.getLoaderConfig({ query: "?name=cheesecake",options: { testLoader: { slices: 8 } } }, "testLoader"); - assert.deepEqual(config, { name: "cheesecake",slices: 8 }); - }); - it("should allow to specify a config property name via loaderContext.query.config", () => { - const config = loaderUtils.getLoaderConfig({ query: "?name=cheesecake&config=otherConfig",options: { otherConfig: { slices: 8 } } }, "testLoader"); - assert.deepEqual(config, { name: "cheesecake",slices: 8 }); - }); - it("should prefer loaderContext.query.slices over loaderContext.options.slices", () => { - const config = loaderUtils.getLoaderConfig({ query: "?slices=8",options: { testLoader: { slices: 4 } } }, "testLoader"); - assert.deepEqual(config, { slices: 8 }); - }); - it("should allow no default key", () => { - const config = loaderUtils.getLoaderConfig({ query: "?slices=8",options: {} }); - assert.deepEqual(config, { slices: 8 }); - }); -}); From ff86dd65d00665b374bb8ee2a0f0ef934a6de5c8 Mon Sep 17 00:00:00 2001 From: Johannes Ewald Date: Sat, 18 Feb 2017 16:05:35 +0100 Subject: [PATCH 3/3] Update README --- README.md | 63 +++++++++++++++++++++++-------------------------------- 1 file changed, 26 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 305fa90..1926ccd 100644 --- a/README.md +++ b/README.md @@ -2,57 +2,46 @@ ## Methods -### `getLoaderConfig` +### getOptions -Recommended way to retrieve the loader config: +Recommended way to retrieve the options of a loader invocation: ```javascript // inside your loader -config = loaderUtils.getLoaderConfig(this, "myLoader"); +const options = loaderUtils.getOptions(this); ``` -Tries to read the loader config from the `webpack.config.js` under the given property name (`"myLoader"` in this case) and merges the result with the loader query. For example, if your `webpack.config.js` had this property... +**Please note:** The returned `options` object is *read-only*. It may be re-used across multiple invocations. +If you pass it on to another library, make sure to make a *deep copy* of it: ```javascript -cheesecakeLoader: { - type: "delicious", - slices: 4 -} +const options = Object.assign({}, loaderUtils.getOptions(this)); +// don't forget nested objects or arrays +options.obj = Object.assign({}, options.obj); +options.arr = options.arr.slice(); +someLibrary(options); ``` -...and your loader was called with `?slices=8`, `getLoaderConfig(this, "cheesecakeLoader")` would return +[assign-deep](https://www.npmjs.com/package/assign-deep) is a good library to make a deep copy of the options. -```javascript -{ - type: "delicious", - slices: 8 -} -``` - -It is recommended that you use the camelCased loader name as your default config property name. +#### Options as query strings -### `parseQuery` - -``` javascript -var query = loaderUtils.parseQuery(this.query); -assert(typeof query == "object"); -if(query.flag) - // ... -``` +If the loader options have been passed as loader query string (`loader?some¶ms`), the string is parsed like this: ``` text -null -> {} -? -> {} -?flag -> { flag: true } -?+flag -> { flag: true } -?-flag -> { flag: false } -?xyz=test -> { xyz: "test" } -?xyz[]=a -> { xyz: ["a"] } -?flag1&flag2 -> { flag1: true, flag2: true } -?+flag1,-flag2 -> { flag1: true, flag2: false } -?xyz[]=a,xyz[]=b -> { xyz: ["a", "b"] } -?a%2C%26b=c%2C%26d -> { "a,&b": "c,&d" } -?{json:5,data:{a:1}} -> { json: 5, data: { a: 1 } } +null -> {} +? -> {} +?flag -> { flag: true } +?+flag -> { flag: true } +?-flag -> { flag: false } +?xyz=test -> { xyz: "test" } +?xyz=1 -> { xyz: "1" } +?xyz[]=a -> { xyz: ["a"] } +?flag1&flag2 -> { flag1: true, flag2: true } +?+flag1,-flag2 -> { flag1: true, flag2: false } +?xyz[]=a,xyz[]=b -> { xyz: ["a", "b"] } +?a%2C%26b=c%2C%26d -> { "a,&b": "c,&d" } +?{data:{a:1},isJSON5:true} -> { data: { a: 1 }, isJSON5: true } ``` ### `stringifyRequest`