Skip to content

Commit 8400367

Browse files
committed
experimenting with mock image component
1 parent 700fa18 commit 8400367

13 files changed

+1029
-1
lines changed

dist/main.js

+8-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/NextImage.jsx

+625
Large diffs are not rendered by default.

src/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ export { useCollectionChunks } from "./useCollectionChunks";
1010
export { CustomChunkCollection } from "./CustomChunkCollection.jsx";
1111
export { ChunkCollectionContext } from "./ChunkCollectionContext.js";
1212
export { CollectionItemWrapper } from "./CollectionItemWrapper.jsx";
13+
export { Image } from "./NextImage.jsx";

src/use-intersection.js

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//@ts-ignore
2+
import { useCallback, useEffect, useRef, useState } from "react";
3+
import { requestIdleCallback, cancelIdleCallback } from "./utilities";
4+
5+
const hasIntersectionObserver = typeof IntersectionObserver !== "undefined";
6+
7+
export function useIntersection({ rootMargin, disabled }) {
8+
const isDisabled = disabled || !hasIntersectionObserver;
9+
10+
const unobserve = useRef();
11+
const [visible, setVisible] = useState(false);
12+
13+
const setRef = useCallback(
14+
(el) => {
15+
if (unobserve.current) {
16+
unobserve.current();
17+
unobserve.current = undefined;
18+
}
19+
20+
if (isDisabled || visible) return;
21+
22+
if (el && el.tagName) {
23+
unobserve.current = observe(
24+
el,
25+
(isVisible) => isVisible && setVisible(isVisible),
26+
{ rootMargin }
27+
);
28+
}
29+
},
30+
[isDisabled, rootMargin, visible]
31+
);
32+
33+
useEffect(() => {
34+
if (!hasIntersectionObserver) {
35+
if (!visible) {
36+
const idleCallback = requestIdleCallback(() => setVisible(true));
37+
return () => cancelIdleCallback(idleCallback);
38+
}
39+
}
40+
}, [visible]);
41+
42+
return [setRef, visible];
43+
}
44+
45+
function observe(element, callback, options) {
46+
const { id, observer, elements } = createObserver(options);
47+
elements.set(element, callback);
48+
49+
observer.observe(element);
50+
return function unobserve() {
51+
elements.delete(element);
52+
observer.unobserve(element);
53+
54+
// Destroy observer when there's nothing left to watch:
55+
if (elements.size === 0) {
56+
observer.disconnect();
57+
observers.delete(id);
58+
}
59+
};
60+
}
61+
62+
const observers = new Map();
63+
function createObserver(options) {
64+
const id = options.rootMargin || "";
65+
let instance = observers.get(id);
66+
if (instance) {
67+
return instance;
68+
}
69+
70+
const elements = new Map();
71+
const observer = new IntersectionObserver((entries) => {
72+
entries.forEach((entry) => {
73+
const callback = elements.get(entry.target);
74+
const isVisible = entry.isIntersecting || entry.intersectionRatio > 0;
75+
if (callback && isVisible) {
76+
callback(isVisible);
77+
}
78+
});
79+
}, options);
80+
81+
observers.set(
82+
id,
83+
(instance = {
84+
id,
85+
observer,
86+
elements,
87+
})
88+
);
89+
return instance;
90+
}

src/utilities/amp-context.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//@ts-ignore
2+
import React from "react";
3+
4+
export const AmpStateContext = React.createContext({});
5+
6+
if (process.env.NODE_ENV !== "production") {
7+
AmpStateContext.displayName = "AmpStateContext";
8+
}

src/utilities/amp.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//@ts-ignore
2+
import React from "react";
3+
import { AmpStateContext } from "./amp-context";
4+
5+
export function isInAmpMode({
6+
ampFirst = false,
7+
hybrid = false,
8+
hasQuery = false,
9+
} = {}) {
10+
return ampFirst || (hybrid && hasQuery);
11+
}
12+
13+
export function useAmp() {
14+
return isInAmpMode(React.useContext(AmpStateContext));
15+
}

src/utilities/head-manager-context.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { createContext } from "react";
2+
3+
export const HeadManagerContext = createContext({
4+
// updateHead,
5+
// mountedInstances,
6+
// updateScripts,
7+
// scripts,
8+
});
9+
10+
if (process.env.NODE_ENV !== "production") {
11+
HeadManagerContext.displayName = "HeadManagerContext";
12+
}

src/utilities/head.jsx

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
//@ts-ignore
2+
import React, { useContext } from "react";
3+
import Effect from "./side-effect.jsx";
4+
import { AmpStateContext } from "./amp-context";
5+
import { HeadManagerContext } from "./head-manager-context";
6+
import { isInAmpMode } from "./amp";
7+
8+
export function defaultHead(inAmpMode = false) {
9+
const head = [<meta charSet="utf-8" />];
10+
if (!inAmpMode) {
11+
head.push(<meta name="viewport" content="width=device-width" />);
12+
}
13+
return head;
14+
}
15+
16+
function onlyReactElement(list, child) {
17+
// React children can be "string" or "number" in this case we ignore them for backwards compat
18+
if (typeof child === "string" || typeof child === "number") {
19+
return list;
20+
}
21+
// Adds support for React.Fragment
22+
if (child.type === React.Fragment) {
23+
return list.concat(
24+
React.Children.toArray(child.props.children).reduce(
25+
(fragmentList, fragmentChild) => {
26+
if (
27+
typeof fragmentChild === "string" ||
28+
typeof fragmentChild === "number"
29+
) {
30+
return fragmentList;
31+
}
32+
return fragmentList.concat(fragmentChild);
33+
},
34+
[]
35+
)
36+
);
37+
}
38+
return list.concat(child);
39+
}
40+
41+
const METATYPES = ["name", "httpEquiv", "charSet", "itemProp"];
42+
43+
/*
44+
returns a function for filtering head child elements
45+
which shouldn't be duplicated, like <title/>
46+
Also adds support for deduplicated `key` properties
47+
*/
48+
function unique() {
49+
const keys = new Set();
50+
const tags = new Set();
51+
const metaTypes = new Set();
52+
const metaCategories = {};
53+
54+
return (h) => {
55+
let isUnique = true;
56+
let hasKey = false;
57+
58+
if (h.key && typeof h.key !== "number" && h.key.indexOf("$") > 0) {
59+
hasKey = true;
60+
const key = h.key.slice(h.key.indexOf("$") + 1);
61+
if (keys.has(key)) {
62+
isUnique = false;
63+
} else {
64+
keys.add(key);
65+
}
66+
}
67+
68+
// eslint-disable-next-line default-case
69+
switch (h.type) {
70+
case "title":
71+
case "base":
72+
if (tags.has(h.type)) {
73+
isUnique = false;
74+
} else {
75+
tags.add(h.type);
76+
}
77+
break;
78+
case "meta":
79+
for (let i = 0, len = METATYPES.length; i < len; i++) {
80+
const metatype = METATYPES[i];
81+
if (!h.props.hasOwnProperty(metatype)) continue;
82+
83+
if (metatype === "charSet") {
84+
if (metaTypes.has(metatype)) {
85+
isUnique = false;
86+
} else {
87+
metaTypes.add(metatype);
88+
}
89+
} else {
90+
const category = h.props[metatype];
91+
const categories = metaCategories[metatype] || new Set();
92+
if ((metatype !== "name" || !hasKey) && categories.has(category)) {
93+
isUnique = false;
94+
} else {
95+
categories.add(category);
96+
metaCategories[metatype] = categories;
97+
}
98+
}
99+
}
100+
break;
101+
}
102+
103+
return isUnique;
104+
};
105+
}
106+
107+
/**
108+
*
109+
* @param headElements List of multiple <Head> instances
110+
*/
111+
function reduceComponents(headElements, props) {
112+
return headElements
113+
.reduce((list, headElement) => {
114+
const headElementChildren = React.Children.toArray(
115+
headElement.props.children
116+
);
117+
return list.concat(headElementChildren);
118+
}, [])
119+
.reduce(onlyReactElement, [])
120+
.reverse()
121+
.concat(defaultHead(props.inAmpMode))
122+
.filter(unique())
123+
.reverse()
124+
.map((c, i) => {
125+
const key = c.key || i;
126+
if (
127+
process.env.NODE_ENV !== "development" &&
128+
process.env.__NEXT_OPTIMIZE_FONTS &&
129+
!props.inAmpMode
130+
) {
131+
if (
132+
c.type === "link" &&
133+
c.props["href"] &&
134+
// TODO(prateekbh@): Replace this with const from `constants` when the tree shaking works.
135+
["https://fonts.googleapis.com/css", "https://use.typekit.net/"].some(
136+
(url) => c.props["href"].startsWith(url)
137+
)
138+
) {
139+
const newProps = { ...(c.props || {}) };
140+
newProps["data-href"] = newProps["href"];
141+
newProps["href"] = undefined;
142+
143+
// Add this attribute to make it easy to identify optimized tags
144+
newProps["data-optimized-fonts"] = true;
145+
146+
return React.cloneElement(c, newProps);
147+
}
148+
}
149+
return React.cloneElement(c, { key });
150+
});
151+
}
152+
153+
/**
154+
* This component injects elements to `<head>` of your page.
155+
* To avoid duplicated `tags` in `<head>` you can use the `key` property, which will make sure every tag is only rendered once.
156+
*/
157+
export function Head({ children }) {
158+
const ampState = useContext(AmpStateContext);
159+
const headManager = useContext(HeadManagerContext);
160+
return (
161+
<Effect
162+
reduceComponentsToState={reduceComponents}
163+
headManager={headManager}
164+
inAmpMode={isInAmpMode(ampState)}
165+
>
166+
{children}
167+
</Effect>
168+
);
169+
}

src/utilities/image-config.js

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export const VALID_LOADERS = [
2+
"default",
3+
"imgix",
4+
"cloudinary",
5+
"akamai",
6+
"custom",
7+
];
8+
9+
export const imageConfigDefault = {
10+
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
11+
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
12+
path: "/_next/image",
13+
loader: "default",
14+
domains: [],
15+
disableStaticImages: false,
16+
minimumCacheTTL: 60,
17+
};

src/utilities/index.js

+7
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,10 @@ export { transformImage } from "./transformImage";
1010
export { computeClassName } from "./computeClassName";
1111
export { api } from "./api";
1212
export { renderChunk } from "./renderChunk.jsx";
13+
export {
14+
requestIdleCallback,
15+
cancelIdleCallback,
16+
} from "./request-idle-callback.js";
17+
export { imageConfigDefault, VALID_LOADERS } from "./image-config.js";
18+
export { toBase64 } from "./to-base-64.js";
19+
export { Head } from "./head.jsx";
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export const requestIdleCallback =
2+
(typeof self !== "undefined" &&
3+
self.requestIdleCallback &&
4+
self.requestIdleCallback.bind(window)) ||
5+
function (cb) {
6+
let start = Date.now();
7+
return setTimeout(function () {
8+
cb({
9+
didTimeout: false,
10+
timeRemaining: function () {
11+
return Math.max(0, 50 - (Date.now() - start));
12+
},
13+
});
14+
}, 1);
15+
};
16+
17+
export const cancelIdleCallback =
18+
(typeof self !== "undefined" &&
19+
self.cancelIdleCallback &&
20+
self.cancelIdleCallback.bind(window)) ||
21+
function (id) {
22+
return clearTimeout(id);
23+
};

0 commit comments

Comments
 (0)