diff --git a/src/index.js b/src/index.js index 92dd3b8..89011f2 100644 --- a/src/index.js +++ b/src/index.js @@ -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); + } + }, }, - }, -}); + }; +}; diff --git a/test/fixtures/test.jsx b/test/fixtures/test-import.jsx similarity index 100% rename from test/fixtures/test.jsx rename to test/fixtures/test-import.jsx diff --git a/test/fixtures/test-require.jsx b/test/fixtures/test-require.jsx new file mode 100644 index 0000000..713cefd --- /dev/null +++ b/test/fixtures/test-require.jsx @@ -0,0 +1,13 @@ +import React from 'react'; + +const MySvg = require('./close.svg'); + +export function MyFunctionIcon() { + return ; +} + +export class MyClassIcon extends React.Component { + render() { + return ; + } +} diff --git a/test/sanity.js b/test/sanity.js index 9f5d6f6..b105166 100644 --- a/test/sanity.js +++ b/test/sanity.js @@ -12,7 +12,7 @@ function assertReactImport(result) { } } -transformFile('test/fixtures/test.jsx', { +transformFile('test/fixtures/test-import.jsx', { babelrc: false, presets: ['react'], plugins: [ @@ -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', { @@ -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'); + } + }); +});