diff --git a/packages/babel-plugin-named-asset-import/index.js b/packages/babel-plugin-named-asset-import/index.js
new file mode 100644
index 00000000000..6fd919bc676
--- /dev/null
+++ b/packages/babel-plugin-named-asset-import/index.js
@@ -0,0 +1,62 @@
+'use strict';
+
+const { extname } = require('path');
+
+function namedAssetImportPlugin({ types: t }) {
+ const visited = new WeakSet();
+
+ return {
+ visitor: {
+ ImportDeclaration(path, { opts: { loaderMap } }) {
+ const sourcePath = path.node.source.value;
+ const ext = extname(sourcePath).substr(1);
+
+ if (visited.has(path.node) || sourcePath.indexOf('!') !== -1) {
+ return;
+ }
+
+ if (loaderMap[ext]) {
+ path.replaceWithMultiple(
+ path.node.specifiers.map(specifier => {
+ if (t.isImportDefaultSpecifier(specifier)) {
+ const newDefaultImport = t.importDeclaration(
+ [
+ t.importDefaultSpecifier(
+ t.identifier(specifier.local.name)
+ ),
+ ],
+ t.stringLiteral(sourcePath)
+ );
+
+ visited.add(newDefaultImport);
+ return newDefaultImport;
+ }
+
+ const newImport = t.importDeclaration(
+ [
+ t.importSpecifier(
+ t.identifier(specifier.local.name),
+ t.identifier(specifier.imported.name)
+ ),
+ ],
+ t.stringLiteral(
+ loaderMap[ext][specifier.imported.name]
+ ? loaderMap[ext][specifier.imported.name].replace(
+ /\[path\]/,
+ sourcePath
+ )
+ : sourcePath
+ )
+ );
+
+ visited.add(newImport);
+ return newImport;
+ })
+ );
+ }
+ },
+ },
+ };
+}
+
+module.exports = namedAssetImportPlugin;
diff --git a/packages/babel-plugin-named-asset-import/package.json b/packages/babel-plugin-named-asset-import/package.json
new file mode 100644
index 00000000000..9c586ac5753
--- /dev/null
+++ b/packages/babel-plugin-named-asset-import/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "babel-plugin-named-asset-import",
+ "version": "0.1.0",
+ "description": "Babel plugin for named asset imports in Create React App",
+ "repository": "facebookincubator/create-react-app",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/facebookincubator/create-react-app/issues"
+ },
+ "main": "index.js",
+ "files": [
+ "index.js"
+ ],
+ "peerDependencies": {
+ "@babel/core": "7.0.0-beta.38"
+ }
+}
diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js
index 66f07baa283..bd5d218cc8d 100644
--- a/packages/react-scripts/config/webpack.config.dev.js
+++ b/packages/react-scripts/config/webpack.config.dev.js
@@ -192,6 +192,18 @@ module.exports = {
babelrc: false,
// @remove-on-eject-end
presets: [require.resolve('babel-preset-react-app')],
+ plugins: [
+ [
+ require.resolve('babel-plugin-named-asset-import'),
+ {
+ loaderMap: {
+ svg: {
+ ReactComponent: 'svgr/webpack![path]',
+ },
+ },
+ },
+ ],
+ ],
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds.
@@ -266,31 +278,6 @@ 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,
- // @remove-on-eject-end
- presets: [require.resolve('babel-preset-react-app')],
- 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 1d9a617c829..6c9a879e851 100644
--- a/packages/react-scripts/config/webpack.config.prod.js
+++ b/packages/react-scripts/config/webpack.config.prod.js
@@ -200,6 +200,18 @@ module.exports = {
babelrc: false,
// @remove-on-eject-end
presets: [require.resolve('babel-preset-react-app')],
+ plugins: [
+ [
+ require.resolve('babel-plugin-named-asset-import'),
+ {
+ loaderMap: {
+ svg: {
+ ReactComponent: 'svgr/webpack![path]',
+ },
+ },
+ },
+ ],
+ ],
compact: true,
highlightCode: true,
},
@@ -308,31 +320,6 @@ 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,
- // @remove-on-eject-end
- presets: [require.resolve('babel-preset-react-app')],
- 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/integration/webpack.test.js b/packages/react-scripts/fixtures/kitchensink/integration/webpack.test.js
index 06ec83602f3..e479be4b81a 100644
--- a/packages/react-scripts/fixtures/kitchensink/integration/webpack.test.js
+++ b/packages/react-scripts/fixtures/kitchensink/integration/webpack.test.js
@@ -71,6 +71,22 @@ describe('Integration', () => {
);
});
+ it('svg component', async () => {
+ const doc = await initDOM('svg-component');
+
+ expect(doc.getElementById('feature-svg-component').textContent).to.equal(
+ ''
+ );
+ });
+
+ it('svg in css', async () => {
+ const doc = await initDOM('svg-in-css');
+
+ expect(
+ doc.getElementsByTagName('style')[0].textContent.replace(/\s/g, '')
+ ).to.match(/\/static\/media\/logo\..+\.svg/);
+ });
+
it('unknown ext inclusion', async () => {
const doc = await initDOM('unknown-ext-inclusion');
diff --git a/packages/react-scripts/fixtures/kitchensink/src/App.js b/packages/react-scripts/fixtures/kitchensink/src/App.js
index d1affb48af9..750f8a90b98 100644
--- a/packages/react-scripts/fixtures/kitchensink/src/App.js
+++ b/packages/react-scripts/fixtures/kitchensink/src/App.js
@@ -82,9 +82,9 @@ class App extends Component {
);
break;
case 'css-modules-inclusion':
- import(
- './features/webpack/CssModulesInclusion'
- ).then(f => this.setFeature(f.default));
+ import('./features/webpack/CssModulesInclusion').then(f =>
+ this.setFeature(f.default)
+ );
break;
case 'custom-interpolation':
import('./features/syntax/CustomInterpolation').then(f =>
@@ -174,6 +174,16 @@ class App extends Component {
this.setFeature(f.default)
);
break;
+ case 'svg-component':
+ import('./features/webpack/SvgComponent').then(f =>
+ this.setFeature(f.default)
+ );
+ break;
+ case 'svg-in-css':
+ import('./features/webpack/SvgInCss').then(f =>
+ this.setFeature(f.default)
+ );
+ break;
case 'template-interpolation':
import('./features/syntax/TemplateInterpolation').then(f =>
this.setFeature(f.default)
diff --git a/packages/react-scripts/fixtures/kitchensink/src/features/webpack/SvgComponent.js b/packages/react-scripts/fixtures/kitchensink/src/features/webpack/SvgComponent.js
index 0eb06a027e3..62bad3b1075 100644
--- a/packages/react-scripts/fixtures/kitchensink/src/features/webpack/SvgComponent.js
+++ b/packages/react-scripts/fixtures/kitchensink/src/features/webpack/SvgComponent.js
@@ -8,4 +8,4 @@
import React from 'react';
import { ReactComponent as Logo } from './assets/logo.svg';
-export default () =>