Skip to content

Commit

Permalink
feat: support Web Worker and placeholderImage as a callback (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
qq15725 committed Feb 20, 2023
1 parent bb5fa80 commit f22b0bc
Show file tree
Hide file tree
Showing 12 changed files with 266 additions and 168 deletions.
11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,13 @@
"types": "./types/index.d.ts",
"require": "./dist/index.cjs",
"import": "./dist/index.mjs"
}
},
"./worker": {
"types": "./types/worker.d.ts",
"require": "./dist/worker.js",
"import": "./dist/worker.js"
},
"./*": "./*"
},
"main": "dist/index.mjs",
"module": "dist/index.mjs",
Expand All @@ -55,7 +61,8 @@
"lint:fix": "eslint src --fix",
"test": "vitest --no-threads --no-isolate",
"coverage": "vitest run --coverage",
"build": "vite build && tsc --project tsconfig.build.json",
"build:worker": "vite build --config vite.worker.config.ts",
"build": "vite build && pnpm build:worker && tsc --project tsconfig.build.json",
"version": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md",
"release": "bumpp package.json --commit \"release: v%s\" --push --all --tag"
},
Expand Down
20 changes: 13 additions & 7 deletions src/context.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import type { Options } from './options'

export type Request = {
type: 'image' | 'text'
resovle?: (response: string) => void
reject?: (error: Error) => void
response: Promise<string>
}

export interface InternalContext<T extends Node> {
/**
* FLAG
Expand Down Expand Up @@ -36,6 +43,11 @@ export interface InternalContext<T extends Node> {
*/
sandbox?: HTMLIFrameElement

/**
* Web Workers
*/
workers: Worker[]

/**
* The set of `font-family` values for all cloend elements
*/
Expand All @@ -54,13 +66,7 @@ export interface InternalContext<T extends Node> {
/**
* All requests for `fetch`
*/
requests: Map<string, {
type: 'image' | 'text'
response: Promise<{
content: string
contentType: string
}>
}>
requests: Map<string, Request>

/**
* All request images count
Expand Down
75 changes: 52 additions & 23 deletions src/create-context.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,26 @@
import { getDefaultRequestInit } from './get-default-request-init'
import {
IN_BROWSER,
SUPPORT_WEB_WORKER,
consoleTime,
consoleTimeEnd,
isElementNode,
isSupportWebp,
supportWebp,
waitUntilLoad,
} from './utils'
import type { Context } from './context'
import type { Context, Request } from './context'
import type { Options } from './options'

export async function createContext<T extends Node>(node: T, options?: Options & { autodestruct?: boolean }): Promise<Context<T>> {
const { workerUrl, workerNumber = 1 } = options || {}

const debug = Boolean(options?.debug)

const ownerDocument = node.ownerDocument ?? (IN_BROWSER ? window.document : undefined)
const ownerWindow = node.ownerDocument?.defaultView ?? (IN_BROWSER ? window : undefined)
const requests = new Map<string, Request>()

const context: Context<T> = {
__CONTEXT__: true,

// InternalContext
node,
ownerDocument,
ownerWindow,
svgStyleElement: createStyleElement(ownerDocument),
defaultComputedStyles: new Map<string, Record<string, any>>(),
fontFamilies: new Set<string>(),
fontCssTexts: new Map<string, string>(),
acceptOfImage: `${ [
isSupportWebp(ownerDocument) && 'image/webp',
'image/svg+xml',
'image/*',
'*/*',
].filter(Boolean).join(',') };q=0.8`,
requests: new Map(),
requestImagesCount: 0,
tasks: [],
autodestruct: false,

// Options
width: 0,
height: 0,
Expand All @@ -59,10 +42,56 @@ export async function createContext<T extends Node>(node: T, options?: Options &
},
font: {},
drawImageInterval: 100,
workerUrl: null,
workerNumber,
onCloneNode: null,
onEmbedNode: null,
onCreateForeignObjectSvg: null,
autodestruct: false,
...options,

__CONTEXT__: true,

// InternalContext
node,
ownerDocument,
ownerWindow,
svgStyleElement: createStyleElement(ownerDocument),
defaultComputedStyles: new Map<string, Record<string, any>>(),
workers: [
...new Array(
SUPPORT_WEB_WORKER && workerUrl && workerNumber
? workerNumber
: 0,
),
].map(() => {
const worker = new Worker(workerUrl!)
worker.onmessage = async event => {
const { url, stream } = event.data
if (stream) {
const data = await (stream as ReadableStream).getReader().read()
requests.get(url)?.resovle?.(data.value)
} else {
requests.get(url)?.reject?.(new Error(`Error receiving message from worker: ${ url }`))
}
}
worker.onmessageerror = event => {
const { url } = event.data
requests.get(url)?.reject?.(new Error(`Error receiving message from worker: ${ url }`))
}
return worker
}),
fontFamilies: new Set<string>(),
fontCssTexts: new Map<string, string>(),
acceptOfImage: `${ [
supportWebp(ownerDocument) && 'image/webp',
'image/svg+xml',
'image/*',
'*/*',
].filter(Boolean).join(',') };q=0.8`,
requests,
requestImagesCount: 0,
tasks: [],
}

debug && consoleTime('wait until load')
Expand Down
15 changes: 9 additions & 6 deletions src/css-url.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { fetchDataUrl } from './fetch'
import { contextFetch } from './fetch'
import { consoleWarn, isDataUrl, resolveUrl } from './utils'
import type { Context } from './context'

Expand All @@ -12,12 +12,15 @@ export async function replaceCssUrlToDataUrl(

for (const url of parseCssUrls(cssText)) {
try {
const dataUrl = await fetchDataUrl(
baseUrl
? resolveUrl(url, baseUrl)
: url,
const dataUrl = await contextFetch(
context,
isImage,
{
url: baseUrl
? resolveUrl(url, baseUrl)
: url,
requestType: isImage ? 'image' : 'text',
responseType: 'base64',
},
)
cssText = cssText.replace(toRE(url), `$1${ dataUrl }$3`)
} catch (error) {
Expand Down
1 change: 1 addition & 0 deletions src/destroy-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export function destroyContext(context: Context) {
}
context.sandbox = undefined
}
context.workers = []
context.fontFamilies.clear()
context.fontCssTexts.clear()
context.requestImagesCount = 0
Expand Down
24 changes: 17 additions & 7 deletions src/embed-image-element.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
import { isDataUrl, isImageElement, isSVGElementNode } from './utils'
import { fetchDataUrl } from './fetch'
import { contextFetch } from './fetch'
import type { Context } from './context'

export function embedImageElement<T extends HTMLImageElement | SVGImageElement>(
clone: T,
context: Context,
): Promise<void>[] {
if (isImageElement(clone) && !isDataUrl(clone.currentSrc || clone.src)) {
const currentSrc = clone.currentSrc || clone.src
const url = clone.currentSrc || clone.src
clone.srcset = ''
clone.dataset.originalSrc = currentSrc
clone.dataset.originalSrc = url
return [
fetchDataUrl(currentSrc, context, true).then(url => {
contextFetch(context, {
url,
imageDom: clone,
requestType: 'image',
responseType: 'base64',
}).then(url => {
clone.src = url
}),
]
} else if (isSVGElementNode(clone) && !isDataUrl(clone.href.baseVal)) {
const currentSrc = clone.href.baseVal
clone.dataset.originalSrc = currentSrc
const url = clone.href.baseVal
clone.dataset.originalSrc = url
return [
fetchDataUrl(currentSrc, context, true).then(url => {
contextFetch(context, {
url,
imageDom: clone,
requestType: 'image',
responseType: 'base64',
}).then(url => {
clone.href.baseVal = url
}),
]
Expand Down
34 changes: 17 additions & 17 deletions src/embed-web-font.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { hasCssUrl, replaceCssUrlToDataUrl } from './css-url'
import { fetch, fetchText } from './fetch'
import { contextFetch } from './fetch'
import { consoleWarn, isCssFontFaceRule } from './utils'
import type { Context } from './context'

Expand Down Expand Up @@ -79,7 +79,11 @@ async function getCssRules(
let importIndex = index + 1
const url = (rule as CSSImportRule).href
try {
const cssText = await fetchText(url, context)
const cssText = await contextFetch(context, {
url,
requestType: 'text',
responseType: 'text',
})
const embedCssText = await embedFonts(cssText, url, context)
for (const rule of parseCss(embedCssText)) {
try {
Expand Down Expand Up @@ -122,21 +126,17 @@ async function embedFonts(cssText: string, baseUrl: string, context: Context): P
await Promise.all(
cssText.match(URL_MATCH_RE)?.map(async location => {
let url = location.replace(URL_REPLACE_RE, '$1')
if (!url.startsWith('https://')) url = new URL(url, baseUrl).href
const rep = await fetch(url, context)
const blob = await rep.blob()
return new Promise<[string, string | ArrayBuffer | null]>(
(resolve, reject) => {
const reader = new FileReader()
reader.onloadend = () => {
// Side Effect
cssText = cssText.replace(location, `url(${ reader.result })`)
resolve([location, reader.result])
}
reader.onerror = reject
reader.readAsDataURL(blob)
},
)
if (!url.startsWith('https://')) {
url = new URL(url, baseUrl).href
}
return contextFetch(context, {
url,
requestType: 'text',
responseType: 'base64',
}).then(base64 => {
// Side Effect
cssText = cssText.replace(location, `url(${ base64 })`)
})
}) ?? [],
)
return cssText
Expand Down
Loading

0 comments on commit f22b0bc

Please sign in to comment.