Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/optimize image #477

Merged
merged 9 commits into from
Apr 15, 2021
915 changes: 582 additions & 333 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"@tds/core-ordered-list": "^3.0.17",
"@tds/core-paragraph": "^2.0.12",
"@tds/core-responsive": "^1.3.3",
"@tds/core-responsive-image": "^1.0.0",
"@tds/core-spinner": "^3.1.11",
"@tds/core-strong": "^2.1.10",
"@tds/core-text": "^3.1.1",
Expand All @@ -91,7 +92,7 @@
"babel-polyfill": "^6.26.0",
"case": "^1.5.4",
"chromedriver": "^76.0.0",
"commitizen": "^4.1.2",
"commitizen": "^4.2.3",
"css-mediaquery": "^0.1.2",
"css-modules-loader-core": "^1.1.0",
"echint": "^4.0.1",
Expand Down
8 changes: 4 additions & 4 deletions packages/Modal/__tests__/__snapshots__/Modal.spec.jsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,8 @@ exports[`Modal renders 1`] = `
.c18 {
position: relative;
top: 0;
width: 24px;
height: 24px;
width: 1.5rem;
height: 1.5rem;
fill: #2a2c2e;
}

Expand Down Expand Up @@ -615,8 +615,8 @@ exports[`Modal renders 1`] = `
.c18 {
position: relative;
top: 0;
width: 24px;
height: 24px;
width: 1.5rem;
height: 1.5rem;
fill: #2a2c2e;
}

Expand Down
108 changes: 108 additions & 0 deletions packages/OptimizeImage/OptimizeImage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import React, { useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import ResponsiveImage from '@tds/core-responsive-image'
import { safeRest } from '@tds/util-helpers'
import checkWebpFeature from './checkWebpFeature'

/**
* @version ./package.json
*/
const OptimizeImage = ({ contentfulAssetUrl, alt, quality, xs, sm, md, lg, xl, ...rest }) => {
// states used to ensure images are downloaded only once
const [hasLoaded, setHasLoaded] = useState(false)
const [imgUrls, setImgUrls] = useState({})

useEffect(() => {
if (!contentfulAssetUrl.match(/.svg/g)) {
// Currently not all browsers support webP
checkWebpFeature(result => {
let format = ''
if (result) {
format = 'fm=webp'
} else if (contentfulAssetUrl.match(/.jpeg/g)) {
Andrew-K-Lam marked this conversation as resolved.
Show resolved Hide resolved
format = 'fm=jpg&fl=progressive'
}

setImgUrls({
xsSrc: `${contentfulAssetUrl}?w=${xs}&q=${quality}&${format}`,
smSrc: `${contentfulAssetUrl}?w=${sm}&q=${quality}&${format}`,
mdSrc: `${contentfulAssetUrl}?w=${md}&q=${quality}&${format}`,
lgSrc: `${contentfulAssetUrl}?w=${lg}&q=${quality}&${format}`,
xlSrc: `${contentfulAssetUrl}?w=${xl}&q=${quality}&${format}`,
fallbackSrc: `${contentfulAssetUrl}?w=${xl}&q=${quality}`,
})
setHasLoaded(true)
})
} else {
setImgUrls({
xsSrc: contentfulAssetUrl,
smSrc: contentfulAssetUrl,
mdSrc: contentfulAssetUrl,
lgSrc: contentfulAssetUrl,
xlSrc: contentfulAssetUrl,
fallbackSrc: contentfulAssetUrl,
})
setHasLoaded(true)
}
}, [])

if (!hasLoaded) return null
return (
<ResponsiveImage
xsSrc={imgUrls.xsSrc}
smSrc={imgUrls.smSrc}
mdSrc={imgUrls.mdSrc}
lgSrc={imgUrls.lgSrc}
xlSrc={imgUrls.xlSrc}
fallbackSrc={imgUrls.fallbackSrc}
alt={alt}
{...safeRest(rest)}
/>
)
}

OptimizeImage.propTypes = {
/**
* The source to load the image.
*/
contentfulAssetUrl: PropTypes.string.isRequired,
/**
* Alternative text to display if image cannot be loaded or a screen reader is used.
*/
alt: PropTypes.string.isRequired,
/**
* Customize quality as a percentage between 1 and 100.
*/
quality: PropTypes.number,
/**
* Customize width for xs screen size in px, this may affect the quality of the image.
*/
xs: PropTypes.number,
/**
* Customize width for sm screen size in px, this may affect the quality of the image.
*/
sm: PropTypes.number,
/**
* Customize width for md screen size in px, this may affect the quality of the image.
*/
md: PropTypes.number,
/**
* Customize width for lg screen size in px, this may affect the quality of the image.
*/
lg: PropTypes.number,
/**
* Customize width for xl screen size in px, this may affect the quality of the image.
*/
xl: PropTypes.number,
}

OptimizeImage.defaultProps = {
quality: 80,
xs: 320,
sm: 576,
md: 768,
lg: 992,
xl: 1200,
}

export default OptimizeImage
14 changes: 14 additions & 0 deletions packages/OptimizeImage/OptimizeImage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
OptimizeImage is used to optimize image files based on screen size

### Usage criteria

- Must use a contentful asset url as the component adapts the url to convert the image file
- May customize quality and screen width sizes. Note: increasing quality and width may affect page performance.
kiesha-telus marked this conversation as resolved.
Show resolved Hide resolved

```jsx
<OptimizeImage
contentfulAssetUrl="https://images.ctfassets.net/3cqlnin176yn/1GfIHDOb3n3uO3wVnKjHZ2/3a3c59c5caa91a315da1b3804cdf0b1b/Alpaca-lets-make-the-future-friendly-Hero-Banner-Tile.jpg"
alt="alpacas"
lg={500}
/>
```
1 change: 1 addition & 0 deletions packages/OptimizeImage/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# TDS Community: OptimizeImage
76 changes: 76 additions & 0 deletions packages/OptimizeImage/__tests__/OptimizeImage.spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React from 'react'
import { mount } from 'enzyme'

import OptimizeImage from '../OptimizeImage'
import checkWebpFeature from '../checkWebpFeature'

jest.mock('../checkWebpFeature')

describe('OptimizeImage', () => {
beforeEach(() => {
checkWebpFeature.mockImplementation(res => res(true))
})

const doMount = (props = {}) =>
mount(
<OptimizeImage
contentfulAssetUrl="https://images.ctfassets.net/Alpaca.jpg"
alt="test-image"
{...props}
/>
)

it('renders', () => {
const optimizeImage = doMount()
expect(optimizeImage).toMatchSnapshot()
})

it('passes additional attributes to the element', () => {
const optimizeImage = doMount({ id: 'the-id', 'data-some-attr': 'some value' })

expect(optimizeImage).toHaveProp('id', 'the-id')
expect(optimizeImage).toHaveProp('data-some-attr', 'some value')
})

it('does not allow custom CSS', () => {
const optimizeImage = doMount({
className: 'my-custom-class',
style: { color: 'hotpink' },
}).find('ResponsiveImage')

expect(optimizeImage).not.toHaveProp('className', 'my-custom-class')
expect(optimizeImage).not.toHaveProp('style')
})

it('should not alter the url if it is an svg', () => {
const optimizeImage = doMount({
contentfulAssetUrl: 'https://images.ctfassets.net/Alpaca.svg',
}).find('ResponsiveImage')

expect(optimizeImage).toHaveProp('xsSrc', 'https://images.ctfassets.net/Alpaca.svg')
})

it('should format with webp if browser supports it', () => {
const optimizeImage = doMount({
contentfulAssetUrl: 'https://images.ctfassets.net/Alpaca.jpg',
}).find('ResponsiveImage')

expect(optimizeImage).toHaveProp(
'xsSrc',
'https://images.ctfassets.net/Alpaca.jpg?w=320&q=80&fm=webp'
)
})

it('should format with progressive jpg if browser does not support webp', () => {
checkWebpFeature.mockImplementation(res => res(false))

const optimizeImage = doMount({
contentfulAssetUrl: 'https://images.ctfassets.net/Alpaca.jpeg',
}).find('ResponsiveImage')

expect(optimizeImage).toHaveProp(
'xsSrc',
'https://images.ctfassets.net/Alpaca.jpeg?w=320&q=80&fm=jpg&fl=progressive'
)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`OptimizeImage renders 1`] = `
<OptimizeImage
alt="test-image"
contentfulAssetUrl="https://images.ctfassets.net/Alpaca.jpg"
lg={992}
md={768}
quality={80}
sm={576}
xl={1200}
xs={320}
>
<ResponsiveImage
alt="test-image"
fallbackSrc="https://images.ctfassets.net/Alpaca.jpg?w=1200&q=80"
lgSrc="https://images.ctfassets.net/Alpaca.jpg?w=992&q=80&fm=webp"
mdSrc="https://images.ctfassets.net/Alpaca.jpg?w=768&q=80&fm=webp"
smSrc="https://images.ctfassets.net/Alpaca.jpg?w=576&q=80&fm=webp"
xlSrc="https://images.ctfassets.net/Alpaca.jpg?w=1200&q=80&fm=webp"
xsSrc="https://images.ctfassets.net/Alpaca.jpg?w=320&q=80&fm=webp"
>
<picture>
<source
media="(min-width: 1200px)"
srcSet="https://images.ctfassets.net/Alpaca.jpg?w=1200&q=80&fm=webp"
/>
<source
media="(min-width: 992px)"
srcSet="https://images.ctfassets.net/Alpaca.jpg?w=992&q=80&fm=webp"
/>
<source
media="(min-width: 768px)"
srcSet="https://images.ctfassets.net/Alpaca.jpg?w=768&q=80&fm=webp"
/>
<source
media="(min-width: 576px)"
srcSet="https://images.ctfassets.net/Alpaca.jpg?w=576&q=80&fm=webp"
/>
<source
media="(max-width: 575px)"
srcSet="https://images.ctfassets.net/Alpaca.jpg?w=320&q=80&fm=webp"
/>
<img
alt="test-image"
src="https://images.ctfassets.net/Alpaca.jpg?w=1200&q=80"
style={
Object {
"width": "100%",
}
}
/>
</picture>
</ResponsiveImage>
</OptimizeImage>
`;
16 changes: 16 additions & 0 deletions packages/OptimizeImage/checkWebpFeature.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// taken directly from Google developers guide on how to detect browser support for WebP
export default async callback => {
// basic support. other test forms exist for lossless, alpha, and animation types.
// check google guide if data strings are needed
const lossy = 'UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA'

const img = document.createElement('img')
img.onload = function onLoad() {
const result = img.width > 0 && img.height > 0
callback(result)
}
img.onerror = function onError() {
callback(false)
}
img.src = `data:image/webp;base64,${lossy}`
}
3 changes: 3 additions & 0 deletions packages/OptimizeImage/index.cjs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const OptimizeImage = require('./dist/index.cjs')

module.exports = OptimizeImage
3 changes: 3 additions & 0 deletions packages/OptimizeImage/index.es.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import OptimizeImage from './dist/index.es'

export default OptimizeImage
32 changes: 32 additions & 0 deletions packages/OptimizeImage/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "@tds/community-optimize-image",
"version": "1.0.0-0",
"description": "",
"main": "index.cjs.js",
"module": "index.es.js",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/telus/tds-community.git"
},
"publishConfig": {
"access": "public"
},
"author": "TELUS digital",
"engines": {
"node": ">=8"
},
"bugs": {
"url": "https://github.com/telus/tds-community/issues"
},
"homepage": "http://tds.telus.com",
"peerDependencies": {
"react": "^16.8.2",
"react-dom": "^16.8.2"
},
"dependencies": {
"@tds/core-responsive-image": "1.0.0",
"@tds/util-helpers": "^1.5.0",
"prop-types": "^15.6.2"
}
}
7 changes: 7 additions & 0 deletions packages/OptimizeImage/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import configure from '../../config/rollup.config'
import { dependencies } from './package.json'

export default configure({
input: './OptimizeImage.jsx',
dependencies,
})
4 changes: 4 additions & 0 deletions packages/Tabs/__tests__/__snapshots__/Tabs.spec.jsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1151,11 +1151,15 @@ exports[`Tabs renders 1`] = `
<Tabs
defaultFocus={false}
defaultIndex={null}
disableUpDownKeys={false}
environment={null}
forceRenderTabPanel={false}
selectedIndex={null}
>
<UncontrolledTabs
className="react-tabs"
disableUpDownKeys={false}
environment={null}
focus={false}
forceRenderTabPanel={false}
onSelect={[Function]}
Expand Down
Loading