Unverified Commit d0e17316 authored by Ian Sutherland's avatar Ian Sutherland Committed by GitHub
Browse files

Named asset import for SVG files (#3907)

* Add named asset import for svg files via babel plugin and webpack loader.

* Fix failing e2e test

* Switched to svgr loader

* Updated SVG component test

* Disable named asset import plugin in test environment

* Added tests for including SVG in CSS

* Update tests

* Moved babel plugin config into webpack config
parent aa8789b1
Showing with 152 additions and 55 deletions
+152 -55
'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;
{
"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"
}
}
......@@ -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.
......
......@@ -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
......
......@@ -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');
......
......@@ -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)
......
......@@ -8,4 +8,4 @@
import React from 'react';
import { ReactComponent as Logo } from './assets/logo.svg';
export default () => <Logo />;
export default () => <Logo id="feature-svg-component" />;
import React from 'react';
import './assets/svg.css';
export default () => <div id="feature-svg-in-css" />;
import React from 'react';
import ReactDOM from 'react-dom';
import SvgInCss from './SvgInCss';
describe('svg in css', () => {
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<SvgInCss />, div);
});
});
#feature-svg-in-css {
background-image: url("./logo.svg");
}
......@@ -28,6 +28,7 @@
"babel-eslint": "8.2.1",
"babel-jest": "22.1.0",
"babel-loader": "8.0.0-beta.0",
"babel-plugin-named-asset-import": "^0.1.0",
"babel-preset-react-app": "^3.1.1",
"case-sensitive-paths-webpack-plugin": "2.1.1",
"chalk": "2.3.0",
......@@ -56,7 +57,7 @@
"raf": "3.4.0",
"react-dev-utils": "^5.0.0",
"style-loader": "0.19.1",
"svgr": "1.6.0",
"svgr": "1.8.1",
"sw-precache-webpack-plugin": "0.11.4",
"thread-loader": "1.1.2",
"uglifyjs-webpack-plugin": "1.1.6",
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment