From 36dc86617930a5cf18af51cf3f53d0ee284d2824 Mon Sep 17 00:00:00 2001 From: Johannes Ewald Date: Fri, 17 Feb 2017 17:28:16 +0100 Subject: [PATCH] Move functions into dedicated files --- index.js | 347 ------------------------------------- lib/getCurrentRequest.js | 13 ++ lib/getHashDigest.js | 53 ++++++ lib/getLoaderConfig.js | 17 ++ lib/getRemainingRequest.js | 13 ++ lib/index.js | 23 +++ lib/interpolateName.js | 98 +++++++++++ lib/isUrlRequest.js | 14 ++ lib/parseQuery.js | 62 +++++++ lib/parseString.js | 19 ++ lib/stringifyRequest.js | 40 +++++ lib/urlToRequest.js | 49 ++++++ package.json | 3 +- 13 files changed, 403 insertions(+), 348 deletions(-) delete mode 100644 index.js create mode 100644 lib/getCurrentRequest.js create mode 100644 lib/getHashDigest.js create mode 100644 lib/getLoaderConfig.js create mode 100644 lib/getRemainingRequest.js create mode 100644 lib/index.js create mode 100644 lib/interpolateName.js create mode 100644 lib/isUrlRequest.js create mode 100644 lib/parseQuery.js create mode 100644 lib/parseString.js create mode 100644 lib/stringifyRequest.js create mode 100644 lib/urlToRequest.js diff --git a/index.js b/index.js deleted file mode 100644 index bd7936f..0000000 --- a/index.js +++ /dev/null @@ -1,347 +0,0 @@ -"use strict"; - -const JSON5 = require("json5"); -const path = require("path"); -const util = require("util"); -const os = require("os"); -const emojiRegex = /[\uD800-\uDFFF]./; -const emojiList = require("emojis-list").filter(emoji => emojiRegex.test(emoji)); -const matchNativeWin32Path = /^[A-Z]:[/\\]|^\\\\/i; -const matchRelativePath = /^\.\.?[/\\]/; - -const baseEncodeTables = { - 26: "abcdefghijklmnopqrstuvwxyz", - 32: "123456789abcdefghjkmnpqrstuvwxyz", // no 0lio - 36: "0123456789abcdefghijklmnopqrstuvwxyz", - 49: "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ", // no lIO - 52: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", - 58: "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ", // no 0lIO - 62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", - 64: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_" -}; -const emojiCache = {}; -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." -); - -function isAbsolutePath(str) { - return path.posix.isAbsolute(str) || path.win32.isAbsolute(str); -} - -function isRelativePath(str) { - return matchRelativePath.test(str); -} - -function encodeStringToEmoji(content, length) { - if(emojiCache[content]) return emojiCache[content]; - length = length || 1; - const emojis = []; - do { - const index = Math.floor(Math.random() * emojiList.length); - emojis.push(emojiList[index]); - emojiList.splice(index, 1); - } while(--length > 0); - const emojiEncoding = emojis.join(""); - emojiCache[content] = emojiEncoding; - return emojiEncoding; -} - -function encodeBufferToBase(buffer, base) { - const encodeTable = baseEncodeTables[base]; - if(!encodeTable) throw new Error("Unknown encoding base" + base); - - const readLength = buffer.length; - - const Big = require("big.js"); - Big.RM = Big.DP = 0; - let b = new Big(0); - for(let i = readLength - 1; i >= 0; i--) { - b = b.times(256).plus(buffer[i]); - } - - let output = ""; - while(b.gt(0)) { - output = encodeTable[b.mod(base)] + output; - b = b.div(base); - } - - Big.DP = 20; - Big.RM = 1; - - return output; -} - -function parseQuery(query) { - const specialValues = { - "null": null, - "true": true, - "false": false - }; - 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 '?'"); - query = query.substr(1); - if(query.substr(0, 1) === "{" && query.substr(-1) === "}") { - return JSON5.parse(query); - } - const queryArgs = query.split(/[,\&]/g); - const result = {}; - queryArgs.forEach(arg => { - const idx = arg.indexOf("="); - if(idx >= 0) { - let name = arg.substr(0, idx); - let value = decodeURIComponent(arg.substr(idx + 1)); - if(specialValues.hasOwnProperty(value)) { - value = specialValues[value]; - } - if(name.substr(-2) === "[]") { - name = decodeURIComponent(name.substr(0, name.length - 2)); - if(!Array.isArray(result[name])) - result[name] = []; - result[name].push(value); - } else { - name = decodeURIComponent(name); - result[name] = value; - } - } else { - if(arg.substr(0, 1) === "-") { - result[decodeURIComponent(arg.substr(1))] = false; - } else if(arg.substr(0, 1) === "+") { - result[decodeURIComponent(arg.substr(1))] = true; - } else { - result[decodeURIComponent(arg)] = true; - } - } - }); - return result; -} - -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; -} - -function stringifyRequest(loaderContext, request) { - const splitted = request.split("!"); - const context = loaderContext.context || (loaderContext.options && loaderContext.options.context); - return JSON.stringify(splitted.map(part => { - // First, separate singlePath from query, because the query might contain paths again - const splittedPart = part.match(/^(.*?)(\?.*)/); - let singlePath = splittedPart ? splittedPart[1] : part; - const query = splittedPart ? splittedPart[2] : ""; - if(isAbsolutePath(singlePath) && context) { - singlePath = path.relative(context, singlePath); - if(isAbsolutePath(singlePath)) { - // If singlePath still matches an absolute path, singlePath was on a different drive than context. - // In this case, we leave the path platform-specific without replacing any separators. - // @see https://github.com/webpack/loader-utils/pull/14 - return singlePath + query; - } - if(isRelativePath(singlePath) === false) { - // Ensure that the relative path starts at least with ./ otherwise it would be a request into the modules directory (like node_modules). - singlePath = "./" + singlePath; - } - } - return singlePath.replace(/\\/g, "/") + query; - }).join("!")); -} - -function dotRequest(obj) { - return obj.request; -} - -function getRemainingRequest(loaderContext) { - if(loaderContext.remainingRequest) - return loaderContext.remainingRequest; - const request = loaderContext.loaders.slice(loaderContext.loaderIndex + 1).map(dotRequest).concat([loaderContext.resource]); - return request.join("!"); -} - -function getCurrentRequest(loaderContext) { - if(loaderContext.currentRequest) - return loaderContext.currentRequest; - const request = loaderContext.loaders.slice(loaderContext.loaderIndex).map(dotRequest).concat([loaderContext.resource]); - return request.join("!"); -} - -function isUrlRequest(url, root) { - // An URL is not an request if - // 1. it's a Data Url - // 2. it's an absolute url or and protocol-relative - // 3. it's some kind of url for a template - if(/^data:|^chrome-extension:|^(https?:)?\/\/|^[\{\}\[\]#*;,'§\$%&\(=?`´\^°<>]/.test(url)) return false; - // 4. It's also not an request if root isn't set and it's a root-relative url - if((root === undefined || root === false) && /^\//.test(url)) return false; - return true; -} - -function urlToRequest(url, root) { - const moduleRequestRegex = /^[^?]*~/; - let request; - - // we can't use path.win32.isAbsolute because it also matches paths starting with a forward slash - if(matchNativeWin32Path.test(url)) { - // absolute windows path, keep it - request = url; - } else if(root !== undefined && root !== false && /^\//.test(url)) { - // if root is set and the url is root-relative - switch(typeof root) { - // 1. root is a string: root is prefixed to the url - case "string": - // special case: `~` roots convert to module request - if(moduleRequestRegex.test(root)) { - request = root.replace(/([^~\/])$/, "$1/") + url.slice(1); - } else { - request = root + url; - } - break; - // 2. root is `true`: absolute paths are allowed - // *nix only, windows-style absolute paths are always allowed as they doesn't start with a `/` - case "boolean": - request = url; - break; - default: - throw new Error("Unexpected parameters to loader-utils 'urlToRequest': url = " + url + ", root = " + root + "."); - } - } else if(/^\.\.?\//.test(url)) { - // A relative url stays - request = url; - } else { - // every other url is threaded like a relative url - request = "./" + url; - } - - // A `~` makes the url an module - if(moduleRequestRegex.test(request)) { - request = request.replace(moduleRequestRegex, ""); - } - - return request; -} - -function parseString(str) { - try { - if(str[0] === "\"") return JSON.parse(str); - if(str[0] === "'" && str.substr(str.length - 1) === "'") { - return parseString( - str - .replace(/\\.|"/g, x => x === "\"" ? "\\\"" : x) - .replace(/^'|'$/g, "\"") - ); - } - return JSON.parse("\"" + str + "\""); - } catch(e) { - return str; - } -} - -function getHashDigest(buffer, hashType, digestType, maxLength) { - hashType = hashType || "md5"; - maxLength = maxLength || 9999; - const hash = require("crypto").createHash(hashType); - hash.update(buffer); - if(digestType === "base26" || digestType === "base32" || digestType === "base36" || - digestType === "base49" || digestType === "base52" || digestType === "base58" || - digestType === "base62" || digestType === "base64") { - return encodeBufferToBase(hash.digest(), digestType.substr(4)).substr(0, maxLength); - } else { - return hash.digest(digestType || "hex").substr(0, maxLength); - } -} - -function interpolateName(loaderContext, name, options) { - let filename; - if(typeof name === "function") { - filename = name(loaderContext.resourcePath); - } else { - filename = name || "[hash].[ext]"; - } - const context = options.context; - const content = options.content; - const regExp = options.regExp; - let ext = "bin"; - let basename = "file"; - let directory = ""; - let folder = ""; - if(loaderContext.resourcePath) { - let resourcePath = loaderContext.resourcePath; - const idx = resourcePath.lastIndexOf("."); - const i = resourcePath.lastIndexOf("\\"); - const j = resourcePath.lastIndexOf("/"); - const p = i < 0 ? j : j < 0 ? i : i < j ? i : j; - if(idx >= 0) { - ext = resourcePath.substr(idx + 1); - resourcePath = resourcePath.substr(0, idx); - } - if(p >= 0) { - basename = resourcePath.substr(p + 1); - resourcePath = resourcePath.substr(0, p + 1); - } - if(typeof context !== "undefined") { - directory = path.relative(context, resourcePath + "_").replace(/\\/g, "/").replace(/\.\.(\/)?/g, "_$1"); - directory = directory.substr(0, directory.length - 1); - } else { - directory = resourcePath.replace(/\\/g, "/").replace(/\.\.(\/)?/g, "_$1"); - } - if(directory.length === 1) { - directory = ""; - } else if(directory.length > 1) { - folder = path.basename(directory); - } - } - let url = filename; - if(content) { - // Match hash template - url = url - .replace( - /\[(?:(\w+):)?hash(?::([a-z]+\d*))?(?::(\d+))?\]/ig, - (all, hashType, digestType, maxLength) => getHashDigest(content, hashType, digestType, parseInt(maxLength, 10)) - ) - .replace( - /\[emoji(?::(\d+))?\]/ig, - (all, length) => encodeStringToEmoji(content, length) - ); - } - url = url - .replace(/\[ext\]/ig, () => ext) - .replace(/\[name\]/ig, () => basename) - .replace(/\[path\]/ig, () => directory) - .replace(/\[folder\]/ig, () => folder); - if(regExp && loaderContext.resourcePath) { - const match = loaderContext.resourcePath.match(new RegExp(regExp)); - match && match.forEach((matched, i) => { - url = url.replace( - new RegExp("\\[" + i + "\\]", "ig"), - matched - ); - }); - } - if(typeof loaderContext.options === "object" && typeof loaderContext.options.customInterpolateName === "function") { - url = loaderContext.options.customInterpolateName.call(loaderContext, url, name, options); - } - return url; -} - -exports.parseQuery = parseQuery; -exports.getLoaderConfig = getLoaderConfig; -exports.stringifyRequest = stringifyRequest; -exports.getRemainingRequest = getRemainingRequest; -exports.getCurrentRequest = getCurrentRequest; -exports.isUrlRequest = isUrlRequest; -exports.urlToRequest = urlToRequest; -exports.parseString = parseString; -exports.getHashDigest = getHashDigest; -exports.interpolateName = interpolateName; diff --git a/lib/getCurrentRequest.js b/lib/getCurrentRequest.js new file mode 100644 index 0000000..f05e112 --- /dev/null +++ b/lib/getCurrentRequest.js @@ -0,0 +1,13 @@ +"use strict"; + +function getCurrentRequest(loaderContext) { + if(loaderContext.currentRequest) + return loaderContext.currentRequest; + const request = loaderContext.loaders + .slice(loaderContext.loaderIndex) + .map(obj => obj.request) + .concat([loaderContext.resource]); + return request.join("!"); +} + +module.exports = getCurrentRequest; diff --git a/lib/getHashDigest.js b/lib/getHashDigest.js new file mode 100644 index 0000000..6f4a427 --- /dev/null +++ b/lib/getHashDigest.js @@ -0,0 +1,53 @@ +"use strict"; + +const baseEncodeTables = { + 26: "abcdefghijklmnopqrstuvwxyz", + 32: "123456789abcdefghjkmnpqrstuvwxyz", // no 0lio + 36: "0123456789abcdefghijklmnopqrstuvwxyz", + 49: "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ", // no lIO + 52: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", + 58: "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ", // no 0lIO + 62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", + 64: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_" +}; + +function encodeBufferToBase(buffer, base) { + const encodeTable = baseEncodeTables[base]; + if(!encodeTable) throw new Error("Unknown encoding base" + base); + + const readLength = buffer.length; + + const Big = require("big.js"); + Big.RM = Big.DP = 0; + let b = new Big(0); + for(let i = readLength - 1; i >= 0; i--) { + b = b.times(256).plus(buffer[i]); + } + + let output = ""; + while(b.gt(0)) { + output = encodeTable[b.mod(base)] + output; + b = b.div(base); + } + + Big.DP = 20; + Big.RM = 1; + + return output; +} + +function getHashDigest(buffer, hashType, digestType, maxLength) { + hashType = hashType || "md5"; + maxLength = maxLength || 9999; + const hash = require("crypto").createHash(hashType); + hash.update(buffer); + if(digestType === "base26" || digestType === "base32" || digestType === "base36" || + digestType === "base49" || digestType === "base52" || digestType === "base58" || + digestType === "base62" || digestType === "base64") { + return encodeBufferToBase(hash.digest(), digestType.substr(4)).substr(0, maxLength); + } else { + return hash.digest(digestType || "hex").substr(0, maxLength); + } +} + +module.exports = getHashDigest; diff --git a/lib/getLoaderConfig.js b/lib/getLoaderConfig.js new file mode 100644 index 0000000..bc70cee --- /dev/null +++ b/lib/getLoaderConfig.js @@ -0,0 +1,17 @@ +"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/getRemainingRequest.js b/lib/getRemainingRequest.js new file mode 100644 index 0000000..f48c71a --- /dev/null +++ b/lib/getRemainingRequest.js @@ -0,0 +1,13 @@ +"use strict"; + +function getRemainingRequest(loaderContext) { + if(loaderContext.remainingRequest) + return loaderContext.remainingRequest; + const request = loaderContext.loaders + .slice(loaderContext.loaderIndex + 1) + .map(obj => obj.request) + .concat([loaderContext.resource]); + return request.join("!"); +} + +module.exports = getRemainingRequest; diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..2d0e642 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,23 @@ +"use strict"; + +const parseQuery = require("./parseQuery"); +const getLoaderConfig = require("./getLoaderConfig"); +const stringifyRequest = require("./stringifyRequest"); +const getRemainingRequest = require("./getRemainingRequest"); +const getCurrentRequest = require("./getCurrentRequest"); +const isUrlRequest = require("./isUrlRequest"); +const urlToRequest = require("./urlToRequest"); +const parseString = require("./parseString"); +const getHashDigest = require("./getHashDigest"); +const interpolateName = require("./interpolateName"); + +exports.parseQuery = parseQuery; +exports.getLoaderConfig = getLoaderConfig; +exports.stringifyRequest = stringifyRequest; +exports.getRemainingRequest = getRemainingRequest; +exports.getCurrentRequest = getCurrentRequest; +exports.isUrlRequest = isUrlRequest; +exports.urlToRequest = urlToRequest; +exports.parseString = parseString; +exports.getHashDigest = getHashDigest; +exports.interpolateName = interpolateName; diff --git a/lib/interpolateName.js b/lib/interpolateName.js new file mode 100644 index 0000000..406238a --- /dev/null +++ b/lib/interpolateName.js @@ -0,0 +1,98 @@ +"use strict"; + +const path = require("path"); +const emojisList = require("emojis-list"); +const getHashDigest = require("./getHashDigest"); + +const emojiRegex = /[\uD800-\uDFFF]./; +const emojiList = emojisList.filter(emoji => emojiRegex.test(emoji)); +const emojiCache = {}; + +function encodeStringToEmoji(content, length) { + if(emojiCache[content]) return emojiCache[content]; + length = length || 1; + const emojis = []; + do { + const index = Math.floor(Math.random() * emojiList.length); + emojis.push(emojiList[index]); + emojiList.splice(index, 1); + } while(--length > 0); + const emojiEncoding = emojis.join(""); + emojiCache[content] = emojiEncoding; + return emojiEncoding; +} + +function interpolateName(loaderContext, name, options) { + let filename; + if(typeof name === "function") { + filename = name(loaderContext.resourcePath); + } else { + filename = name || "[hash].[ext]"; + } + const context = options.context; + const content = options.content; + const regExp = options.regExp; + let ext = "bin"; + let basename = "file"; + let directory = ""; + let folder = ""; + if(loaderContext.resourcePath) { + let resourcePath = loaderContext.resourcePath; + const idx = resourcePath.lastIndexOf("."); + const i = resourcePath.lastIndexOf("\\"); + const j = resourcePath.lastIndexOf("/"); + const p = i < 0 ? j : j < 0 ? i : i < j ? i : j; + if(idx >= 0) { + ext = resourcePath.substr(idx + 1); + resourcePath = resourcePath.substr(0, idx); + } + if(p >= 0) { + basename = resourcePath.substr(p + 1); + resourcePath = resourcePath.substr(0, p + 1); + } + if(typeof context !== "undefined") { + directory = path.relative(context, resourcePath + "_").replace(/\\/g, "/").replace(/\.\.(\/)?/g, "_$1"); + directory = directory.substr(0, directory.length - 1); + } else { + directory = resourcePath.replace(/\\/g, "/").replace(/\.\.(\/)?/g, "_$1"); + } + if(directory.length === 1) { + directory = ""; + } else if(directory.length > 1) { + folder = path.basename(directory); + } + } + let url = filename; + if(content) { + // Match hash template + url = url + .replace( + /\[(?:(\w+):)?hash(?::([a-z]+\d*))?(?::(\d+))?\]/ig, + (all, hashType, digestType, maxLength) => getHashDigest(content, hashType, digestType, parseInt(maxLength, 10)) + ) + .replace( + /\[emoji(?::(\d+))?\]/ig, + (all, length) => encodeStringToEmoji(content, length) + ); + } + url = url + .replace(/\[ext\]/ig, () => ext) + .replace(/\[name\]/ig, () => basename) + .replace(/\[path\]/ig, () => directory) + .replace(/\[folder\]/ig, () => folder); + if(regExp && loaderContext.resourcePath) { + const match = loaderContext.resourcePath.match(new RegExp(regExp)); + match && match.forEach((matched, i) => { + url = url.replace( + new RegExp("\\[" + i + "\\]", "ig"), + matched + ); + }); + } + if(typeof loaderContext.options === "object" && typeof loaderContext.options.customInterpolateName === "function") { + url = loaderContext.options.customInterpolateName.call(loaderContext, url, name, options); + } + return url; +} + +module.exports = interpolateName; diff --git a/lib/isUrlRequest.js b/lib/isUrlRequest.js new file mode 100644 index 0000000..bee968e --- /dev/null +++ b/lib/isUrlRequest.js @@ -0,0 +1,14 @@ +"use strict"; + +function isUrlRequest(url, root) { + // An URL is not an request if + // 1. it's a Data Url + // 2. it's an absolute url or and protocol-relative + // 3. it's some kind of url for a template + if(/^data:|^chrome-extension:|^(https?:)?\/\/|^[\{\}\[\]#*;,'§\$%&\(=?`´\^°<>]/.test(url)) return false; + // 4. It's also not an request if root isn't set and it's a root-relative url + if((root === undefined || root === false) && /^\//.test(url)) return false; + return true; +} + +module.exports = isUrlRequest; diff --git a/lib/parseQuery.js b/lib/parseQuery.js new file mode 100644 index 0000000..20e2bd2 --- /dev/null +++ b/lib/parseQuery.js @@ -0,0 +1,62 @@ +"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, + "false": false +}; + +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 '?'"); + query = query.substr(1); + if(query.substr(0, 1) === "{" && query.substr(-1) === "}") { + return JSON5.parse(query); + } + const queryArgs = query.split(/[,\&]/g); + const result = {}; + queryArgs.forEach(arg => { + const idx = arg.indexOf("="); + if(idx >= 0) { + let name = arg.substr(0, idx); + let value = decodeURIComponent(arg.substr(idx + 1)); + if(specialValues.hasOwnProperty(value)) { + value = specialValues[value]; + } + if(name.substr(-2) === "[]") { + name = decodeURIComponent(name.substr(0, name.length - 2)); + if(!Array.isArray(result[name])) + result[name] = []; + result[name].push(value); + } else { + name = decodeURIComponent(name); + result[name] = value; + } + } else { + if(arg.substr(0, 1) === "-") { + result[decodeURIComponent(arg.substr(1))] = false; + } else if(arg.substr(0, 1) === "+") { + result[decodeURIComponent(arg.substr(1))] = true; + } else { + result[decodeURIComponent(arg)] = true; + } + } + }); + return result; +} + +module.exports = parseQuery; diff --git a/lib/parseString.js b/lib/parseString.js new file mode 100644 index 0000000..ca69dfd --- /dev/null +++ b/lib/parseString.js @@ -0,0 +1,19 @@ +"use strict"; + +function parseString(str) { + try { + if(str[0] === "\"") return JSON.parse(str); + if(str[0] === "'" && str.substr(str.length - 1) === "'") { + return parseString( + str + .replace(/\\.|"/g, x => x === "\"" ? "\\\"" : x) + .replace(/^'|'$/g, "\"") + ); + } + return JSON.parse("\"" + str + "\""); + } catch(e) { + return str; + } +} + +module.exports = parseString; diff --git a/lib/stringifyRequest.js b/lib/stringifyRequest.js new file mode 100644 index 0000000..46e9ae2 --- /dev/null +++ b/lib/stringifyRequest.js @@ -0,0 +1,40 @@ +"use strict"; + +const path = require("path"); + +const matchRelativePath = /^\.\.?[/\\]/; + +function isAbsolutePath(str) { + return path.posix.isAbsolute(str) || path.win32.isAbsolute(str); +} + +function isRelativePath(str) { + return matchRelativePath.test(str); +} + +function stringifyRequest(loaderContext, request) { + const splitted = request.split("!"); + const context = loaderContext.context || (loaderContext.options && loaderContext.options.context); + return JSON.stringify(splitted.map(part => { + // First, separate singlePath from query, because the query might contain paths again + const splittedPart = part.match(/^(.*?)(\?.*)/); + let singlePath = splittedPart ? splittedPart[1] : part; + const query = splittedPart ? splittedPart[2] : ""; + if(isAbsolutePath(singlePath) && context) { + singlePath = path.relative(context, singlePath); + if(isAbsolutePath(singlePath)) { + // If singlePath still matches an absolute path, singlePath was on a different drive than context. + // In this case, we leave the path platform-specific without replacing any separators. + // @see https://github.com/webpack/loader-utils/pull/14 + return singlePath + query; + } + if(isRelativePath(singlePath) === false) { + // Ensure that the relative path starts at least with ./ otherwise it would be a request into the modules directory (like node_modules). + singlePath = "./" + singlePath; + } + } + return singlePath.replace(/\\/g, "/") + query; + }).join("!")); +} + +module.exports = stringifyRequest; diff --git a/lib/urlToRequest.js b/lib/urlToRequest.js new file mode 100644 index 0000000..06c3cd0 --- /dev/null +++ b/lib/urlToRequest.js @@ -0,0 +1,49 @@ +"use strict"; + +// we can't use path.win32.isAbsolute because it also matches paths starting with a forward slash +const matchNativeWin32Path = /^[A-Z]:[/\\]|^\\\\/i; + +function urlToRequest(url, root) { + const moduleRequestRegex = /^[^?]*~/; + let request; + + if(matchNativeWin32Path.test(url)) { + // absolute windows path, keep it + request = url; + } else if(root !== undefined && root !== false && /^\//.test(url)) { + // if root is set and the url is root-relative + switch(typeof root) { + // 1. root is a string: root is prefixed to the url + case "string": + // special case: `~` roots convert to module request + if(moduleRequestRegex.test(root)) { + request = root.replace(/([^~\/])$/, "$1/") + url.slice(1); + } else { + request = root + url; + } + break; + // 2. root is `true`: absolute paths are allowed + // *nix only, windows-style absolute paths are always allowed as they doesn't start with a `/` + case "boolean": + request = url; + break; + default: + throw new Error("Unexpected parameters to loader-utils 'urlToRequest': url = " + url + ", root = " + root + "."); + } + } else if(/^\.\.?\//.test(url)) { + // A relative url stays + request = url; + } else { + // every other url is threaded like a relative url + request = "./" + url; + } + + // A `~` makes the url an module + if(moduleRequestRegex.test(request)) { + request = request.replace(moduleRequestRegex, ""); + } + + return request; +} + +module.exports = urlToRequest; diff --git a/package.json b/package.json index 5941e71..7301665 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "istanbul": "^0.3.14", "mocha": "^1.21.4" }, + "main": "lib/index.js", "files": [ - "index.js" + "lib" ] }