Skip to content

Commit

Permalink
Support returning react components when inlining svg files (cyrilwann…
Browse files Browse the repository at this point in the history
  • Loading branch information
cyrilwanner authored Aug 8, 2020
1 parent 43aba14 commit 75df11d
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 2 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ module.exports = {
| limit | `8192` | `number` | Images smaller than this number (in bytes) will get inlined with a data-uri. |
| optimize | `true` | `boolean` | If this plugin should not optimized images, set this to `false`. You can still resize images, convert them to WebP and use other features in that case. |
| cacheFolder | `'node_modules/optimized-images-loader/.cache'` | `string` | Images will be cached in this folder to avoid long build times. |
| includeStrategy | `string` | `'string'` | When using the [?include](#include) query param, it returns a string by default. By setting this value to `'react'`, it returns a React component instead (requires manually installing the additional `@svgr/core` package). |
| name | `'[name]-[contenthash].[ext]'` | `string` | File name of the images after they got processed. Additionally to the [default placeholders](https://github.com/webpack-contrib/file-loader#placeholders), `[width]` and `[height]` are also available. |
| outputPath | | `string` | Images will be saved in this directory instead of the default webpack outputPath. |
| publicPath | | `string` | The public path that should be used for image URLs instead of the default webpack publicPath. |
Expand Down Expand Up @@ -90,6 +91,8 @@ This loader also provides a variety of query params to provide you even more opt

The image will now directly be included in your HTML without a data-uri or a reference to your file.

By default, it will be included as a normal `string`. If you are in a React project and wish to transform it into a React component, set the [`includeStrategy`](#options) to `'react'` and run `npm install @svgr/core`.

#### ?webp

If this `?webp` query parameter is specified, `optimized-images-loader` automatically converts the image to the new WebP format.
Expand Down
2 changes: 2 additions & 0 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import { WebpOptions } from 'sharp';
export interface LoaderOptions {
optimize?: boolean;
cacheFolder?: string;
includeStrategy?: 'string' | 'react';
mozjpeg?: MozjpegOptions;
oxipng?: OxipngOptions;
webp?: WebpOptions;
gifsicle?: GifsicleOptions;
svgo?: Record<string, unknown>;
svgr?: Record<string, unknown>;
}

export interface OptionObject {
Expand Down
5 changes: 5 additions & 0 deletions src/parseQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface ImageOptions {
forceInline?: boolean;
forceUrl?: boolean;
processLoaders?: boolean;
component?: 'react';
lqip?: 'blur' | 'colors';
}

Expand Down Expand Up @@ -45,6 +46,10 @@ const parseQuery = (rawQuery: string, loaderOptions: LoaderOptions): ImageOption
// include raw image (used for svg)
if (typeof query.include !== 'undefined') {
options.processLoaders = false;

if (loaderOptions.includeStrategy === 'react') {
options.component = 'react';
}
}

// resize image
Expand Down
46 changes: 44 additions & 2 deletions src/processLoaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const enrichResult = (
// an array means it was not processed by the url-/file-loader and the result should still be an array
// instead of a string. so in this case, append the additional export information to the array prototype
if (Array.isArray(result)) {
return `var res = ${JSON.stringify(result)};res.width=${width};res.height=${height};res.format=${JSON.stringify(
return `var res=${JSON.stringify(result)};res.width=${width};res.height=${height};res.format=${JSON.stringify(
format || '',
)};module.exports = res;`;
}
Expand All @@ -34,7 +34,7 @@ const enrichResult = (

const output = result.replace(/((module\.exports\s*=)\s*)([^\s].*[^;])(;$|$)/g, 'var src = $3;');

return `${output}module.exports = {src:src,width:${width},height:${height},format:${JSON.stringify(
return `${output}module.exports={src:src,width:${width},height:${height},format:${JSON.stringify(
format || '',
)},toString:function(){return src;}};`;
};
Expand All @@ -57,6 +57,43 @@ const replaceName = (
.replace(/\[height\]/g, `${imageOptions.height || originalImageInfo.height}`);
};

/**
* Convert the image into a component
*
* @param {string} image Processed image
* @param {{ width?: number; height?: number; format?: string }} originalImageInfo Metadata of original image
* @param {ImageOptions} imageOptions Image options
* @param {OptionObject} loaderOptions Loader options
*/
const convertToComponent = (
image: string,
originalImageInfo: { width?: number; height?: number; format?: string },
imageOptions: ImageOptions,
loaderOptions: OptionObject,
): string => {
if (imageOptions.component === 'react') {
const svgr = require('@svgr/core').default; // eslint-disable-line
const babel = require('@babel/core'); // eslint-disable-line

const code = svgr.sync(image, loaderOptions.svgr || {}, { componentName: 'SvgComponent' });
const transformed = babel.transformSync(code, {
caller: {
name: 'optimized-images-loader',
},
babelrc: false,
configFile: false,
presets: [
babel.createConfigItem(require('@babel/preset-react'), { type: 'preset' }), // eslint-disable-line
babel.createConfigItem([require('@babel/preset-env'), { modules: false }], { type: 'preset' }), // eslint-disable-line
],
});

return transformed.code;
}

throw new Error(`Unknown component type ${imageOptions.component}`);
};

/**
* Process further loaders (url-loader & file-loader)
*
Expand All @@ -76,6 +113,11 @@ const processLoaders = (
): string => {
// do not apply further loaders if not needed
if (imageOptions.processLoaders === false) {
// transform result to a component
if (imageOptions.component === 'react') {
return convertToComponent(image.toString(), originalImageInfo, imageOptions, loaderOptions);
}

if (Array.isArray(image)) {
return enrichResult(image, originalImageInfo, imageOptions);
}
Expand Down

0 comments on commit 75df11d

Please sign in to comment.