From 4324ef1c6de945c69f7588494fea6df198be19e7 Mon Sep 17 00:00:00 2001 From: Ian Sutherland Date: Wed, 17 Jan 2018 12:40:56 -0700 Subject: [PATCH] Import SVGs as React components (#1388) (#3718) * Import SVGs as React components (#1388) * Updated webpack production config and fixed tests * Improved Jest SVG file transform * Improved SVG tests * Add a comment * Update webpack.config.prod.js # Conflicts: # packages/react-scripts/package.json --- .../config/jest/fileTransform.js | 12 ++++++++- .../config/webpack.config.dev.js | 25 +++++++++++++++++++ .../config/webpack.config.prod.js | 25 +++++++++++++++++++ .../src/features/webpack/SvgComponent.js | 11 ++++++++ .../src/features/webpack/SvgComponent.test.js | 18 +++++++++++++ packages/react-scripts/package.json | 3 ++- 6 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 packages/react-scripts/fixtures/kitchensink/src/features/webpack/SvgComponent.js create mode 100644 packages/react-scripts/fixtures/kitchensink/src/features/webpack/SvgComponent.test.js diff --git a/packages/react-scripts/config/jest/fileTransform.js b/packages/react-scripts/config/jest/fileTransform.js index 38910e18b..b5aa17e0f 100644 --- a/packages/react-scripts/config/jest/fileTransform.js +++ b/packages/react-scripts/config/jest/fileTransform.js @@ -15,6 +15,16 @@ const path = require('path'); module.exports = { process(src, filename) { - return `module.exports = ${JSON.stringify(path.basename(filename))};`; + const assetFilename = JSON.stringify(path.basename(filename)); + + if (filename.match(/\.svg$/)) { + return `module.exports = { + __esModule: true, + default: ${assetFilename}, + ReactComponent: () => ${assetFilename}, + };`; + } + + return `module.exports = ${assetFilename};`; }, }; diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js index 924e3e4f0..4253f3d31 100644 --- a/packages/react-scripts/config/webpack.config.dev.js +++ b/packages/react-scripts/config/webpack.config.dev.js @@ -220,6 +220,31 @@ module.exports = { }, ], }, + // Allows you to use two kinds of imports for SVG: + // import logoUrl from './logo.svg'; gives you the URL. + // import { ReactComponent as Logo } from './logo.svg'; gives you a component. + { + test: /\.svg$/, + use: [ + { + loader: require.resolve('babel-loader'), + options: { + // @remove-on-eject-begin + babelrc: false, + presets: [require.resolve('babel-preset-react-app')], + // @remove-on-eject-end + cacheDirectory: true, + }, + }, + require.resolve('svgr/webpack'), + { + loader: require.resolve('file-loader'), + options: { + name: 'static/media/[name].[hash:8].[ext]', + }, + }, + ], + }, // "file" loader makes sure those assets get served by WebpackDevServer. // When you `import` an asset, you get its (virtual) filename. // In production, they would get copied to the `build` folder. diff --git a/packages/react-scripts/config/webpack.config.prod.js b/packages/react-scripts/config/webpack.config.prod.js index 81196493d..4803f893a 100644 --- a/packages/react-scripts/config/webpack.config.prod.js +++ b/packages/react-scripts/config/webpack.config.prod.js @@ -246,6 +246,31 @@ module.exports = { ), // Note: this won't work without `new ExtractTextPlugin()` in `plugins`. }, + // Allows you to use two kinds of imports for SVG: + // import logoUrl from './logo.svg'; gives you the URL. + // import { ReactComponent as Logo } from './logo.svg'; gives you a component. + { + test: /\.svg$/, + use: [ + { + loader: require.resolve('babel-loader'), + options: { + // @remove-on-eject-begin + babelrc: false, + presets: [require.resolve('babel-preset-react-app')], + // @remove-on-eject-end + cacheDirectory: true, + }, + }, + require.resolve('svgr/webpack'), + { + loader: require.resolve('file-loader'), + options: { + name: 'static/media/[name].[hash:8].[ext]', + }, + }, + ], + }, // "file" loader makes sure assets end up in the `build` folder. // When you `import` an asset, you get its filename. // This loader doesn't use a "test" so it will catch all modules diff --git a/packages/react-scripts/fixtures/kitchensink/src/features/webpack/SvgComponent.js b/packages/react-scripts/fixtures/kitchensink/src/features/webpack/SvgComponent.js new file mode 100644 index 000000000..0eb06a027 --- /dev/null +++ b/packages/react-scripts/fixtures/kitchensink/src/features/webpack/SvgComponent.js @@ -0,0 +1,11 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import { ReactComponent as Logo } from './assets/logo.svg'; + +export default () => ; diff --git a/packages/react-scripts/fixtures/kitchensink/src/features/webpack/SvgComponent.test.js b/packages/react-scripts/fixtures/kitchensink/src/features/webpack/SvgComponent.test.js new file mode 100644 index 000000000..1b63788c7 --- /dev/null +++ b/packages/react-scripts/fixtures/kitchensink/src/features/webpack/SvgComponent.test.js @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import SvgComponent from './SvgComponent'; + +describe('svg component', () => { + it('renders without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render(, div); + expect(div.textContent).toBe('logo.svg'); + }); +}); diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index 46d9cbee4..4fd3c3264 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -44,7 +44,8 @@ "source-map-loader": "^0.2.1", "react-dev-utils": "^5.0.1", "resolve": "1.6.0", - "style-loader": "0.19.0", + "style-loader": "0.19.1", + "svgr": "1.6.0", "sw-precache-webpack-plugin": "0.11.4", "ts-jest": "22.0.1", "ts-loader": "^2.3.7",