Socket
Socket
Sign inDemoInstall

resolve-url-loader

Package Overview
Dependencies
20
Maintainers
1
Versions
51
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 4.0.0-alpha.3 to 4.0.0-alpha.4

lib/join-function/fs-utils.js

33

index.js

@@ -15,5 +15,5 @@ /*

var valueProcessor = require('./lib/value-processor');
var joinFn = require('./lib/join-function');
var logToTestHarness = require('./lib/log-to-test-harness');
var valueProcessor = require('./lib/value-processor'),
joinFn = require('./lib/join-function'),
logToTestHarness = require('./lib/log-to-test-harness');

@@ -78,3 +78,2 @@ const DEPRECATED_OPTIONS = {

{
fs : loader.fs,
sourceMap: loader.sourceMap,

@@ -111,9 +110,23 @@ engine : 'postcss',

);
} else if (options.join.length !== 1) {
} else if (options.join.length !== 2) {
return handleAsError(
'loader misconfiguration',
'"join" Function must take exactly 1 arguments (options hash)'
'"join" Function must take exactly 2 arguments (options, loader)'
);
}
// validate the result of calling the join option
var joinProper = options.join(options, loader);
if (typeof joinProper !== 'function') {
return handleAsError(
'loader misconfiguration',
'"join" option must itself return a Function when it is called'
);
} else if (joinProper.length !== 1) {
return handleAsError(
'loader misconfiguration',
'"join" Function must create a function that takes exactly 1 arguments (item)'
);
}
// validate root option

@@ -204,6 +217,10 @@ if (typeof options.root === 'string') {

outputSourceMap : !!options.sourceMap,
transformDeclaration: valueProcessor(loader.resourcePath, options),
absSourceMap : absSourceMap,
sourceMapConsumer : sourceMapConsumer,
removeCR : options.removeCR
removeCR : options.removeCR,
transformDeclaration: valueProcessor({
join : joinProper,
root : options.root,
directory: path.dirname(loader.resourcePath)
})
}))

@@ -210,0 +227,0 @@ .catch(onFailure)

@@ -149,3 +149,3 @@ /*

if (error.message === 'Cannot find module \'' + moduleName + '\'') {
throw new Error('To use the "rework" engine you must install the optionalPeerDependencies');
throw new Error('to use the "rework" engine you must install the optionalPeerDependencies');
}

@@ -152,0 +152,0 @@ else {

@@ -7,39 +7,52 @@ /*

var path = require('path');
const path = require('path');
var PACKAGE_NAME = require('../../package.json').name;
const PACKAGE_NAME = require('../../package.json').name;
/**
* Paths are formatted to have posix style path separators and those within the CWD are made relative to CWD.
*
* @param {string} absolutePath An absolute path to format
* @returns {string} the formatted path
*/
const pathToString = (absolutePath) => {
if (absolutePath === '') {
return '-empty-';
} else {
const relative = path.relative(process.cwd(), absolutePath).split(path.sep);
const segments =
(relative[0] !== '..') ? ['.'].concat(relative).filter(Boolean) :
(relative.lastIndexOf('..') < 2) ? relative :
absolutePath.split(path.sep);
return segments.join('/');
}
};
exports.pathToString = pathToString;
/**
* Format a debug message.
*
* @param {string} file The file being processed by webpack
* @param {string} filename The file being processed by webpack
* @param {string} uri A uri path, relative or absolute
* @param {Array<string>} bases Absolute base paths up to and including the found one
* @param {boolean} isFound Indicates the last base was a positive match
* @param {Array<{base:string,joined:string,isSuccess:boolean}>} attempts An array of attempts, possibly empty
* @return {string} Formatted message
*/
function formatJoinMessage(file, uri, bases, isFound) {
return [PACKAGE_NAME + ': ' + pathToString(file) + ': ' + uri]
.concat(bases.map(pathToString))
.concat(isFound ? 'FOUND' : 'NOT FOUND')
.join('\n ');
const formatJoinMessage = (filename, uri, attempts) => {
const attemptToCells = (_, i, array) => {
const { base: prev } = (i === 0) ? {} : array[i-1];
const { base: curr, joined } = array[i];
return [(curr === prev) ? '' : pathToString(curr), pathToString(joined)];
};
/**
* If given path is within `process.cwd()` then show relative posix path, otherwise show absolute posix path.
*
* @param {string} absolute An absolute path
* @return {string} A relative or absolute path
*/
function pathToString(absolute) {
if (!absolute) {
return '-empty-';
} else {
var relative = path.relative(process.cwd(), absolute)
.split(path.sep);
const formatCells = (lines) => {
const maxWidth = lines.reduce((max, [cellA]) => Math.max(max, cellA.length), 0);
return lines.map(([cellA, cellB]) => [cellA.padEnd(maxWidth), cellB]).map((cells) => cells.join(' --> '));
};
return ((relative[0] === '..') ? absolute.split(path.sep) : ['.'].concat(relative).filter(Boolean))
.join('/');
}
}
}
return [PACKAGE_NAME + ': ' + pathToString(filename) + ': ' + uri]
.concat(attempts.length === 0 ? '-empty-' : formatCells(attempts.map(attemptToCells)))
.concat(attempts.some(({ isSuccess }) => isSuccess) ? 'FOUND' : 'NOT FOUND')
.join('\n ');
};

@@ -60,18 +73,16 @@ exports.formatJoinMessage = formatJoinMessage;

*/
function createDebugLogger(debug) {
var log = !!debug && ((typeof debug === 'function') ? debug : console.log),
cache = {};
return log ? actuallyLog : noop;
const createDebugLogger = (debug) => {
const log = !!debug && ((typeof debug === 'function') ? debug : console.log);
const cache = {};
return log ?
((msgFn, params) => {
const key = Function.prototype.toString.call(msgFn) + JSON.stringify(params);
if (!cache[key]) {
cache[key] = true;
log(msgFn.apply(null, params));
}
}) :
(() => undefined);
};
function noop() {}
function actuallyLog(msgFn, params) {
var key = Function.prototype.toString.call(msgFn) + JSON.stringify(params);
if (!cache[key]) {
cache[key] = true;
log(msgFn.apply(null, params));
}
}
}
exports.createDebugLogger = createDebugLogger;

@@ -7,206 +7,230 @@ /*

var path = require('path');
const path = require('path');
var sanitiseIterable = require('./sanitise-iterable'),
debug = require('./debug');
const { createDebugLogger, formatJoinMessage } = require('./debug');
const fsUtils = require('./fs-utils');
/**
* Generated name from "flower" series
* @see https://gf.dev/sprintname
*/
var CURRENT_SCHEME = require('../../package.json').scheme;
const ITERATION_SAFETY_LIMIT = 100e3;
/**
* Webpack `fs` from `enhanced-resolve` doesn't support `existsSync()` so we shim using `statsSync()`.
* Wrap a function such that it always returns a generator of tuple elements.
*
* @param {{statSync:function(string):{isFile:function():boolean}}} webpackFs The webpack `fs` from `loader.fs`.
* @param {string} absolutePath Absolute path to the file in question
* @returns {boolean} True where file exists, else False
* @param {function({uri:string},...):(Array|Iterator)<[string,string]|string>} fn The function to wrap
* @returns {function({uri:string},...):(Array|Iterator)<[string,string]>} A function that always returns tuple elements
*/
function webpackExistsSync(webpackFs, absolutePath) {
try {
return webpackFs.statSync(absolutePath).isFile();
} catch (e) {
return false;
}
}
const asGenerator = (fn) => {
const toTuple = (defaults) => (value) => {
const partial = [].concat(value);
return [...partial, ...defaults.slice(partial.length)];
};
exports.webpackExistsSync = webpackExistsSync;
const isTupleUnique = (v, i, a) => {
const required = v.join(',');
return a.findIndex((vv) => vv.join(',') === required) === i;
};
/**
* The default iterable factory will order `subString` then `value` then `property` then `selector`.
*
* @param {string} filename The absolute path of the file being processed
* @param {string} uri The uri given in the file webpack is processing
* @param {boolean} isAbsolute True for absolute URIs, false for relative URIs
* @param {{subString:string, value:string, property:string, selector:string}} bases A hash of possible base paths
* @param {{fs:Object, root:string, debug:boolean|function}} options The loader options including webpack file system
* @returns {Array<string>} An iterable of possible base paths in preference order
*/
function defaultJoinGenerator(filename, uri, isAbsolute, bases, options) {
return isAbsolute ? [options.root] : [bases.subString, bases.value, bases.property, bases.selector];
}
return (item, ...rest) => {
const {uri} = item;
const mapTuple = toTuple([null, uri]);
const pending = fn(item, ...rest);
if (Array.isArray(pending)) {
return pending.map(mapTuple).filter(isTupleUnique)[Symbol.iterator]();
} else if (
pending &&
(typeof pending === 'object') &&
(typeof pending.next === 'function') &&
(pending.next.length === 0)
) {
return pending;
} else {
throw new TypeError(`in "join" function expected "generator" to return Array|Iterator`);
}
};
};
exports.defaultJoinGenerator = defaultJoinGenerator;
exports.asGenerator = asGenerator;
/**
* The default operation simply joins the given base to the uri and returns it where it exists.
* A high-level utility to create a join function.
*
* The result of `next()` represents the eventual result and needs to be returned otherwise.
* The `generator` is responsible for ordering possible base paths. The `operation` is responsible for joining a single
* `base` path with the given `uri`. The `predicate` is responsible for reporting whether the single joined value is
* successful as the overall result.
*
* If none of the expected files exist then any given `fallback` argument to `next()` is used even if it does not exist.
* Both the `generator` and `operation` may be `function*()` or simply `function(...):Array<string>`.
*
* @param {string} filename The absolute path of the file being processed
* @param {string} uri The uri given in the file webpack is processing
* @param {string} base A value from the iterator currently being processed
* @param {function(?string):<string|Array<string>>} next Optionally set fallback then recurse next iteration
* @param {{fs:Object, root:string, debug:boolean|function}} options The loader options including webpack file system
* @returns {string|Array<string>} Result from the last iteration that occurred
* @param {function({uri:string, isAbsolute:boolean, bases:{subString:string, value:string, property:string,
* selector:string}}, {filename:string, fs:Object, debug:function|boolean, root:string}):
* (Array<string>|Iterator<string>)} generator A function that takes the hash of base paths from the `engine` and
* returns ordered iterable of paths to consider
* @returns {function({filename:string, fs:Object, debug:function|boolean, root:string}):
* (function({uri:string, isAbsolute:boolean, bases:{subString:string, value:string, property:string,
* selector:string}}):string)} join implementation
*/
function defaultJoinOperation(filename, uri, base, next, options) {
var absolute = path.normalize(path.join(base, uri)),
isSuccess = webpackExistsSync(options.fs, absolute) && options.fs.statSync(absolute).isFile();
return isSuccess ? absolute : next(absolute);
}
const createJoinImplementation = (generator) => (item, options, loader) => {
const { isAbsolute } = item;
const { root } = options;
const { fs } = loader;
exports.defaultJoinOperation = defaultJoinOperation;
// generate the iterator
const iterator = generator(item, options, loader);
const isValidIterator = iterator && typeof iterator === 'object' && typeof iterator.next === 'function';
if (!isValidIterator) {
throw new Error('expected generator to return Iterator');
}
// run the iterator lazily and record attempts
const { isFileSync, isDirectorySync } = fsUtils(fs);
const attempts = [];
for (let i = 0; i < ITERATION_SAFETY_LIMIT; i++) {
const { value, done } = iterator.next();
if (done) {
break;
} else if (value) {
const tuple = Array.isArray(value) && value.length === 2 ? value : null;
if (!tuple) {
throw new Error('expected Iterator values to be tuple of [string,string], do you need asGenerator utility?');
}
// skip elements where base or uri is non-string
// noting that we need to support base="" when root=""
const [base, uri] = value;
if ((typeof base === 'string') && (typeof uri === 'string')) {
// validate
const isValidBase = (isAbsolute && base === root) || (path.isAbsolute(base) && isDirectorySync(base));
if (!isValidBase) {
throw new Error(`expected "base" to be absolute path to a valid directory, got "${base}"`);
}
// make the attempt
const joined = path.normalize(path.join(base, uri));
const isFallback = true;
const isSuccess = isFileSync(joined);
attempts.push({base, uri, joined, isFallback, isSuccess});
if (isSuccess) {
break;
}
// validate any non-strings are falsey
} else {
const isValidTuple = value.every((v) => (typeof v === 'string') || !v);
if (!isValidTuple) {
throw new Error('expected Iterator values to be tuple of [string,string]');
}
}
}
}
return attempts;
};
exports.createJoinImplementation = createJoinImplementation;
/**
* The default join function iterates over possible base paths until a suitable join is found.
* A low-level utility to create a join function.
*
* The first base path is used as fallback for the case where none of the base paths can locate the actual file.
* The `implementation` function processes an individual `item` and returns an Array of attempts. Each attempt consists
* of a `base` and a `joined` value with `isSuccessful` and `isFallback` flags.
*
* @type {function}
*/
exports.defaultJoin = createJoinFunction({
name : 'defaultJoin',
scheme : CURRENT_SCHEME,
generator: defaultJoinGenerator,
operation: defaultJoinOperation
});
/**
* A utility to create a join function.
* In the case that any attempt `isSuccessful` then its `joined` value is the outcome. Otherwise the first `isFallback`
* attempt is used. If there is no successful or fallback attempts then `null` is returned indicating no change to the
* original URI in the CSS.
*
* Refer to implementation of `defaultJoinGenerator` and `defaultJoinOperation`.
* The `attempts` Array is logged to console when in `debug` mode.
*
* @param {string} name Name for the resulting join function
* @param {string} scheme A keyword that confirms your implementation matches the current scheme.
* @param {function(string, {subString:string, value:string, property:string, selector:string}, Object):
* (Array<string>|Iterator<string>)} generator A function that takes the hash of base paths from the `engine` and
* returns ordered iterable of paths to consider
* @param {function({filename:string, uri:string, base:string}, function(?string):<string|Array<string>>,
* {fs:Object, root:string}):(string|Array<string>)} operation A function that tests values and returns joined paths
* @returns {function(string, {fs:Object, debug:function|boolean, root:string}):
* (function(string, {subString:string, value:string, property:string, selector:string}):string)} join function factory
* @param {function({uri:string, query:string, isAbsolute:boolean, bases:{subString:string, value:string,
* property:string, selector:string}}, {filename:string, fs:Object, debug:function|boolean, root:string}):
* Array<{base:string,joined:string,fallback?:string,result?:string}>} implementation A function accepts an item and
* returns a list of attempts
* @returns {function({filename:string, fs:Object, debug:function|boolean, root:string}):
* (function({uri:string, isAbsolute:boolean, bases:{subString:string, value:string, property:string,
* selector:string}}):string)} join function
*/
function createJoinFunction({ name, scheme, generator, operation }) {
if (typeof scheme !== 'string' || scheme.toLowerCase() !== CURRENT_SCHEME) {
throw new Error(`Custom join function has changed, please update to the latest scheme. Refer to the docs.`);
}
const createJoinFunction = (name, implementation) => {
const assertAttempts = (value) => {
const isValid =
Array.isArray(value) && value.every((v) =>
v &&
(typeof v === 'object') &&
(typeof v.base === 'string') &&
(typeof v.uri === 'string') &&
(typeof v.joined === 'string') &&
(typeof v.isSuccess === 'boolean') &&
(typeof v.isFallback === 'boolean')
);
if (!isValid) {
throw new Error(`expected implementation to return Array of {base, uri, joined, isSuccess, isFallback}`);
} else {
return value;
}
};
/**
* A factory for a join function with logging.
*
* Options are curried and a join function proper is returned.
*
* @param {{fs:Object, root:string, debug:boolean|function}} options The loader options including webpack file system
*/
function join(options) {
var log = debug.createDebugLogger(options.debug);
const assertJoined = (value) => {
const isValid = value && (typeof value === 'string') && path.isAbsolute(value) || (value === null);
if (!isValid) {
throw new Error(`expected "joined" to be absolute path, got "${value}"`);
} else {
return value;
}
};
/**
* Join function proper.
*
* For absolute uri only `uri` will be provided and no `bases`.
*
* @param {string} filename The current file being processed
* @param {string} uri A uri path, relative or absolute
* @param {boolean} isAbsolute True for absolute URIs, false for relative URIs
* @param {{subString:string, value:string, property:string, selector:string}} bases Hash of possible base paths
* @return {string} Just the uri where base is empty or the uri appended to the base
*/
return function joinProper(filename, uri, isAbsolute, bases) {
var iterator = sanitiseIterable(generator(filename, uri, isAbsolute, bases, options)),
result = reduceIterator({inputs:[], outputs:[], isFound:false}, iterator),
lastOutput = result.outputs[result.outputs.length-1],
fallback = result.outputs.find(Boolean) || uri;
const join = (options, loader) => {
const { debug } = options;
const { resourcePath } = loader;
const log = createDebugLogger(debug);
log(debug.formatJoinMessage, [filename, uri, result.inputs, result.isFound]);
return (item) => {
const { uri } = item;
const attempts = implementation(item, options, loader);
assertAttempts(attempts, !!debug);
return result.isFound ? lastOutput : fallback;
const { joined: fallback } = attempts.find(({ isFallback }) => isFallback) || {};
const { joined: result } = attempts.find(({ isSuccess }) => isSuccess) || {};
/**
* Run the next iterator value.
*
* @param {Array<string>} accumulator Current result
* @returns {Array<string>} Updated result
*/
function reduceIterator(accumulator) {
var inputs = accumulator.inputs || [],
outputs = accumulator.outputs || [],
nextItem = iterator.next();
log(formatJoinMessage, [resourcePath, uri, attempts]);
if (nextItem.done) {
return accumulator;
} else {
var base = assertAbsolute(nextItem.value, 'expected Iterator<string> of absolute base path', ''),
pending = operation(filename, uri, base, next, options);
if (!!pending && typeof pending === 'object') {
return pending;
} else {
assertAbsolute(pending, 'operation must return an absolute path or the result of calling next()');
return {
inputs : inputs.concat(base),
outputs: outputs.concat(pending),
isFound: true
};
}
}
/**
* Provide a possible fallback but run the next iteration either way.
*
* @param {string} fallback? Optional absolute path as fallback value
* @returns {Array<string>} Nested result
*/
function next(fallback) {
assertAbsolute(fallback, 'next() expects absolute path string or no argument', null, undefined);
return reduceIterator({
inputs : inputs.concat(base),
outputs: outputs.concat(fallback || []),
isFound: false
});
}
/**
* Assert that the given value is an absolute path or some other accepted literal.
*
* @param {*} candidate Possible result
* @param {string} message Error message
* @param {...*} alsoAcceptable? Any number of simple values that are also acceptable
* @throws An error with the given message where the candidate fails the assertion
*/
function assertAbsolute(candidate, message, ...alsoAcceptable) {
var isValid = (alsoAcceptable.indexOf(candidate) >= 0) ||
(typeof candidate === 'string') && path.isAbsolute(candidate);
if (!isValid) {
throw new Error(message);
}
return candidate;
}
}
return assertJoined(result || fallback || null);
};
}
};
function toString() {
return '[Function ' + name + ']';
}
const toString = () => '[Function ' + name + ']';
return Object.assign(join, !!name && {
toString: toString,
toJSON : toString
toString,
toJSON: toString
});
}
};
exports.createJoinFunction = createJoinFunction;
/**
* The default iterable factory will order `subString` then `value` then `property` then `selector`.
*
* @param {string} uri The uri given in the file webpack is processing
* @param {boolean} isAbsolute True for absolute URIs, false for relative URIs
* @param {string} subString A possible base path
* @param {string} value A possible base path
* @param {string} property A possible base path
* @param {string} selector A possible base path
* @param {string} root The loader options.root value where given
* @returns {Array<string>} An iterable of possible base paths in preference order
*/
const defaultJoinGenerator = asGenerator(
({ uri, isAbsolute, bases: { subString, value, property, selector } }, { root }) =>
isAbsolute ? [root] : [subString, value, property, selector]
);
exports.defaultJoinGenerator = defaultJoinGenerator;
/**
* @type {function({filename:string, fs:Object, debug:function|boolean, root:string}):
* (function({uri:string, isAbsolute:boolean, bases:{subString:string, value:string, property:string,
* selector:string}}):string)} join function
*/
exports.defaultJoin = createJoinFunction(
'defaultJoin',
createJoinImplementation(defaultJoinGenerator)
);

@@ -13,13 +13,11 @@ /*

*
* @param {string} filename The current file being processed
* @param {{fs:Object, debug:function|boolean, join:function, root:string}} options Options hash
* @param {function(Object):string} join The inner join function
* @param {string} root The loader options.root value where given
* @param {string} directory The directory of the file webpack is currently processing
* @return {function} value processing function
*/
function valueProcessor(filename, options) {
function valueProcessor({ join, root, directory }) {
var URL_STATEMENT_REGEX = /(url\s*\(\s*)(?:(['"])((?:(?!\2).)*)(\2)|([^'"](?:(?!\)).)*[^'"]))(\s*\))/g,
QUERY_REGEX = /([?#])/g;
var directory = path.dirname(filename),
joinProper = options.join(options);
/**

@@ -87,23 +85,25 @@ * Process the given CSS declaration value.

// split into uri and query/hash and then find the absolute path to the uri
// construct iterator as late as possible in case sourcemap is invalid at this location
var split = unescaped.split(QUERY_REGEX),
uri = split[0],
query = split.slice(1).join(''),
absolute = testIsRelative(uri) && joinProper(filename, uri, false, getPathsAtChar(position)) ||
testIsAbsolute(uri) && joinProper(filename, uri, true, getPathsAtChar(position));
// split into uri and query/hash and then determine if the uri is some type of file
var split = unescaped.split(QUERY_REGEX),
uri = split[0],
query = split.slice(1).join(''),
isRelative = testIsRelative(uri),
isAbsolute = testIsAbsolute(uri);
// not all URIs are files
if (!absolute) {
return element;
} else {
return loaderUtils.urlToRequest(
path.relative(directory, absolute).replace(/\\/g, '/') + query // #6 - backslashes are not legal in URI
);
// file like URIs are processed but not all URIs are files
if (isRelative || isAbsolute) {
var bases = getPathsAtChar(position), // construct iterator as late as possible in case sourcemap invalid
absolute = join({ uri, query, isAbsolute, bases });
if (typeof absolute === 'string') {
var relative = path.relative(directory, absolute)
.replace(/\\/g, '/'); // #6 - backslashes are not legal in URI
return loaderUtils.urlToRequest(relative + query);
}
}
}
// everything else, including parentheses and quotation (where present) and media statements
else {
return element;
}
return element;
}

@@ -133,3 +133,3 @@ };

function testIsAbsolute(uri) {
return !!uri && (typeof options.root === 'string') && loaderUtils.isUrlRequest(uri, options.root) &&
return !!uri && (typeof root === 'string') && loaderUtils.isUrlRequest(uri, root) &&
(/^\//.test(uri) || path.isAbsolute(uri));

@@ -136,0 +136,0 @@ }

{
"name": "resolve-url-loader",
"version": "4.0.0-alpha.3",
"version": "4.0.0-alpha.4",
"description": "Webpack loader that resolves relative paths in url() statements based on the original source file",

@@ -55,4 +55,3 @@ "main": "index.js",

}
},
"scheme": "alstroemeria"
}
}

@@ -62,2 +62,4 @@ # Resolve URL Loader

> **Upgrading?** the [changelog](CHANGELOG.md) shows how to migrate your webpack config.
### Install

@@ -64,0 +66,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc