Skip to content

Commit

Permalink
Support export { default } from ‘path/to.svg.
Browse files Browse the repository at this point in the history
Fixes #30.
  • Loading branch information
ljharb committed Nov 13, 2017
1 parent 8376efa commit ccabe7c
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 70 deletions.
174 changes: 104 additions & 70 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,86 @@ const buildSvgWithDefaults = template(`
SVG_NAME.defaultProps = SVG_DEFAULT_PROPS_CODE;
`);

const exportSVG = template(`
var SVG_NAME = function SVG_NAME(props) { return SVG_CODE; };
export { SVG_NAME };
`);

const exportSVGwithDefaults = template(`
var SVG_NAME = function SVG_NAME(props) { return SVG_CODE; };
SVG_NAME.defaultProps = SVG_DEFAULT_PROPS_CODE;
export { SVG_NAME };
`);

function transform(t, path, state) {
const { source } = path.node;
if (!source) { return; }
const importPath = source.value;
const { ignorePattern, caseSensitive } = state.opts;
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}`);
}
const rawSource = readFileSync(svgPath, 'utf8');
const optimizedSource = state.opts.svgo === false
? rawSource
: optimize(rawSource, state.opts.svgo);

const escapeSvgSource = escapeBraces(optimizedSource);

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

traverse(parsedSvgAst, transformSvg(t));

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

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 = [];

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);
}

return opts;
}
}

let ignoreRegex;

export default ({ types: t }) => ({
Expand All @@ -36,79 +116,33 @@ export default ({ types: t }) => ({
}
},
},
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;
}
ExportNamedDeclaration(path, state) {
const opts = transform(t, path, state);
if (!opts) { return; }

if (opts.SVG_DEFAULT_PROPS_CODE) {
const svgReplacement = exportSVGwithDefaults(opts);
path.replaceWithMultiple(svgReplacement);
} else {
const svgReplacement = exportSVG(opts);
path.replaceWith(svgReplacement);
}
// 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}`);
}
const rawSource = readFileSync(svgPath, 'utf8');
const optimizedSource = state.opts.svgo === false
? rawSource
: optimize(rawSource, state.opts.svgo);

const escapeSvgSource = escapeBraces(optimizedSource);

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

traverse(parsedSvgAst, transformSvg(t));

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

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 = [];

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);
}
const { file } = state;
file.get('ensureReact')();
},
ImportDeclaration(path, state) {
const opts = transform(t, path, state);
if (!opts) { return; }

if (opts.SVG_DEFAULT_PROPS_CODE) {
const svgReplacement = buildSvgWithDefaults(opts);
path.replaceWithMultiple(svgReplacement);
} else {
const svgReplacement = buildSvg(opts);
path.replaceWith(svgReplacement);
}
file.get('ensureReact')();
if (opts.SVG_DEFAULT_PROPS_CODE) {
const svgReplacement = buildSvgWithDefaults(opts);
path.replaceWithMultiple(svgReplacement);
} else {
const svgReplacement = buildSvg(opts);
path.replaceWith(svgReplacement);
}
const { file } = state;
file.get('ensureReact')();
},
},
});
1 change: 1 addition & 0 deletions test/fixtures/test-export-from.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './close.svg';
15 changes: 15 additions & 0 deletions test/sanity.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,18 @@ transformFile('test/fixtures/test-no-svg-or-react.js', {
throw new Error('Test failed: React import was present');
}
});

transformFile('test/fixtures/test-export-from.jsx', {
babelrc: false,
presets: ['react'],
plugins: [
'../../src/index',
],
}, (err, result) => {
if (err) throw err;
console.log('test/fixtures/test-export-from.jsx', result.code);
assertReactImport(result);
if (!/React\.createElement/.test(result.code)) {
throw new Error('no react elements found');
}
});

0 comments on commit ccabe7c

Please sign in to comment.