Skip to content

Commit

Permalink
Begining of video tag optimizations
Browse files Browse the repository at this point in the history
  • Loading branch information
georges-gomes committed Dec 11, 2024
1 parent 686f544 commit a94eedf
Showing 11 changed files with 228 additions and 114 deletions.
2 changes: 1 addition & 1 deletion packages/www/public/features/iframe/index.md
Original file line number Diff line number Diff line change
@@ -3,5 +3,5 @@ title: iframe
jampack: "--onlyoptim"
---

`jampack` lazy load iframes [below the fold](/features/optimize-above-the-fold/).
`jampack` lazy loads iframes below [the fold](/features/optimize-above-the-fold/).

16 changes: 16 additions & 0 deletions packages/www/public/features/video/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
title: Video
jampack: "--onlyoptim"
---

`jampack` optimize videos below [the fold](/features/optimize-above-the-fold/).

## Autoplay videos

Videos below the fold with attribute `autoplay` are lazy loaded using JavaScript.

## Click-to-play videos

Videos without `autoplay` and with a `poster` get a `preload="none"` attribute to postpone the loading of the video until user request.

As of today, `jampack` doesn't automatically create posters for video. It's a TODO.
27 changes: 27 additions & 0 deletions packages/www/public/features/video/source/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<h1>Lazy load videos below the fold</h1>
<video
src="https://cdn.divriots.com/f2w/motion1-v1.mp4"
autoplay=""
muted=""
loop=""
playsinline=""
webkit-playsinline=""
></video>
<the-fold></the-fold>
<div>The fold</div>
<video
src="https://cdn.divriots.com/f2w/motion3-v1.mp4"
autoplay=""
muted=""
loop=""
playsinline=""
webkit-playsinline=""
></video>
</body>
</html>
1 change: 1 addition & 0 deletions packages/www/src/config.ts
Original file line number Diff line number Diff line change
@@ -58,6 +58,7 @@ export const featuresDirs = [
'embed-small-images',
'images-max-width',
'inline-critical-css',
'video',
'iframe',
'prefetch-links',
'browser-compatibility',
6 changes: 6 additions & 0 deletions src/config-default.ts
Original file line number Diff line number Diff line change
@@ -67,6 +67,12 @@ const default_options: Options = {
how: 'native',
},
},
video: {
autoplay_lazyload: {
when: 'below-the-fold',
how: 'js',
},
},
misc: {
prefetch_links: 'off',
},
10 changes: 10 additions & 0 deletions src/config-types.ts
Original file line number Diff line number Diff line change
@@ -77,6 +77,16 @@ export type Options = {
| 'js'; // Using IntersectionObserver. Requires ~1Ko of JS but is more precise than native lazyload
};
};
video: {
autoplay_lazyload: {
// Only for videos with autoplay
when: // Default: 'below-the-fold'
| 'never' // All video are loaded eagerly
| 'below-the-fold' // videos are lazy loaded only if they are below the fold
| 'always'; // Not recommended
how: 'js'; // Using IntersectionObserver. Requires ~1Ko of JS
};
};
misc: {
prefetch_links: 'in-viewport' | 'off';
};
184 changes: 95 additions & 89 deletions src/optimize.ts
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ import { inlineCriticalCss } from './optimizers/inline-critical-css.js';
import { prefetch_links_in_viewport } from './optimizers/prefetch-links.js';
import { GlobalState } from './state.js';
import { processIframe } from './optimizers/process-iframe.js';
import { processVideo } from './optimizers/process-video.js';

const UNPIC_DEFAULT_HOST_REGEX = /^https:\/\/n\//g;
const ABOVE_FOLD_DATA_ATTR = 'data-abovethefold';
@@ -46,97 +47,36 @@ async function analyse(state: GlobalState, file: string): Promise<void> {

const theFold = getTheFold($);

const imgs = $('img');
const imgsArray: cheerio.Element[] = [];
imgs.each((index, imgElement) => {
imgsArray.push(imgElement);
});

// Process images sequentially
const spinnerImg = ora({ prefixText: ' ' }).start();
for (let i = 0; i < imgsArray.length; i++) {
const imgElement = imgsArray[i];

spinnerImg.text = kleur.dim(
`<img> [${i + 1}/${imgsArray.length}] ${$(imgElement).attr('src')}`
);

const img = $(imgElement);
const isAboveTheFold = isElementAboveTheFold(img, imgElement, theFold);
img.removeAttr(ABOVE_FOLD_DATA_ATTR);

try {
await processImage(state, file, img, isAboveTheFold);
} catch (e) {
state.reportIssue(file, {
type: 'erro',
msg:
(e as Error).message ||
`Unexpected error while processing image: ${JSON.stringify(e)}`,
});
}
}

// Reset spinner
spinnerImg.text = kleur.dim(
`<img> [${imgsArray.length}/${imgsArray.length}]`
await processTag(
state,
file,
$,
'img',
'src',
processImage,
theFold,
appendToBody
);

// Notify issues
const issues = state.issues.get(file);
if (issues) {
spinnerImg.fail();
console.log(
kleur.red(` ${issues.length} issue${issues.length > 1 ? 's' : ''}`)
);
} else {
spinnerImg.succeed();
}

const iframes = $('iframe');
const iframesArray: cheerio.Element[] = [];
iframes.each((index, ifElement) => {
iframesArray.push(ifElement);
});

// Process iframes sequentially
const spinnerIframe = ora({ prefixText: ' ' }).start();
for (let i = 0; i < iframesArray.length; i++) {
const ifElement = iframesArray[i];

spinnerIframe.text = kleur.dim(
`<iframe> [${i + 1}/${iframesArray.length}] ${$(ifElement).attr('src')}`
);

const ifr = $(ifElement);
const isAboveTheFold = isElementAboveTheFold(ifr, ifElement, theFold);
ifr.removeAttr(ABOVE_FOLD_DATA_ATTR);

try {
await processIframe(
state,
file,
$,
ifElement,
isAboveTheFold,
appendToBody
);
} catch (e) {
state.reportIssue(file, {
type: 'erro',
msg:
(e as Error).message ||
`Unexpected error while processing image: ${JSON.stringify(e)}`,
});
}
}

// Reset spinner
spinnerIframe.text = kleur.dim(
`<iframe> [${iframesArray.length}/${iframesArray.length}]`
await processTag(
state,
file,
$,
'iframe',
'src',
processIframe,
theFold,
appendToBody
);
await processTag(
state,
file,
$,
'video',
'src',
processVideo,
theFold,
appendToBody
);

spinnerIframe.succeed();

// Remove the fold
//
@@ -1023,3 +963,69 @@ export async function optimize(
await analyse(state, file);
}
}

async function processTag(
state: GlobalState,
file: string,
$: cheerio.CheerioAPI,
tag: 'img' | 'iframe' | 'video',
attribute_to_log: string,
processor: (
state: GlobalState,
file: string,
tag: cheerio.Cheerio<cheerio.Element>,
isAboveTheFold: boolean,
appendToBody: Record<string, string>
) => Promise<void>,
theFold: number,
appendToBody: Record<string, string>
) {
const previous_issues = state.issues.get(file);

const tags: cheerio.Cheerio<cheerio.Element> = $(tag);
const tagsArray: cheerio.Element[] = [];
tags.each((index, tagElement) => {
tagsArray.push(tagElement);
});

// Process tags sequentially
const spinnerImg = ora({ prefixText: ' ' }).start();
for (let i = 0; i < tagsArray.length; i++) {
const tagElement = tagsArray[i];

spinnerImg.text = kleur.dim(
`<${tag}> [${i + 1}/${tagsArray.length}] ${$(tagElement).attr(
attribute_to_log
)}`
);

const el = $(tagElement);
const isAboveTheFold = isElementAboveTheFold(el, tagElement, theFold);
el.removeAttr(ABOVE_FOLD_DATA_ATTR);

try {
await processor(state, file, el, isAboveTheFold, appendToBody);
} catch (e) {
state.reportIssue(file, {
type: 'erro',
msg:
(e as Error).message ||
`Unexpected error while processing ${tag}: ${JSON.stringify(e)}`,
});
}
}

// Reset spinner
spinnerImg.text = kleur.dim(
`<${tag}> [${tagsArray.length}/${tagsArray.length}]`
);

// Notify issues
const issues = state.issues.get(file);
const newIssues = (issues?.length || 0) - (previous_issues?.length || 0);
if (newIssues > 0) {
spinnerImg.fail();
} else {
spinnerImg.succeed();
}
}
26 changes: 3 additions & 23 deletions src/optimizers/process-iframe.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import * as cheerio from '@divriots/cheerio';
import type { GlobalState } from '../state.js';
import { install_dependency } from '../utils/install-dep.js';
import { install_dependency, install_lozad } from '../utils/install-dep.js';

export async function processIframe(
state: GlobalState,
htmlfile: string,
$: cheerio.CheerioAPI,
imgElement: cheerio.Element,
iframe: cheerio.Cheerio<cheerio.Element>,
isAboveTheFold: boolean,
appendToBody: Record<string, string>
): Promise<void> {
const iframe = $(imgElement);

// Reset loading attribute
iframe.removeAttr('loading');

@@ -32,24 +29,7 @@ export async function processIframe(
iframe.attr('class', 'jampack-lozad');
iframe.attr('data-src', src);
iframe.removeAttr('src');
await install_dependency(
state,
htmlfile,
{
source: {
npm_package_name: 'lozad',
absolute_path_to_file: '/dist',
filename: 'lozad.es.js',
},
destination: {
folder_name: 'lozad-1.16',
code_loader: `import lozad from "./lozad.es.js";
const observer = lozad('.jampack-lozad', { rootMargin: '100px 0px', threshold: [0.1] });
observer.observe();`,
},
},
appendToBody
);
await install_lozad(state, htmlfile, appendToBody);
}
}
}
43 changes: 43 additions & 0 deletions src/optimizers/process-video.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import * as cheerio from '@divriots/cheerio';
import type { GlobalState } from '../state.js';
import { install_lozad } from '../utils/install-dep.js';

export async function processVideo(
state: GlobalState,
htmlfile: string,
video: cheerio.Cheerio<cheerio.Element>,
isAboveTheFold: boolean,
appendToBody: Record<string, string>
): Promise<void> {
const autoplay = video.attr('autoplay');
if (!autoplay || autoplay === '0' || autoplay === 'false') {
// If the video is not autoplay we can postpone loading
// if there is a poster
if (video.attr('poster')) {
video.attr('preload', 'none');
return;
}

// TODO create poster for videos without poster
}

const lazyloadOptions = state.options.video.autoplay_lazyload;
if (lazyloadOptions.when === 'never') {
return;
}

if (
lazyloadOptions.when === 'always' ||
(lazyloadOptions.when === 'below-the-fold' && !isAboveTheFold)
) {
if (lazyloadOptions.how === 'js') {
video.attr('class', 'jampack-lozad');
const src = video.attr('src');
if (src) {
video.attr('data-src', src);
video.removeAttr('src');
}
await install_lozad(state, htmlfile, appendToBody);
}
}
}
Loading

0 comments on commit a94eedf

Please sign in to comment.