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

[example] More expansive device detection and not load hidden imaged #38

Merged
merged 3 commits into from Dec 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion .vscode/settings.json
Expand Up @@ -18,5 +18,8 @@
},
"tslint.autoFixOnSave": true,
"typescript.tsdk": "./node_modules/typescript/lib",
"debug.node.autoAttach": "on"
"debug.node.autoAttach": "on",
"cSpell.words": [
"resizable"
]
}
55 changes: 54 additions & 1 deletion example/app.tsx
Expand Up @@ -34,8 +34,37 @@ const LargeStyle: CSSProperties = {
backgroundColor: "red",
}

// From https://www.smashingmagazine.com/2013/07/simple-responsive-images-with-css-background-images/
const Img: React.SFC<
{ src: string; aspectRatio: number } & React.HTMLProps<HTMLSpanElement>
> = ({ src, aspectRatio, style, ...props }) => (
<span
{...props}
style={{
width: "100%",
display: "inline-block",
fontSize: "0",
lineHeight: "0",
verticalAlign: "middle",
backgroundSize: "100%",
backgroundPosition: "50% 50%",
backgroundRepeat: "no-repeat",
backgroundImage: `url(${src})`,
...style,
}}
>
<span
style={{
display: "block",
height: "0",
paddingTop: `${(aspectRatio * 100).toFixed(2)}%`,
}}
/>
</span>
)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of this is to automatically size the container according to the actual aspect ratio. In cases the width and height are fixed this shouldn’t be necessary.


export const App: React.SFC = () => (
<div style={{ width: "400px" }}>
<div>
<div>
<h1>
Default <code>&lt;div&gt;</code> container
Expand Down Expand Up @@ -135,5 +164,29 @@ export const App: React.SFC = () => (
</div>
</Media>
</div>
<div>
<h1>Example of not loading hidden images</h1>
<Media lessThan="md">
<Img
aspectRatio={1.261044176706827}
src="https://d32dm0rphc51dk.cloudfront.net/JAo7pAN1p63YwolybeZgOg/small.jpg"
style={{ width: "100px" }}
/>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this translate to Reaction and other consumers? What will be required on the app side of things?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the most extreme version imaginable, I don’t believe we have any place on Artsy where the size of the image isn’t constrained along at least one axis. The important part is using background-image to display the image, as only when CSS is in control will the display value have any effect on whether or not the image will be downloaded.

Copy link
Member

@damassi damassi Nov 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess what I'm wondering is is there's anything we have to change in our codebase -- modify all images to use a background image rather than img src or swap all call-sites to use a new component?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. Yeah I think we’d have an Image component, that abstracts whatever we need away, and then do a img -> Image pass.

</Media>
<Media at="md">
<Img
aspectRatio={1.261044176706827}
src="https://d32dm0rphc51dk.cloudfront.net/JAo7pAN1p63YwolybeZgOg/medium.jpg"
style={{ width: "400px" }}
/>
</Media>
<Media at="lg">
<Img
aspectRatio={1.261044176706827}
src="https://d32dm0rphc51dk.cloudfront.net/JAo7pAN1p63YwolybeZgOg/large.jpg"
style={{ width: "800px" }}
/>
</Media>
</div>
</div>
)
57 changes: 0 additions & 57 deletions example/onlyMatchListForUserAgent.ts

This file was deleted.

173 changes: 120 additions & 53 deletions example/server.tsx
Expand Up @@ -2,67 +2,85 @@ import Webpack from "webpack"
import WebpackDevServer from "webpack-dev-server"
import webpackConfig from "./webpack.config"
import express from "express"

import ReactDOMServer from "react-dom/server"
import React from "react"
import chalk from "chalk"

import { findDevice } from "@artsy/detect-responsive-traits"

import { createMediaStyle, MediaContextProvider, SSRStyleID } from "./setup"
import {
createMediaStyle,
findBreakpointsForWidths,
findBreakpointAtWidth,
MediaContextProvider,
SortedBreakpoints,
SSRStyleID,
} from "./setup"
import { App } from "./app"
import { onlyMatchListForUserAgent } from "./onlyMatchListForUserAgent"

const app = express()

app.get("/", (_req, res) => {
res.send(`
<!DOCTYPE html>
<html>
<body>
<ul>
<li><a href="/ssr-only">server-side rendering <em>only</em></a></li>
<li><a href="/client-only">client-side rendering <em>only</em></a></li>
<li><a href="/rehydration">server-side rendering <em>and</em> client-side rehydration</a></li>
</ul>
</body>
</html>
`)
})
/**
* Find the breakpoints and interactions that the server should render
*/
function onlyMatchListForUserAgent(userAgent: string): OnlyMatchList {
const device = findDevice(userAgent)
if (!device) {
log(userAgent)
return null
} else {
const supportsHover = device.touch ? "notHover" : "hover"
const onlyMatch: OnlyMatchList = device.resizable
? [
supportsHover,
...findBreakpointsForWidths(device.minWidth, device.maxWidth),
]
: [
supportsHover,
findBreakpointAtWidth(device.minWidth),
findBreakpointAtWidth(device.maxWidth),
]
log(userAgent, onlyMatch, device.description)
return onlyMatch
}
}

/**
* Demonstrate server-side _only_ rendering
*/
app.get("/ssr-only", (req, res) => {
const onlyMatch = onlyMatchListForUserAgent(req.header("User-Agent"))
res.send(`
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style type="text/css">${createMediaStyle()}</style>
</head>
<body>
res.send(
template({
includeCSS: true,
body: `
<div id="react-root">
${ReactDOMServer.renderToString(
<MediaContextProvider onlyMatch={onlyMatch}>
<MediaContextProvider
onlyMatch={onlyMatchListForUserAgent(req.header("User-Agent"))}
>
<App />
</MediaContextProvider>
)}
</div>
</body>
</html>
`)
`,
})
)
})

/**
* Demonstrate server-side rendering _with_ client-side JS rehydration
*/
app.get("/rehydration", (req, res) => {
const onlyMatch = onlyMatchListForUserAgent(req.header("User-Agent"))
res.send(`
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style type="text/css" id="${SSRStyleID}">${createMediaStyle()}</style>
</head>
<body>
res.send(
template({
includeCSS: true,
body: `
<div id="loading-indicator">Loading…</div>
<div id="react-root">
${ReactDOMServer.renderToString(
<MediaContextProvider onlyMatch={onlyMatch}>
<MediaContextProvider
onlyMatch={onlyMatchListForUserAgent(req.header("User-Agent"))}
>
<App />
</MediaContextProvider>
)}
Expand All @@ -77,19 +95,19 @@ app.get("/rehydration", (req, res) => {
document.getElementById("loading-indicator").remove();
}, 1000)
</script>
</body>
</html>
`)
`,
})
)
})

/**
* Demonstrate client-side JS _only_ rendering
*/
app.get("/client-only", (_req, res) => {
res.send(`
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
res.send(
template({
includeCSS: false,
body: `
<div id="react-root">Loading…</div>
<script>
setTimeout(function () {
Expand All @@ -98,11 +116,60 @@ app.get("/client-only", (_req, res) => {
document.getElementsByTagName("head")[0].appendChild(script);
}, 1000)
</script>
</body>
</html>
`)
`,
})
)
})

/**
* Misc things that are not of interest for demonstrating react-responsive-media
*/

// TODO: Simplify this hideous typing.
type OnlyMatchList = Array<"hover" | "notHover" | (typeof SortedBreakpoints)[0]>

app.get("/", (_req, res) => {
res.send(
template({
includeCSS: false,
body: `
<ul>
<li><a href="/ssr-only">server-side rendering <em>only</em></a></li>
<li><a href="/client-only">client-side rendering <em>only</em></a></li>
<li><a href="/rehydration">server-side rendering <em>and</em> client-side rehydration</a></li>
</ul>
`,
})
)
})

const template = ({ includeCSS, body }) => `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5">
${
includeCSS
? `<style type="text/css" id="${SSRStyleID}">${createMediaStyle()}</style>`
: ""
}
</head>
<body>
${body}
</body>
</html>
`

function log(userAgent: string, onlyMatch?: string[], device?: string) {
// tslint:disable-next-line:no-console
console.log(
`Render: ${chalk.green(onlyMatch ? onlyMatch.join(", ") : "ALL")}\n` +
`Device: ${device ? chalk.green(device) : chalk.red("n/a")}\n` +
`User-Agent: ${chalk.yellow(userAgent)}\n`
)
}

const compiler = Webpack(webpackConfig)
const devServerOptions = Object.assign({}, webpackConfig.devServer, {
stats: {
Expand Down
3 changes: 2 additions & 1 deletion example/setup.ts
Expand Up @@ -17,7 +17,8 @@ const ExampleAppMedia = createMedia({
export const Media = ExampleAppMedia.Media
export const MediaContextProvider = ExampleAppMedia.MediaContextProvider
export const createMediaStyle = ExampleAppMedia.createMediaStyle
export const findBreakpointsForWidth = ExampleAppMedia.findBreakpointsForWidth
export const findBreakpointsForWidths = ExampleAppMedia.findBreakpointsForWidths
export const findBreakpointAtWidth = ExampleAppMedia.findBreakpointAtWidth
export const SortedBreakpoints = ExampleAppMedia.SortedBreakpoints

export const SSRStyleID = "ssr-rrm-style"
7 changes: 7 additions & 0 deletions example/tsconfig.json
@@ -0,0 +1,7 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"rootDirs": ["../example", "../src"],
"resolveJsonModule": true
}
}
3 changes: 3 additions & 0 deletions package.json
Expand Up @@ -41,13 +41,15 @@
"react": "^16.3.0"
},
"devDependencies": {
"@artsy/detect-responsive-traits": "^0.0.1",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/node": "^7.0.0",
"@babel/plugin-proposal-class-properties": "^7.1.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.0.0",
"@types/chalk": "^2.2.0",
"@types/express": "^4.16.0",
"@types/jest": "^23.1.0",
"@types/node": "^10.3.0",
Expand All @@ -59,6 +61,7 @@
"babel-jest": "^23.0.1",
"babel-loader": "^8.0.4",
"babel-preset-env": "^1.7.0",
"chalk": "^2.4.1",
"concurrently": "^3.5.1",
"conventional-changelog-ember": "^2.0.0",
"express": "^4.16.4",
Expand Down