diff --git a/.yarn/cache/caniuse-lite-npm-1.0.30001516-399d435c2f-044adf3493.zip b/.yarn/cache/caniuse-lite-npm-1.0.30001516-399d435c2f-044adf3493.zip
deleted file mode 100644
index 670c47bc..00000000
Binary files a/.yarn/cache/caniuse-lite-npm-1.0.30001516-399d435c2f-044adf3493.zip and /dev/null differ
diff --git a/.yarn/cache/caniuse-lite-npm-1.0.30001705-be4a37ca97-42c3b2164b.zip b/.yarn/cache/caniuse-lite-npm-1.0.30001705-be4a37ca97-42c3b2164b.zip
new file mode 100644
index 00000000..5681f818
Binary files /dev/null and b/.yarn/cache/caniuse-lite-npm-1.0.30001705-be4a37ca97-42c3b2164b.zip differ
diff --git a/.yarn/cache/cheerio-npm-1.0.0-837785b57b-ade4344811.zip b/.yarn/cache/cheerio-npm-1.0.0-837785b57b-ade4344811.zip
new file mode 100644
index 00000000..f1be92c1
Binary files /dev/null and b/.yarn/cache/cheerio-npm-1.0.0-837785b57b-ade4344811.zip differ
diff --git a/.yarn/cache/cheerio-select-npm-2.1.0-e92bc2f296-843d6d4799.zip b/.yarn/cache/cheerio-select-npm-2.1.0-e92bc2f296-843d6d4799.zip
new file mode 100644
index 00000000..e6e7bc62
Binary files /dev/null and b/.yarn/cache/cheerio-select-npm-2.1.0-e92bc2f296-843d6d4799.zip differ
diff --git a/.yarn/cache/css-select-npm-5.1.0-9365a79de5-2772c049b1.zip b/.yarn/cache/css-select-npm-5.1.0-9365a79de5-2772c049b1.zip
new file mode 100644
index 00000000..5c72f05f
Binary files /dev/null and b/.yarn/cache/css-select-npm-5.1.0-9365a79de5-2772c049b1.zip differ
diff --git a/.yarn/cache/domutils-npm-3.2.2-290180a284-ae941d56f0.zip b/.yarn/cache/domutils-npm-3.2.2-290180a284-ae941d56f0.zip
new file mode 100644
index 00000000..3c1b832a
Binary files /dev/null and b/.yarn/cache/domutils-npm-3.2.2-290180a284-ae941d56f0.zip differ
diff --git a/.yarn/cache/encoding-sniffer-npm-0.2.0-7e717bfa93-05ad76b674.zip b/.yarn/cache/encoding-sniffer-npm-0.2.0-7e717bfa93-05ad76b674.zip
new file mode 100644
index 00000000..f5f04472
Binary files /dev/null and b/.yarn/cache/encoding-sniffer-npm-0.2.0-7e717bfa93-05ad76b674.zip differ
diff --git a/.yarn/cache/htmlparser2-npm-9.1.0-5ef8394060-e5f8d51939.zip b/.yarn/cache/htmlparser2-npm-9.1.0-5ef8394060-e5f8d51939.zip
new file mode 100644
index 00000000..79276bb9
Binary files /dev/null and b/.yarn/cache/htmlparser2-npm-9.1.0-5ef8394060-e5f8d51939.zip differ
diff --git a/.yarn/cache/parse5-htmlparser2-tree-adapter-npm-7.1.0-02959cc692-98326fc544.zip b/.yarn/cache/parse5-htmlparser2-tree-adapter-npm-7.1.0-02959cc692-98326fc544.zip
new file mode 100644
index 00000000..3e3e48f3
Binary files /dev/null and b/.yarn/cache/parse5-htmlparser2-tree-adapter-npm-7.1.0-02959cc692-98326fc544.zip differ
diff --git a/.yarn/cache/parse5-npm-7.2.1-c48f333f28-11253cf8aa.zip b/.yarn/cache/parse5-npm-7.2.1-c48f333f28-11253cf8aa.zip
new file mode 100644
index 00000000..d751f80f
Binary files /dev/null and b/.yarn/cache/parse5-npm-7.2.1-c48f333f28-11253cf8aa.zip differ
diff --git a/.yarn/cache/parse5-parser-stream-npm-7.1.2-94131559b2-75b232d460.zip b/.yarn/cache/parse5-parser-stream-npm-7.1.2-94131559b2-75b232d460.zip
new file mode 100644
index 00000000..047bd10a
Binary files /dev/null and b/.yarn/cache/parse5-parser-stream-npm-7.1.2-94131559b2-75b232d460.zip differ
diff --git a/.yarn/cache/undici-npm-6.21.2-96736d59b9-4d7227910b.zip b/.yarn/cache/undici-npm-6.21.2-96736d59b9-4d7227910b.zip
new file mode 100644
index 00000000..d4f70f7b
Binary files /dev/null and b/.yarn/cache/undici-npm-6.21.2-96736d59b9-4d7227910b.zip differ
diff --git a/.yarn/cache/whatwg-encoding-npm-3.1.1-7dfe21cf7d-f75a614224.zip b/.yarn/cache/whatwg-encoding-npm-3.1.1-7dfe21cf7d-f75a614224.zip
new file mode 100644
index 00000000..13fdfcd7
Binary files /dev/null and b/.yarn/cache/whatwg-encoding-npm-3.1.1-7dfe21cf7d-f75a614224.zip differ
diff --git a/.yarn/cache/whatwg-mimetype-npm-4.0.0-ebb293a688-f97edd4b4e.zip b/.yarn/cache/whatwg-mimetype-npm-4.0.0-ebb293a688-f97edd4b4e.zip
new file mode 100644
index 00000000..8ef2ab2e
Binary files /dev/null and b/.yarn/cache/whatwg-mimetype-npm-4.0.0-ebb293a688-f97edd4b4e.zip differ
diff --git a/package.json b/package.json
index 33956e9a..afa5991d 100644
--- a/package.json
+++ b/package.json
@@ -66,6 +66,7 @@
"@weknow/gatsby-remark-twitter": "^0.2.3",
"babel-plugin-styled-components": "^2.1.4",
"block-elements": "^1.2.0",
+ "cheerio": "^1.0.0",
"cross-env": "^7.0.2",
"eslint": "^7.30.0",
"eslint-config-prettier": "^8.3.0",
@@ -89,7 +90,7 @@
"gatsby-transformer-remark": "^6.11.0",
"gatsby-transformer-sharp": "^5.11.0",
"js-yaml": "^3.14.0",
- "json5": "^2.2.1",
+ "json5": "^2.2.3",
"mdast-util-toc": "^5.0.3",
"mime-types": "^2.1.35",
"posthtml-parser": "^0.11.0",
diff --git a/plugins/gatsby-remark-3perf-transformer/index.js b/plugins/gatsby-remark-3perf-transformer/index.js
index 823e8a0a..0eae540e 100644
--- a/plugins/gatsby-remark-3perf-transformer/index.js
+++ b/plugins/gatsby-remark-3perf-transformer/index.js
@@ -5,6 +5,8 @@ const visitWithAncestors = require('unist-util-visit-parents');
const { parser } = require('posthtml-parser');
const mime = require('mime-types');
const createMediaMarkup = require('./createMediaMarkup');
+const cheerio = require('cheerio');
+const JSON5 = require('json5');
module.exports = ({ markdownAST }) => {
// Add a
wrapper around each table
@@ -117,9 +119,31 @@ module.exports = ({ markdownAST }) => {
},
);
+ visit(
+ markdownAST,
+ (node) => node.type === 'html' && parseHTMLCodeOptions(node)?.wordWrap,
+ (node) => {
+ const $ = cheerio.load(node.value);
+ const root = $('div.gatsby-highlight');
+
+ if (root.length === 0) {
+ throw new Error(
+ `gatsby-remark-3perf-transformer: Expected a div with class "gatsby-highlight" but got ${
+ $.root().children().first().prop('tagName') || 'no element'
+ } with class "${
+ $.root().children().first().attr('class') || 'no class'
+ }"`,
+ );
+ }
+
+ root.attr('data-word-wrap', '');
+ node.value = $.html();
+ },
+ );
+
// Spread lists that have new lines between list items
visitWithAncestors(markdownAST, 'list', (node, ancestors) => {
- // Don’t do anything with table-of-contents lists
+ // Don't do anything with table-of-contents lists
if (ancestors.some((ancestor) => ancestor.type === 'Toc')) {
return;
}
@@ -133,3 +157,39 @@ module.exports = ({ markdownAST }) => {
return markdownAST;
};
+
+function parseHTMLCodeOptions(node) {
+ // If you have Markdown like
+ // ```javascript{wordWrap: true}
+ // ...
+ // ```
+ // then node.lang will be everything before the space (`javascript{wordWrap:`) and node.meta will be everything after the space (`true`).
+ // Thus, we need to concatenate these to get the original string (`javascript{wordWrap: true}`).
+ // This mimics what gatsby-remark-prismjs does: https://github.com/gatsbyjs/gatsby/blob/346314d678d9465c998c12466f810b21592f6cb5/packages/gatsby-remark-prismjs/src/index.js#L38-L48
+ if (!node.lang) return;
+ const language = node.meta ? node.lang + node.meta : node.lang;
+ if (!language) return;
+
+ const optionsStringWithoutBraces = language.split('{')[1];
+ if (!optionsStringWithoutBraces) return;
+
+ const optionsString = '{' + optionsStringWithoutBraces;
+
+ try {
+ const optionsJson = JSON5.parse(optionsString);
+ return optionsJson;
+ } catch (e) {
+ console.error(
+ 'gatsby-remark-3perf-transformer: Failed to parse options for a code block:',
+ e,
+ );
+ console.error('gatsby-remark-3perf-transformer: Original node:', node);
+ console.error(
+ 'gatsby-remark-3perf-transformer: Options string:',
+ optionsString,
+ );
+ throw new Error(
+ `gatsby-remark-3perf-transformer: Failed to parse options for a code block: ${e.message}. See the error message above for more details.`,
+ );
+ }
+}
diff --git a/src/components/BlogFooterAccordion/index.tsx b/src/components/BlogFooterAccordion/index.tsx
index dc3b22ee..b0ce5045 100644
--- a/src/components/BlogFooterAccordion/index.tsx
+++ b/src/components/BlogFooterAccordion/index.tsx
@@ -10,15 +10,9 @@ const stuffToPromote = [
href: '/talks/web-perf-101/',
},
{
- imageKey: 'content',
- title: 'Case studies, guides, and open-source tools',
- href: '/content',
- },
-
- {
- imageKey: 'services',
- title: 'Make your site or web app fast',
- href: '/#services',
+ imageKey: 'reactConcurrency',
+ title: 'React Concurrency, Explained',
+ href: '/talks/react-concurrency/',
},
];
@@ -36,13 +30,8 @@ const BlogFooterAccordion = ({ className }: BlogFooterAccordionProps) => {
gatsbyImageData(height: 130, placeholder: NONE, layout: FIXED)
}
}
- services: file(relativePath: { eq: "BlogFooterAccordion/services.png" }) {
- childImageSharp {
- gatsbyImageData(height: 130, placeholder: NONE, layout: FIXED)
- }
- }
- content: file(
- relativePath: { eq: "BlogFooterAccordion/content-views.png" }
+ reactConcurrency: file(
+ relativePath: { eq: "BlogFooterAccordion/react-concurrency.png" }
) {
childImageSharp {
gatsbyImageData(height: 130, placeholder: NONE, layout: FIXED)
diff --git a/src/components/BlogFooterAccordion/react-concurrency.png b/src/components/BlogFooterAccordion/react-concurrency.png
new file mode 100644
index 00000000..7dfeb5f4
Binary files /dev/null and b/src/components/BlogFooterAccordion/react-concurrency.png differ
diff --git a/src/components/BlogFooterAccordion/services.png b/src/components/BlogFooterAccordion/services.png
deleted file mode 100644
index 9f8a7970..00000000
Binary files a/src/components/BlogFooterAccordion/services.png and /dev/null differ
diff --git a/src/components/HomeLeftRightWrapper/index.tsx b/src/components/HomeLeftRightWrapper/index.tsx
deleted file mode 100755
index 37301bb9..00000000
--- a/src/components/HomeLeftRightWrapper/index.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { JSXChildrenProp } from '../../types';
-import { Left, Right, Wrapper } from './styled';
-
-interface HomeLeftRightWrapperProps {
- alwaysVertical?: boolean;
- left: JSXChildrenProp;
- right: JSXChildrenProp;
- className?: string;
-}
-
-const HomeLeftRightWrapper = ({
- left,
- right,
- className = '',
- alwaysVertical = false,
-}: HomeLeftRightWrapperProps) => (
-
- {left}
- {right}
-
-);
-
-export default HomeLeftRightWrapper;
diff --git a/src/components/HomeLeftRightWrapper/styled.ts b/src/components/HomeLeftRightWrapper/styled.ts
deleted file mode 100755
index 974ecc31..00000000
--- a/src/components/HomeLeftRightWrapper/styled.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import styled, { css } from 'styled-components';
-import media from '../../styles/media';
-
-export const Left = styled.div``;
-
-export const Right = styled.div``;
-
-export const Wrapper = styled.section<{
- $alwaysVertical: boolean;
-}>`
- ${(props) =>
- !props.$alwaysVertical &&
- css`
- ${media.notSmall`
- display: flex;
-
- ${Left} {
- flex: 4;
- margin: 0 60px 24px 0;
- }
-
- ${Right} {
- flex: 7;
- }
- `}
- `}
-`;
diff --git a/src/components/HomeSectionAbout/factoid-bg.svg b/src/components/HomeSectionAbout/factoid-bg.svg
deleted file mode 100644
index 2d0b8fd5..00000000
--- a/src/components/HomeSectionAbout/factoid-bg.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/components/HomeSectionAbout/index.tsx b/src/components/HomeSectionAbout/index.tsx
deleted file mode 100755
index 036d9592..00000000
--- a/src/components/HomeSectionAbout/index.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import { graphql, useStaticQuery } from 'gatsby';
-import { GraphqlImage } from '../../types';
-import Section, { SectionKind } from '../Section';
-import {
- Intro,
- Image,
- Link,
- LinkWrapper,
- Primary,
- Links,
- Wrapper,
- Factoid,
- Number,
-} from './styled';
-
-interface AboutSectionData {
- talkImage: GraphqlImage;
-}
-
-interface AboutSectionProps {
- data: AboutSectionData;
-}
-
-const AboutSection = ({ data }: AboutSectionProps) => (
-
-);
-
-const AboutSectionWithQuery = () => {
- const data: AboutSectionData = useStaticQuery(graphql`
- {
- talkImage: file(
- relativePath: { eq: "HomeSectionAbout/talk_cropped.jpg" }
- ) {
- childImageSharp {
- gatsbyImageData(width: 727, placeholder: NONE, layout: CONSTRAINED)
- }
- }
- }
- `);
- return
;
-};
-
-export default AboutSectionWithQuery;
diff --git a/src/components/HomeSectionAbout/styled.ts b/src/components/HomeSectionAbout/styled.ts
deleted file mode 100644
index db7f6874..00000000
--- a/src/components/HomeSectionAbout/styled.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import styled from 'styled-components';
-import media from '../../styles/media';
-import { gridSize } from '../../styles/variables';
-import _Image from '../Image';
-import _Link from '../Link';
-import factoidBackground from './factoid-bg.svg';
-
-export const Intro = styled.div`
- margin-bottom: ${gridSize * 2}px;
-`;
-
-export const Image = styled(_Image)`
- max-width: 600px;
- display: block;
-`;
-
-export const Primary = styled.div``;
-export const Links = styled.div`
- ${media.small`
- display: none;
- `}
-`;
-
-export const Wrapper = styled.div`
- ${media.notSmall`
- display: flex;
-
- ${Primary} {
- flex: 7;
- margin: 0 60px 24px 0;
- }
-
- ${Links} {
- flex: 4;
- }
- `}
-`;
-
-export const LinkWrapper = styled.div`
- margin-bottom: ${gridSize}px;
-`;
-
-export const Link = styled(_Link)``;
-
-export const Factoid = styled.div`
- width: 150px;
- height: 150px;
- margin-top: ${gridSize * 3}px;
- padding: 30px;
- border-radius: 50%;
- background: url(${factoidBackground});
- background-size: cover;
- background-position: center;
-
- display: flex;
- text-align: center;
- justify-content: center;
- align-items: center;
- flex-direction: column;
-
- font-size: 12px;
-`;
-
-export const Number = styled.div`
- font-weight: 900;
- font-size: 32px;
-`;
diff --git a/src/components/HomeSectionClients/appsmith.svg b/src/components/HomeSectionClients/appsmith.svg
deleted file mode 100644
index 3d129299..00000000
--- a/src/components/HomeSectionClients/appsmith.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/src/components/HomeSectionClients/castor.svg b/src/components/HomeSectionClients/castor.svg
deleted file mode 100644
index 27e58663..00000000
--- a/src/components/HomeSectionClients/castor.svg
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/components/HomeSectionClients/common.svg b/src/components/HomeSectionClients/common.svg
deleted file mode 100644
index 89e2be35..00000000
--- a/src/components/HomeSectionClients/common.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
diff --git a/src/components/HomeSectionClients/fat-llama.svg b/src/components/HomeSectionClients/fat-llama.svg
deleted file mode 100755
index 1854ad8e..00000000
--- a/src/components/HomeSectionClients/fat-llama.svg
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
- image/svg+xml
-
-
-
-
-
-
-
-
-
diff --git a/src/components/HomeSectionClients/finder.svg b/src/components/HomeSectionClients/finder.svg
deleted file mode 100644
index 062a5fe6..00000000
--- a/src/components/HomeSectionClients/finder.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/src/components/HomeSectionClients/framer.svg b/src/components/HomeSectionClients/framer.svg
deleted file mode 100644
index 32f346f4..00000000
--- a/src/components/HomeSectionClients/framer.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
diff --git a/src/components/HomeSectionClients/google.svg b/src/components/HomeSectionClients/google.svg
deleted file mode 100755
index 1ee9372e..00000000
--- a/src/components/HomeSectionClients/google.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
\ No newline at end of file
diff --git a/src/components/HomeSectionClients/hugo.svg b/src/components/HomeSectionClients/hugo.svg
deleted file mode 100644
index 1aa9b66b..00000000
--- a/src/components/HomeSectionClients/hugo.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/src/components/HomeSectionClients/index.tsx b/src/components/HomeSectionClients/index.tsx
deleted file mode 100755
index e0f4d3e2..00000000
--- a/src/components/HomeSectionClients/index.tsx
+++ /dev/null
@@ -1,276 +0,0 @@
-import Section, { SectionKind } from '../Section';
-import {
- AppsmithLogo,
- CasesItemsContainer,
- CastorLogo,
- Content,
- FinderLogo,
- FramerLogo,
- GoogleLogo,
- HugoLogo,
- NdaLogo,
- LogoText,
- Mark,
- Tag,
- TagContainer,
-} from './styled';
-
-const ClientsSection = () => (
-
-
-
-
-
-
- education
-
-
-
- We worked with Google on educating developers about web performance.
- This targets the long-term goal of making the web faster.
-
-
-
-
-
- This is some of the highest quality tutorial style documentation on
- webpack I’ve come across
-
-
-
-
-
-
-
-
-
- Hugo helps to keep meeting notes
- and tasks in one place.
-
-
- react performance
- core web vitals
-
-
-
- The Hugo team was noticing their React app was slow here and there.
- They weren’t sure what was causing that, so they reached out to
- PerfPerfPerf for help.
-
-
- We identified multiple loading and runtime performance optimizations
- and guided Hugo through implementing them. As a result,{' '}
-
- Hugo’s JS init time decreased 2.5×, the editor input latency
- improved 1.7×,
- {' '}
- and the Lighthouse score (even not being a goal) increased by 23
- points.
-
-
-
- We were more than satisfied working with Ivan. Ivan communicated
- very well throughout the whole process, and{' '}
-
- the level of detail in his suggestions and recommendations was
- just outstanding
-
- .
-
- Christian Sampaio, CTO @ Hugo
-
-
-
-
-
-
-
- Finder helps 2 million
- Australian customers track spending, find deals, and save money.
-
-
- core web vitals
-
-
-
- A huge chunk of Finder’s new customers come to Finder from search.
- Finder wanted to make the site faster and grow their organic traffic,
- and they asked us to help.
-
-
- We helped Finder to move Core Web Vitals to green{' '}
- for 98% of their pages . As a result,{' '}
- Finder’s organic traffic grew 50% , and conversion also
- increased.
-
-
-
-
-
-
-
- Castor is a tech platform
- that makes clinical trials easier.
-
-
- react performance
- core web vitals
-
-
-
- Castor reached out to us when they were building a new product. They
- noticed the product’s bundle was too large, realized Castor’s design
- system caused this, but weren’t sure how to fix it.
-
-
- We jumped in and made the design system load faster. We also optimized
- the runtime performance of a few critical components, increasing their
- responsiveness from 1..2 to 20..30 FPS .
-
-
-
- In just a couple weeks, Ivan significantly improved runtime
- performance and tree shaking of our key components. One of our
- products’ bundle got 3 times smaller – with{' '}
- 10× less JS needed for the initial load .
-
-
-
-
-
-
-
-
-
- Appsmith is a low-code
- platform for building internal apps.
-
-
- react performance
-
-
-
- With Appsmith, we did three React rendering performance audits. This
- helped us to focus on different aspects of app’s UI speed. With audit
- recommendations implemented, the app got 1.9…2.8× faster {' '}
- across multiple user interactions.
-
-
-
-
-
-
-
- This customer was an enterprise marketing agency with an undisclosed
- name.
-
-
- lighthouse score
-
-
-
- PerfPerfPerf worked with an enterprise marketing agency to push its
- site’s PageSpeed Insights score from ~50 to ~95 . This was
- critical for converting customers – before the optimizations, some
- customers would see a poor score and churn away.
-
-
- We analyzed the site, found every issue affecting the score, figured
- out how to solve them, and guided the team through implementing the
- solutions.
-
-
-
- Ivan’s advice was all exceptionally clear.{' '}
-
- I really appreciated Ivan including his whole thought process,
- {' '}
- including what he tried that may not have worked – before arriving
- at a final solution.
-
-
-
- Ivan’s responsiveness was a godsend and a clear cut above any
- other consultants
- {' '}
- we’ve used in the past, and the overall level of organization +
- timetable he brought was truly fantastic!
-
- Ryan, Marketing Consultant
-
-
-
-
-
-
-
- Framer is an website design tool.
-
-
- react performance
- build performance
- core web vitals
-
-
-
- For Framer, higher performance means happier users and better
- retention.
-
-
- We worked with Framer to perfect the loading speed of Framer UI and
- Framer Sites. At the Framer UI,{' '}
-
- we reduced Speed Index and First CPU Idle of the UI by 40-45%
-
- . With Framer Sites, we’ve been working together to improve the Core
- Web Vitals and the Lighthouse score. We also improved the performance
- of internal tools, to make engineers more productive and save costs.
-
-
-
- We’ve been very satisfied by working with Ivan! Ivan helped us to
- improve loading performance, runtime performance, and tooling around
- it.{' '}
-
- The quality of his work, approach, documentation, etc. has been
- outstanding.
-
-
-
-
-
-
-
-);
-
-export default ClientsSection;
diff --git a/src/components/HomeSectionClients/nda.svg b/src/components/HomeSectionClients/nda.svg
deleted file mode 100644
index 81d5b776..00000000
--- a/src/components/HomeSectionClients/nda.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/components/HomeSectionClients/styled.ts b/src/components/HomeSectionClients/styled.ts
deleted file mode 100755
index e1c2172e..00000000
--- a/src/components/HomeSectionClients/styled.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-import styled from 'styled-components';
-import media from '../../styles/media';
-import { sizes, colors, gridSize } from '../../styles/variables';
-import appsmithUrl from './appsmith.svg';
-import castorUrl from './castor.svg';
-import finderUrl from './finder.svg';
-import framerUrl from './framer.svg';
-import googleUrl from './google.svg';
-import hugoUrl from './hugo.svg';
-import ndaUrl from './nda.svg';
-
-export const CasesItemsContainer = styled.div`
- column-gap: ${6 * gridSize}px;
-
- /* Keep the font size consistent across desktop and mobile */
- --homepage-font-size-regular: ${sizes.fontDefault}px;
- --homepage-font-size-small: ${sizes.fontSmall}px;
- font-size: var(--homepage-font-size-regular);
-
- ${media.small`
- columns: unset;
- `}
-
- ${media.notSmall`
- columns: 2;
- `}
-`;
-
-export const Content = styled.div`
- margin-bottom: ${9 * gridSize}px;
- break-inside: avoid;
- page-break-inside: avoid;
-
- ${media.small`
- &:last-child {
- margin-bottom: 0;
- }
- `}
-`;
-
-export const LogoText = styled.div`
- margin-bottom: ${sizes.paragraphSpacing}px;
- font-size: var(--homepage-font-size-small);
-`;
-
-const Logo = styled.img`
- display: block;
- margin: 0 0 12px;
- height: 48px;
-
- ${media.notSmall`
- margin-top: 10px;
- `};
-`;
-
-export const TagContainer = styled.div`
- margin-top: 8px;
- display: flex;
- flex-wrap: wrap;
- gap: 4px 8px;
-`;
-
-export const Tag = styled.div`
- flex: none;
- background: ${colors.brightYellow};
- padding: 2px 8px;
- border-radius: 2px;
-`;
-
-export const Mark = styled.mark`
- background: ${colors.softYellow};
-`;
-
-export const GoogleLogo = styled(Logo).attrs({
- src: googleUrl,
-})`
- margin-bottom: 4px;
-`;
-
-export const CastorLogo = styled(Logo).attrs({
- src: castorUrl,
-})``;
-
-export const AppsmithLogo = styled(Logo).attrs({
- src: appsmithUrl,
-})``;
-
-export const HugoLogo = styled(Logo).attrs({
- src: hugoUrl,
-})``;
-
-export const FramerLogo = styled(Logo).attrs({
- src: framerUrl,
-})``;
-
-export const FinderLogo = styled(Logo).attrs({
- src: finderUrl,
-})``;
-
-export const NdaLogo = styled(Logo).attrs({
- src: ndaUrl,
-})``;
diff --git a/src/components/HomeSectionContact/index.tsx b/src/components/HomeSectionContact/index.tsx
deleted file mode 100644
index e490dfe3..00000000
--- a/src/components/HomeSectionContact/index.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import { graphql, useStaticQuery } from 'gatsby';
-import { GraphqlImage } from '../../types';
-import {
- PromptContainer,
- Container,
- Contact,
- ContactImage,
- Link,
-} from './styled';
-
-interface ContactSectionData {
- iamakulov: GraphqlImage;
-}
-
-interface ContactSectionProps {
- className?: string;
-}
-
-const ContactSection = ({
- className = '',
- data,
-}: ContactSectionProps & { data: ContactSectionData }) => (
-
-
- book a call
-
-
- Interested? We’d be glad to help. Book a call with{' '}
-
- {' '}
- Ivan
-
- , and we’ll figure out how to get you fast.
-
-
- Or, prefer emails? Drop us a message at{' '}
- perf@3perf.com .
-
-
-
-
-);
-
-const ContactSectionWithQuery = (props: ContactSectionProps) => {
- const data: ContactSectionData = useStaticQuery(graphql`
- fragment ContactImage on File {
- childImageSharp {
- gatsbyImageData(
- width: 24
- height: 24
- quality: 75
- placeholder: NONE
- layout: FIXED
- )
- }
- }
-
- {
- iamakulov: file(
- sourceInstanceName: { eq: "shared" }
- relativePath: { eq: "iamakulov.jpg" }
- ) {
- ...ContactImage
- }
- }
- `);
-
- return
;
-};
-
-export default ContactSectionWithQuery;
diff --git a/src/components/HomeSectionContact/styled.tsx b/src/components/HomeSectionContact/styled.tsx
deleted file mode 100755
index 1c0236b9..00000000
--- a/src/components/HomeSectionContact/styled.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import styled from 'styled-components';
-import media from '../../styles/media';
-import { gridSize } from '../../styles/variables';
-import Image, { ImageProps } from '../Image';
-
-export const Container = styled.div`
- margin: 0 -60px;
- max-width: 900px;
- padding: 30px 100px 40px 60px;
- border-radius: 4px;
- background: #ffdb01;
- color: #000;
-
- --link-color: black;
- --link-border-color: rgba(0, 0, 0, 0.5);
-`;
-
-export const Link = styled.a`
- font-size: 84px;
- font-weight: bold;
- white-space: nowrap;
- border-bottom: none;
- text-decoration: underline;
-
- ${media.small`
- font-size: calc(10vw);
- `};
-`;
-
-export const PromptContainer = styled.div`
- margin-top: ${gridSize * 3}px;
-`;
-
-export const Contact = styled.span`
- white-space: nowrap;
-`;
-
-export const ContactImage = (props: ImageProps) => (
-
-);
diff --git a/src/components/HomeSectionMaterials/index.tsx b/src/components/HomeSectionMaterials/index.tsx
deleted file mode 100755
index 6c876809..00000000
--- a/src/components/HomeSectionMaterials/index.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-import { graphql, useStaticQuery } from 'gatsby';
-import { GraphqlImage } from '../../types';
-import HomeLeftRightWrapper from '../HomeLeftRightWrapper';
-import Link from '../Link';
-import Section, { SectionKind } from '../Section';
-import {
- Image,
- LinkWrapper,
- LinkBlock,
- LinkDescription,
- LinkText,
-} from './styled';
-
-interface MaterialsSectionData {
- webPerf101: GraphqlImage;
-}
-
-interface MaterialsSectionProps {
- data: MaterialsSectionData;
-}
-
-const MaterialsSection = ({ data }: MaterialsSectionProps) => (
-
-
-
- Web Performance 101
-
- A comprehensive guide into modern web performance
-
-
- }
- right={
- <>
-
-
- Case study: Analyzing Notion app performance
-
-
- How to make a React app load ~30% faster – by tuning some configs
- and delaying some scripts
-
-
-
-
- Preload, prefetch and other <link> tags
-
-
- HTML has as much as 5 different tags to preload something. This is
- a detailed deep-dive into each of them
-
-
-
-
- How to load polyfills only when needed
-
-
- We’ve been asked: “These days, how do you typically serve
- polyfills only to browsers that need them?”
-
-
-
- More →
-
- >
- }
- />
-
-);
-
-const MaterialsSectionWithQuery = () => {
- const data: MaterialsSectionData = useStaticQuery(graphql`
- {
- webPerf101: file(
- relativePath: { eq: "HomeSectionMaterials/web-perf-101.png" }
- ) {
- childImageSharp {
- gatsbyImageData(width: 600, placeholder: NONE, layout: CONSTRAINED)
- }
- }
- }
- `);
- return
;
-};
-
-export default MaterialsSectionWithQuery;
diff --git a/src/components/HomeSectionMaterials/styled.ts b/src/components/HomeSectionMaterials/styled.ts
deleted file mode 100644
index 3c4805d6..00000000
--- a/src/components/HomeSectionMaterials/styled.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import styled from 'styled-components';
-import media from '../../styles/media';
-import { linkStyles, linkActiveStyles } from '../../styles/shared-styles';
-import { gridSize } from '../../styles/variables';
-import GatsbyImage from '../Image';
-import Link from '../Link';
-
-export const LinkWrapper = styled.div`
- margin-bottom: ${gridSize}px;
-`;
-
-export const Image = styled(GatsbyImage)`
- /* Because gatsby-image uses inline styles */
- display: block !important;
- margin-bottom: ${gridSize}px;
-
- ${media.small`
- max-width: 250px;
- `}
-`;
-
-export const LinkText = styled.span``;
-
-export const LinkBlock = styled(Link)`
- color: inherit;
- display: block;
- border: none;
- margin-bottom: ${gridSize}px;
-
- ${LinkText} {
- ${linkStyles}
- }
-
- &:hover,
- &:focus,
- &:active {
- ${LinkText} {
- ${linkActiveStyles}
- }
- }
-`;
-
-export const LinkDescription = styled.div`
- font-size: var(--homepage-font-size-small);
-`;
diff --git a/src/components/HomeSectionMaterials/web-perf-101.png b/src/components/HomeSectionMaterials/web-perf-101.png
deleted file mode 100644
index c68c721c..00000000
Binary files a/src/components/HomeSectionMaterials/web-perf-101.png and /dev/null differ
diff --git a/src/components/HomeSectionServices/audit-desktop.png b/src/components/HomeSectionServices/audit-desktop.png
deleted file mode 100755
index bf570e65..00000000
Binary files a/src/components/HomeSectionServices/audit-desktop.png and /dev/null differ
diff --git a/src/components/HomeSectionServices/audit-mobile.png b/src/components/HomeSectionServices/audit-mobile.png
deleted file mode 100644
index 3df75311..00000000
Binary files a/src/components/HomeSectionServices/audit-mobile.png and /dev/null differ
diff --git a/src/components/HomeSectionServices/david.jpg b/src/components/HomeSectionServices/david.jpg
deleted file mode 100644
index e761630f..00000000
Binary files a/src/components/HomeSectionServices/david.jpg and /dev/null differ
diff --git a/src/components/HomeSectionServices/index.tsx b/src/components/HomeSectionServices/index.tsx
deleted file mode 100755
index 92e8b080..00000000
--- a/src/components/HomeSectionServices/index.tsx
+++ /dev/null
@@ -1,306 +0,0 @@
-import { graphql, useStaticQuery } from 'gatsby';
-import { GraphqlImage } from '../../types';
-import { SectionKind } from '../Section';
-import {
- ActionButton,
- Column,
- ImageText,
- DesktopImage,
- MobileImageWrapper,
- MobileImage,
- Note,
- Text,
- Columns,
- H3,
- Section,
- SvgMask,
- Blockquote,
- BlockquoteFooter,
- BlockquoteImage,
- BlockquoteName,
- BlockquoteTextWrapper,
-} from './styled';
-
-interface ServicesSectionProps {
- className?: string;
- data: {
- auditDesktop: GraphqlImage & {
- childImageSharp: { gatsbyImageData: { height: number } };
- };
- optimizationDesktop: GraphqlImage & {
- childImageSharp: { gatsbyImageData: { height: number } };
- };
- auditMobile: GraphqlImage;
- optimizationMobile: GraphqlImage;
- workshopMobile: GraphqlImage;
- workshopDesktop: GraphqlImage & {
- childImageSharp: { fixed: { height: number } };
- };
- davidAvatar: GraphqlImage;
- nicolasAvatar: GraphqlImage;
- piotrAvatar: GraphqlImage;
- };
-}
-
-const ServicesSection = ({ className = '', data }: ServicesSectionProps) => (
-
-
-
-
-
-
-
-
-
-
-
-
- Find What To Improve 🔬
-
- What: we’ll analyze the site, identify all
- performance issues, and tell you which ones matter the most & how
- to fix them.
-
-
- Great when: you need a step-by-step guide into
- improving your Core Web Vitals or app responsiveness.
-
-
-
-
- I want to marry that audit document! The level of detail,
- including going through our own codebase and pin-pointing where
- exactly improvements need to happen, it’s just... wow
-
-
-
-
- Piotr Krawiec · Product Engineer @
- Framer
-
-
-
- Format: An audit document with concrete actionable
- steps · Takes one-two weeks · One month of support afterwards (to make
- sure the implementation goes smoothly)
-
-
- Want an example? See these public case studies:{' '}
- Notion app ·{' '}
- Causal app
-
-
-
-
-
-
-
-
-
- Optimize the App 🛠
-
- What: we’ll join your project, roll up our sleeves,
- find all performance bottlenecks, and apply precise optimizations –
- all by ourselves.
-
-
- Great when: you don’t want to distract the team from
- delivering business functionality.
-
-
-
-
- In just a couple weeks, Ivan significantly improved runtime
- performance and tree shaking of our key components. One of our
- products’ bundle got 3 times smaller – with 10× less JS needed for
- the initial load!
-
-
-
-
-
- David Sigley
- {' '}
- · Head of Engineering @ Castor
-
-
-
-
-
-
-
-
-
-
- Grow The Team 🧑💻
-
- What: we’ll teach your team everything we know about
- React performance or Core Web Vitals.
-
-
- How this looks: we’ll take a bunch of slow sites or
- apps → profile each one → and make each one faster, learning multiple
- performance tools and techniques in the process.
-
-
- Great when: you want to learn how to keep your site
- fast on your own.
-
-
-
-
- Ivan is immensely knowledgeable in React, in performance and in
- the combination of the two. I really recommend PerfPerfPerf’s
- React workshop to developers interested in getting the most out of
- their apps and in understanding the inner workings of React
- profiling.
-
-
-
-
-
- Nicolás Delfino
- {' '}
- · Lead consultant & Performance competence lead @ 13|37
-
-
-
- Format: Covers React performance or Core Web
- Vitals · Online or offline · Takes 12-16 hours.{' '}
- More details
-
-
-
-
-
- Open Source 💛
-
- Are you a non-commercial open-source project? Our work wouldn’t be
- possible without open-source tools. Reach out, and, if we have
- capacity, we’ll be glad to help for free.
-
-
-
- Something Else 🎁
-
- Want something custom? We’ll be glad to help. Reach us, and let’s
- figure out how our experience can be relevant.
-
-
-
- Get a Quote
-
-);
-
-const ServicesSectionWithQuery = () => {
- const data: ServicesSectionProps['data'] = useStaticQuery(graphql`
- {
- auditDesktop: file(
- relativePath: { eq: "HomeSectionServices/audit-desktop.png" }
- ) {
- childImageSharp {
- gatsbyImageData(width: 900, placeholder: NONE, layout: FIXED)
- }
- }
- optimizationDesktop: file(
- relativePath: { eq: "HomeSectionServices/optimization-desktop.png" }
- ) {
- childImageSharp {
- gatsbyImageData(width: 900, placeholder: NONE, layout: FIXED)
- }
- }
- workshopDesktop: file(
- relativePath: { eq: "HomeSectionServices/workshop-desktop.png" }
- ) {
- childImageSharp {
- gatsbyImageData(width: 900, placeholder: NONE, layout: FIXED)
- }
- }
- auditMobile: file(
- relativePath: { eq: "HomeSectionServices/audit-mobile.png" }
- ) {
- childImageSharp {
- gatsbyImageData(width: 224, placeholder: NONE, layout: FIXED)
- }
- }
- optimizationMobile: file(
- relativePath: { eq: "HomeSectionServices/optimization-mobile.png" }
- ) {
- childImageSharp {
- gatsbyImageData(width: 224, placeholder: NONE, layout: FIXED)
- }
- }
- workshopMobile: file(
- relativePath: { eq: "HomeSectionServices/workshop-mobile.png" }
- ) {
- childImageSharp {
- gatsbyImageData(width: 224, placeholder: NONE, layout: FIXED)
- }
- }
-
- davidAvatar: file(relativePath: { eq: "HomeSectionServices/david.jpg" }) {
- childImageSharp {
- gatsbyImageData(height: 20, placeholder: NONE, layout: FIXED)
- }
- }
-
- nicolasAvatar: file(
- relativePath: { eq: "HomeSectionServices/nicolas.jpg" }
- ) {
- childImageSharp {
- gatsbyImageData(height: 20, placeholder: NONE, layout: FIXED)
- }
- }
-
- piotrAvatar: file(relativePath: { eq: "HomeSectionServices/piotr.jpg" }) {
- childImageSharp {
- gatsbyImageData(height: 20, placeholder: NONE, layout: FIXED)
- }
- }
- }
- `);
-
- return
;
-};
-
-export default ServicesSectionWithQuery;
diff --git a/src/components/HomeSectionServices/nicolas.jpg b/src/components/HomeSectionServices/nicolas.jpg
deleted file mode 100644
index 2c2d98de..00000000
Binary files a/src/components/HomeSectionServices/nicolas.jpg and /dev/null differ
diff --git a/src/components/HomeSectionServices/optimization-desktop.png b/src/components/HomeSectionServices/optimization-desktop.png
deleted file mode 100755
index 69723897..00000000
Binary files a/src/components/HomeSectionServices/optimization-desktop.png and /dev/null differ
diff --git a/src/components/HomeSectionServices/optimization-mobile.png b/src/components/HomeSectionServices/optimization-mobile.png
deleted file mode 100644
index 53c82698..00000000
Binary files a/src/components/HomeSectionServices/optimization-mobile.png and /dev/null differ
diff --git a/src/components/HomeSectionServices/piotr.jpg b/src/components/HomeSectionServices/piotr.jpg
deleted file mode 100644
index 3708b18c..00000000
Binary files a/src/components/HomeSectionServices/piotr.jpg and /dev/null differ
diff --git a/src/components/HomeSectionServices/styled.ts b/src/components/HomeSectionServices/styled.ts
deleted file mode 100755
index 87bdde93..00000000
--- a/src/components/HomeSectionServices/styled.ts
+++ /dev/null
@@ -1,201 +0,0 @@
-import styled, { css } from 'styled-components';
-import media from '../../styles/media';
-import { gridSize, sizes } from '../../styles/variables';
-import _ActionButton from '../ActionButton';
-import _Image from '../Image';
-import _Section from '../Section';
-
-const columnGapVertical = gridSize * 8;
-const columnGapHorizontal = gridSize * 8;
-
-export const Section = styled(_Section)``;
-
-export const SvgMask = styled.svg`
- width: 0;
- height: 0;
-`;
-
-export const MobileImageWrapper = styled.div``;
-
-export const MobileImage = styled(_Image)``;
-
-export const DesktopImage = styled(_Image)``;
-
-export const Text = styled.div``;
-
-interface ImageTextProps {
- $direction: 'forward' | 'reverse';
- $desktopImageHeight: number;
-}
-
-export const ImageText = styled.div
`
- display: flex;
- position: relative;
-
- &:not(:last-child) {
- margin-bottom: ${columnGapVertical}px;
- }
-
- ${media.small`
- flex-direction: column;
-
- ${MobileImageWrapper} {
- filter: drop-shadow(0 10px 20px rgba(0, 0, 0, 0.1));
- flex: none;
- }
-
- ${MobileImage} {
- clip-path: url(#serviceMobileMask);
- }
-
- ${DesktopImage} {
- display: none;
- }
-
- ${Text} {
- margin-top: ${gridSize * 3}px;
- }
- `}
-
- ${media.notSmall`
- min-height: ${(props: ImageTextProps) => props.$desktopImageHeight}px;
-
- ${MobileImageWrapper} {
- display: none;
- }
-
- ${DesktopImage} {
- position: absolute;
- flex: none;
- align-self: flex-start;
-
- ${(props: ImageTextProps) =>
- props.$direction === 'forward' &&
- css`
- right: 350px;
- margin-right: ${gridSize * 2}px;
- `}
-
- ${(props: ImageTextProps) =>
- props.$direction === 'reverse' &&
- css`
- left: 350px;
- margin-left: ${gridSize * 2}px;
- `}
- }
-
- ${Text} {
- margin: ${(props: ImageTextProps) =>
- props.$direction === 'forward' ? '0 0 0 auto' : '0 auto 0 0'};
- margin-top: ${gridSize * 4}px;
- width: 350px;
- }
- `}
-`;
-
-export const Note = styled.p`
- ${media.small`
- font-size: ${sizes.fontSmall}px;
- `}
-
- ${media.notSmall`
- font-size: ${sizes.fontDefault}px;
- `}
-`;
-
-export const Columns = styled.div`
- ${media.small`
- margin-top: ${columnGapVertical}px;
- `}
-
- ${media.notSmall`
- display: flex;
- margin: 0 -${columnGapHorizontal / 2}px;
- `}
-`;
-
-export const Column = styled.div`
- ${media.small`
- &:not(:last-child) {
- margin-bottom: ${columnGapVertical}px;
- }
- `}
-
- ${media.notSmall`
- flex: 1;
- margin: 0 ${columnGapHorizontal / 2}px;
- `}
-`;
-
-export const H3 = styled.h3`
- margin: 0 0 ${gridSize}px;
- font-size: 24px;
-`;
-
-export const ActionButton = styled(_ActionButton)`
- margin-top: ${gridSize * 8}px;
-`;
-
-export const Blockquote = styled.blockquote`
- border: none;
- padding: 0;
-
- /* Customize the desktop appearance */
- ${media.notSmall`
- font-size: var(--homepage-font-size-small);
-
- footer {
- font-size: ${sizes.fontSmall}px;
- }
- `}
-`;
-
-export const BlockquoteTextWrapper = styled.div`
- position: relative;
- padding: ${gridSize * 1.5}px ${gridSize * 2}px;
- background: white;
- border-radius: 8px;
- margin-left: -${gridSize * 1.5}px;
-
- &::after {
- content: '';
- position: absolute;
- bottom: -12px;
- left: ${gridSize * 2}px;
- width: 0;
- height: 0;
- border-top: 12px solid transparent;
- border-bottom: 12px solid transparent;
- border-left: 12px solid white;
- }
-`;
-
-const imageSize = 20; /* px */
-const imageAndNameSpacing = 6; /* px */
-const imageOffsetOutsideContainer = 4; /* 4 */
-export const BlockquoteFooter = styled.footer`
- margin-top: ${gridSize * 2.5}px;
- margin-left: ${imageSize +
- imageAndNameSpacing -
- imageOffsetOutsideContainer}px;
-
- /* Remove the default “—” prefix */
- &::before {
- content: none;
- }
-`;
-
-export const BlockquoteName = styled.span`
- color: black;
- display: inline-flex;
- align-items: center;
-`;
-
-export const BlockquoteImage = styled(_Image)`
- position: absolute;
- transform: translateX(-${imageSize + imageAndNameSpacing}px);
- flex: none;
- width: ${imageSize}px;
- height: ${imageSize}px;
- border-radius: 50%;
-`;
diff --git a/src/components/HomeSectionServices/workshop-desktop.png b/src/components/HomeSectionServices/workshop-desktop.png
deleted file mode 100644
index fbb16666..00000000
Binary files a/src/components/HomeSectionServices/workshop-desktop.png and /dev/null differ
diff --git a/src/components/HomeSectionServices/workshop-mobile.png b/src/components/HomeSectionServices/workshop-mobile.png
deleted file mode 100644
index 35f55758..00000000
Binary files a/src/components/HomeSectionServices/workshop-mobile.png and /dev/null differ
diff --git a/src/components/HomeSectionTestimonials/appsmith.svg b/src/components/HomeSectionTestimonials/appsmith.svg
deleted file mode 100644
index 2efe02c5..00000000
--- a/src/components/HomeSectionTestimonials/appsmith.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/src/components/HomeSectionTestimonials/castor.svg b/src/components/HomeSectionTestimonials/castor.svg
deleted file mode 100644
index 6127b39b..00000000
--- a/src/components/HomeSectionTestimonials/castor.svg
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/components/HomeSectionTestimonials/common.svg b/src/components/HomeSectionTestimonials/common.svg
deleted file mode 100644
index 6a7daec4..00000000
--- a/src/components/HomeSectionTestimonials/common.svg
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/components/HomeSectionTestimonials/fat-llama.svg b/src/components/HomeSectionTestimonials/fat-llama.svg
deleted file mode 100644
index c3b4b86f..00000000
--- a/src/components/HomeSectionTestimonials/fat-llama.svg
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/components/HomeSectionTestimonials/finder.svg b/src/components/HomeSectionTestimonials/finder.svg
deleted file mode 100644
index d3c5c0f8..00000000
--- a/src/components/HomeSectionTestimonials/finder.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/src/components/HomeSectionTestimonials/framer.svg b/src/components/HomeSectionTestimonials/framer.svg
deleted file mode 100644
index a266eb9b..00000000
--- a/src/components/HomeSectionTestimonials/framer.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/components/HomeSectionTestimonials/google.svg b/src/components/HomeSectionTestimonials/google.svg
deleted file mode 100644
index fe4a2eeb..00000000
--- a/src/components/HomeSectionTestimonials/google.svg
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/components/HomeSectionTestimonials/hugo.svg b/src/components/HomeSectionTestimonials/hugo.svg
deleted file mode 100644
index a3fd8af6..00000000
--- a/src/components/HomeSectionTestimonials/hugo.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/src/components/HomeSectionTestimonials/index.tsx b/src/components/HomeSectionTestimonials/index.tsx
deleted file mode 100644
index 3018dc72..00000000
--- a/src/components/HomeSectionTestimonials/index.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import appsmithLogo from './appsmith.svg';
-import castorLogo from './castor.svg';
-import commonLogo from './common.svg';
-import fatLlamaLogo from './fat-llama.svg';
-import finderLogo from './finder.svg';
-import framerLogo from './framer.svg';
-import googleLogo from './google.svg';
-import hugoLogo from './hugo.svg';
-import restreamLogo from './restream.svg';
-import sitepointLogo from './sitepoint.svg';
-import {
- Blockquote,
- BlockquoteText,
- BlockquoteFooter,
- Container,
- Logo,
- Logos,
-} from './styled';
-import togglLogo from './toggl.svg';
-
-const HomeSectionTestimonials = () => (
-
-
-
- A ton of extremely useful, actionable feedback that directly and
- significantly improved our First Meaningful Paint, Time to Interactive,
- [and] Speed Index
-
-
- — Cihat Imamoglu, Senior Software Engineer @{' '}
- Fat Llama
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-);
-
-export default HomeSectionTestimonials;
diff --git a/src/components/HomeSectionTestimonials/restream.svg b/src/components/HomeSectionTestimonials/restream.svg
deleted file mode 100644
index 4a742e67..00000000
--- a/src/components/HomeSectionTestimonials/restream.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/src/components/HomeSectionTestimonials/sitepoint.svg b/src/components/HomeSectionTestimonials/sitepoint.svg
deleted file mode 100644
index 2d0b072e..00000000
--- a/src/components/HomeSectionTestimonials/sitepoint.svg
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/components/HomeSectionTestimonials/styled.tsx b/src/components/HomeSectionTestimonials/styled.tsx
deleted file mode 100644
index 2a961666..00000000
--- a/src/components/HomeSectionTestimonials/styled.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import styled from 'styled-components';
-import media from '../../styles/media';
-import { colors, gridSize } from '../../styles/variables';
-
-export const Container = styled.div``;
-
-export const Blockquote = styled.blockquote`
- /* Reset inherited styles */
- all: unset;
-
- display: flex;
- margin: 0;
-
- ${media.small`
- flex-direction: column;
- `}
-`;
-
-export const BlockquoteText = styled.p`
- position: relative;
-
- flex: 3;
- margin-right: 36px;
-
- font-size: 32px;
-
- &::before {
- content: '“';
- position: absolute;
- color: ${colors.brightYellow};
- font-size: 120px;
- z-index: -1;
- font-weight: 900;
- top: -50px;
- left: -40px;
- }
-
- ${media.small`
- margin-right: 0;
- font-size: 24px;
- `}
-`;
-
-export const BlockquoteFooter = styled.footer`
- /* Reset inherited styles */
- all: unset;
-
- flex: 1;
-
- ${media.small`
- margin-top: 24px;
- `}
-
- &::before {
- /* Reset inherited styles */
- all: unset;
- }
-`;
-
-export const Logos = styled.div`
- display: flex;
- flex-wrap: wrap;
- gap: ${3 * gridSize}px ${8 * gridSize}px;
-
- margin-top: ${8 * gridSize}px;
-
- ${media.small`
- gap: ${4 * gridSize}px ${2 * gridSize}px;
- `}
-`;
-
-export const Logo = styled.img`
- ${media.small`
- height: 31px;
- width: auto;
- `}
-`;
diff --git a/src/components/HomeSectionTestimonials/toggl.svg b/src/components/HomeSectionTestimonials/toggl.svg
deleted file mode 100644
index 7a8be91a..00000000
--- a/src/components/HomeSectionTestimonials/toggl.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/components/Nav/index.tsx b/src/components/Nav/index.tsx
index abd4175d..1eccaa3a 100644
--- a/src/components/Nav/index.tsx
+++ b/src/components/Nav/index.tsx
@@ -3,14 +3,11 @@ import NavBase, { NavBaseProps } from '../NavBase';
const Nav = (props: NavBaseProps) => (
diff --git a/src/components/Section/index.tsx b/src/components/Section/index.tsx
deleted file mode 100755
index 93558a7f..00000000
--- a/src/components/Section/index.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import { JSXChildrenProp } from '../../types';
-import HomeLeftRightWrapper from '../HomeLeftRightWrapper';
-import { Content, H2 } from './styled';
-
-interface SectionProps {
- children: JSXChildrenProp;
- className?: string;
- sectionKind?: SectionKind;
- title: JSXChildrenProp;
-}
-
-export enum SectionKind {
- HORIZONTAL,
- VERTICAL,
-}
-
-const Section = ({
- title,
- children,
- className = '',
- sectionKind = SectionKind.HORIZONTAL,
-}: SectionProps) => (
- {title}}
- right={{children} }
- alwaysVertical={sectionKind === SectionKind.VERTICAL}
- />
-);
-
-export default Section;
diff --git a/src/components/Section/styled.ts b/src/components/Section/styled.ts
deleted file mode 100755
index e38c7412..00000000
--- a/src/components/Section/styled.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import styled from 'styled-components';
-import media from '../../styles/media';
-
-export const H2 = styled.h2`
- margin: 0 0 24px;
- font-size: 48px;
- line-height: 1.2;
-
- ${media.small`
- font-size: 32px;
- `};
-`;
-
-export const Content = styled.div`
- padding-top: 12px;
-`;
diff --git a/src/content/blog/packer/cover-facebook.jpg b/src/content/blog/packer/cover-facebook.jpg
new file mode 100644
index 00000000..b50690f8
Binary files /dev/null and b/src/content/blog/packer/cover-facebook.jpg differ
diff --git a/src/content/blog/packer/cover-twitter.jpg b/src/content/blog/packer/cover-twitter.jpg
new file mode 100644
index 00000000..f162c0ad
Binary files /dev/null and b/src/content/blog/packer/cover-twitter.jpg differ
diff --git a/src/content/blog/packer/index.md b/src/content/blog/packer/index.md
new file mode 100644
index 00000000..f87c6f81
--- /dev/null
+++ b/src/content/blog/packer/index.md
@@ -0,0 +1,373 @@
+---
+title: 'Performance Archaeology: Packer.js, a JS Minifier from 2004'
+alternativeTitles:
+ social: 'Performance Archaeology: Packer.js'
+ seo: 'Performance Archaeology: Packer.js, a JS Minifier from 2004'
+author:
+ id: iamakulov
+ name: Ivan Akulov
+ link: https://iamakulov.com
+ twitterId: iamakulov
+ facebookId: '100002052594007'
+description: 'Reverse-engineering a surprisingly effective JS minifier from 2004'
+rssDescription: |
+ Back in 2018, while doing a performance audit for a client, I stumbled upon an unusually-looking piece of code:
+
+ eval(function(p,a,c,k,e,r){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String))...
+
+ This code was weird. Why an eval()? What’s with p,a,c,k,e,r which clearly combines into some name? I couldn’t help but dig into that, and the trip took me all the way back to 2004.
+socialImage:
+ facebook: './cover-facebook.jpg'
+ twitter: './cover-twitter.jpg'
+date:
+ published: 2025-03-23T20:00:00
+ modified: 2025-03-23T20:00:00
+---
+
+Back in 2018, when doing a performance audit for a client, I stumbled upon an unusually-looking piece of code:
+
+```javascript{wordWrap: true}
+/*!
+ * Head JS
+ * Copyright Tero Piirainen
+ * License MIT
+ * Version 1.0.3
+ *
+ * https://github.com/headjs/headjs
+ */
+eval(function(p,a,c,k,e,r){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(newRegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(7(n,t){"1A 1B";7 r(n){a[a.A]=n}7 k(n){m t=31 32(" ?\\\\b"+n+"\\\\b");c.19=c.19.29(t,"")}7 p(n,t){C(m i=0,r=n.A;in?(i.Q.V&&r("V-"+n),i.Q.E&&r("E-"+n)):tt);u.G("1D",f
+```javascript{wordWrap: true}
+eval(function(p,a,c,k,e,r){e=function(c){return c.toString(a)};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('6 4(2){5 3=0;7(5 1=0;1<2.8;++1)3+=2[1];9 3}a.b(4(c,d,e))',15,15,'|i|arguments|result|sum|var|function|for|length|return|console|log|10|20|30'.split('|'),0,{}))
+```
+
+:::
+
+What does this code do? Let’s format it:
+
+```javascript
+eval(
+ (function (p, a, c, k, e, r) {
+ e = function (c) {
+ return c.toString(a);
+ };
+ if (!''.replace(/^/, String)) {
+ while (c--) r[e(c)] = k[c] || e(c);
+ k = [
+ function (e) {
+ return r[e];
+ },
+ ];
+ e = function () {
+ return '\\w+';
+ };
+ c = 1;
+ }
+ while (c--)
+ if (k[c]) p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c]);
+ return p;
+ })(
+ '6 4(2){5 3=0;7(5 1=0;1<2.8;++1)3+=2[1];9 3}a.b(4(c,d,e))',
+ 15,
+ 15,
+ '|i|arguments|result|sum|var|function|for|length|return|console|log|10|20|30'.split(
+ '|',
+ ),
+ 0,
+ {},
+ ),
+);
+```
+
+and try to understand what each of its parts does:
+
+- **High-level structure**
+
+ ```javascript
+ eval(
+ (function (p, a, c, k, e, r) {...})
+ (...)
+ )
+ ```
+
+ At the high level, the code declares a function (`function (p, a, c, k, e, r)`), immediately calls it with some arguments, and then passes the return value into `eval()`.
+
+ `p`, `a`, `c`, `k`, `e`, and `r` are the function’s arguments, and they receive the following values:
+
+- **Argument 1 (`p`)**
+
+ ```javascript{wordWrap: true}
+ /* p = */ '6 4(2){5 3=0;7(5 1=0;1<2.8;++1)3+=2[1];9 3}a.b(4(c,d,e))';
+ ```
+
+ This is the minified version of the original code! Note how
+
+ - all special characters in the original code remain unchanged in the minified code: `for (var i = 0; i < arguments.length; ++i)` becomes `7(5 1=0;1<2.8;++1)`
+ - but all keywords are replaced with numbers or letters: `function sum(arguments)` becomes `6 4(2)`, and `console.log()` becomes `a.b(...)`
+
+ What do these numbers (`6`, `4`, `2`, etc) and letters (`a`, `b`, etc) correspond to? These seem to come from...
+
+- **Argument 4 (`k`)**
+
+ ```javascript
+ /* k = */ "|i|arguments|result|sum|var|function|for|length|return|console|log|10|20|30"
+ .split("|"),
+
+ // Results in
+ // ['', 'i', 'arguments', 'result', 'sum', 'var',
+ // 'function', 'for', 'length', 'return', 'console',
+ // 'log', '10', '20', '30']
+ ```
+
+ These are keywords extracted from the original code.
+
+ When Packer minifies `function sum(arguments)`, it extracts each keyword, puts it into this array, and replaces each keyword with its index in the array (in [base-62 encoding](https://en.wikipedia.org/wiki/Base62)). So `function sum(arguments)` becomes `6 4(2)`, and `console.log(...)` becomes `a.b(...)`.
+
+ Why is item 0 in this array empty? That’s because number `0` is already used in the original code (`var result = 0`). Keeping that number as-is results in smaller code than replacing it with an index.
+
+- **Arguments 2 (`a`) and 3 (`c`)**
+
+ ```javascript
+ /* a = */ 15,
+ /* c = */ 15,
+ ```
+
+ These arguments specify extra details about the keywords array above.
+
+ Argument 2 is the base of the encoding used to index keywords. A few paragraphs above, I said keywords are encoded using base-62 encoding, but that’s not actually true. Technically, the base of the encoding is variable, and can be anything from 1 to 62. In practice, it always equals the number of keywords – unless there are more than 62 keywords, in which case it’s just 62. This is equivalent to fixed base-62 encoding, so I’m not sure why 62 isn’t just hard-coded.
+
+ Argument 3 is the number of all keywords in the array.
+
+- **Arguments 5 (`e`) and 6 (`r`)**
+
+ ```javascript
+ /* e = */ 0,
+ /* r = */ {},
+ ```
+
+ Arguments 5 and 6 are always `0` and `{}`. They’re used to store some intermediate data.
+
+- **Runtime**
+
+ ```javascript
+ function (p, a, c, k, e, r) {
+ e = function (c) {
+ return c.toString(a);
+ };
+ if (!"".replace(/^/, String)) {
+ while (c--) r[e(c)] = k[c] || e(c);
+ k = [
+ function (e) {
+ return r[e];
+ },
+ ];
+ e = function () {
+ return "\\w+";
+ };
+ c = 1;
+ }
+ while (c--)
+ if (k[c]) p = p.replace(new RegExp("\\b" + e(c) + "\\b", "g"), k[c]);
+ return p;
+ }
+ ```
+
+ Finally, this is the _runtime_ of Packer. It takes the minified code ("6 4(2){5 3=0; 7(5 1=0;1<2.8;++1) 3+=2[1]; 9 3} a.b(4(c,d,e))") and the list of keywords ("|i |arguments |result |sum |var |function |for |length |return |console |log |10 |20 |30") – and replaces each identifier in the first string with the corresponding keyword from the second string. Once it’s done, it returns the unminified code string:
+
+ ```javascript{wordWrap: true}
+ 'function sum(arguments){var result = 0;for(var i=0;i
+```javascript
+const a={name:"Biba",age:3,color:"black"}
+const b={name:"Rex",age:4,color:"brown"}
+const c={name:"Tweety",age:2,color:"yellow"}
+console.log(a,b,c)
+```
+
+Packer, however, will get you down to this:
+
+```javascript
+eval(function(p,a,c,k,e,r){...}(
+ '0 7={1:"a",5:3,6:"b"}0 8={1:"c",5:4,6:"d"}0 9={1:"e",5:2,6:"f"}g.h(7,8,9)',
+ 18,
+ 18,
+ 'const|name||||age|color|cat|dog|bird|Biba|black|Rex|brown|Tweety|yellow|console|log'.split('|'),
+ 0,
+ {}
+))
+```
+
+Notice how `const`, `name`, `age`, and `color` (which are repeated over and over in the Terser version) get replaced with a single number in the Packer version? That‘s what ultimately helps the Packer version to be smaller.
+
+:::sidenote[Of course, it’s not always Gzip – it [could also be Brotli or zstd](https://paulcalvano.com/2024-03-19-choosing-between-gzip-brotli-and-zstandard-compression/). But I’m not getting into the trenches here.]
+Does this mean you should use Packer as your minifier of choice? Absolutely not. Every modern properly configured server applies yet another level of compression to any text file it sends. This compression is called Gzip, and it [does the same thing Packer does](https://3perf.com/talks/web-perf-101/#http-brotli-1): deduplicates repeated strings. Except it’s _much_ more effective at that:
+
+```shell
+# Terser
+$ cat ./jquery-1.3.2.terser.min.js | wc -c # Size before gzip
+55120
+$ cat ./jquery-1.3.2.terser.min.js | gzip-size # Size after gzip
+18.6 kB
+
+# Packer
+$ cat ./jquery-1.3.2.packer.min.js | wc -c # Size before gzip
+41022
+$ cat ./jquery-1.3.2.terser.min.js | gzip-size # Size after gzip
+20.6 kB
+```
+
+And this is just the size aspect. Packer relies on `eval()`, and `eval()` is terrible because it’s unsafe, [incompatible with Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#unsafe_eval_expressions), [disables some V8 optimizations](https://groups.google.com/g/v8-users/c/jlISWv1nXWU), and so on.
+
+# Bonus: Packer is ES2015-Compatible
+
+Fun fact: Packer is ES2015+-compatible! Packing
+
+```javascript
+function* getAnimals() {
+ yield { name: 'Biba', age: 3, color: 'black' };
+}
+
+for await (const animal of getAnimals()) {
+ console.log(animal);
+}
+```
+
+produces:
+
+```javascript
+eval(function(p,a,c,k,e,r){...}(
+ '2*0(){4{5:"6",7:3,8:"9"}}a b(c 1 d 0()){e.f(1)}',
+ 16,
+ 16,
+ 'getAnimals|animal|function||yield|name|Biba|age|color|black|for|await|const|of|console|log'.split('|'),
+ 0,
+ {}
+))
+```
+
+which evaluates with no issues. This is surprising for a tool from 2007, and is also pretty amusing given that [it took years](https://github.com/mishoo/UglifyJS/issues/448) for UglifyJS – the then-most-popular minifier – to support ES2015.
+
+How does Packer support ES2015+? Modern minifiers work by parsing a JavaScript file into [an abstract syntax tree](https://astexplorer.net/). Any new syntax they don’t recognize breaks that process. Packer, however, is incredibly simple; its implementation takes [less than a thousand lines of code](https://github.com/tholu/php-packer/blob/master/src/Packer.php), and the only thing it does is string manipulation. For this tool, any keywords – no matter if from ES3 or from ES2018 – are just words to replace! So as long as you don’t rely on [automatic semicolon insertion](https://en.wikibooks.org/wiki/JavaScript/Automatic_semicolon_insertion) a bit too much:
+
+```javascript
+function* getAnimals() {
+ yield { name: 'Biba', age: 3, color: 'black' };
+}
+
+for await (const animal of getAnimals()) {
+ console.log(animal);
+}
+
+// Gets packed into (roughly)
+//
+// function*getAnimals(){yield{name:"Biba",age:3,color:"black"}}
+// for await(const animal of getAnimals()){console.log(animal)}
+//
+// and works.
+//
+// But
+
+function* getAnimals() {
+ yield { name: 'Biba', age: 3, color: 'black' }; // no semicolon here
+ yield { name: 'Rex', age: 4, color: 'brown' }; // no semicolon here either
+ yield { name: 'Tweety', age: 2, color: 'yellow' };
+}
+
+for await (const animal of getAnimals()) {
+ console.log(animal);
+}
+
+// gets packed into (roughly)
+//
+// function*getAnimals(){yield{name:"Biba",age:3,color:"black"}yield{name:"Rex",age:4,color:"brown"}yield{name:"Tweety",age:2,color:"yellow"}}
+// for await(const animal of getAnimals()){console.log(animal)}
+//
+// and crashes with
+//
+// Uncaught SyntaxError: Unexpected identifier 'yield'
+//
+// because you need a `;` after the `yield`s
+```
+
+...Packer will just work.
diff --git a/src/pages/content/background.svg b/src/pages/background.svg
similarity index 100%
rename from src/pages/content/background.svg
rename to src/pages/background.svg
diff --git a/src/pages/content/index.tsx b/src/pages/content/index.tsx
deleted file mode 100644
index 018417e7..00000000
--- a/src/pages/content/index.tsx
+++ /dev/null
@@ -1,326 +0,0 @@
-import { graphql } from 'gatsby';
-import GatsbyImage from '../../components/Image';
-import Layout from '../../components/Layout';
-import { LogoKind } from '../../components/Logo';
-import { NavKind } from '../../components/NavBase';
-import WidthWrapper from '../../components/WidthWrapper';
-import { GraphqlImage, JSXChildrenProp } from '../../types';
-import facebookCoverUrl from './facebook-cover.png';
-import {
- Background,
- Badge,
- BadgeImage,
- Footer,
- Header,
- ItemDescription,
- ItemImage,
- ItemLink,
- ItemTitle,
- MailchimpSubscribe,
- Nav,
- Section,
- SectionHeader,
-} from './styled';
-import twitterCoverUrl from './twitter-cover.png';
-
-interface ContentPageProps {
- data: {
- reactConcurrency: GraphqlImage;
- webpackLibs: GraphqlImage;
- notion: GraphqlImage;
- reexports: GraphqlImage;
- polyfills: GraphqlImage;
- awesomeWebpackPerf: GraphqlImage;
- };
-}
-
-interface ContentItemProps {
- image?: GraphqlImage;
- title: string | JSXChildrenProp;
- description?: string | JSXChildrenProp;
- badge?: string | JSXChildrenProp;
- link: string;
-}
-const ContentItem = ({
- image,
- title,
- description,
- link,
- badge,
-}: ContentItemProps) => {
- return (
-
- {image && (
-
-
-
- )}
-
- {title}
-
- {badge && {badge} }
-
- {description && {description} }
-
- );
-};
-
-export function Head() {
- return (
- <>
- Talks, articles and tools by PerfPerfPerf
-
-
-
- >
- );
-}
-
-const ContentPage = (props: ContentPageProps) => {
- return (
-
-
-
-
-
- Case Studies
-
- Guides
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- References
-
-
-
- Performance tips & tricks for popular JS libraries.
- Created in collaboration with the Google Chrome team
-
- >
- }
- />
-
- A curated list of webpack tools for web performance
-
- >
- }
- />
-
-
- Non-webpack tools for web performance
- >
- }
- />
-
- Trivia
-
-
-
- /*#__PURE__*/}
- description="What it is and why it’s in every JS bundle – even though you likely never heard of it"
- />
-
- Antipattern: <link rel="preload">{' '}
- and fonts
- >
- }
- />
-
-
- Tools
-
-
-
- A webpack plugin that removes unused Moment locales and makes
- the bundle hundreds of KBs smaller
-
- >
- }
- />
-
-
-
-
-
-
- );
-};
-
-export default ContentPage;
-
-export const query = graphql`
- {
- reactConcurrency: file(
- relativePath: { eq: "talks/react-concurrency/cover.png" }
- ) {
- childImageSharp {
- gatsbyImageData(height: 150, placeholder: NONE, layout: FIXED)
- }
- }
- notion: file(relativePath: { eq: "content/notion-social.png" }) {
- childImageSharp {
- gatsbyImageData(height: 150, placeholder: NONE, layout: FIXED)
- }
- }
- webpackLibs: file(relativePath: { eq: "content/webpack-libs.png" }) {
- childImageSharp {
- gatsbyImageData(height: 150, placeholder: NONE, layout: FIXED)
- }
- }
- reexports: file(relativePath: { eq: "content/reexports.png" }) {
- childImageSharp {
- gatsbyImageData(height: 150, placeholder: NONE, layout: FIXED)
- }
- }
- polyfills: file(relativePath: { eq: "content/polyfills.png" }) {
- childImageSharp {
- gatsbyImageData(height: 150, placeholder: NONE, layout: FIXED)
- }
- }
- awesomeWebpackPerf: file(
- relativePath: { eq: "content/awesome-webpack-perf.png" }
- ) {
- childImageSharp {
- gatsbyImageData(height: 150, placeholder: NONE, layout: FIXED)
- }
- }
- }
-`;
diff --git a/src/pages/content/styled.ts b/src/pages/content/styled.ts
deleted file mode 100755
index 33918352..00000000
--- a/src/pages/content/styled.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-import styled from 'styled-components';
-import _Footer from '../../components/Footer';
-import Link from '../../components/Link';
-import _MailchimpSubscribe from '../../components/MailchimpSubscribe';
-import _Nav from '../../components/Nav';
-import backgroundUrl from '../../pages/content/background.svg';
-import media from '../../styles/media';
-import { linkActiveStyles, linkStyles } from '../../styles/shared-styles';
-import { colors, gridSize, sizes } from '../../styles/variables';
-
-export const Background = styled.div`
- background: #222;
- color: white;
- min-height: 100vh;
- overflow: hidden;
- font-size: ${sizes.fontLarge}px;
-
- --link-color: white;
- --link-border-color: rgba(255, 255, 255, 0.25);
-`;
-
-export const Nav = styled(_Nav)`
- margin-top: ${sizes.navTopMargin}px;
- margin-bottom: ${gridSize * 4}px;
- font-size: 16px;
-`;
-
-export const Header = styled.h1`
- margin: ${gridSize * 8}px 0 ${gridSize * 4}px;
- font-size: 60px;
- line-height: 1.2;
- color: ${colors.brightYellow};
- font-weight: 900;
-
- background: url('${backgroundUrl}') center bottom;
- background-size: cover;
- -webkit-background-clip: text;
- background-clip: text;
- -webkit-text-fill-color: transparent;
-
- ${media.small`
- font-size: 48px;
- `}
-`;
-
-export const Section = styled.div`
- columns: 2 300px;
- column-gap: ${gridSize * 6}px;
-`;
-
-export const SectionHeader = styled.h2`
- margin: ${gridSize * 8}px 0 ${gridSize * 4}px;
- font-size: 48px;
- font-weight: bold;
- line-height: 1;
-
- ${media.small`
- font-size: 36px;
- `}
-`;
-
-export const ItemLink = styled(Link)`
- display: block;
- border: none;
- margin-bottom: ${gridSize * 4}px;
- page-break-inside: avoid;
- break-inside: avoid;
-`;
-
-export const ItemTitle = styled.div`
- display: inline;
- font-weight: bold;
-
- ${linkStyles}
-
- ${ItemLink}:hover &,
- ${ItemLink}:focus &,
- ${ItemLink}:active & {
- ${linkActiveStyles}
- }
-`;
-
-export const ItemImage = styled.div``;
-
-export const ItemDescription = styled.div`
- margin-top: ${gridSize * 0.5}px;
- font-size: ${sizes.fontDefault}px;
-`;
-
-export const Footer = styled(_Footer)`
- margin-top: ${gridSize * 6}px;
- margin-bottom: ${gridSize * 2}px;
-`;
-
-export const BadgeImage = styled.img`
- display: block;
- margin-top: ${gridSize}px;
-`;
-
-export const Badge = styled.span`
- border-radius: 999px;
- padding: 2px 8px;
-
- border: 1px solid ${colors.brightYellow};
- background-size: 100px;
- color: ${colors.brightYellow};
-
- font-size: 10px;
- text-transform: uppercase;
- letter-spacing: 1px;
- white-space: nowrap;
- vertical-align: middle;
-`;
-
-export const MailchimpSubscribe = styled(_MailchimpSubscribe)`
- margin: ${gridSize * 6}px -${gridSize * 3}px;
- max-width: 600px;
- padding: ${gridSize * 2}px ${gridSize * 3}px;
- border-radius: 4px;
- background: rgba(255, 255, 255, 0.1);
-`;
diff --git a/src/pages/content/facebook-cover.png b/src/pages/facebook-cover.png
similarity index 100%
rename from src/pages/content/facebook-cover.png
rename to src/pages/facebook-cover.png
diff --git a/src/pages/index.styled.ts b/src/pages/index.styled.ts
index e2ba5b3c..ae633541 100755
--- a/src/pages/index.styled.ts
+++ b/src/pages/index.styled.ts
@@ -1,143 +1,147 @@
-'use client';
-
-import styled, { createGlobalStyle } from 'styled-components';
-import _ActionButton from '../components/ActionButton';
-import _ContactSection from '../components/HomeSectionContact';
-import _Link from '../components/Link';
+import styled from 'styled-components';
+import _Footer from '../components/Footer';
+import Image from '../components/Image';
+import Link from '../components/Link';
+import _MailchimpSubscribe from '../components/MailchimpSubscribe';
import _Nav from '../components/Nav';
import media from '../styles/media';
-import { linkActiveStyles } from '../styles/shared-styles';
-import { colors, sizes, gridSize } from '../styles/variables';
-
-const listAndBlockquoteInnerSpacing = 24;
-export const IndexPageGlobalStyles = createGlobalStyle`
- body {
- --homepage-font-size-regular: ${sizes.fontLarge}px;
- --homepage-font-size-small: ${sizes.fontDefault}px;
+import { linkActiveStyles, linkStyles } from '../styles/shared-styles';
+import { colors, gridSize, sizes } from '../styles/variables';
+import backgroundUrl from './background.svg';
- font-size: var(--homepage-font-size-regular);
-
- ${media.small`
- --homepage-font-size-regular: ${sizes.fontDefault}px;
- --homepage-font-size-small: ${sizes.fontSmall}px;
- `}
- }
-
- *:is(ul, blockquote, p) + *:is(ul, blockquote, p) {
- margin-top: ${sizes.paragraphSpacing}px;
- }
+export const Background = styled.div`
+ background: #151515;
+ color: white;
+ min-height: 100vh;
+ overflow: hidden;
+ font-size: ${sizes.fontLarge}px;
- ul {
- list-style: none;
- margin: 0;
- padding-left: ${listAndBlockquoteInnerSpacing}px;
- }
+ --link-color: white;
+ --link-border-color: rgba(255, 255, 255, 0.25);
+`;
- ul > li::before {
- content: '—';
- position: absolute;
- transform: translateX(-${listAndBlockquoteInnerSpacing}px);
- }
+export const Nav = styled(_Nav)`
+ margin-top: ${sizes.navTopMargin}px;
+ margin-bottom: ${gridSize * 4}px;
+ font-size: 16px;
+`;
- blockquote {
- margin: 0;
- padding: ${gridSize * 1.5}px 0 ${gridSize * 2}px
- ${listAndBlockquoteInnerSpacing - 4}px;
- border-left: 4px solid #eee;
- }
+export const Header = styled.h1`
+ margin: ${gridSize * 8}px 0 ${gridSize * 4}px;
+ font-size: 60px;
+ line-height: 1.2;
+ color: ${colors.brightYellow};
+ font-weight: 900;
- blockquote footer {
- margin-top: ${gridSize}px;
- font-size: var(--homepage-font-size-small);
- color: #777;
+ background: url('${backgroundUrl}') center bottom;
+ background-size: cover;
+ -webkit-background-clip: text;
+ background-clip: text;
+ -webkit-text-fill-color: transparent;
- &::before {
- content: '— ';
- }
- }
+ ${media.small`
+ font-size: 36px;
+ margin-bottom: ${gridSize * 2}px;
+ `}
`;
-const Background = styled.div`
- // Make the component wrap margins of nested elements
- overflow: hidden;
+export const Section = styled.div`
+ columns: 2 300px;
+ column-gap: ${gridSize * 6}px;
`;
-export const HeaderBackground = styled(Background)`
- margin-bottom: 60px;
- background: black;
-`;
+export const SectionHeader = styled.h2`
+ margin: ${gridSize * 8}px 0 ${gridSize * 4}px;
+ font-size: 48px;
+ font-weight: bold;
+ line-height: 1;
-export const ActionButton = styled(_ActionButton)`
- margin-bottom: 60px;
+ ${media.small`
+ font-size: 36px;
+ `}
`;
-export const NewArticleBackground = styled.div`
- background: ${colors.brightYellow};
- padding: ${gridSize}px 0;
+export const ItemLink = styled(Link)`
+ display: block;
+ border: none;
+ margin-bottom: ${gridSize * 4}px;
+ page-break-inside: avoid;
+ break-inside: avoid;
`;
-export const NewArticleLink = styled(_Link)`
- color: black;
- border-bottom-color: rgba(0, 0, 0, 0.25);
+export const ItemTitle = styled.div`
+ display: inline;
+ font-weight: bold;
+
+ ${linkStyles}
- &:hover,
- &:focus,
- &:active {
+ ${ItemLink}:hover &,
+ ${ItemLink}:focus &,
+ ${ItemLink}:active & {
${linkActiveStyles}
}
`;
-export const ServicesBackground = styled(Background)`
- padding: 60px 0 96px;
- background: ${colors.softYellow};
+export const ItemImage = styled.div``;
+
+export const ItemDescription = styled.div`
+ margin-top: ${gridSize * 0.5}px;
+ font-size: ${sizes.fontDefault}px;
`;
-export const FooterWrapper = styled(Background)`
- padding: 0 0 24px;
+export const Footer = styled(_Footer)`
+ margin-top: ${gridSize * 6}px;
+ margin-bottom: ${gridSize * 2}px;
`;
-export const Nav = styled(_Nav)`
- margin-top: ${sizes.navTopMargin}px;
- font-size: 16px;
+export const BadgeImage = styled.img`
+ display: block;
+ margin-top: ${gridSize}px;
`;
-export const Header = styled.header`
- margin: 120px 0 60px;
- max-width: 800px;
+export const Badge = styled.span`
+ border-radius: 999px;
+ padding: 2px 8px;
- ${media.small`
- margin-top: 100px;
- margin-bottom: 40px;
- `};
-`;
+ border: 1px solid ${colors.brightYellow};
+ background-size: 100px;
+ color: ${colors.brightYellow};
-export const H1 = styled.h1`
- margin: 0;
- font-size: 48px;
- line-height: 1.2;
- font-weight: 900;
- color: white;
+ font-size: 10px;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ white-space: nowrap;
+ vertical-align: middle;
+`;
- ${media.small`
- font-size: 36px;
- `}
+export const MailchimpSubscribe = styled(_MailchimpSubscribe)`
+ margin: ${gridSize * 6}px -${gridSize * 3}px;
+ max-width: 600px;
+ padding: ${gridSize * 2}px ${gridSize * 3}px;
+ border-radius: 4px;
+ background: rgba(255, 255, 255, 0.1);
`;
-export const Mark = styled.mark`
- background: inherit;
- color: ${colors.brightYellow};
- font: inherit;
+export const AvatarImage = styled(Image)`
+ width: 24px;
+ height: 24px;
+ border-radius: 50%;
+ overflow: hidden;
+ vertical-align: -4px;
`;
-export const SectionWrapper = styled.div<{ $marginBottom?: number }>`
- margin-bottom: ${(props) =>
- props.$marginBottom === undefined ? 80 : props.$marginBottom}px;
+export const NameLink = styled.a`
+ white-space: nowrap;
+
+ border-bottom: none;
`;
-export const ContactSection = styled(_ContactSection)`
- margin: 120px 0;
+export const Name = styled.span`
+ ${linkStyles}
- ${media.small`
- margin: 60px 0;
- `};
+ ${NameLink}:hover &,
+ ${NameLink}:focus &,
+ ${NameLink}:active & {
+ ${linkActiveStyles}
+ }
`;
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index 285e0d64..12f62990 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -1,150 +1,352 @@
import { graphql } from 'gatsby';
-import Footer from '../components/Footer';
-import AboutSection from '../components/HomeSectionAbout';
-import ClientsSection from '../components/HomeSectionClients';
-import MaterialsSection from '../components/HomeSectionMaterials';
-import ServicesSection from '../components/HomeSectionServices';
-import HomeSectionTestimonials from '../components/HomeSectionTestimonials';
+import { Suspense } from 'react';
+import GatsbyImage from '../components/Image';
import Layout from '../components/Layout';
import { LogoKind } from '../components/Logo';
import { NavKind } from '../components/NavBase';
import WidthWrapper from '../components/WidthWrapper';
+import type { GraphqlImage, JSXChildrenProp } from '../types';
+import facebookCoverUrl from './facebook-cover.png';
import {
- ActionButton,
- FooterWrapper,
- ContactSection,
+ Background,
+ Badge,
+ BadgeImage,
+ Footer,
Header,
- HeaderBackground,
- H1,
+ ItemDescription,
+ ItemImage,
+ ItemLink,
+ ItemTitle,
+ MailchimpSubscribe,
Nav,
- NewArticleBackground,
- NewArticleLink,
- SectionWrapper,
- ServicesBackground,
- IndexPageGlobalStyles,
- Mark,
+ Section,
+ SectionHeader,
+ AvatarImage,
+ NameLink,
+ Name,
} from './index.styled';
+import twitterCoverUrl from './twitter-cover.png';
-interface IndexPageProps {
+interface ContentPageProps {
data: {
- allMarkdownRemark: {
- edges: [
- {
- node: {
- fields: {
- slug: string;
- };
- frontmatter: {
- title: string;
- };
- };
- },
- ];
- };
+ reactConcurrency: GraphqlImage;
+ webpackLibs: GraphqlImage;
+ notion: GraphqlImage;
+ reexports: GraphqlImage;
+ polyfills: GraphqlImage;
+ awesomeWebpackPerf: GraphqlImage;
+ photo: GraphqlImage;
};
}
+interface ContentItemProps {
+ image?: GraphqlImage;
+ title: string | JSXChildrenProp;
+ description?: string | JSXChildrenProp;
+ badge?: string | JSXChildrenProp;
+ link: string;
+}
+
+const ContentItem = ({
+ image,
+ title,
+ description,
+ link,
+ badge,
+}: ContentItemProps) => {
+ return (
+
+ {image && (
+
+
+
+ )}
+
+ {title}
+
+ {badge && {badge} }
+
+ {description && {description} }
+
+ );
+};
+
export function Head() {
return (
<>
- PerfPerfPerf · Web performance consulting
-
-
+ Web Performance Talks, Articles and Tools | PerfPerfPerf
+
+
+
>
);
}
-const IndexPage = ({ data }: IndexPageProps) => (
-
-
-
-
-
-
-
- Make your site or web app fast → get more revenue
- & happier users
-
-
-
- Get a Quote
-
-
-
+const ContentPage = ({ data }: ContentPageProps) => {
+ return (
+
+
- New article: {' '}
-
- {data.allMarkdownRemark.edges[0].node.frontmatter.title}
-
+
+ Web Performance, Explained
+
+ React and Core Web Vitals writing from{' '}
+
+ {' '}
+ Ivan Akulov
+
+ , a web performance engineer and a Google Developer Expert.
+
+
+ Backed by {new Date().getFullYear() - 2017} {' '}
+ years of doing performance work with Google, Framer, Toggl,
+ Restream, and many more companies.
+
+ Case Studies
+
+ Guides
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ References
+
+
+
+ Performance tips & tricks for popular JS libraries.
+ Created in collaboration with the Google Chrome team
+
+ >
+ }
+ />
+
+ A curated list of webpack tools for web performance
+
+ >
+ }
+ />
+
+
+ Non-webpack tools for web performance
+ >
+ }
+ />
+
+ Trivia
+
+
+
+ /*#__PURE__*/}
+ description="What it is and why it's in every JS bundle – even though you likely never heard of it"
+ />
+
+ Antipattern: <link rel="preload">{' '}
+ and fonts
+ >
+ }
+ />
+
+
+ Tools
+
+
+
+ A webpack plugin that removes unused Moment locales and makes
+ the bundle hundreds of KBs smaller
+
+ >
+ }
+ />
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-);
+
+
+ );
+};
-export default IndexPage;
+export default ContentPage;
export const query = graphql`
- # Retrieve the latest article
- query {
- allMarkdownRemark(
- filter: { fields: { slug: {}, sourceName: { eq: "blog" } } }
- sort: { frontmatter: { date: { published: DESC } } }
- limit: 1
+ {
+ reactConcurrency: file(
+ relativePath: { eq: "talks/react-concurrency/cover.png" }
) {
- edges {
- node {
- fields {
- slug
- }
- frontmatter {
- title
- }
- }
+ childImageSharp {
+ gatsbyImageData(height: 150, placeholder: NONE, layout: FIXED)
+ }
+ }
+ notion: file(relativePath: { eq: "notion-social.png" }) {
+ childImageSharp {
+ gatsbyImageData(height: 150, placeholder: NONE, layout: FIXED)
+ }
+ }
+ webpackLibs: file(relativePath: { eq: "webpack-libs.png" }) {
+ childImageSharp {
+ gatsbyImageData(height: 150, placeholder: NONE, layout: FIXED)
+ }
+ }
+ reexports: file(relativePath: { eq: "reexports.png" }) {
+ childImageSharp {
+ gatsbyImageData(height: 150, placeholder: NONE, layout: FIXED)
+ }
+ }
+ polyfills: file(relativePath: { eq: "polyfills.png" }) {
+ childImageSharp {
+ gatsbyImageData(height: 150, placeholder: NONE, layout: FIXED)
+ }
+ }
+ awesomeWebpackPerf: file(relativePath: { eq: "awesome-webpack-perf.png" }) {
+ childImageSharp {
+ gatsbyImageData(height: 150, placeholder: NONE, layout: FIXED)
+ }
+ }
+ photo: file(relativePath: { eq: "photo.jpg" }) {
+ childImageSharp {
+ gatsbyImageData(width: 24, height: 24, layout: FIXED, placeholder: NONE)
}
}
}
diff --git a/src/pages/content/notion-social.png b/src/pages/notion-social.png
similarity index 100%
rename from src/pages/content/notion-social.png
rename to src/pages/notion-social.png
diff --git a/src/pages/photo.jpg b/src/pages/photo.jpg
new file mode 100644
index 00000000..829d7468
Binary files /dev/null and b/src/pages/photo.jpg differ
diff --git a/src/pages/content/polyfills.png b/src/pages/polyfills.png
similarity index 100%
rename from src/pages/content/polyfills.png
rename to src/pages/polyfills.png
diff --git a/src/pages/content/reexports.png b/src/pages/reexports.png
similarity index 100%
rename from src/pages/content/reexports.png
rename to src/pages/reexports.png
diff --git a/src/pages/subscribe/background-lowres.jpg b/src/pages/subscribe/background-lowres.jpg
deleted file mode 100755
index 3256049f..00000000
Binary files a/src/pages/subscribe/background-lowres.jpg and /dev/null differ
diff --git a/src/pages/subscribe/background.jpg b/src/pages/subscribe/background.jpg
deleted file mode 100755
index f3780df0..00000000
Binary files a/src/pages/subscribe/background.jpg and /dev/null differ
diff --git a/src/pages/subscribe/index.tsx b/src/pages/subscribe/index.tsx
deleted file mode 100644
index d975f019..00000000
--- a/src/pages/subscribe/index.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import { RouteComponentProps } from '@reach/router';
-import { parse } from 'query-string';
-import Layout from '../../components/Layout';
-import { LogoKind } from '../../components/Logo';
-import WidthWrapper from '../../components/WidthWrapper';
-import {
- Background,
- Container,
- Footer,
- Header,
- MailchimpSubscribe,
- Nav,
-} from './styled';
-
-const getEmailFromQueryString = (queryString: string) => {
- const email = parse(queryString).email;
-
- if (!email) {
- return undefined;
- }
-
- // `email` may be an array if there’re multiple `&email=` specified
- if (Array.isArray(email)) {
- return email[0];
- }
-
- return email;
-};
-
-export const Head = () => (
- <>
- Subscribe · PerfPerfPerf
- >
-);
-
-const SubscribePage = ({ location }: RouteComponentProps) => {
- return (
-
-
-
-
-
-
-
- We’re PerfPerfPerf. We teach performance.
-
-
-
-
-
-
-
- );
-};
-
-export default SubscribePage;
diff --git a/src/pages/subscribe/styled.ts b/src/pages/subscribe/styled.ts
deleted file mode 100755
index 114ec32e..00000000
--- a/src/pages/subscribe/styled.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import styled from 'styled-components';
-import _Footer from '../../components/Footer';
-import _MailchimpSubscribe from '../../components/MailchimpSubscribe';
-import _Nav from '../../components/Nav';
-import backgroundLowresUrl from '../../pages/subscribe/background-lowres.jpg';
-import backgroundUrl from '../../pages/subscribe/background.jpg';
-import media from '../../styles/media';
-import { gridSize, sizes } from '../../styles/variables';
-
-export const Background = styled.div`
- min-height: 100vh;
- background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)),
- url(${backgroundUrl}), url(${backgroundLowresUrl});
- background-color: #111;
- background-size: cover;
- background-position: center top;
- background-attachment: fixed;
-
- color: white;
-
- --link-color: white;
- --link-border-color: rgba(255, 255, 255, 0.25);
-`;
-
-export const Container = styled.div`
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- min-height: 100vh;
-
- font-size: ${sizes.fontLarge}px;
-
- ${media.small`
- font-size: ${sizes.fontDefault}px;
- `}
-`;
-
-export const Nav = styled(_Nav)`
- margin-top: ${sizes.navTopMargin}px;
- margin-bottom: ${gridSize * 4}px;
- font-size: 16px;
-`;
-
-export const Header = styled.h1`
- margin: 0 0 ${gridSize * 3}px;
- font-size: 3em;
- line-height: 1;
-`;
-
-export const MailchimpSubscribe = styled(_MailchimpSubscribe)`
- margin-top: ${gridSize * 5}px;
- max-width: 600px;
- padding: ${gridSize * 2}px ${gridSize * 3}px;
- border-radius: 4px;
- background: rgba(255, 255, 255, 0.1);
-`;
-
-export const Footer = styled(_Footer)`
- margin-top: ${gridSize * 8}px;
- margin-bottom: ${gridSize * 2}px;
- font-size: ${sizes.fontSmall}px;
-`;
diff --git a/src/components/HomeSectionAbout/talk_cropped.jpg b/src/pages/talk.jpg
similarity index 100%
rename from src/components/HomeSectionAbout/talk_cropped.jpg
rename to src/pages/talk.jpg
diff --git a/src/pages/talks/quick-apps/index.tsx b/src/pages/talks/quick-apps/index.tsx
index 474f228b..991770e8 100755
--- a/src/pages/talks/quick-apps/index.tsx
+++ b/src/pages/talks/quick-apps/index.tsx
@@ -1,6 +1,5 @@
import styled from 'styled-components';
import Layout from '../../../components/Layout';
-import { LogoKind } from '../../../components/Logo';
import WidthWrapper from '../../../components/WidthWrapper';
import media from '../../../styles/media';
import { Footer, Nav } from '../styled';
@@ -25,7 +24,7 @@ const QuickAppsPage = () => {
return (
-
+
, performance.now()
- , and React Summit
+ , React Summit , and more
>
}
/>
diff --git a/src/pages/talks/web-perf-101/index.tsx b/src/pages/talks/web-perf-101/index.tsx
index d545389d..76fce2b6 100755
--- a/src/pages/talks/web-perf-101/index.tsx
+++ b/src/pages/talks/web-perf-101/index.tsx
@@ -1,7 +1,6 @@
import { graphql } from 'gatsby';
import { IGatsbyImageData } from 'gatsby-plugin-image';
import Layout from '../../../components/Layout';
-import { LogoKind } from '../../../components/Logo';
import WidthWrapper from '../../../components/WidthWrapper';
import { SlideGatsbyImage } from '../../../components/talks/Slide';
import TalkHeader from '../../../components/talks/TalkHeader';
@@ -181,7 +180,7 @@ const WebPerf101Page = ({ data }: WebPerf101PageProps) => {
return (
-
+
diff --git a/src/templates/blog/index.tsx b/src/templates/blog/index.tsx
index 18cdf416..8974e8a0 100644
--- a/src/templates/blog/index.tsx
+++ b/src/templates/blog/index.tsx
@@ -174,8 +174,8 @@ const Component = ({ data }: QueryProps) => {
)}
diff --git a/src/templates/blog/styled.ts b/src/templates/blog/styled.ts
index f1d736b0..a2c7dd75 100644
--- a/src/templates/blog/styled.ts
+++ b/src/templates/blog/styled.ts
@@ -96,10 +96,6 @@ const listStyles = css`
padding-left: 32px;
}
- ul {
- list-style-type: '— ';
- }
-
/* Duplicating the class to increase specificity */
p + .list_compact.list_compact,
.list_compact.list_compact + p {
@@ -184,6 +180,14 @@ const gatsbyHighlightStyles = css`
padding: 0;
background: unset;
}
+
+ &[data-word-wrap] {
+ > pre,
+ > pre > code {
+ white-space: pre-wrap;
+ overflow-wrap: break-word;
+ }
+ }
}
`;
diff --git a/src/templates/legal/styled.ts b/src/templates/legal/styled.ts
index c932c5e8..aecc39f5 100644
--- a/src/templates/legal/styled.ts
+++ b/src/templates/legal/styled.ts
@@ -81,10 +81,6 @@ const listStyles = css`
padding-left: 32px;
}
- ul {
- list-style-type: '— ';
- }
-
/* Duplicating the class to increase specificity */
p + .list_compact.list_compact,
p + .list_compact.list_compact {
diff --git a/static/_redirects b/static/_redirects
index 26ef4fd8..9b9c6137 100644
--- a/static/_redirects
+++ b/static/_redirects
@@ -4,3 +4,4 @@
/talks/react18-concurrency /talks/react-concurrency
/rc /talks/react-concurrency
/perf101 /talks/web-perf-101
+/content /
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index c70e7f24..60bce9bc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -36,6 +36,7 @@ __metadata:
aws-lambda: ^1.0.6
babel-plugin-styled-components: ^2.1.4
block-elements: ^1.2.0
+ cheerio: ^1.0.0
classnames: ^2.2.6
cross-env: ^7.0.2
eslint: ^7.30.0
@@ -68,7 +69,7 @@ __metadata:
gatsby-transformer-sharp: ^5.11.0
invariant: ^2.2.4
js-yaml: ^3.14.0
- json5: ^2.2.1
+ json5: ^2.2.3
lodash: ^4.17.15
mdast-util-toc: ^5.0.3
mime-types: ^2.1.35
@@ -5975,9 +5976,9 @@ __metadata:
linkType: hard
"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001464, caniuse-lite@npm:^1.0.30001503":
- version: 1.0.30001516
- resolution: "caniuse-lite@npm:1.0.30001516"
- checksum: 044adf3493b734a356a2922445a30095a0f6de6b9194695cdf74deafe7bef658e85858a31177762c2813f6e1ed2722d832d59eee0ecb2151e93a611ee18cb21f
+ version: 1.0.30001705
+ resolution: "caniuse-lite@npm:1.0.30001705"
+ checksum: 42c3b2164bc1f9522dccc80c9770f2825c229a3a6b2e7448c3c7bf330bba45dcb69496e32256a0dd6bf136a2f33617cb23883634132145d6787746a5770aba16
languageName: node
linkType: hard
@@ -6158,6 +6159,39 @@ __metadata:
languageName: node
linkType: hard
+"cheerio-select@npm:^2.1.0":
+ version: 2.1.0
+ resolution: "cheerio-select@npm:2.1.0"
+ dependencies:
+ boolbase: ^1.0.0
+ css-select: ^5.1.0
+ css-what: ^6.1.0
+ domelementtype: ^2.3.0
+ domhandler: ^5.0.3
+ domutils: ^3.0.1
+ checksum: 843d6d479922f28a6c5342c935aff1347491156814de63c585a6eb73baf7bb4185c1b4383a1195dca0f12e3946d737c7763bcef0b9544c515d905c5c44c5308b
+ languageName: node
+ linkType: hard
+
+"cheerio@npm:^1.0.0":
+ version: 1.0.0
+ resolution: "cheerio@npm:1.0.0"
+ dependencies:
+ cheerio-select: ^2.1.0
+ dom-serializer: ^2.0.0
+ domhandler: ^5.0.3
+ domutils: ^3.1.0
+ encoding-sniffer: ^0.2.0
+ htmlparser2: ^9.1.0
+ parse5: ^7.1.2
+ parse5-htmlparser2-tree-adapter: ^7.0.0
+ parse5-parser-stream: ^7.1.2
+ undici: ^6.19.5
+ whatwg-mimetype: ^4.0.0
+ checksum: ade4344811dcad5b5d78392506ef6bab1900c13a65222c869e745a38370d287f4b94838ac6d752883a84d937edb62b5bd0deaf70e6f38054acbfe3da4881574a
+ languageName: node
+ linkType: hard
+
"cheerio@npm:^1.0.0-rc.10":
version: 1.0.0-rc.10
resolution: "cheerio@npm:1.0.0-rc.10"
@@ -7255,6 +7289,19 @@ __metadata:
languageName: node
linkType: hard
+"css-select@npm:^5.1.0":
+ version: 5.1.0
+ resolution: "css-select@npm:5.1.0"
+ dependencies:
+ boolbase: ^1.0.0
+ css-what: ^6.1.0
+ domhandler: ^5.0.2
+ domutils: ^3.0.1
+ nth-check: ^2.0.1
+ checksum: 2772c049b188d3b8a8159907192e926e11824aea525b8282981f72ba3f349cf9ecd523fdf7734875ee2cb772246c22117fc062da105b6d59afe8dcd5c99c9bda
+ languageName: node
+ linkType: hard
+
"css-selector-parser@npm:^1.0.0, css-selector-parser@npm:^1.1.0":
version: 1.4.1
resolution: "css-selector-parser@npm:1.4.1"
@@ -7307,7 +7354,7 @@ __metadata:
languageName: node
linkType: hard
-"css-what@npm:^6.0.1":
+"css-what@npm:^6.0.1, css-what@npm:^6.1.0":
version: 6.1.0
resolution: "css-what@npm:6.1.0"
checksum: b975e547e1e90b79625918f84e67db5d33d896e6de846c9b584094e529f0c63e2ab85ee33b9daffd05bff3a146a1916bec664e18bb76dd5f66cbff9fc13b2bbe
@@ -8212,6 +8259,17 @@ __metadata:
languageName: node
linkType: hard
+"domutils@npm:^3.1.0":
+ version: 3.2.2
+ resolution: "domutils@npm:3.2.2"
+ dependencies:
+ dom-serializer: ^2.0.0
+ domelementtype: ^2.3.0
+ domhandler: ^5.0.3
+ checksum: ae941d56f03d857077d55dde9297e960a625229fc2b933187cc4123084d7c2d2517f58283a7336567127029f1e008449bac8ac8506d44341e29e3bb18e02f906
+ languageName: node
+ linkType: hard
+
"dot-case@npm:^3.0.4":
version: 3.0.4
resolution: "dot-case@npm:3.0.4"
@@ -8396,6 +8454,16 @@ __metadata:
languageName: node
linkType: hard
+"encoding-sniffer@npm:^0.2.0":
+ version: 0.2.0
+ resolution: "encoding-sniffer@npm:0.2.0"
+ dependencies:
+ iconv-lite: ^0.6.3
+ whatwg-encoding: ^3.1.1
+ checksum: 05ad76b674066e62abc80427eb9e89ecf5ed50f4d20c392f7465992d309215687e3ae1ae8b5d5694fb258f4517c759694c3b413d6c724e1024e1cf98750390eb
+ languageName: node
+ linkType: hard
+
"encoding@npm:^0.1.13":
version: 0.1.13
resolution: "encoding@npm:0.1.13"
@@ -8496,7 +8564,7 @@ __metadata:
languageName: node
linkType: hard
-"entities@npm:^4.2.0, entities@npm:^4.4.0":
+"entities@npm:^4.2.0, entities@npm:^4.4.0, entities@npm:^4.5.0":
version: 4.5.0
resolution: "entities@npm:4.5.0"
checksum: 853f8ebd5b425d350bffa97dd6958143179a5938352ccae092c62d1267c4e392a039be1bae7d51b6e4ffad25f51f9617531fedf5237f15df302ccfb452cbf2d7
@@ -12010,6 +12078,18 @@ __metadata:
languageName: node
linkType: hard
+"htmlparser2@npm:^9.1.0":
+ version: 9.1.0
+ resolution: "htmlparser2@npm:9.1.0"
+ dependencies:
+ domelementtype: ^2.3.0
+ domhandler: ^5.0.3
+ domutils: ^3.1.0
+ entities: ^4.5.0
+ checksum: e5f8d5193967e4a500226f37bdf2c0f858cecb39dde14d0439f24bf2c461a4342778740d988fbaba652b0e4cb6052f7f2e99e69fc1a329a86c629032bb76e7c8
+ languageName: node
+ linkType: hard
+
"http-cache-semantics@npm:3.8.1":
version: 3.8.1
resolution: "http-cache-semantics@npm:3.8.1"
@@ -12155,7 +12235,7 @@ __metadata:
languageName: node
linkType: hard
-"iconv-lite@npm:^0.6.2":
+"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3":
version: 0.6.3
resolution: "iconv-lite@npm:0.6.3"
dependencies:
@@ -13505,7 +13585,7 @@ __metadata:
languageName: node
linkType: hard
-"json5@npm:^2.1.2, json5@npm:^2.2.0, json5@npm:^2.2.1, json5@npm:^2.2.2":
+"json5@npm:^2.1.2, json5@npm:^2.2.0, json5@npm:^2.2.1, json5@npm:^2.2.2, json5@npm:^2.2.3":
version: 2.2.3
resolution: "json5@npm:2.2.3"
bin:
@@ -16339,6 +16419,25 @@ __metadata:
languageName: node
linkType: hard
+"parse5-htmlparser2-tree-adapter@npm:^7.0.0":
+ version: 7.1.0
+ resolution: "parse5-htmlparser2-tree-adapter@npm:7.1.0"
+ dependencies:
+ domhandler: ^5.0.3
+ parse5: ^7.0.0
+ checksum: 98326fc5443e2149e10695adbfd0b0b3383c54398799f858b4ac2914adb199af8fcc90c2143aa5f7fd5f9482338f26ef253b468722f34d50bb215ec075d89fe9
+ languageName: node
+ linkType: hard
+
+"parse5-parser-stream@npm:^7.1.2":
+ version: 7.1.2
+ resolution: "parse5-parser-stream@npm:7.1.2"
+ dependencies:
+ parse5: ^7.0.0
+ checksum: 75b232d460bce6bd0e35012750a78ef034f40ccf550b7c6cec3122395af6b4553202ad3663ad468cf537ead5a2e13b6727670395fd0ff548faccad1dc2dc93cf
+ languageName: node
+ linkType: hard
+
"parse5@npm:5.1.0":
version: 5.1.0
resolution: "parse5@npm:5.1.0"
@@ -16360,6 +16459,15 @@ __metadata:
languageName: node
linkType: hard
+"parse5@npm:^7.0.0, parse5@npm:^7.1.2":
+ version: 7.2.1
+ resolution: "parse5@npm:7.2.1"
+ dependencies:
+ entities: ^4.5.0
+ checksum: 11253cf8aa2e7fc41c004c64cba6f2c255f809663365db65bd7ad0e8cf7b89e436a563c20059346371cc543a6c1b567032088883ca6a2cbc88276c666b68236d
+ languageName: node
+ linkType: hard
+
"parseurl@npm:^1.3.3, parseurl@npm:~1.3.3":
version: 1.3.3
resolution: "parseurl@npm:1.3.3"
@@ -21420,6 +21528,13 @@ __metadata:
languageName: node
linkType: hard
+"undici@npm:^6.19.5":
+ version: 6.21.2
+ resolution: "undici@npm:6.21.2"
+ checksum: 4d7227910bfee0703ea5c5c9d4343bcb2a80d2ce2eb64698b6fb8cc48852e29f7c7c623126161a5073fd594c9040ae7e7ecc8e093fe6e84a9394dd2595754ec5
+ languageName: node
+ linkType: hard
+
"unherit@npm:^1.0.4":
version: 1.1.3
resolution: "unherit@npm:1.1.3"
@@ -22419,6 +22534,15 @@ __metadata:
languageName: node
linkType: hard
+"whatwg-encoding@npm:^3.1.1":
+ version: 3.1.1
+ resolution: "whatwg-encoding@npm:3.1.1"
+ dependencies:
+ iconv-lite: 0.6.3
+ checksum: f75a61422421d991e4aec775645705beaf99a16a88294d68404866f65e92441698a4f5b9fa11dd609017b132d7b286c3c1534e2de5b3e800333856325b549e3c
+ languageName: node
+ linkType: hard
+
"whatwg-mimetype@npm:^2.2.0, whatwg-mimetype@npm:^2.3.0":
version: 2.3.0
resolution: "whatwg-mimetype@npm:2.3.0"
@@ -22426,6 +22550,13 @@ __metadata:
languageName: node
linkType: hard
+"whatwg-mimetype@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "whatwg-mimetype@npm:4.0.0"
+ checksum: f97edd4b4ee7e46a379f3fb0e745de29fe8b839307cc774300fd49059fcdd560d38cb8fe21eae5575b8f39b022f23477cc66e40b0355c2851ce84760339cef30
+ languageName: node
+ linkType: hard
+
"whatwg-url@npm:^5.0.0":
version: 5.0.0
resolution: "whatwg-url@npm:5.0.0"