Skip to content

Commit 6d87b25

Browse files
fatfisztleunen
authored andcommitted
feat: Add regular expression support (#132)
Closes #88. Closes #127.
1 parent 4325ad5 commit 6d87b25

4 files changed

Lines changed: 143 additions & 0 deletions

File tree

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,28 @@ Specify the plugin in your `.babelrc` with the custom root or alias. Here's an e
5353
- `cwd`: By default, the working directory is the one used for the resolver, but you can override it for your project.
5454
- The custom value `babelrc` will make the plugin look for the closest babelrc configuration based on the file to parse.
5555

56+
### Regular expression alias
57+
58+
It is possible to specify an alias using a regular expression. To do that, either start an alias with `'^'` or end it with `'$'`:
59+
60+
```json
61+
{
62+
"plugins": [
63+
["module-resolver", {
64+
"alias": {
65+
"^@namespace/foo-(.+)": "packages/\\1"
66+
}
67+
}]
68+
]
69+
}
70+
```
71+
72+
Using the config from this example `'@namespace/foo-bar'` will become `'packages/bar'`.
73+
74+
You can reference the n-th matched group with `'\\n'` (`'\\0'` refers to the whole matched path).
75+
76+
To use the backslash character (`\`) just escape it like so: `'\\\\'` (double escape is needed because of JSON already using `\` for escaping).
77+
5678
### Updating from babel-plugin-module-alias
5779

5880
babel-plugin-module-resolver is a new version of the old babel-plugin-module-alias. Therefore, you also need to make a few modifications to your plugin configuration to make it work with this new plugin.

src/getRealPath.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,23 @@ function getRealPathFromAliasConfig(sourcePath, absCurrentFile, alias, cwd) {
7070
return toLocalPath(toPosixPath(mapToRelative(cwd, absCurrentFile, newPath)));
7171
}
7272

73+
function getRealPathFromRegExpConfig(sourcePath, regExps) {
74+
let aliasedSourceFile;
75+
76+
regExps.find(([regExp, substitute]) => {
77+
const execResult = regExp.exec(sourcePath);
78+
79+
if (execResult === null) {
80+
return false;
81+
}
82+
83+
aliasedSourceFile = substitute(execResult);
84+
return true;
85+
});
86+
87+
return aliasedSourceFile;
88+
}
89+
7390
export default function getRealPath(sourcePath, currentFile, opts) {
7491
if (sourcePath[0] === '.') {
7592
return sourcePath;
@@ -81,6 +98,7 @@ export default function getRealPath(sourcePath, currentFile, opts) {
8198

8299
const { cwd, extensions, pluginOpts } = opts;
83100
const rootDirs = pluginOpts.root || [];
101+
const regExps = pluginOpts.regExps;
84102
const alias = pluginOpts.alias || {};
85103

86104
const sourceFileFromRoot = getRealPathFromRootConfig(
@@ -97,5 +115,12 @@ export default function getRealPath(sourcePath, currentFile, opts) {
97115
return sourceFileFromAlias;
98116
}
99117

118+
const sourceFileFromRegExp = getRealPathFromRegExpConfig(
119+
sourcePath, regExps,
120+
);
121+
if (sourceFileFromRegExp) {
122+
return sourceFileFromRegExp;
123+
}
124+
100125
return sourcePath;
101126
}

src/index.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ export function mapModule(sourcePath, currentFile, pluginOpts, cwd) {
1919
});
2020
}
2121

22+
function isRegExp(string) {
23+
return string.startsWith('^') || string.endsWith('$');
24+
}
25+
2226
export function manipulatePluginOptions(pluginOpts) {
2327
if (pluginOpts.root) {
2428
// eslint-disable-next-line no-param-reassign
@@ -32,6 +36,30 @@ export function manipulatePluginOptions(pluginOpts) {
3236
}, []);
3337
}
3438

39+
// eslint-disable-next-line no-param-reassign
40+
pluginOpts.regExps = [];
41+
42+
if (pluginOpts.alias) {
43+
Object.keys(pluginOpts.alias)
44+
.filter(isRegExp)
45+
.forEach((key) => {
46+
const parts = pluginOpts.alias[key].split('\\\\');
47+
48+
function substitute(execResult) {
49+
return parts
50+
.map(part =>
51+
part.replace(/\\\d+/g, number => execResult[number.slice(1)] || ''),
52+
)
53+
.join('\\');
54+
}
55+
56+
pluginOpts.regExps.push([new RegExp(key), substitute]);
57+
58+
// eslint-disable-next-line no-param-reassign
59+
delete pluginOpts.alias[key];
60+
});
61+
}
62+
3563
return pluginOpts;
3664
}
3765

test/index.test.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,13 @@ describe('module-resolver', () => {
197197
'awesome/components': './test/testproject/src/components',
198198
abstract: 'npm:concrete',
199199
underscore: 'lodash',
200+
'^@namespace/foo-(.+)': 'packages/\\1',
201+
'styles/.+\\.(css|less|scss)$': 'style-proxy.\\1',
202+
'^single-backslash': 'pas\\\\sed',
203+
'^non-existing-match': 'pas\\42sed',
204+
'^regexp-priority': 'miss',
205+
'regexp-priority$': 'miss',
206+
'regexp-priority': 'hit',
200207
},
201208
}],
202209
],
@@ -287,6 +294,67 @@ describe('module-resolver', () => {
287294
aliasTransformerOpts,
288295
);
289296
});
297+
298+
describe('with a regular expression', () => {
299+
describe('should support replacing parts of a path', () => {
300+
testRequireImport(
301+
'@namespace/foo-bar',
302+
'packages/bar',
303+
aliasTransformerOpts,
304+
);
305+
});
306+
307+
describe('should support replacing parts of a complex path', () => {
308+
testRequireImport(
309+
'@namespace/foo-bar/component.js',
310+
'packages/bar/component.js',
311+
aliasTransformerOpts,
312+
);
313+
});
314+
315+
describe('should support complex regular expressions', () => {
316+
['css', 'less', 'scss'].forEach((extension) => {
317+
testRequireImport(
318+
`styles/style.${extension}`,
319+
`style-proxy.${extension}`,
320+
aliasTransformerOpts,
321+
);
322+
});
323+
});
324+
325+
describe('should ignore unmatched paths', () => {
326+
testRequireImport(
327+
'styles/style.js',
328+
'styles/style.js',
329+
aliasTransformerOpts,
330+
);
331+
});
332+
333+
describe('should transform double backslash into a single one', () => {
334+
testRequireImport(
335+
'single-backslash',
336+
// This is a string literal, so in the code it will actually be "pas\\sed"
337+
'pas\\\\sed',
338+
aliasTransformerOpts,
339+
);
340+
});
341+
342+
describe('should replece missing matches with an empty string', () => {
343+
testRequireImport(
344+
'non-existing-match',
345+
'passed',
346+
aliasTransformerOpts,
347+
);
348+
});
349+
350+
describe('should have higher priority than a simple alias', () => {
351+
testRequireImport(
352+
'regexp-priority',
353+
'hit',
354+
aliasTransformerOpts,
355+
);
356+
});
357+
});
290358
});
291359

292360
describe('with custom cwd', () => {

0 commit comments

Comments
 (0)