From 9299d9a1a9743a823c4625df2dee106c5ecc4882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Ruci=C5=84ski?= Date: Wed, 13 Dec 2017 01:07:36 +0100 Subject: [PATCH] feat: Allow using a function to define the result of an alias (#245) --- DOCS.md | 30 ++++++++++++++++++-- src/normalizeOptions.js | 41 ++++++++++++++++----------- test/index.test.js | 61 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 18 deletions(-) diff --git a/DOCS.md b/DOCS.md index 660b7d0..0416751 100644 --- a/DOCS.md +++ b/DOCS.md @@ -66,6 +66,32 @@ You can reference the n-th matched group with `'\\n'` (`'\\0'` refers to the who To use the backslash character (`\`) just escape it like so: `'\\\\'` (double escape is needed because of JSON already using `\` for escaping). +### Passing a substitute function + +If you need even more power over the aliased path, you can pass a function in the alias configuration: + +```js +module.exports = { + plugins: [ + ["module-resolver", { + alias: { + "foo": ([, name]) => `bar${name}`, + "^@namespace/foo-(.+)": ([, name]) => `packages/${name}` + } + }] + ] +} +``` + +Using the config from this example: +* `'foo'` will become `'bar'` (`name` is empty) +* `'foo/baz'` will become `'bar/baz'` (`name` includes the slash in this case) +* `'@namespace/foo-bar'` will become `'packages/bar'` + +The only argument is the result of calling `RegExp.prototype.exec` on the matched path. It's an array with the matched string and all matched groups. + +Because the function is only called when there is a match, the argument can never be `null`. + ## extensions An array of extensions used in the resolver. @@ -95,7 +121,7 @@ Array of functions and methods that will have their first argument transformed. "plugins": [ ["module-resolver", { "transformFunctions": [ - "require", + "require", "require.resolve", "System.import", "jest.genMockFromModule", @@ -224,4 +250,4 @@ const realPath = resolvePath(sourcePath, currentFile, opts); For each path in the file you can use `resolvePath` to get the same path that module-resolver will output. -`currentFile` can be either a relative path (will be resolved with respect to the CWD, not `opts.cwd`), or an absolute path. \ No newline at end of file +`currentFile` can be either a relative path (will be resolved with respect to the CWD, not `opts.cwd`), or an absolute path. diff --git a/src/normalizeOptions.js b/src/normalizeOptions.js index 5a2ea42..fd76adb 100644 --- a/src/normalizeOptions.js +++ b/src/normalizeOptions.js @@ -78,18 +78,27 @@ function normalizeRoot(optsRoot, cwd) { }, []); } -function getAliasPair(key, value) { - const parts = value.split('\\\\'); +function getAliasTarget(key, isKeyRegExp) { + const regExpPattern = isKeyRegExp ? key : `^${key}(/.*|)$`; + return new RegExp(regExpPattern); +} + +function getAliasSubstitute(value, isKeyRegExp) { + if (typeof value === 'function') { + return value; + } - function substitute(execResult) { - return parts - .map(part => - part.replace(/\\\d+/g, number => execResult[number.slice(1)] || ''), - ) - .join('\\'); + if (!isKeyRegExp) { + return ([, match]) => `${value}${match}`; } - return [new RegExp(key), substitute]; + const parts = value.split('\\\\'); + + return execResult => parts + .map(part => + part.replace(/\\\d+/g, number => execResult[number.slice(1)] || ''), + ) + .join('\\'); } function normalizeAlias(optsAlias) { @@ -99,18 +108,18 @@ function normalizeAlias(optsAlias) { const aliasArray = Array.isArray(optsAlias) ? optsAlias : [optsAlias]; - return aliasArray.reduce((acc, alias) => { + return aliasArray.reduce((aliasPairs, alias) => { const aliasKeys = Object.keys(alias); aliasKeys.forEach((key) => { - const aliasPair = isRegExp(key) - ? getAliasPair(key, alias[key]) - : getAliasPair(`^${key}(/.*|)$`, `${alias[key]}\\1`); - - acc.push(aliasPair); + const isKeyRegExp = isRegExp(key); + aliasPairs.push([ + getAliasTarget(key, isKeyRegExp), + getAliasSubstitute(alias[key], isKeyRegExp), + ]); }); - return acc; + return aliasPairs; }, []); } diff --git a/test/index.test.js b/test/index.test.js index 1b2ff34..7faace4 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -492,6 +492,67 @@ describe('module-resolver', () => { }); }); + describe('with a function', () => { + const mockSubstitute = jest.fn(); + const regExpSubsituteOpts = { + babelrc: false, + plugins: [ + [plugin, { + alias: { + 'basic-function': mockSubstitute, + '^@regexp-function/(.+)': mockSubstitute, + }, + }], + ], + }; + + beforeEach(() => { + mockSubstitute.mockClear(); + }); + + it('should call the substitute with the right arguments (basic)', () => { + mockSubstitute.mockReturnValue('./test/testproject/test'); + + testWithImport( + 'basic-function/something', + './test/testproject/test', + regExpSubsituteOpts, + ); + + expect(mockSubstitute.mock.calls.length).toBe(1); + + const execResult = Object.assign( + ['basic-function/something', '/something'], + { + index: 0, + input: 'basic-function/something', + }, + ); + expect(mockSubstitute).toBeCalledWith(execResult); + }); + + it('should call the substitute with the right arguments (regexp)', () => { + mockSubstitute.mockReturnValue('./test/testproject/test'); + + testWithImport( + '@regexp-function/something', + './test/testproject/test', + regExpSubsituteOpts, + ); + + expect(mockSubstitute.mock.calls.length).toBe(1); + + const execResult = Object.assign( + ['@regexp-function/something', 'something'], + { + index: 0, + input: '@regexp-function/something', + }, + ); + expect(mockSubstitute).toBeCalledWith(execResult); + }); + }); + describe('with the plugin applied twice', () => { const doubleAliasTransformerOpts = { babelrc: false,