Skip to content

Commit 98af4cf

Browse files
authored
Convert Phase 4: Asset and Image Processing files to TypeScript (#56638)
1 parent fec3cf2 commit 98af4cf

File tree

4 files changed

+103
-77
lines changed

4 files changed

+103
-77
lines changed

src/content-render/unified/rewrite-asset-img-tags.js renamed to src/content-render/unified/rewrite-asset-img-tags.ts

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { Element, Node } from 'hast'
12
import { visit, SKIP } from 'unist-util-visit'
23
import { IMAGE_DENSITY } from '../../assets/lib/image-density'
34

@@ -9,12 +10,16 @@ export const MAX_WIDTH = 1440
910
const DEFAULT_IMAGE_DENSITY = '2x'
1011

1112
// Matches any <img> tags with an href that starts with `/assets/`
12-
const matcher = (node) =>
13-
node.type === 'element' &&
14-
node.tagName === 'img' &&
15-
node.properties &&
16-
node.properties.src &&
17-
node.properties.src.startsWith('/assets/')
13+
function isAssetImg(node: Node): node is Element {
14+
return (
15+
node.type === 'element' &&
16+
(node as Element).tagName === 'img' &&
17+
!!(node as Element).properties &&
18+
!!(node as Element).properties?.src &&
19+
typeof (node as Element).properties?.src === 'string' &&
20+
((node as Element).properties?.src as string).startsWith('/assets/')
21+
)
22+
}
1823

1924
/**
2025
* Where it can mutate the AST to swap from:
@@ -30,17 +35,20 @@ const matcher = (node) =>
3035
*
3136
* */
3237
export default function rewriteAssetImgTags() {
33-
return (tree) => {
34-
visit(tree, matcher, (node) => {
35-
if (node.properties.src.endsWith('.png')) {
38+
return (tree: Node) => {
39+
visit(tree, 'element', (node: Node) => {
40+
if (!isAssetImg(node)) return
41+
42+
const src = node.properties?.src as string
43+
if (src.endsWith('.png')) {
3644
const copyPNG = structuredClone(node)
3745

38-
const originalSrc = node.properties.src
46+
const originalSrc = src
3947
const originalSrcWithoutCb = originalSrc.replace(/cb-\w+\//, '')
40-
const webpSrc = injectMaxWidth(node.properties.src.replace(/\.png$/, '.webp'), MAX_WIDTH)
48+
const webpSrc = injectMaxWidth(src.replace(/\.png$/, '.webp'), MAX_WIDTH)
4149
const srcset = `${webpSrc} ${IMAGE_DENSITY[originalSrcWithoutCb] || DEFAULT_IMAGE_DENSITY}`
4250

43-
const sourceWEBP = {
51+
const sourceWEBP: Element = {
4452
type: 'element',
4553
tagName: 'source',
4654
properties: {
@@ -49,12 +57,15 @@ export default function rewriteAssetImgTags() {
4957
},
5058
children: [],
5159
}
52-
node.children.push(sourceWEBP)
5360

61+
node.children = node.children || []
62+
node.children.push(sourceWEBP)
5463
node.children.push(copyPNG)
5564
node.tagName = 'picture'
65+
5666
delete node.properties.alt
5767
delete node.properties.src
68+
5869
// Don't go further or else you end up in an infinite recursion
5970
return SKIP
6071
}
@@ -68,7 +79,7 @@ export default function rewriteAssetImgTags() {
6879
* For example, if the pathname is `/assets/cb-1234/images/foo.png`
6980
* return `/assets/cb-1234/_mw-1440/images/foo.png`
7081
*/
71-
function injectMaxWidth(pathname, maxWidth) {
82+
function injectMaxWidth(pathname: string, maxWidth: number): string {
7283
const split = pathname.split('/')
7384
// This prefix needs to match what's possibly expected in dynamic-assets.js
7485
const inject = `mw-${maxWidth}`

src/content-render/unified/rewrite-asset-urls.js renamed to src/content-render/unified/rewrite-asset-urls.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
11
import fs from 'fs'
2-
2+
import type { Element, Node } from 'hast'
33
import { visit } from 'unist-util-visit'
44

55
// Matches any <img> tags with an href that starts with `/assets/` or '/public/'
6-
const matcher = (node) =>
7-
node.type === 'element' &&
8-
node.tagName === 'img' &&
9-
node.properties &&
10-
node.properties.src &&
11-
(node.properties.src.startsWith('/assets/') || node.properties.src.startsWith('/public/'))
6+
function isAssetOrPublicImg(node: Node): node is Element {
7+
return (
8+
node.type === 'element' &&
9+
(node as Element).tagName === 'img' &&
10+
!!(node as Element).properties &&
11+
!!(node as Element).properties?.src &&
12+
typeof (node as Element).properties?.src === 'string' &&
13+
(((node as Element).properties?.src as string).startsWith('/assets/') ||
14+
((node as Element).properties?.src as string).startsWith('/public/'))
15+
)
16+
}
1217

1318
// Content authors write images like `![Alt](/assets/images/foo.png`, but
1419
// for caching purposes we want to rewrite those so they can be cached
1520
// indefinitely.
1621
export default function rewriteImgSources() {
17-
return (tree) => {
18-
visit(tree, matcher, (node) => {
22+
return (tree: Node) => {
23+
visit(tree, 'element', (node: Node) => {
24+
if (!isAssetOrPublicImg(node)) return
25+
1926
const newSrc = getNewSrc(node)
2027
if (newSrc) {
2128
node.properties.src = newSrc
@@ -24,8 +31,8 @@ export default function rewriteImgSources() {
2431
}
2532
}
2633

27-
function getNewSrc(node) {
28-
const { src } = node.properties
34+
function getNewSrc(node: Element): string | undefined {
35+
const src = node.properties?.src as string
2936
if (!src.startsWith('/')) return
3037

3138
try {
@@ -46,7 +53,7 @@ function getNewSrc(node) {
4653
const split = src.split('/')
4754
split.splice(2, 0, `cb-${hash}`)
4855
return split.join('/')
49-
} catch (err) {
56+
} catch {
5057
console.warn(
5158
`Failed to get a hash for ${src} ` +
5259
'(This is mostly harmless and can happen with outdated translations).',

src/content-render/unified/wrap-procedural-images.js

Lines changed: 0 additions & 51 deletions
This file was deleted.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import type { Element, Node, Parent } from 'hast'
2+
import { visitParents } from 'unist-util-visit-parents'
3+
4+
/**
5+
* Where it can mutate the AST to swap from:
6+
*
7+
* <ol>
8+
* <li>
9+
* <img src="..." />
10+
*
11+
* to:
12+
*
13+
* <ol>
14+
* <li>
15+
* <div class="procedural-image-wrapper">
16+
* <img src="..." />
17+
*
18+
*
19+
* */
20+
21+
function isImgElement(node: Node): node is Element {
22+
return node.type === 'element' && (node as Element).tagName === 'img'
23+
}
24+
25+
function insideOlLi(ancestors: Parent[]): boolean {
26+
const li = ancestors.findIndex((node) => (node as Element).tagName === 'li')
27+
if (li > -1) {
28+
const ol = ancestors.slice(0, li).findIndex((node) => (node as Element).tagName === 'ol')
29+
return ol > -1
30+
}
31+
return false
32+
}
33+
34+
function visitor(node: Element, ancestors: Parent[]): void {
35+
if (insideOlLi(ancestors)) {
36+
const shallowClone: Element = Object.assign({}, node)
37+
shallowClone.tagName = 'div'
38+
shallowClone.properties = { class: 'procedural-image-wrapper' }
39+
shallowClone.children = [node]
40+
const parent = ancestors.at(-1)
41+
if (parent && parent.children) {
42+
parent.children = parent.children.map((child) => {
43+
if (child.type === 'element' && (child as Element).tagName === 'img') {
44+
return shallowClone
45+
}
46+
return child
47+
})
48+
}
49+
}
50+
}
51+
52+
export default function wrapProceduralImages() {
53+
return (tree: Node) =>
54+
visitParents(tree, 'element', (node: Node, ancestors: Parent[]) => {
55+
if (isImgElement(node)) {
56+
visitor(node, ancestors)
57+
}
58+
})
59+
}

0 commit comments

Comments
 (0)