From 0361226adb8a4919cc1dfd5f3ba19dc013889808 Mon Sep 17 00:00:00 2001 From: Gabriel Rohden Date: Thu, 5 Dec 2024 11:12:06 -0300 Subject: [PATCH] refactor: make default props emission disabled by default and use object assign instead --- README.md | 1 + package.json | 1 + src/index.js | 48 +++++++++++++++++++---- test/fixtures/test-props.jsx | 5 +++ test/sanity.js | 76 +++++++++++++++++++++++++++--------- 5 files changed, 106 insertions(+), 25 deletions(-) create mode 100644 test/fixtures/test-props.jsx diff --git a/README.md b/README.md index a8ba375..b9825bb 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ npm install --save-dev babel-plugin-inline-react-svg - `ignorePattern` - A pattern that imports will be tested against to selectively ignore imports. - `caseSensitive` - A boolean value that if true will require file paths to match with case-sensitivity. Useful to ensure consistent behavior if working on both a case-sensitive operating system like Linux and a case-insensitive one like OS X or Windows. +- `emitDeprecatedDefaultProps` - A boolean value that if true will make the package keep emitting the [deprecated](https://github.com/facebook/react/pull/16210) `.defaultProps` declaration for the generated SVG components - `svgo` - svgo options (`false` to disable). Example: ```json { diff --git a/package.json b/package.json index b971fbe..32fba73 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "@babel/helper-plugin-utils": "^7.0.0", "@babel/parser": "^7.0.0", "lodash.isplainobject": "^4.0.6", + "object.assign": "^4.1.5", "resolve": "^2.0.0-next.5", "svgo": "^2.8.0" }, diff --git a/src/index.js b/src/index.js index 2934b6c..a579149 100644 --- a/src/index.js +++ b/src/index.js @@ -25,30 +25,46 @@ export default declare(({ SVG_NAME, SVG_CODE, SVG_DEFAULT_PROPS_CODE, + EMIT_DEPRECATED_DEFAULT_PROPS, }) => { + const defaultProps = SVG_DEFAULT_PROPS_CODE + ? 'var props = objectAssign({}, SVG_DEFAULT_PROPS_CODE, overrides);' + : ''; + const PROPS_NAME = SVG_DEFAULT_PROPS_CODE ? 'overrides' : 'props'; + const namedTemplate = ` - var SVG_NAME = function SVG_NAME(props) { return SVG_CODE; }; - ${SVG_DEFAULT_PROPS_CODE ? 'SVG_NAME.defaultProps = SVG_DEFAULT_PROPS_CODE;' : ''} + ${SVG_DEFAULT_PROPS_CODE ? '' : ''} + var SVG_NAME = function SVG_NAME(PROPS_NAME) { ${defaultProps} return SVG_CODE; }; + ${SVG_DEFAULT_PROPS_CODE && EMIT_DEPRECATED_DEFAULT_PROPS ? 'SVG_NAME.defaultProps = SVG_DEFAULT_PROPS_CODE;' : ''} ${IS_EXPORT ? 'export { SVG_NAME };' : ''} `; const anonymousTemplate = ` - var Component = function (props) { return SVG_CODE; }; - ${SVG_DEFAULT_PROPS_CODE ? 'Component.defaultProps = SVG_DEFAULT_PROPS_CODE;' : ''} + var Component = function (PROPS_NAME) { ${defaultProps}; return SVG_CODE; }; + ${SVG_DEFAULT_PROPS_CODE && EMIT_DEPRECATED_DEFAULT_PROPS ? 'Component.defaultProps = SVG_DEFAULT_PROPS_CODE;' : ''} Component.displayName = 'EXPORT_FILENAME'; export default Component; `; if (SVG_NAME !== 'default') { - return template(namedTemplate)({ SVG_NAME, SVG_CODE, SVG_DEFAULT_PROPS_CODE }); + return template(namedTemplate)({ + SVG_NAME, SVG_CODE, SVG_DEFAULT_PROPS_CODE, PROPS_NAME, + }); } - return template(anonymousTemplate)({ SVG_CODE, SVG_DEFAULT_PROPS_CODE, EXPORT_FILENAME }); + return template(anonymousTemplate)({ + SVG_CODE, SVG_DEFAULT_PROPS_CODE, EXPORT_FILENAME, PROPS_NAME, + }); }; function applyPlugin(importIdentifier, importPath, path, state, isExport, exportFilename) { 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, + emitDeprecatedDefaultProps = false, + } = state.opts; const { file, filename } = state; let newPath; if (ignorePattern) { @@ -93,6 +109,8 @@ export default declare(({ SVG_CODE: svgCode, IS_EXPORT: isExport, EXPORT_FILENAME: exportFilename, + // https://github.com/facebook/react/pull/16210 + EMIT_DEPRECATED_DEFAULT_PROPS: emitDeprecatedDefaultProps, }; // Move props off of element and into defaultProps @@ -124,6 +142,8 @@ export default declare(({ file.get('ensureReact')(); file.set('ensureReact', () => {}); + file.get('ensureObjectAssign')(); + file.set('ensureObjectAssign', () => {}); } return newPath; } @@ -138,6 +158,20 @@ export default declare(({ if (typeof filename === 'undefined' && typeof opts.filename !== 'string') { throw new TypeError('the "filename" option is required when transforming code'); } + + if (!path.scope.hasBinding('object.assign/implementation')) { + const assignDeclaration = t.importDeclaration([ + t.importDefaultSpecifier(t.identifier('objectAssign')), + ], t.stringLiteral('object.assign/implementation')); + + file.set('ensureObjectAssign', () => { + const [newPath] = path.unshiftContainer('body', assignDeclaration); + newPath.get('specifiers').forEach((specifier) => { path.scope.registerBinding('module', specifier); }); + }); + } else { + file.set('ensureObjectAssign', () => {}); + } + if (!path.scope.hasBinding('React')) { const reactImportDeclaration = t.importDeclaration([ t.importDefaultSpecifier(t.identifier('React')), diff --git a/test/fixtures/test-props.jsx b/test/fixtures/test-props.jsx new file mode 100644 index 0000000..c8abd54 --- /dev/null +++ b/test/fixtures/test-props.jsx @@ -0,0 +1,5 @@ +import SVG from './close.svg'; + +export function MyFunctionIcon() { + return ; +} diff --git a/test/sanity.js b/test/sanity.js index c397fba..8bac918 100644 --- a/test/sanity.js +++ b/test/sanity.js @@ -4,13 +4,34 @@ import path from 'path'; import transformTs from '@babel/plugin-transform-typescript'; import inlineReactSvgPlugin from '../src'; -function assertReactImport(result) { - const match = result.code.match(/import React from ['"]react['"]/g); - if (!match) { - throw new Error('no React import found'); +function assertMatchImport(name, matchRegex) { + return (result) => { + const match = result.code.match(matchRegex()); + if (!match) { + throw new Error(`no ${name} import found`); + } + if (match.length !== 1) { + throw new Error(`more or less than one match found: ${match}\n${result.code}`); + } + }; +} + +const assertReactImport = assertMatchImport('React', () => /import React from ['"]react['"]/g); + +const assertObjectAssignImport = assertMatchImport( + 'object.assign/implementation', + () => /import objectAssign from ['"]object.assign\/implementation['"]/g, +); + +function assertDefaultProps(shouldExist, result) { + const exists = (/\.defaultProps = /g).test(result.code); + + if (!exists && shouldExist) { + throw new Error('defaultProps needs to be present'); } - if (match.length !== 1) { - throw new Error(`more or less than one match found: ${match}\n${result.code}`); + + if (exists && !shouldExist) { + throw new Error('defaultProps shouldn\'t be present'); } } @@ -29,8 +50,10 @@ transformFile('test/fixtures/test-import.jsx', { }, (err, result) => { if (err) throw err; assertReactImport(result); + assertObjectAssignImport(result); + assertDefaultProps(false, result); validateDefaultProps(result); - console.log('test/fixtures/test-import.jsx', result.code); + console.log('test/fixtures/test-import.jsx\n', result.code); }); transformFile('test/fixtures/test-multiple-svg.jsx', { @@ -42,8 +65,10 @@ transformFile('test/fixtures/test-multiple-svg.jsx', { }, (err, result) => { if (err) throw err; assertReactImport(result); + assertObjectAssignImport(result); + assertDefaultProps(false, result); validateDefaultProps(result); - console.log('test/fixtures/test-multiple-svg.jsx', result.code); + console.log('test/fixtures/test-multiple-svg.jsx\n', result.code); }); transformFile('test/fixtures/test-no-react.jsx', { @@ -54,8 +79,10 @@ transformFile('test/fixtures/test-no-react.jsx', { ], }, (err, result) => { if (err) throw err; - console.log('test/fixtures/test-no-react.jsx', result.code); + console.log('test/fixtures/test-no-react.jsx\n', result.code); assertReactImport(result); + assertObjectAssignImport(result); + assertDefaultProps(false, result); validateDefaultProps(result); }); @@ -78,8 +105,10 @@ transformFile('test/fixtures/test-no-duplicate-react.jsx', { ], }, (err, result) => { if (err) throw err; - console.log('test/fixtures/test-no-duplicate-react.jsx', result.code); + console.log('test/fixtures/test-no-duplicate-react.jsx\n', result.code); assertReactImport(result); + assertObjectAssignImport(result); + assertDefaultProps(false, result); validateDefaultProps(result); }); @@ -111,7 +140,7 @@ transformFile('test/fixtures/test-no-svg-or-react.js', { ], }, (err, result) => { if (err) throw err; - console.log('test/fixtures/test-no-svg-or-react.js', result.code); + console.log('test/fixtures/test-no-svg-or-react.js\n', result.code); if (/React/.test(result.code)) { throw new Error('Test failed: React import was present'); } @@ -145,7 +174,7 @@ transformFile('test/fixtures/test-dynamic-require.jsx', { ], }, (err, result) => { if (err) throw err; - console.log('test/fixtures/test-dynamic-require.jsx', result.code); + console.log('test/fixtures/test-dynamic-require.jsx\n', result.code); }); const filename = 'test/fixtures/test-import-read-file.jsx'; @@ -156,7 +185,7 @@ transform(fs.readFileSync(filename), { ], }, (err, result) => { if (err) throw err; - console.log('test/fixtures/test-import-read-file.jsx', result.code); + console.log('test/fixtures/test-import-read-file.jsx\n', result.code); }); transformFile('test/fixtures/test-export-default.jsx', { @@ -166,7 +195,7 @@ transformFile('test/fixtures/test-export-default.jsx', { ], }, (err, result) => { if (err) throw err; - console.log('test/fixtures/test-export-default.jsx', result.code); + console.log('test/fixtures/test-export-default.jsx\n', result.code); }); transformFile('test/fixtures/test-export-default-as.jsx', { @@ -182,7 +211,7 @@ transformFile('test/fixtures/test-export-default-as.jsx', { ], }, (err, result) => { if (err) throw err; - console.log('test/fixtures/test-export-default-as.jsx', result.code); + console.log('test/fixtures/test-export-default-as.jsx\n', result.code); }); transformFile('test/fixtures/test-export-all-as.jsx', { @@ -192,7 +221,7 @@ transformFile('test/fixtures/test-export-all-as.jsx', { ], }, (err, result) => { if (err) throw err; - console.log('test/fixtures/test-export-all-as.jsx', result.code); + console.log('test/fixtures/test-export-all-as.jsx\n', result.code); }); transformFile('test/fixtures/test-root-styled.jsx', { @@ -202,7 +231,7 @@ transformFile('test/fixtures/test-root-styled.jsx', { ], }, (err, result) => { if (err) throw err; - console.log('test/fixtures/test-root-styled.jsx', result.code); + console.log('test/fixtures/test-root-styled.jsx\n', result.code); }); transformFile('test/fixtures/test-commented.jsx', { @@ -212,7 +241,18 @@ transformFile('test/fixtures/test-commented.jsx', { ], }, (err, result) => { if (err) throw err; - console.log('test/fixtures/test-commented.jsx', result.code); + console.log('test/fixtures/test-commented.jsx\n', result.code); +}); + +transformFile('test/fixtures/test-props.jsx', { + presets: ['airbnb'], + plugins: [ + [inlineReactSvgPlugin, { emitDeprecatedDefaultProps: true }], + ], +}, (err, result) => { + if (err) throw err; + assertDefaultProps(true, result); + console.log('test/fixtures/test-props.jsx\n', result.code); }); /* TODO: uncomment if babel fixes its parsing for SVGs