Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alias resolution #81

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,27 @@ npm install --save-dev babel-plugin-inline-react-svg
]
]
}
```

- *`root`* - A relative path string (Starting from CWD) that is used for alias resolution.
- *`alias`* - An object describing aliases for module resolution where the key is the alias to be used and the value is a string or array of paths. Example:

```javascript
['babel-plugin-inline-react-svg', {
root: path.resolve(__dirname),
alias: {
images: 'src/images'
icons: ['src/images/icons', 'node_modules/external-module/icons']
}
}],
```

```javascript
// Resolved to <root>/src/images/logo.svg
import MySvg from 'images/logo.svg';

// Checks first for <root>/src/images/icons/cross.svg, then <root>/node_modules/external-module/icons/cross.svg
import AnotherSvg from 'icons/cross.svg';
```

### Via CLI
Expand Down
53 changes: 45 additions & 8 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { extname, dirname, parse as parseFilename } from 'path';
import { readFileSync } from 'fs';
import {
extname,
dirname,
parse as parseFilename,
resolve as resolvePath,
} from 'path';
import { readFileSync, existsSync } from 'fs';
import { parse } from '@babel/parser';
import { declare } from '@babel/helper-plugin-utils';
import resolve from 'resolve';
Expand Down Expand Up @@ -48,7 +53,13 @@ export default declare(({
if (typeof importPath !== 'string') {
throw new TypeError('`applyPlugin` `importPath` must be a string');
}
const { ignorePattern, caseSensitive, filename: providedFilename } = state.opts;
const {
ignorePattern,
caseSensitive,
filename: providedFilename,
root,
alias,
} = state.opts;
const { file, filename } = state;
if (ignorePattern) {
// Only set the ignoreRegex once:
Expand All @@ -61,14 +72,40 @@ export default declare(({
// This plugin only applies for SVGs:
if (extname(importPath) === '.svg') {
const iconPath = filename || providedFilename;
const svgPath = resolve.sync(importPath, { basedir: dirname(iconPath) });
if (caseSensitive && !fileExistsWithCaseSync(svgPath)) {
throw new Error(`File path didn't match case of file on disk: ${svgPath}`);
const aliasPart = importPath.split('/')[0];
const aliasMatch = alias ? alias[aliasPart] : undefined;
Copy link
Collaborator

@ljharb ljharb Feb 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if someone imports from __proto__ or toString ?

const svgPaths = [];
let chosenPath;

if (aliasMatch) {
const resolveRoot = resolvePath(process.cwd(), root || './');
const aliasMatches = typeof aliasMatch === 'string' ? [aliasMatch] : aliasMatch;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const aliasMatches = typeof aliasMatch === 'string' ? [aliasMatch] : aliasMatch;
const aliasMatches = [].concat(aliasMatch);


aliasMatches.forEach((match) => {
const aliasedPath = resolvePath(resolveRoot, match);
svgPaths.push(resolvePath(aliasedPath, importPath.replace(`${aliasPart}/`, '')));
});
} else {
svgPaths.push(resolve.sync(importPath, { basedir: dirname(iconPath) }));
}
if (!svgPath) {

for (let i = 0; i < svgPaths.length; i += 1) {
const svgPath = svgPaths[i];
if (caseSensitive && !fileExistsWithCaseSync(svgPath)) {
throw new Error(`File path didn't match case of file on disk: ${svgPath}`);
}

if (svgPath && existsSync(svgPath)) {
chosenPath = svgPath;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this could use .find ? (or the array.prototype.find package)

break;
}
}

if (!chosenPath) {
throw new Error(`File path does not exist: ${importPath}`);
}
const rawSource = readFileSync(svgPath, 'utf8');

const rawSource = readFileSync(chosenPath, 'utf8');
const optimizedSource = state.opts.svgo === false
? rawSource
: optimize(rawSource, state.opts.svgo);
Expand Down
13 changes: 13 additions & 0 deletions test/fixtures/test-alias-case-sensitive.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';

import MySvg from 'myalias/CLOSE.svg';

export default function MyFunctionIcon() {
return <MySvg />;
}

export class MyClassIcon extends React.Component {
render() {
return <MySvg />;
}
}
24 changes: 24 additions & 0 deletions test/fixtures/test-alias-folders.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';

import MySvg from 'myalias/close.svg';
import Svg2 from 'myalias/close3.svg';

export default function MyFunctionIcon() {
return (
<>
<MySvg />
<Svg2 />
</>
);
}

export class MyClassIcon extends React.Component {
render() {
return (
<>
<MySvg />
<Svg2 />
</>
);
}
}
24 changes: 24 additions & 0 deletions test/fixtures/test-alias-multiple.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';

import MySvg from 'myalias/close.svg';
import Svg2 from 'myalias/close2.svg';

export default function MyFunctionIcon() {
return (
<>
<MySvg />
<Svg2 />
</>
);
}

export class MyClassIcon extends React.Component {
render() {
return (
<>
<MySvg />
<Svg2 />
</>
);
}
}
13 changes: 13 additions & 0 deletions test/fixtures/test-alias.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';

import MySvg from 'myalias/close.svg';

export default function MyFunctionIcon() {
return <MySvg />;
}

export class MyClassIcon extends React.Component {
render() {
return <MySvg />;
}
}
2 changes: 2 additions & 0 deletions test/fixtures/testfolder/close3.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
133 changes: 133 additions & 0 deletions test/sanity.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,136 @@ transformFile('test/fixtures/test-export-default-as.jsx', {
if (err) throw err;
console.log('test/fixtures/test-export-default-as.jsx', result.code);
});

/**
* Alias tests
*/
// Alias without root
transformFile('test/fixtures/test-alias.jsx', {
babelrc: false,
presets: ['@babel/preset-react'],
plugins: [
[inlineReactSvgPlugin, {
alias: {
myalias: 'test/fixtures',
},
}],
],
}, (err, result) => {
if (err) throw err;
console.log('test/fixtures/test-alias.jsx (without root)', result.code);
});

// Alias with root
transformFile('test/fixtures/test-alias.jsx', {
babelrc: false,
presets: ['@babel/preset-react'],
plugins: [
[inlineReactSvgPlugin, {
root: path.resolve(__dirname),
alias: {
myalias: 'fixtures',
},
}],
],
}, (err, result) => {
if (err) throw err;
console.log('test/fixtures/test-alias.jsx (with root)', result.code);
});

// Alias with root including subdirectory
transformFile('test/fixtures/test-alias.jsx', {
babelrc: false,
presets: ['@babel/preset-react'],
plugins: [
[inlineReactSvgPlugin, {
root: path.resolve(__dirname, '..', 'test'),
alias: {
myalias: 'fixtures',
},
}],
],
}, (err, result) => {
if (err) throw err;
console.log('test/fixtures/test-alias.jsx (with complex root)', result.code);
});

// Multiple aliases defined
transformFile('test/fixtures/test-alias-multiple.jsx', {
babelrc: false,
presets: ['@babel/preset-react'],
plugins: [
[inlineReactSvgPlugin, {
root: path.resolve(__dirname),
alias: {
myalias: 'fixtures',
myalias2: 'fixtures',
},
}],
],
}, (err, result) => {
if (err) throw err;
console.log('test/fixtures/test-alias.jsx (with multiple aliases)', result.code);
});

// Alias with multiple folders
transformFile('test/fixtures/test-alias-folders.jsx', {
babelrc: false,
presets: ['@babel/preset-react'],
plugins: [
[inlineReactSvgPlugin, {
root: path.resolve(__dirname),
alias: {
myalias: ['fixtures', 'fixtures/testfolder'],
},
}],
],
}, (err, result) => {
if (err) throw err;
console.log('test/fixtures/test-alias-folders.jsx', result.code);
});

// Error on import failure with aliases
transformFile('test/fixtures/test-alias.jsx', {
babelrc: false,
presets: ['@babel/preset-react'],
plugins: [
[inlineReactSvgPlugin, {
root: path.resolve(__dirname),
alias: {
myalias: 'fixtures/testfolder',
},
}],
],
}, (err, result) => {
if (err && err.message.indexOf('File path does not exist') !== -1) {
console.log('test/fixtures/test-alias.jsx (no svg)', 'Test passed: Expected file path does not exist was thrown');
} else {
throw new Error(`Test failed: Expected file path does not exist wasn't thrown, got: ${err.message}`);
}
});

// Error on case mismatch
if (fs.existsSync(path.resolve('./PACKAGE.JSON'))) {
transformFile('test/fixtures/test-alias-case-sensitive.jsx', {
babelrc: false,
presets: ['@babel/preset-react'],
plugins: [
[inlineReactSvgPlugin, {
caseSensitive: true,
root: path.resolve(__dirname),
alias: {
myalias: 'fixtures',
},
}],
],
}, (err) => {
if (err && err.message.indexOf('match case') !== -1) {
console.log('test/fixtures/test-alias-case-sensitive.jsx', 'Test passed: Expected case sensitive error was thrown');
} else {
throw new Error(`Test failed: Expected case sensitive error wasn‘t thrown, got: ${err.message}`);
}
});
} else {
console.log('# SKIP: alias case-sensitive check; on a case-sensitive filesystem');
}