Skip to content

Commit

Permalink
[New] add support for require
Browse files Browse the repository at this point in the history
  • Loading branch information
knoid authored and ljharb committed May 18, 2018
1 parent 7dc34fa commit 512996e
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 80 deletions.
169 changes: 91 additions & 78 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,93 +21,106 @@ const buildSvgWithDefaults = template(`

let ignoreRegex;

export default ({ types: t }) => ({
visitor: {
Program: {
enter({ scope, node }, { file }) {
if (!scope.hasBinding('React')) {
const reactImportDeclaration = t.importDeclaration([
t.importDefaultSpecifier(t.identifier('React')),
], t.stringLiteral('react'));

file.set('ensureReact', () => { node.body.unshift(reactImportDeclaration); });
} else {
file.set('ensureReact', () => {});
}
},
},
ImportDeclaration(path, state) {
const importPath = path.node.source.value;
const { ignorePattern, caseSensitive } = state.opts;
const { file } = state;
if (ignorePattern) {
// Only set the ignoreRegex once:
ignoreRegex = ignoreRegex || new RegExp(ignorePattern);
// Test if we should ignore this:
if (ignoreRegex.test(importPath)) {
return;
}
export default ({ types: t }) => {
function applyPlugin(importIdentifier, importPath, path, state) {
const { ignorePattern, caseSensitive } = state.opts;
const { file } = state;
if (ignorePattern) {
// Only set the ignoreRegex once:
ignoreRegex = ignoreRegex || new RegExp(ignorePattern);
// Test if we should ignore this:
if (ignoreRegex.test(importPath)) {
return;
}
// This plugin only applies for SVGs:
if (extname(importPath) === '.svg') {
// We only support the import default specifier, so let's use that identifier:
const importIdentifier = path.node.specifiers[0].local;
const iconPath = state.file.opts.filename;
const svgPath = resolveFrom(dirname(iconPath), importPath);
if (caseSensitive && !fileExistsWithCaseSync(svgPath)) {
throw new Error(`File path didn't match case of file on disk: ${svgPath}`);
}
if (!svgPath) {
throw new Error(`File path does not exist: ${importPath}`);
}
const rawSource = readFileSync(svgPath, 'utf8');
const optimizedSource = state.opts.svgo === false
? rawSource
: optimize(rawSource, state.opts.svgo);
}
// This plugin only applies for SVGs:
if (extname(importPath) === '.svg') {
const iconPath = state.file.opts.filename;
const svgPath = resolveFrom(dirname(iconPath), importPath);
if (caseSensitive && !fileExistsWithCaseSync(svgPath)) {
throw new Error(`File path didn't match case of file on disk: ${svgPath}`);
}
if (!svgPath) {
throw new Error(`File path does not exist: ${importPath}`);
}
const rawSource = readFileSync(svgPath, 'utf8');
const optimizedSource = state.opts.svgo === false
? rawSource
: optimize(rawSource, state.opts.svgo);

const escapeSvgSource = escapeBraces(optimizedSource);
const escapeSvgSource = escapeBraces(optimizedSource);

const parsedSvgAst = parse(escapeSvgSource, {
sourceType: 'module',
plugins: ['jsx'],
});
const parsedSvgAst = parse(escapeSvgSource, {
sourceType: 'module',
plugins: ['jsx'],
});

traverse(parsedSvgAst, transformSvg(t));

traverse(parsedSvgAst, transformSvg(t));
const svgCode = traverse.removeProperties(parsedSvgAst.program.body[0].expression);

const svgCode = traverse.removeProperties(parsedSvgAst.program.body[0].expression);
const opts = {
SVG_NAME: importIdentifier,
SVG_CODE: svgCode,
};

const opts = {
SVG_NAME: importIdentifier,
SVG_CODE: svgCode,
};
// Move props off of element and into defaultProps
if (svgCode.openingElement.attributes.length > 1) {
const keepProps = [];
const defaultProps = [];

// Move props off of element and into defaultProps
if (svgCode.openingElement.attributes.length > 1) {
const keepProps = [];
const defaultProps = [];
svgCode.openingElement.attributes.forEach((prop) => {
if (prop.type === 'JSXSpreadAttribute') {
keepProps.push(prop);
} else {
defaultProps.push(t.objectProperty(t.identifier(prop.name.name), prop.value));
}
});

svgCode.openingElement.attributes.forEach((prop) => {
if (prop.type === 'JSXSpreadAttribute') {
keepProps.push(prop);
} else {
defaultProps.push(t.objectProperty(t.identifier(prop.name.name), prop.value));
}
});
svgCode.openingElement.attributes = keepProps;
opts.SVG_DEFAULT_PROPS_CODE = t.objectExpression(defaultProps);
}

svgCode.openingElement.attributes = keepProps;
opts.SVG_DEFAULT_PROPS_CODE = t.objectExpression(defaultProps);
}
if (opts.SVG_DEFAULT_PROPS_CODE) {
const svgReplacement = buildSvgWithDefaults(opts);
path.replaceWithMultiple(svgReplacement);
} else {
const svgReplacement = buildSvg(opts);
path.replaceWith(svgReplacement);
}
file.get('ensureReact')();
file.set('ensureReact', () => {});
}
}

return {
visitor: {
Program: {
enter({ scope, node }, { file }) {
if (!scope.hasBinding('React')) {
const reactImportDeclaration = t.importDeclaration([
t.importDefaultSpecifier(t.identifier('React')),
], t.stringLiteral('react'));

if (opts.SVG_DEFAULT_PROPS_CODE) {
const svgReplacement = buildSvgWithDefaults(opts);
path.replaceWithMultiple(svgReplacement);
} else {
const svgReplacement = buildSvg(opts);
path.replaceWith(svgReplacement);
file.set('ensureReact', () => { node.body.unshift(reactImportDeclaration); });
} else {
file.set('ensureReact', () => {});
}
},
},
CallExpression(path, state) {
const { node } = path;
const filePath = node.arguments.length > 0 && node.arguments[0].value;
if (node.callee.name === 'require' && t.isVariableDeclarator(path.parent)) {
applyPlugin(path.parent.id, filePath, path.parentPath.parentPath, state);
}
file.get('ensureReact')();
file.set('ensureReact', () => {});
}
},
ImportDeclaration(path, state) {
const { node } = path;
if (node.specifiers.length > 0) {
applyPlugin(node.specifiers[0].local, node.source.value, path, state);
}
},
},
},
});
};
};
File renamed without changes.
13 changes: 13 additions & 0 deletions test/fixtures/test-require.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';

const MySvg = require('./close.svg');

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

export class MyClassIcon extends React.Component {
render() {
return <MySvg />;
}
}
25 changes: 23 additions & 2 deletions test/sanity.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function assertReactImport(result) {
}
}

transformFile('test/fixtures/test.jsx', {
transformFile('test/fixtures/test-import.jsx', {
babelrc: false,
presets: ['react'],
plugins: [
Expand All @@ -21,7 +21,7 @@ transformFile('test/fixtures/test.jsx', {
}, (err, result) => {
if (err) throw err;
assertReactImport(result);
console.log('test/fixtures/test.jsx', result.code);
console.log('test/fixtures/test-import.jsx', result.code);
});

transformFile('test/fixtures/test-multiple-svg.jsx', {
Expand Down Expand Up @@ -81,3 +81,24 @@ transformFile('test/fixtures/test-no-svg-or-react.js', {
throw new Error('Test failed: React import was present');
}
});

transformFile('test/fixtures/test-import.jsx', {
presets: ['airbnb'],
plugins: [
'../../src/index',
],
}, (err1, importResult) => {
if (err1) throw err1;
console.log('test/fixtures/test-import.jsx', importResult.code);
transformFile('test/fixtures/test-require.jsx', {
presets: ['airbnb'],
plugins: [
'../../src/index',
],
}, (err2, requireResult) => {
if (err2) throw err2;
if (importResult.code !== requireResult.code) {
throw new Error('Test failed: Import and require tests don\'t match');
}
});
});

0 comments on commit 512996e

Please sign in to comment.