mirror of
https://github.com/LukeHagar/sveltekit-og.git
synced 2025-12-06 04:21:37 +00:00
html-satori based
This commit is contained in:
@@ -1,20 +1,30 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:svelte/recommended',
|
||||
'prettier'
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
|
||||
plugins: ['svelte3', '@typescript-eslint'],
|
||||
ignorePatterns: ['*.cjs'],
|
||||
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
|
||||
settings: {
|
||||
'svelte3/typescript': () => require('typescript')
|
||||
},
|
||||
plugins: ['@typescript-eslint'],
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 2020
|
||||
ecmaVersion: 2020,
|
||||
extraFileExtensions: ['.svelte']
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es2017: true,
|
||||
node: true
|
||||
}
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.svelte'],
|
||||
parser: 'svelte-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,40 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,19 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
27
.github/workflows/publish.yml
vendored
27
.github/workflows/publish.yml
vendored
@@ -1,27 +0,0 @@
|
||||
on:
|
||||
push:
|
||||
branches: main
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI: true
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- uses: pnpm/action-setup@v2
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 8.6.1
|
||||
- run: pnpm install
|
||||
- run: pnpm test:build
|
||||
- run: pnpm build
|
||||
#- run: pnpm test
|
||||
- uses: JS-DevTools/npm-publish@v2
|
||||
with:
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
package: "package"
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,13 +1,12 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/dist
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
.idea
|
||||
package
|
||||
.vercel
|
||||
pnpm-lock.yaml
|
||||
dist
|
||||
|
||||
21
LICENSE
21
LICENSE
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Ether Corps
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
203
README.md
203
README.md
@@ -1,189 +1,58 @@
|
||||
# SvelteKit Open Graph Image Generation
|
||||
# create-svelte
|
||||
|
||||
Dynamically generate Open Graph images from an HTML+CSS template or Svelte component using fast and efficient conversion from HTML > SVG > PNG. Based on [Satori](https://github.com/vercel/satori#documentation). No headless browser required.
|
||||
Everything you need to build a Svelte library, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
|
||||
|
||||
## Installation
|
||||
Read more about creating a library [in the docs](https://kit.svelte.dev/docs/packaging).
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```bash
|
||||
pnpm install -D @ethercorps/sveltekit-og
|
||||
# create a new project in the current directory
|
||||
npm create svelte@latest
|
||||
|
||||
# create a new project in my-app
|
||||
npm create svelte@latest my-app
|
||||
```
|
||||
|
||||
> Using with Cloudflare Pages or Workers then you have to provide `url` polyfill by just installing it as `devDependency`.
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```bash
|
||||
pnpm i -D url
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
## Usage
|
||||
Everything inside `src/lib` is part of your library, everything inside `src/routes` can be used as a showcase or preview app.
|
||||
|
||||
Create a file at `/src/routes/og/+server.ts`. Alternatively, you can use JavaScript by removing the types from this example.
|
||||
## Building
|
||||
|
||||
```typescript
|
||||
// src/routes/og/+server.ts
|
||||
import { ImageResponse } from '@ethercorps/sveltekit-og';
|
||||
import { RequestHandler } from './$types';
|
||||
To build your library:
|
||||
|
||||
const template = `
|
||||
<div tw="bg-gray-50 flex w-full h-full items-center justify-center">
|
||||
<div tw="flex flex-col md:flex-row w-full py-12 px-4 md:items-center justify-between p-8">
|
||||
<h2 tw="flex flex-col text-3xl sm:text-4xl font-bold tracking-tight text-gray-900 text-left">
|
||||
<span>Ready to dive in?</span>
|
||||
<span tw="text-indigo-600">Start your free trial today.</span>
|
||||
</h2>
|
||||
<div tw="mt-8 flex md:mt-0">
|
||||
<div tw="flex rounded-md shadow">
|
||||
<a href="#" tw="flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-5 py-3 text-base font-medium text-white">Get started</a>
|
||||
</div>
|
||||
<div tw="ml-3 flex rounded-md shadow">
|
||||
<a href="#" tw="flex items-center justify-center rounded-md border border-transparent bg-white px-5 py-3 text-base font-medium text-indigo-600">Learn more</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const fontFile = await fetch('https://og-playground.vercel.app/inter-latin-ext-400-normal.woff');
|
||||
const fontData: ArrayBuffer = await fontFile.arrayBuffer();
|
||||
|
||||
export const GET: RequestHandler = async () => {
|
||||
return await ImageResponse(template, {
|
||||
height: 630,
|
||||
width: 1200,
|
||||
fonts: [
|
||||
{
|
||||
name: 'Inter Latin',
|
||||
data: fontData,
|
||||
weight: 400
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
```bash
|
||||
npm run package
|
||||
```
|
||||
|
||||
Then run `npm dev` and visit `localhost:5173/og` to view your generated PNG. Remember that hot module reloading does not work with server routes, so if you change your HTML or CSS, hard refresh the route to see changes.
|
||||
To create a production version of your showcase app:
|
||||
|
||||
## Example Output
|
||||
|
||||

|
||||
|
||||
## Headers
|
||||
|
||||
When run in development, image headers contain `cache-control: no-cache, no-store`. In production, image headers contain `'cache-control': 'public, immutable, no-transform, max-age=31536000'`, which caches the image for 1 year. In both cases, the `'content-type': 'image/png'` is used.
|
||||
|
||||
## Styling
|
||||
|
||||
Notice that our example uses TailwindCSS classes (e.g. `tw="bg-gray-50"`). Alternatively, your HTML can contain style attributes using any of [the subset of CSS supported by Satori](https://github.com/vercel/satori#css).
|
||||
|
||||
Satori supports only a subset of HTML and CSS. For full details, see [Satori’s documentation](https://github.com/vercel/satori#documentation). Notably, Satori only supports flex-based layouts.
|
||||
|
||||
## Fonts
|
||||
|
||||
Satori supports `ttf`, `otf`, and `woff` font formats; `woff2` is not supported. To maximize the font parsing speed, `ttf` or `otf` are recommended over `woff`.
|
||||
|
||||
By default, `@ethercorps/sveltekit-og` includes only 'Noto Sans' font. If you need to use other fonts, you can specify them as shown in the example. Notably, you can also import a font file that is stored locally within your project and are not required to use fetch.
|
||||
|
||||
## Examples
|
||||
|
||||
- `ImageResponse` · [_source_](/src/routes/new/+server.ts) · [_demo_](https://sveltekit-og-five.vercel.app/new)
|
||||
- `componentToImageResponse` · [_source_](/src/routes/component-og/) · [_demo_](https://sveltekit-og-five.vercel.app/component-og)
|
||||
|
||||
## API Reference
|
||||
|
||||
The package exposes an `ImageResponse` and `componentToImageResponse` constructors, with the following options available:
|
||||
|
||||
```typescript
|
||||
import {ImageResponse, componentToImageResponse} from '@ethercorps/sveltekit-og'
|
||||
import {SvelteComponent} from "svelte";
|
||||
|
||||
// ...
|
||||
ImageResponse(
|
||||
element : string,
|
||||
options : {
|
||||
width ? : number = 1200
|
||||
height ? : number = 630,
|
||||
backgroundColor ? : string = "#fff"
|
||||
fonts ? : {
|
||||
name: string,
|
||||
data: ArrayBuffer,
|
||||
weight: number,
|
||||
style: 'normal' | 'italic'
|
||||
}[]
|
||||
debug ? : boolean = false
|
||||
graphemeImages ? : Record<string, string>;
|
||||
loadAdditionalAsset ? : (languageCode: string, segment: string) => Promise<SatoriOptions["fonts"] | string | undefined>;
|
||||
// Options that will be passed to the HTTP response
|
||||
status ? : number = 200
|
||||
statusText ? : string
|
||||
headers ? : Record<string, string>
|
||||
})
|
||||
|
||||
componentToImageResponse(
|
||||
component : typeof SvelteComponent,
|
||||
props : {}, // All export let example inside prop dictionary
|
||||
options : {
|
||||
width ? : number = 1200
|
||||
height ? : number = 630
|
||||
fonts ? : {
|
||||
name: string,
|
||||
data: ArrayBuffer,
|
||||
weight: number,
|
||||
style: 'normal' | 'italic'
|
||||
}[]
|
||||
debug ? : boolean = false
|
||||
graphemeImages ? : Record<string, string>;
|
||||
loadAdditionalAsset ? : (languageCode: string, segment: string) => Promise<SatoriOptions["fonts"] | string | undefined>;
|
||||
// Options that will be passed to the HTTP response
|
||||
status ? : number = 200
|
||||
statusText ? : string
|
||||
headers ? : Record<string, string>
|
||||
})
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Changelog
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
### v1.2.3 Update (Breaking Changes)
|
||||
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
|
||||
|
||||
> Now you have to install dependency by yourself which will make it easier to build for all plateforms.
|
||||
## Publishing
|
||||
|
||||
Go into the `package.json` and give your package the desired name through the `"name"` option. Also consider adding a `"license"` field and point it to a `LICENSE` file which you can create from a template (one popular option is the [MIT license](https://opensource.org/license/mit/)).
|
||||
|
||||
To publish your library to [npm](https://www.npmjs.com):
|
||||
|
||||
```bash
|
||||
npm publish
|
||||
```
|
||||
npm i @resvg/resvg-js
|
||||
```
|
||||
|
||||
```
|
||||
npm i satori
|
||||
```
|
||||
|
||||
> From now on their will be no issues related to build, and soon this library going to have its own documentation.
|
||||
|
||||
### v1.2.2 Update (Breaking Change)
|
||||
|
||||
- We don't provide access to satori from `@ethercorps/sveltekit-og`.
|
||||
|
||||
### v1.0.0 Update (Breaking Changes)
|
||||
|
||||
Finally, We have added html to react like element like object converter out of the box and with svelte compiler.
|
||||
Now you can use `{ toReactElement }` with `"@ethercorps/sveltekit-og"` like:
|
||||
|
||||
- We have changed to function based instead of class based ImageResponse and componentToImageResponse.
|
||||
- Removed `@resvg/resvg-wasm` with `@resvg/resvg-js` because of internal errors.
|
||||
- Removed `satori-html` because now we have `toReactElement` out of the box with svelte compiler.
|
||||
> If you find a problem related to undefined a please check [_vite.config.js_](/vite.config.ts) and add ` define: { _a: 'undefined' } in config.`
|
||||
|
||||
> If you find any issue and have suggestion for this project please open a ticket and if you want to contribute please create a new discussion.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
This project will not be possible without the following projects:
|
||||
|
||||
- [Satori & @vercel/og](https://github.com/vercel/satori)
|
||||
- [Noto by Google Fonts](https://fonts.google.com/noto)
|
||||
- [svg2png-wasm](https://github.com/ssssota/svg2png-wasm)
|
||||
|
||||
## Authors
|
||||
|
||||
- [@theetherGit](https://www.github.com/theetherGit)
|
||||
- [@etherCorps](https://www.github.com/etherCorps)
|
||||
|
||||
## Contributors
|
||||
|
||||
- [@jasongitmail](https://github.com/jasongitmail)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"format": "prettier --plugin-search-dir . --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ethercorps/sveltekit-og": "link:../../package",
|
||||
"@ethercorps/sveltekit-og": "link:../../",
|
||||
"@playwright/test": "^1.34.3",
|
||||
"@sveltejs/adapter-cloudflare": "^2.3.0",
|
||||
"@sveltejs/kit": "^1.20.2",
|
||||
|
||||
6
examples/cf-page-build/pnpm-lock.yaml
generated
6
examples/cf-page-build/pnpm-lock.yaml
generated
@@ -1,4 +1,4 @@
|
||||
lockfileVersion: '6.1'
|
||||
lockfileVersion: '6.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
@@ -6,8 +6,8 @@ settings:
|
||||
|
||||
devDependencies:
|
||||
'@ethercorps/sveltekit-og':
|
||||
specifier: link:../../package
|
||||
version: link:../../package
|
||||
specifier: link:../../
|
||||
version: link:../..
|
||||
'@playwright/test':
|
||||
specifier: ^1.34.3
|
||||
version: 1.34.3
|
||||
|
||||
@@ -11,19 +11,19 @@
|
||||
"format": "prettier --plugin-search-dir . --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ethercorps/sveltekit-og": "link:../../package",
|
||||
"@playwright/test": "^1.34.3",
|
||||
"@sveltejs/adapter-cloudflare-workers": "^1.1.2",
|
||||
"@sveltejs/adapter-static": "^2.0.2",
|
||||
"@sveltejs/kit": "^1.20.2",
|
||||
"eslint": "^8.42.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"@ethercorps/sveltekit-og": "link:../..",
|
||||
"@playwright/test": "^1.37.1",
|
||||
"@sveltejs/adapter-cloudflare-workers": "^1.1.4",
|
||||
"@sveltejs/adapter-static": "^2.0.3",
|
||||
"@sveltejs/kit": "^1.24.0",
|
||||
"eslint": "^8.48.0",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-plugin-svelte": "^2.10.1",
|
||||
"svelte": "^3.59.1",
|
||||
"vite": "^4.3.9",
|
||||
"url": "^0.11.0"
|
||||
"svelte": "^3.59.2",
|
||||
"url": "^0.11.1",
|
||||
"vite": "^4.4.9"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
|
||||
526
examples/cf-workers-build/pnpm-lock.yaml
generated
526
examples/cf-workers-build/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
101
package.json
101
package.json
@@ -1,69 +1,56 @@
|
||||
{
|
||||
"name": "@ethercorps/sveltekit-og",
|
||||
"version": "2.0.1",
|
||||
"private": false,
|
||||
"name": "sveltekit-og",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "vite dev --host",
|
||||
"build:prod": "vite build",
|
||||
"build": "svelte-kit sync && svelte-package",
|
||||
"dev": "vite dev",
|
||||
"build": "vite build && npm run package",
|
||||
"preview": "vite preview",
|
||||
"prepublishOnly": "echo 'Did you mean to publish `./package/`, instead of `./`?' && exit 1",
|
||||
"test:build": "pnpm build && pnpm test:vercel:build && pnpm test:netlify:build && pnpm test:pages:build && pnpm test:workers:build && pnpm test:node:build",
|
||||
"test:vercel:build": "cd examples/vercel-build && pnpm install && pnpm build",
|
||||
"test:netlify:build": "cd examples/netlify-build && pnpm install && pnpm build",
|
||||
"test:pages:build": "cd examples/cf-page-build && pnpm install && pnpm build",
|
||||
"test:workers:build": "cd examples/cf-workers-build && pnpm install && pnpm build",
|
||||
"test:node:build": "cd examples/node-build && pnpm install && pnpm build",
|
||||
"test": "playwright test",
|
||||
"package": "svelte-kit sync && svelte-package && publint",
|
||||
"prepublishOnly": "npm run package",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"test": "vitest",
|
||||
"lint": "prettier --plugin-search-dir . --check . && eslint .",
|
||||
"format": "prettier --plugin-search-dir . --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@builder.io/partytown": "^0.8.0",
|
||||
"@playwright/test": "^1.37.1",
|
||||
"@sveltejs/adapter-auto": "next",
|
||||
"@sveltejs/adapter-vercel": "^2.4.3",
|
||||
"@sveltejs/kit": "^1.24.0",
|
||||
"@sveltejs/package": "2.2.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||
"@typescript-eslint/parser": "^5.62.0",
|
||||
"autoprefixer": "^10.4.15",
|
||||
"brace": "^0.11.1",
|
||||
"eslint": "^8.48.0",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"eslint-plugin-svelte": "^2.33.0",
|
||||
"postcss": "^8.4.29",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-plugin-svelte": "^2.10.1",
|
||||
"prism-svelte": "^0.5.0",
|
||||
"prismjs": "^1.29.0",
|
||||
"svelte": "^4.2.0",
|
||||
"svelte-check": "^3.5.1",
|
||||
"svelte-preprocess": "^5.0.4",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^4.4.9"
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"svelte": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"!dist/**/*.test.*",
|
||||
"!dist/**/*.spec.*"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"svelte": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^2.0.0",
|
||||
"@sveltejs/kit": "^1.20.4",
|
||||
"@sveltejs/package": "^2.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||
"@typescript-eslint/parser": "^5.45.0",
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-svelte": "^2.30.0",
|
||||
"prettier": "^2.8.0",
|
||||
"prettier-plugin-svelte": "^2.10.1",
|
||||
"publint": "^0.1.9",
|
||||
"svelte": "^4.0.5",
|
||||
"svelte-check": "^3.4.3",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^4.4.2",
|
||||
"vitest": "^0.34.0"
|
||||
},
|
||||
"svelte": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"satori": "^0.10.3",
|
||||
"svg2png-wasm": "^1.4.0"
|
||||
},
|
||||
"keywords": [
|
||||
"open graph image",
|
||||
"open graph",
|
||||
"og image",
|
||||
"og:image",
|
||||
"social",
|
||||
"card",
|
||||
"sveltekit og",
|
||||
"sveltekit-og",
|
||||
"@ethercorps/sveltekit-og"
|
||||
],
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/etherCorps/sveltekit-og",
|
||||
"homepage": "https://github.com/etherCorps/sveltekit-og#readme"
|
||||
"@vercel/og": "^0.5.17",
|
||||
"satori-html": "^0.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import type { PlaywrightTestConfig } from '@playwright/test';
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
webServer: {
|
||||
command: 'npm run build && npm run preview',
|
||||
port: 4173
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
1561
pnpm-lock.yaml
generated
1561
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
};
|
||||
14
src/app.css
14
src/app.css
@@ -1,14 +0,0 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@font-face {
|
||||
font-family: 'Ian Mono';
|
||||
src: url('/iaw-mono-var.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
font-family: Ian Mono, monospace;
|
||||
}
|
||||
}
|
||||
17
src/app.d.ts
vendored
17
src/app.d.ts
vendored
@@ -1,11 +1,12 @@
|
||||
/// <reference types="@sveltejs/kit" />
|
||||
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
// and what to do when importing types
|
||||
declare namespace App {
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface Error {}
|
||||
// interface Platform {}
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
|
||||
29
src/app.html
29
src/app.html
@@ -2,36 +2,11 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/logo.webp" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<!-- HTML Meta Tags -->
|
||||
<title>@ethercorps/sveltekit-og</title>
|
||||
<meta name="description" content="Demo example for @ethercorps/sveltekit-og" />
|
||||
|
||||
<!-- Facebook Meta Tags -->
|
||||
<meta property="og:url" content="https://sveltekit-og.ethercorps.io/" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="@ethercorps/sveltekit-og" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="A typescript implementation of vercel/og for sveltekit. Dynamically generate Open Graph images from an HTML+CSS template or Svelte component using fast and efficient conversion from HTML > SVG > PNG. Based on Satori. No headless browser required."
|
||||
/>
|
||||
<meta property="og:image" content="https://sveltekit-og.ethercorps.io/og?w=1200&h=630" />
|
||||
|
||||
<!-- Twitter Meta Tags -->
|
||||
<meta name="twitter:card" content="https://sveltekit-og.ethercorps.io/" />
|
||||
<meta property="twitter:domain" content="sveltekit-og.ethercorps.io" />
|
||||
<meta property="twitter:url" content="https://sveltekit-og.ethercorps.io/" />
|
||||
<meta name="twitter:title" content="undefined" />
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="A typescript implementation of vercel/og for sveltekit. Dynamically generate Open Graph images from an HTML+CSS template or Svelte component using fast and efficient conversion from HTML > SVG > PNG. Based on Satori. No headless browser required."
|
||||
/>
|
||||
<meta name="twitter:image" content="https://sveltekit-og.ethercorps.io/og?w=1200&h=675" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div>%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
<script>
|
||||
let menuBar = false;
|
||||
</script>
|
||||
|
||||
<!-- navbar goes here -->
|
||||
<nav class="bg-gray-100">
|
||||
<div class="max-w-full mx-auto px-4">
|
||||
<div class="flex justify-between">
|
||||
<div class="flex space-x-4">
|
||||
<!-- logo -->
|
||||
<div>
|
||||
<a
|
||||
href="#"
|
||||
class="flex items-center py-5 px-2 text-gray-700 hover:text-gray-900 space-x-3"
|
||||
>
|
||||
<svg viewBox="0 0 75 65" fill="black" class="w-4 h-4 rotate-90">
|
||||
<path d="M37.59.25l36.95 64H.64l36.95-64z" />
|
||||
</svg>
|
||||
<span class="font-bold">Sveltekit-OG</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- secondary nav -->
|
||||
<div class="hidden md:flex items-center space-x-1">
|
||||
<a
|
||||
href="https://github.com/etherCorps/sveltekit-og/#readme"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
class="py-5 px-3 underline">Docs</a
|
||||
>
|
||||
<a
|
||||
href="https://github.com/etherCorps/sveltekit-og"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
class="py-5 px-3 underline">Github</a
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- mobile button goes here -->
|
||||
<div class="md:hidden flex items-center">
|
||||
<button
|
||||
class="mobile-menu-button"
|
||||
on:click={() => {
|
||||
menuBar = !menuBar;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
class="w-6 h-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 6h16M4 12h16M4 18h16"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- mobile menu -->
|
||||
<div class="mobile-menu {menuBar ? '' : 'hidden'} md:hidden">
|
||||
<a href="#" class="block py-2 px-4 text-sm hover:bg-gray-200">Docs</a>
|
||||
<a href="#" class="block py-2 px-4 text-sm hover:bg-gray-200">Github</a>
|
||||
</div>
|
||||
</nav>
|
||||
7
src/index.test.ts
Normal file
7
src/index.test.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
describe('sum test', () => {
|
||||
it('adds 1 + 2 to equal 3', () => {
|
||||
expect(1 + 2).toBe(3);
|
||||
});
|
||||
});
|
||||
78
src/lib/api.ts
Normal file
78
src/lib/api.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { html } from "satori-html";
|
||||
import {ImageResponse as IR} from "@vercel/og"
|
||||
import type {SvelteComponent} from "svelte";
|
||||
|
||||
export const ImageResponse = async (htmlTemplate: string, options?: ImageResponseOptions) => {
|
||||
const reactVNode = html(`${htmlTemplate}`);
|
||||
console.log(reactVNode)
|
||||
return new IR(reactVNode, options)
|
||||
};
|
||||
|
||||
export const componentToImageResponse = async (component: SvelteComponent, props: Record<string, any>, options?: ImageResponseOptions) => {
|
||||
const ssrSvelte = component.render(props);
|
||||
console.log(ssrSvelte);
|
||||
return ImageResponse(`${ssrSvelte.html}<style>${ssrSvelte.css.code}</style>`, options)
|
||||
};
|
||||
|
||||
declare const apis: {
|
||||
twemoji: (code: any) => string;
|
||||
openmoji: string;
|
||||
blobmoji: string;
|
||||
noto: string;
|
||||
fluent: (code: any) => string;
|
||||
fluentFlat: (code: any) => string;
|
||||
};
|
||||
|
||||
declare type EmojiType = keyof typeof apis;
|
||||
|
||||
type Weight = 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
|
||||
type Style$1 = 'normal' | 'italic';
|
||||
|
||||
interface FontOptions {
|
||||
data: Buffer | ArrayBuffer;
|
||||
name: string;
|
||||
weight?: Weight;
|
||||
style?: Style$1;
|
||||
lang?: string;
|
||||
}
|
||||
|
||||
export declare type ImageResponseOptions = ImageOptions & ConstructorParameters<typeof Response>[1];
|
||||
|
||||
declare type ImageOptions = {
|
||||
/**
|
||||
* The width of the image.
|
||||
*
|
||||
* @type {number}
|
||||
* @default 1200
|
||||
*/
|
||||
width?: number;
|
||||
/**
|
||||
* The height of the image.
|
||||
*
|
||||
* @type {number}
|
||||
* @default 630
|
||||
*/
|
||||
height?: number;
|
||||
/**
|
||||
* Display debug information on the image.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
*/
|
||||
debug?: boolean;
|
||||
/**
|
||||
* A list of fonts to use.
|
||||
*
|
||||
* @type {{ data: ArrayBuffer; name: string; weight?: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900; style?: 'normal' | 'italic' }[]}
|
||||
* @default Noto Sans Latin Regular.
|
||||
*/
|
||||
fonts?: FontOptions[];
|
||||
/**
|
||||
* Using a specific Emoji style. Defaults to `twemoji`.
|
||||
*
|
||||
* @link https://github.com/vercel/og#emoji
|
||||
* @type {EmojiType}
|
||||
* @default 'twemoji'
|
||||
*/
|
||||
emoji?: EmojiType;
|
||||
};
|
||||
@@ -1,98 +1 @@
|
||||
import satori, { type SatoriOptions } from 'satori';
|
||||
import type { SvelteComponent } from 'svelte';
|
||||
import toReactElement from './toReactElement.js';
|
||||
import { svg2png, initialize, type ConvertOptions } from 'svg2png-wasm';
|
||||
|
||||
let initialized = false;
|
||||
|
||||
const fontFile = await fetch('https://sveltekit-og.ethercorps.io/noto-sans.ttf');
|
||||
const fontData: ArrayBuffer = await fontFile.arrayBuffer();
|
||||
|
||||
const indexWasmRes = await fetch('https://unpkg.com/svg2png-wasm/svg2png_wasm_bg.wasm');
|
||||
const svg2PngWasmBuffer = await indexWasmRes.arrayBuffer();
|
||||
|
||||
const initSvgToPng = async () => {
|
||||
await initialize(svg2PngWasmBuffer).catch((e) => console.log(e));
|
||||
initialized = true;
|
||||
};
|
||||
|
||||
const ImageResponse = async (htmlTemplate: string, optionsByUser: ImageResponseOptions) => {
|
||||
const options = Object.assign({ width: 1200, height: 630, debug: !1 }, optionsByUser);
|
||||
const svg = await satori(toReactElement(htmlTemplate), {
|
||||
width: options.width,
|
||||
height: options.height,
|
||||
debug: options.debug,
|
||||
fonts: options.fonts || [
|
||||
{
|
||||
name: 'sans serif',
|
||||
data: fontData,
|
||||
style: 'normal',
|
||||
weight: 700
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!initialized) {
|
||||
await initSvgToPng();
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
const defaultConfig: ConvertOptions = {
|
||||
width: options.width, // optional
|
||||
height: options.height // optional
|
||||
};
|
||||
|
||||
if (Object.hasOwn(options, 'backgroundColor')) {
|
||||
defaultConfig.backgroundColor = options.backgroundColor;
|
||||
}
|
||||
|
||||
const png = await svg2png(svg, defaultConfig);
|
||||
|
||||
return new Response(png, {
|
||||
headers: {
|
||||
'Content-Type': 'image/png',
|
||||
'cache-control': 'public, immutable, no-transform, max-age=31536000',
|
||||
...options.headers
|
||||
},
|
||||
|
||||
status: options.status,
|
||||
statusText: options.statusText
|
||||
});
|
||||
};
|
||||
|
||||
const componentToImageResponse = (
|
||||
component: typeof SvelteComponent<any>,
|
||||
props = {},
|
||||
optionsByUser: ImageResponseOptions
|
||||
) => {
|
||||
const htmlTemplate = componentToMarkup(component, props);
|
||||
return ImageResponse(htmlTemplate, optionsByUser);
|
||||
};
|
||||
|
||||
const componentToMarkup = (component: typeof SvelteComponent<any>, props = {}) => {
|
||||
const SvelteRenderedMarkup = (component as any).render(props);
|
||||
let htmlTemplate = `${SvelteRenderedMarkup.html}`;
|
||||
if (SvelteRenderedMarkup && SvelteRenderedMarkup.css && SvelteRenderedMarkup.css.code) {
|
||||
htmlTemplate = `${SvelteRenderedMarkup.html}<style>${SvelteRenderedMarkup.css.code}</style>`;
|
||||
}
|
||||
return htmlTemplate;
|
||||
};
|
||||
|
||||
type ImageResponseOptions = ConstructorParameters<typeof Response>[1] & ImageOptions;
|
||||
|
||||
type ImageOptions = {
|
||||
width?: number;
|
||||
height?: number;
|
||||
debug?: boolean;
|
||||
fonts?: SatoriOptions['fonts'];
|
||||
backgroundColor?: string;
|
||||
graphemeImages?: Record<string, string>;
|
||||
loadAdditionalAsset?: (
|
||||
languageCode: string,
|
||||
segment: string
|
||||
) => Promise<SatoriOptions['fonts'] | string | undefined>;
|
||||
};
|
||||
|
||||
export type ImageResponseType = typeof ImageResponse;
|
||||
|
||||
export { componentToImageResponse, ImageResponse, toReactElement };
|
||||
export {componentToImageResponse, ImageResponse, type ImageResponseOptions} from "./api.js"
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
import { parse, walk } from 'svelte/compiler';
|
||||
import type { Ast } from 'svelte/types/compiler/interfaces';
|
||||
|
||||
/* Start of code from satori-html for cssToObject converter*/
|
||||
const camelize = (ident: string) => ident.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
||||
const cssToObject = (str: string) => {
|
||||
const obj: Record<string, string> = {};
|
||||
let t = 0;
|
||||
let pair = ['', ''];
|
||||
const flags: Record<string, number> = {};
|
||||
for (const c of str) {
|
||||
if (!flags['('] && c === ':') {
|
||||
t = 1;
|
||||
} else if (c === ';') {
|
||||
const [decl = '', value = ''] = pair;
|
||||
obj[camelize(decl.trim())] = value.trim();
|
||||
t = 0;
|
||||
pair = ['', ''];
|
||||
} else {
|
||||
pair[t] += c;
|
||||
switch (c) {
|
||||
case '(': {
|
||||
flags[c]++;
|
||||
break;
|
||||
}
|
||||
case ')': {
|
||||
flags['(']--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const [decl = '', value = ''] = pair;
|
||||
if (decl.trim() && value.trim()) {
|
||||
obj[camelize(decl.trim())] = value.trim();
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
||||
const nodeMap = new WeakMap();
|
||||
interface VNode {
|
||||
type: string;
|
||||
props: {
|
||||
style?: Record<string, any>;
|
||||
children?: string | VNode | VNode[];
|
||||
[prop: string]: any;
|
||||
};
|
||||
}
|
||||
const root: VNode = {
|
||||
type: 'div',
|
||||
props: {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
},
|
||||
children: []
|
||||
}
|
||||
};
|
||||
/* End of satori-html */
|
||||
|
||||
export const toReactElement = (htmlString: string): VNode => {
|
||||
const svelteAST: Ast = parse(htmlString);
|
||||
walk(svelteAST, {
|
||||
enter(node: any, parent: any, prop: any, index: any) {
|
||||
let newNode: any = {};
|
||||
if (node.type === 'Fragment') {
|
||||
nodeMap.set(node, root);
|
||||
} else if (node.type === 'Element') {
|
||||
newNode.type = node.name;
|
||||
const { ...props } = node.attributes;
|
||||
if (node.attributes.length > 0) {
|
||||
node.attributes.forEach((attribute: any) => {
|
||||
if (attribute.name === 'style') {
|
||||
props['style'] = cssToObject(attribute.value[0].data) as any;
|
||||
} else props[attribute.name] = attribute.value[0].data as any;
|
||||
});
|
||||
delete props[0];
|
||||
}
|
||||
props.children = [] as unknown as string;
|
||||
Object.assign(newNode, { props });
|
||||
nodeMap.set(node, newNode);
|
||||
if (parent) {
|
||||
const newParent = nodeMap.get(parent);
|
||||
newParent.props.children[index] = newNode;
|
||||
}
|
||||
} else if (node.type === 'Text') {
|
||||
newNode = node.data.trim();
|
||||
if (newNode) {
|
||||
if (parent && parent.type !== 'Attribute') {
|
||||
const newParent = nodeMap.get(parent);
|
||||
if (parent.children.length === 1) {
|
||||
newParent.props.children = newNode;
|
||||
} else {
|
||||
newParent.props.children[index] = newNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
};
|
||||
|
||||
export default toReactElement;
|
||||
@@ -1,71 +0,0 @@
|
||||
<script>
|
||||
import '../app.css';
|
||||
import Navbar from '../components/Navbar.svelte';
|
||||
|
||||
import { onMount } from 'svelte'
|
||||
import { partytownSnippet } from '@builder.io/partytown/integration'
|
||||
|
||||
// Add the Partytown script to the DOM head
|
||||
let scriptEl
|
||||
onMount(
|
||||
() => {
|
||||
if (scriptEl) {
|
||||
scriptEl.textContent = partytownSnippet()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<!-- Config options -->
|
||||
<script>
|
||||
// Forward the necessary functions to the web worker layer
|
||||
partytown = {
|
||||
forward: ['dataLayer.push'],
|
||||
resolveUrl: (url) => {
|
||||
const siteUrl = 'https://sveltekit-og.ethercorps.io/proxytown';
|
||||
|
||||
if (url.hostname === 'www.googletagmanager.com') {
|
||||
const proxyUrl = new URL(`${siteUrl}/gtm`)
|
||||
|
||||
const gtmId = new URL(url).searchParams.get('id')
|
||||
gtmId && proxyUrl.searchParams.append('id', gtmId)
|
||||
|
||||
return proxyUrl
|
||||
} else if (url.hostname === 'www.google-analytics.com') {
|
||||
const proxyUrl = new URL(`${siteUrl}/ga`)
|
||||
|
||||
return proxyUrl
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<!-- `partytownSnippet` is inserted here -->
|
||||
<script bind:this={scriptEl}></script>
|
||||
|
||||
<script
|
||||
type="text/partytown"
|
||||
src="https://www.googletagmanager.com/gtag/js?id=G-RQGCXL5D07"></script>
|
||||
<script type="text/partytown">
|
||||
window.dataLayer = window.dataLayer || []
|
||||
|
||||
function gtag() {
|
||||
dataLayer.push(arguments)
|
||||
}
|
||||
|
||||
gtag('js', new Date())
|
||||
gtag('config', 'G-RQGCXL5D07', {
|
||||
page_path: window.location.pathname
|
||||
})
|
||||
</script>
|
||||
</svelte:head>
|
||||
|
||||
<div class="max-h-screen">
|
||||
<Navbar />
|
||||
<slot />
|
||||
</div>
|
||||
@@ -1,240 +0,0 @@
|
||||
<script>
|
||||
import {base} from "$app/paths";
|
||||
import Prism from 'prismjs';
|
||||
import 'prism-svelte';
|
||||
import 'prismjs/themes/prism-tomorrow.css';
|
||||
const source = `
|
||||
/src/routes/new/+server.ts
|
||||
|
||||
import { ImageResponse } from '@ethercorps/sveltekit-og';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
const template = \`
|
||||
<div tw="bg-gray-50 flex w-full h-full items-center justify-center">
|
||||
<div tw="flex flex-col md:flex-row w-full py-12 px-4 md:items-center justify-between p-8">
|
||||
<h2 tw="flex flex-col text-3xl sm:text-4xl font-bold tracking-tight text-gray-900 text-left">
|
||||
<span>Ready to dive in?</span>
|
||||
<span tw="text-indigo-600">Start your free trial today.</span>
|
||||
</h2>
|
||||
<div tw="mt-8 flex md:mt-0">
|
||||
<div tw="flex rounded-md shadow">
|
||||
<a href="#" tw="flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-5 py-3 text-base font-medium text-white">Get started</a>
|
||||
</div>
|
||||
<div tw="ml-3 flex rounded-md shadow">
|
||||
<a href="#" tw="flex items-center justify-center rounded-md border border-transparent bg-white px-5 py-3 text-base font-medium text-indigo-600">Learn more</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
\`;
|
||||
|
||||
const fontFile400 = await fetch(
|
||||
'https://raw.githubusercontent.com/etherCorps/sveltekit-og/main/static/inter-latin-ext-400-normal.woff'
|
||||
);
|
||||
|
||||
const fontFile700 = await fetch(
|
||||
'https://raw.githubusercontent.com/etherCorps/sveltekit-og/main/static/inter-latin-ext-700-normal.woff'
|
||||
);
|
||||
|
||||
const fontData400: ArrayBuffer = await fontFile400.arrayBuffer();
|
||||
const fontData700: ArrayBuffer = await fontFile700.arrayBuffer();
|
||||
|
||||
export const GET: RequestHandler = async () => {
|
||||
return await ImageResponse(template, {
|
||||
height: 250,
|
||||
width: 500,
|
||||
fonts: [
|
||||
{
|
||||
name: 'Inter Latin',
|
||||
data: fontData400,
|
||||
weight: 400
|
||||
},
|
||||
{
|
||||
name: 'Inter Latin',
|
||||
data: fontData700,
|
||||
weight: 700
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
`;
|
||||
|
||||
const apiReference = `
|
||||
import {ImageResponse, componentToImageResponse} from '@ethercorps/sveltekit-og'
|
||||
import {SvelteComponent} from "svelte";
|
||||
|
||||
// ...
|
||||
ImageResponse(
|
||||
element : string,
|
||||
options : {
|
||||
width ? : number = 1200
|
||||
height ? : number = 630,
|
||||
backgroundColor ? : string = "#fff"
|
||||
fonts ? : {
|
||||
name: string,
|
||||
data: ArrayBuffer,
|
||||
weight: number,
|
||||
style: 'normal' | 'italic'
|
||||
}[]
|
||||
debug ? : boolean = false
|
||||
graphemeImages ? : Record<string, string>;
|
||||
loadAdditionalAsset ? : (languageCode: string, segment: string) => Promise<SatoriOptions["fonts"] | string | undefined>;
|
||||
// Options that will be passed to the HTTP response
|
||||
status ? : number = 200
|
||||
statusText ? : string
|
||||
headers ? : Record<string, string>
|
||||
})
|
||||
|
||||
componentToImageResponse(
|
||||
component : typeof SvelteComponent,
|
||||
props : {}, // All export let example inside prop dictionary
|
||||
options : {
|
||||
width ? : number = 1200
|
||||
height ? : number = 630
|
||||
fonts ? : {
|
||||
name: string,
|
||||
data: ArrayBuffer,
|
||||
weight: number,
|
||||
style: 'normal' | 'italic'
|
||||
}[]
|
||||
debug ? : boolean = false
|
||||
graphemeImages ? : Record<string, string>;
|
||||
loadAdditionalAsset ? : (languageCode: string, segment: string) => Promise<SatoriOptions["fonts"] | string | undefined>;
|
||||
// Options that will be passed to the HTTP response
|
||||
status ? : number = 200
|
||||
statusText ? : string
|
||||
headers ? : Record<string, string>
|
||||
})
|
||||
|
||||
`;
|
||||
const highlightedQuickEg = Prism.highlight(source, Prism.languages.svelte, 'svelte');
|
||||
const highlightedApiReference = Prism.highlight(apiReference, Prism.languages.svelte, 'svelte');
|
||||
|
||||
</script>
|
||||
|
||||
<div class="mx-10 mt-5 pb-10 space-y-5">
|
||||
<section id="introduction" class="space-y-3">
|
||||
<h1 class="font-bold text-2xl">Introduction</h1>
|
||||
<p class="">
|
||||
SvelteKit-OG is use to dynamically generate Open Graph images from an HTML+CSS template or
|
||||
Svelte component using fast and efficient conversion from <br /> HTML > SVG > PNG. Based on Satori.
|
||||
No headless browser required.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section id="installation" class="space-y-4">
|
||||
<h1 class="font-bold text-2xl">Installation</h1>
|
||||
<p>
|
||||
Use your favourite package manager and add <span class="bg-gray-100 px-2 py-1 rounded-full"
|
||||
>@ethercorps/sveltekit-og</span
|
||||
>
|
||||
as your <span class="bg-gray-100 px-2 py-1 rounded-full">devDependency</span>.
|
||||
</p>
|
||||
<div id="pnpm-install-og">
|
||||
Example:
|
||||
<code class="border rounded-full bg-gray-100 px-3 py-1.5 text-gray-900">
|
||||
pnpm i -D @ethercorps/sveltekit-og
|
||||
</code>
|
||||
</div>
|
||||
<div class="border-x-4 border-gray-900 rounded-lg leading-9">
|
||||
<p class="ml-2">
|
||||
If you are using it on with <span class="bg-gray-100 px-2 py-1 rounded-full">
|
||||
cloudflare pages
|
||||
</span>
|
||||
or <span class="bg-gray-100 px-2 py-1 rounded-full">cloudflare workers </span> then you have
|
||||
to provide <span class="bg-gray-100 px-2 py-1 rounded-full">polyfills</span> for
|
||||
<span class="bg-gray-100 px-2 py-1 rounded-full">url</span>. You can simply add it to your
|
||||
<span class="bg-gray-100 px-2 py-1 rounded-full">devDependency</span>,
|
||||
<br /> To install
|
||||
<code class="border rounded-full bg-gray-100 px-3 py-1.5 text-gray-900">
|
||||
pnpm i -D url
|
||||
</code>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<section id="usage" class="space-y-3">
|
||||
<h1 class="font-bold text-2xl">Usage</h1>
|
||||
<p class="">
|
||||
Create a file at <span class="bg-gray-100 px-2 py-1 rounded-full">
|
||||
/src/routes/og/+server.ts
|
||||
</span>. Alternatively, you can use JavaScript by removing the types from this example.
|
||||
</p>
|
||||
<div class="border-x-4 border-gray-900 rounded-lg w-fit px-2 leading-9">
|
||||
<p class="ml-2">
|
||||
Route can be anything but it should have only one file
|
||||
<span class="bg-gray-100 px-2 py-1 rounded-full"> +server.ts </span>
|
||||
</p>
|
||||
</div>
|
||||
<pre class="bg-gray-100 overflow-auto px-3 py-1.5 rounded-xl"><code class="" >{@html highlightedQuickEg}</code></pre>
|
||||
<div class="border-x-4 border-gray-900 rounded-lg w-fit px-2 leading-9">
|
||||
<p class="ml-2">
|
||||
Then run <span class="bg-gray-100 px-2 py-1 rounded-full"> pnpm run dev </span> and visit <span class="bg-gray-100 px-2 py-1 rounded-full"> localhost:5173/og </span> to view your generated PNG. Remember that hot module reloading does not work with server routes, so if you change your HTML or CSS, hard refresh the route to see changes.
|
||||
</p>
|
||||
</div>
|
||||
<h2 class="font-bold text-xl">
|
||||
Image Output: <a target="_blank" class="hover:text-indigo-500 transition-all" rel="noreferrer" href="https://sveltekit-og.ethercorps.io/new"> Live Version</a>
|
||||
<img loading="lazy" class="rounded-lg mt-5 shadow-lg shadow-indigo-200" src="https://sveltekit-og.ethercorps.io/new" alt="@ethercorps/sveltekit-og Demo OG Generated PNG" />
|
||||
</h2>
|
||||
</section>
|
||||
|
||||
<section id="headers" class="space-y-3">
|
||||
<h1 class="font-bold text-2xl">Headers</h1>
|
||||
<p class="">
|
||||
Notice that our example uses TailwindCSS classes (e.g. <span class="bg-gray-100 px-2 py-1 rounded-full">tw="bg-gray-50"</span>). Alternatively, your HTML can contain style attributes using any of <a href="https://github.com/vercel/satori#css" target="_blank" rel="noreferrer" class="text-indigo-500">the subset of CSS supported by Satori</a>.
|
||||
<br />
|
||||
<br />
|
||||
Satori supports only a subset of HTML and CSS. For full details, see <a href="https://github.com/vercel/satori#documentation" target="_blank" rel="noreferrer" class="text-indigo-500">Satori’s documentation</a>. Notably, Satori only supports flex-based layouts.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section id="fonts" class="space-y-3">
|
||||
<h1 class="font-bold text-2xl">Fonts</h1>
|
||||
<p class="">
|
||||
Satori supports <span class="bg-gray-100 px-2 py-1 rounded-full">ttf</span>, <span class="bg-gray-100 px-2 py-1 rounded-full">otf</span>, and <span class="bg-gray-100 px-2 py-1 rounded-full">woff</span> font formats; <span class="bg-gray-100 px-2 py-1 rounded-full">woff2</span> is not supported. To maximize the font parsing speed, <span class="bg-gray-100 px-2 py-1 rounded-full">ttf</span> or <span class="bg-gray-100 px-2 py-1 rounded-full">otf</span> are recommended over <span class="bg-gray-100 px-2 py-1 rounded-full">woff</span>.
|
||||
<br />
|
||||
<br />
|
||||
By default, <span class="bg-gray-100 px-2 py-1 rounded-full">@ethercorps/sveltekit-og</span> includes only 'Noto Sans' font. If you need to use other fonts, you can specify them as shown in the example. Notably, you can also import a font file that is stored locally within your project and are not required to use fetch.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section id="examples" class="space-y-3">
|
||||
<h1 class="font-bold text-2xl">Examples</h1>
|
||||
|
||||
<li><span class="bg-gray-100 px-2 py-1 rounded-full">ImageResponse</span> - <a href="https://github.com/etherCorps/sveltekit-og/blob/main/src/routes/new/+server.ts" target="_blank" rel="noreferrer" class="text-indigo-500">Source</a> - <a href="https://sveltekit-og-five.vercel.app/new" target="_blank" rel="noreferrer" class="text-indigo-500">Demo</a></li>
|
||||
<li><span class="bg-gray-100 px-2 py-1 rounded-full">componentToImageResponse</span> - <a href="https://github.com/etherCorps/sveltekit-og/tree/main/src/routes/component-og" target="_blank" rel="noreferrer" class="text-indigo-500">Source</a> - <a href="https://sveltekit-og-five.vercel.app/component-og" target="_blank" rel="noreferrer" class="text-indigo-500">Demo</a></li>
|
||||
</section>
|
||||
|
||||
<section id="api-reference" class="space-y-3">
|
||||
<h1 class="font-bold text-2xl">Api Reference</h1>
|
||||
|
||||
<p>
|
||||
The package exposes an <span class="bg-gray-100 px-2 py-1 rounded-full">ImageResponse</span> and <span class="bg-gray-100 px-2 py-1 rounded-full">componentToImageResponse</span> constructors, with the following options available.
|
||||
</p>
|
||||
<pre class="bg-gray-100 overflow-auto px-3 py-1.5 rounded-xl"><code class="" >{@html highlightedApiReference}</code></pre>
|
||||
</section>
|
||||
|
||||
<div id="footer" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 place-content-evenly">
|
||||
<section id="acknowledgements" class="space-y-3 bg-indigo-50 px-3 py-1.5 shadow-md w-full shadow-indigo-500 rounded-lg border border-indigo-500">
|
||||
<h1 class="font-bold text-2xl">Acknowledgements</h1>
|
||||
<p>This project will not be possible without the following projects:</p>
|
||||
|
||||
<li><a href="https://github.com/vercel/satori" target="_blank" rel="noreferrer" class="text-indigo-500">Satori & Vercel-OG</a></li>
|
||||
<li><a href="https://fonts.google.com/noto" target="_blank" rel="noreferrer" class="text-indigo-500">Noto by Google Fonts</a></li>
|
||||
<li><a href="https://github.com/ssssota/svg2png-wasm" target="_blank" rel="noreferrer" class="text-indigo-500">svg2png-wasm</a></li>
|
||||
</section>
|
||||
<section id="author" class="space-y-3 bg-indigo-50 px-3 py-1.5 shadow-md w-full shadow-indigo-500 rounded-lg border border-indigo-500">
|
||||
<h1 class="font-bold text-2xl">Author</h1>
|
||||
<p>I would like to thank myself:</p>
|
||||
|
||||
<li><a href="https://github.com/etherCorps" target="_blank" rel="noreferrer" class="text-indigo-500">@ethercorps</a></li>
|
||||
<li><a href="https://github.com/theetherGit" target="_blank" rel="noreferrer" class="text-indigo-500">@theetherGit</a></li>
|
||||
</section>
|
||||
<section id="contributors" class="space-y-3 bg-indigo-50 px-3 py-1.5 shadow-md w-full shadow-indigo-500 rounded-lg border border-indigo-500">
|
||||
<h1 class="font-bold text-2xl">Contributors</h1>
|
||||
<p>Without your commits, support, ideas and works every library is incomplete. So, special thanks to:</p>
|
||||
|
||||
<li><a href="https://github.com/jasongitmail" target="_blank" rel="noreferrer" class="text-indigo-500">@jasongitmail</a></li>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
export let prerender = true
|
||||
7
src/routes/+server.ts
Normal file
7
src/routes/+server.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type {RequestHandler} from "@sveltejs/kit";
|
||||
import {ImageResponse} from "$lib";
|
||||
|
||||
export const GET: RequestHandler = async () => {
|
||||
const html = '<div style="color: black; background: aqua; height: 100vh;">hello, world</div>';
|
||||
return ImageResponse(html)
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
import OG from './OG.svelte';
|
||||
import { componentToImageResponse } from '$lib';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
const fontFile = await fetch(
|
||||
'https://raw.githubusercontent.com/etherCorps/sveltekit-og/main/static/inter-latin-ext-700-normal.woff'
|
||||
);
|
||||
const fontData: ArrayBuffer = await fontFile.arrayBuffer();
|
||||
|
||||
export const GET: RequestHandler = async () => {
|
||||
return await componentToImageResponse(
|
||||
OG,
|
||||
{ text: 'Ready to dive in?', spanText: 'Start your free trial today.' },
|
||||
{
|
||||
height: 250,
|
||||
width: 500,
|
||||
fonts: [
|
||||
{
|
||||
name: 'Inter Latin',
|
||||
data: fontData,
|
||||
weight: 700
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -1,29 +0,0 @@
|
||||
<script>
|
||||
export let text;
|
||||
export let spanText;
|
||||
</script>
|
||||
|
||||
<div tw="bg-gray-50 flex w-full h-full items-center justify-center">
|
||||
<div tw="flex flex-col md:flex-row w-full py-12 px-4 md:items-center justify-between p-8">
|
||||
<h2 tw="flex flex-col text-3xl sm:text-4xl font-bold tracking-tight text-gray-900 text-left">
|
||||
<span>{text}</span>
|
||||
<span tw="text-indigo-600">{spanText}</span>
|
||||
</h2>
|
||||
<div tw="mt-8 flex md:mt-0">
|
||||
<div tw="flex rounded-md shadow">
|
||||
<a
|
||||
href="#"
|
||||
tw="flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-5 py-3 text-base font-medium text-white"
|
||||
>Get started</a
|
||||
>
|
||||
</div>
|
||||
<div tw="ml-3 flex rounded-md shadow">
|
||||
<a
|
||||
href="#"
|
||||
tw="flex items-center justify-center rounded-md border border-transparent bg-white px-5 py-3 text-base font-medium text-indigo-600"
|
||||
>Learn more</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,50 +0,0 @@
|
||||
`/src/routes/new/+server.ts`
|
||||
|
||||
import { ImageResponse } from '$lib';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
const template = `
|
||||
<div tw="bg-gray-50 flex w-full h-full items-center justify-center">
|
||||
<div tw="flex flex-col md:flex-row w-full py-12 px-4 md:items-center justify-between p-8">
|
||||
<h2 tw="flex flex-col text-3xl sm:text-4xl font-bold tracking-tight text-gray-900 text-left">
|
||||
<span>Ready to dive in?</span>
|
||||
<span tw="text-indigo-600">Start your free trial today.</span>
|
||||
</h2>
|
||||
<div tw="mt-8 flex md:mt-0">
|
||||
<div tw="flex rounded-md shadow">
|
||||
<a href="#" tw="flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-5 py-3 text-base font-medium text-white">Get started</a>
|
||||
</div>
|
||||
<div tw="ml-3 flex rounded-md shadow">
|
||||
<a href="#" tw="flex items-center justify-center rounded-md border border-transparent bg-white px-5 py-3 text-base font-medium text-indigo-600">Learn more</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
const fontFile400 = await fetch(
|
||||
'https://raw.githubusercontent.com/etherCorps/sveltekit-og/main/static/inter-latin-ext-400-normal.woff'
|
||||
);
|
||||
const fontFile700 = await fetch(
|
||||
'https://raw.githubusercontent.com/etherCorps/sveltekit-og/main/static/inter-latin-ext-700-normal.woff'
|
||||
);
|
||||
const fontData400: ArrayBuffer = await fontFile400.arrayBuffer();
|
||||
const fontData700: ArrayBuffer = await fontFile700.arrayBuffer();
|
||||
|
||||
export const GET: RequestHandler = async () => {
|
||||
return await ImageResponse(template, {
|
||||
height: 250,
|
||||
width: 500,
|
||||
fonts: [
|
||||
{
|
||||
name: 'Inter Latin',
|
||||
data: fontData400,
|
||||
weight: 400
|
||||
},
|
||||
{
|
||||
name: 'Inter Latin',
|
||||
data: fontData700,
|
||||
weight: 700
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
import type {RequestHandler} from "@sveltejs/kit";
|
||||
import {ImageResponse} from "$lib";
|
||||
import {to_number} from "svelte/internal";
|
||||
|
||||
const htmlSrc = `
|
||||
<div style="display: flex; height: 100%; width: 100%; align-items: center; justify-content: center; flex-direction: column; background-image: linear-gradient(to bottom, #dbf4ff, #fff1f1); font-size: 60px; letter-spacing: -2px; font-weight: 700; text-align: center;">
|
||||
<div style="background-image: linear-gradient(90deg, rgb(0, 124, 240, 1), rgb(0, 223, 216)); background-clip: text; -webkit-background-clip: text; color: transparent;">Design</div>
|
||||
<div style="background-image: linear-gradient(90deg, rgb(121, 40, 202, 1), rgb(255, 0, 128)); background-clip: text; -webkit-background-clip: text; color: transparent;">Develop</div>
|
||||
<div style="background-image: linear-gradient(90deg, rgb(255, 77, 77, 1), rgb(249, 203, 40)); background-clip: text; -webkit-background-clip: text; color: transparent;">SvelteKit OG</div>
|
||||
</div>
|
||||
|
||||
`;
|
||||
|
||||
export const GET: RequestHandler = async ({request, url}) => {
|
||||
return ImageResponse(htmlSrc, {
|
||||
width: to_number(url.searchParams.get('w')) || 600,
|
||||
height: to_number(url.searchParams.get('h')) || 420,
|
||||
})
|
||||
};
|
||||
10
src/routes/sc/+server.ts
Normal file
10
src/routes/sc/+server.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type {RequestHandler} from "@sveltejs/kit";
|
||||
import {componentToImageResponse} from "$lib";
|
||||
import OG from "./OG.svelte";
|
||||
|
||||
export const GET: RequestHandler = async () => {
|
||||
return await componentToImageResponse(
|
||||
OG,
|
||||
{ text: 'Ready to dive in?', spanText: 'Start your free trial today.' }
|
||||
);
|
||||
};
|
||||
29
src/routes/sc/OG.svelte
Normal file
29
src/routes/sc/OG.svelte
Normal file
@@ -0,0 +1,29 @@
|
||||
<script>
|
||||
export let text;
|
||||
export let spanText;
|
||||
</script>
|
||||
|
||||
<div tw="bg-gray-50 flex w-full h-full items-center justify-center">
|
||||
<div tw="flex flex-col md:flex-row w-full py-12 px-4 md:items-center justify-between p-8">
|
||||
<h2 tw="flex flex-col text-3xl sm:text-4xl font-bold tracking-tight text-gray-900 text-left">
|
||||
<span>{text}</span>
|
||||
<span tw="text-indigo-600">{spanText}</span>
|
||||
</h2>
|
||||
<div tw="mt-8 flex md:mt-0">
|
||||
<div tw="flex rounded-md shadow">
|
||||
<a
|
||||
href="#"
|
||||
tw="flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-5 py-3 text-base font-medium text-white"
|
||||
>Get started</a
|
||||
>
|
||||
</div>
|
||||
<div tw="ml-3 flex rounded-md shadow">
|
||||
<a
|
||||
href="#"
|
||||
tw="flex items-center justify-center rounded-md border border-transparent bg-white px-5 py-3 text-base font-medium text-indigo-600"
|
||||
>Learn more</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
BIN
static/demo.png
BIN
static/demo.png
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
static/logo.png
BIN
static/logo.png
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB |
BIN
static/logo.webp
BIN
static/logo.webp
Binary file not shown.
|
Before Width: | Height: | Size: 6.0 KiB |
Binary file not shown.
@@ -1,572 +0,0 @@
|
||||
/* Partytown 0.8.0 - MIT builder.io */
|
||||
(window => {
|
||||
const isPromise = v => "object" == typeof v && v && v.then;
|
||||
const noop = () => {};
|
||||
const len = obj => obj.length;
|
||||
const getConstructorName = obj => {
|
||||
var _a, _b, _c;
|
||||
try {
|
||||
const constructorName = null === (_a = null == obj ? void 0 : obj.constructor) || void 0 === _a ? void 0 : _a.name;
|
||||
if (constructorName) {
|
||||
return constructorName;
|
||||
}
|
||||
} catch (e) {}
|
||||
try {
|
||||
const zoneJsConstructorName = null === (_c = null === (_b = null == obj ? void 0 : obj.__zone_symbol__originalInstance) || void 0 === _b ? void 0 : _b.constructor) || void 0 === _c ? void 0 : _c.name;
|
||||
if (zoneJsConstructorName) {
|
||||
return zoneJsConstructorName;
|
||||
}
|
||||
} catch (e) {}
|
||||
return "";
|
||||
};
|
||||
const startsWith = (str, val) => str.startsWith(val);
|
||||
const isValidMemberName = memberName => !(startsWith(memberName, "webkit") || startsWith(memberName, "toJSON") || startsWith(memberName, "constructor") || startsWith(memberName, "toString") || startsWith(memberName, "_"));
|
||||
const getNodeName = node => 11 === node.nodeType && node.host ? "#s" : node.nodeName;
|
||||
const randomId = () => Math.round(Math.random() * Number.MAX_SAFE_INTEGER).toString(36);
|
||||
const defineConstructorName = (Cstr, value) => ((obj, memberName, descriptor) => Object.defineProperty(obj, memberName, {
|
||||
...descriptor,
|
||||
configurable: true
|
||||
}))(Cstr, "name", {
|
||||
value: value
|
||||
});
|
||||
const htmlConstructorTags = {
|
||||
Anchor: "a",
|
||||
DList: "dl",
|
||||
Image: "img",
|
||||
OList: "ol",
|
||||
Paragraph: "p",
|
||||
Quote: "q",
|
||||
TableCaption: "caption",
|
||||
TableCell: "td",
|
||||
TableCol: "colgroup",
|
||||
TableRow: "tr",
|
||||
TableSection: "tbody",
|
||||
UList: "ul"
|
||||
};
|
||||
const svgConstructorTags = {
|
||||
Graphics: "g",
|
||||
SVG: "svg"
|
||||
};
|
||||
const InstanceIdKey = Symbol();
|
||||
const CreatedKey = Symbol();
|
||||
const instances = new Map;
|
||||
const mainRefs = new Map;
|
||||
const winCtxs = {};
|
||||
const windowIds = new WeakMap;
|
||||
const getAndSetInstanceId = (instance, instanceId) => {
|
||||
if (instance) {
|
||||
if (instanceId = windowIds.get(instance)) {
|
||||
return instanceId;
|
||||
}
|
||||
(instanceId = instance[InstanceIdKey]) || setInstanceId(instance, instanceId = randomId());
|
||||
return instanceId;
|
||||
}
|
||||
};
|
||||
const getInstance = (winId, instanceId, win, doc, docId) => {
|
||||
if ((win = winCtxs[winId]) && win.$window$) {
|
||||
if (winId === instanceId) {
|
||||
return win.$window$;
|
||||
}
|
||||
doc = win.$window$.document;
|
||||
docId = instanceId.split(".").pop();
|
||||
if ("d" === docId) {
|
||||
return doc;
|
||||
}
|
||||
if ("e" === docId) {
|
||||
return doc.documentElement;
|
||||
}
|
||||
if ("h" === docId) {
|
||||
return doc.head;
|
||||
}
|
||||
if ("b" === docId) {
|
||||
return doc.body;
|
||||
}
|
||||
}
|
||||
return instances.get(instanceId);
|
||||
};
|
||||
const setInstanceId = (instance, instanceId, now) => {
|
||||
if (instance) {
|
||||
instances.set(instanceId, instance);
|
||||
instance[InstanceIdKey] = instanceId;
|
||||
instance[CreatedKey] = now = Date.now();
|
||||
if (now > lastCleanup + 5e3) {
|
||||
instances.forEach(((storedInstance, instanceId) => {
|
||||
storedInstance[CreatedKey] < lastCleanup && storedInstance.nodeType && !storedInstance.isConnected && instances.delete(instanceId);
|
||||
}));
|
||||
lastCleanup = now;
|
||||
}
|
||||
}
|
||||
};
|
||||
let lastCleanup = 0;
|
||||
const mainWindow = window.parent;
|
||||
const docImpl = document.implementation.createHTMLDocument();
|
||||
const config = mainWindow.partytown || {};
|
||||
const libPath = (config.lib || "/~partytown/") + "debug/";
|
||||
const logMain = msg => {
|
||||
console.debug.apply(console, [ "%cMain 🌎", "background: #717171; color: white; padding: 2px 3px; border-radius: 2px; font-size: 0.8em;", msg ]);
|
||||
};
|
||||
const winIds = [];
|
||||
const normalizedWinId = winId => {
|
||||
winIds.includes(winId) || winIds.push(winId);
|
||||
return winIds.indexOf(winId) + 1;
|
||||
};
|
||||
const defineCustomElement = (winId, worker, ceData) => {
|
||||
const Cstr = defineConstructorName(class extends winCtxs[winId].$window$.HTMLElement {}, ceData[0]);
|
||||
const ceCallbackMethods = "connectedCallback,disconnectedCallback,attributeChangedCallback,adoptedCallback".split(",");
|
||||
ceCallbackMethods.map((callbackMethodName => Cstr.prototype[callbackMethodName] = function(...args) {
|
||||
worker.postMessage([ 15, winId, getAndSetInstanceId(this), callbackMethodName, args ]);
|
||||
}));
|
||||
Cstr.observedAttributes = ceData[1];
|
||||
return Cstr;
|
||||
};
|
||||
const serializeForWorker = ($winId$, value, added, type, cstrName, prevInstanceId) => void 0 !== value && (type = typeof value) ? "string" === type || "number" === type || "boolean" === type || null == value ? [ 0, value ] : "function" === type ? [ 6 ] : (added = added || new Set) && Array.isArray(value) ? added.has(value) ? [ 1, [] ] : added.add(value) && [ 1, value.map((v => serializeForWorker($winId$, v, added))) ] : "object" === type ? serializedValueIsError(value) ? [ 14, {
|
||||
name: value.name,
|
||||
message: value.message,
|
||||
stack: value.stack
|
||||
} ] : "" === (cstrName = getConstructorName(value)) ? [ 2, {} ] : "Window" === cstrName ? [ 3, [ $winId$, $winId$ ] ] : "HTMLCollection" === cstrName || "NodeList" === cstrName ? [ 7, Array.from(value).map((v => serializeForWorker($winId$, v, added)[1])) ] : cstrName.endsWith("Event") ? [ 5, serializeObjectForWorker($winId$, value, added) ] : "CSSRuleList" === cstrName ? [ 12, Array.from(value).map(serializeCssRuleForWorker) ] : startsWith(cstrName, "CSS") && cstrName.endsWith("Rule") ? [ 11, serializeCssRuleForWorker(value) ] : "CSSStyleDeclaration" === cstrName ? [ 13, serializeObjectForWorker($winId$, value, added) ] : "Attr" === cstrName ? [ 10, [ value.name, value.value ] ] : value.nodeType ? [ 3, [ $winId$, getAndSetInstanceId(value), getNodeName(value), prevInstanceId ] ] : [ 2, serializeObjectForWorker($winId$, value, added, true, true) ] : void 0 : value;
|
||||
const serializeObjectForWorker = (winId, obj, added, includeFunctions, includeEmptyStrings, serializedObj, propName, propValue) => {
|
||||
serializedObj = {};
|
||||
if (!added.has(obj)) {
|
||||
added.add(obj);
|
||||
for (propName in obj) {
|
||||
if (isValidMemberName(propName)) {
|
||||
propValue = "path" === propName && getConstructorName(obj).endsWith("Event") ? obj.composedPath() : obj[propName];
|
||||
(includeFunctions || "function" != typeof propValue) && (includeEmptyStrings || "" !== propValue) && (serializedObj[propName] = serializeForWorker(winId, propValue, added));
|
||||
}
|
||||
}
|
||||
}
|
||||
return serializedObj;
|
||||
};
|
||||
const serializeCssRuleForWorker = cssRule => {
|
||||
let obj = {};
|
||||
let key;
|
||||
for (key in cssRule) {
|
||||
validCssRuleProps.includes(key) && (obj[key] = String(cssRule[key]));
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
const serializedValueIsError = value => value instanceof window.top.Error;
|
||||
const deserializeFromWorker = (worker, serializedTransfer, serializedType, serializedValue) => {
|
||||
if (serializedTransfer) {
|
||||
serializedType = serializedTransfer[0];
|
||||
serializedValue = serializedTransfer[1];
|
||||
return 0 === serializedType ? serializedValue : 4 === serializedType ? deserializeRefFromWorker(worker, serializedValue) : 1 === serializedType ? serializedValue.map((v => deserializeFromWorker(worker, v))) : 3 === serializedType ? getInstance(serializedValue[0], serializedValue[1]) : 5 === serializedType ? constructEvent(deserializeObjectFromWorker(worker, serializedValue)) : 2 === serializedType ? deserializeObjectFromWorker(worker, serializedValue) : 8 === serializedType ? serializedValue : 9 === serializedType ? new window[serializedTransfer[2]](serializedValue) : void 0;
|
||||
}
|
||||
};
|
||||
const deserializeRefFromWorker = (worker, {$winId$: $winId$, $instanceId$: $instanceId$, $refId$: $refId$}, ref) => {
|
||||
ref = mainRefs.get($refId$);
|
||||
if (!ref) {
|
||||
ref = function(...args) {
|
||||
worker.postMessage([ 9, {
|
||||
$winId$: $winId$,
|
||||
$instanceId$: $instanceId$,
|
||||
$refId$: $refId$,
|
||||
$thisArg$: serializeForWorker($winId$, this),
|
||||
$args$: serializeForWorker($winId$, args)
|
||||
} ]);
|
||||
};
|
||||
mainRefs.set($refId$, ref);
|
||||
}
|
||||
return ref;
|
||||
};
|
||||
const constructEvent = eventProps => new ("detail" in eventProps ? CustomEvent : Event)(eventProps.type, eventProps);
|
||||
const deserializeObjectFromWorker = (worker, serializedValue, obj, key) => {
|
||||
obj = {};
|
||||
for (key in serializedValue) {
|
||||
obj[key] = deserializeFromWorker(worker, serializedValue[key]);
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
const validCssRuleProps = "cssText,selectorText,href,media,namespaceURI,prefix,name,conditionText".split(",");
|
||||
const mainAccessHandler = async (worker, accessReq) => {
|
||||
let accessRsp = {
|
||||
$msgId$: accessReq.$msgId$
|
||||
};
|
||||
let totalTasks = len(accessReq.$tasks$);
|
||||
let i = 0;
|
||||
let task;
|
||||
let winId;
|
||||
let applyPath;
|
||||
let instance;
|
||||
let rtnValue;
|
||||
let isLast;
|
||||
for (;i < totalTasks; i++) {
|
||||
try {
|
||||
isLast = i === totalTasks - 1;
|
||||
task = accessReq.$tasks$[i];
|
||||
winId = task.$winId$;
|
||||
applyPath = task.$applyPath$;
|
||||
!winCtxs[winId] && winId.startsWith("f_") && await new Promise((resolve => {
|
||||
let check = 0;
|
||||
let callback = () => {
|
||||
winCtxs[winId] || check++ > 1e3 ? resolve() : requestAnimationFrame(callback);
|
||||
};
|
||||
callback();
|
||||
}));
|
||||
if (1 === applyPath[0] && applyPath[1] in winCtxs[winId].$window$) {
|
||||
setInstanceId(new winCtxs[winId].$window$[applyPath[1]](...deserializeFromWorker(worker, applyPath[2])), task.$instanceId$);
|
||||
} else {
|
||||
instance = getInstance(winId, task.$instanceId$);
|
||||
if (instance) {
|
||||
rtnValue = applyToInstance(worker, winId, instance, applyPath, isLast, task.$groupedGetters$);
|
||||
task.$assignInstanceId$ && ("string" == typeof task.$assignInstanceId$ ? setInstanceId(rtnValue, task.$assignInstanceId$) : winCtxs[task.$assignInstanceId$.$winId$] = {
|
||||
$winId$: task.$assignInstanceId$.$winId$,
|
||||
$window$: {
|
||||
document: rtnValue
|
||||
}
|
||||
});
|
||||
if (isPromise(rtnValue)) {
|
||||
rtnValue = await rtnValue;
|
||||
isLast && (accessRsp.$isPromise$ = true);
|
||||
}
|
||||
isLast && (accessRsp.$rtnValue$ = serializeForWorker(winId, rtnValue, void 0, void 0, void 0, task.$instanceId$));
|
||||
} else {
|
||||
accessRsp.$error$ = `Error finding instance "${task.$instanceId$}" on window ${normalizedWinId(winId)}`;
|
||||
console.error(accessRsp.$error$, task);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
isLast ? accessRsp.$error$ = String(e.stack || e) : console.error(e);
|
||||
}
|
||||
}
|
||||
return accessRsp;
|
||||
};
|
||||
const applyToInstance = (worker, winId, instance, applyPath, isLast, groupedGetters) => {
|
||||
let i = 0;
|
||||
let l = len(applyPath);
|
||||
let next;
|
||||
let current;
|
||||
let previous;
|
||||
let args;
|
||||
let groupedRtnValues;
|
||||
for (;i < l; i++) {
|
||||
current = applyPath[i];
|
||||
next = applyPath[i + 1];
|
||||
previous = applyPath[i - 1];
|
||||
try {
|
||||
if (!Array.isArray(next)) {
|
||||
if ("string" == typeof current || "number" == typeof current) {
|
||||
if (i + 1 === l && groupedGetters) {
|
||||
groupedRtnValues = {};
|
||||
groupedGetters.map((propName => groupedRtnValues[propName] = instance[propName]));
|
||||
return groupedRtnValues;
|
||||
}
|
||||
instance = instance[current];
|
||||
} else {
|
||||
if (0 === next) {
|
||||
instance[previous] = deserializeFromWorker(worker, current);
|
||||
return;
|
||||
}
|
||||
if ("function" == typeof instance[previous]) {
|
||||
args = deserializeFromWorker(worker, current);
|
||||
"define" === previous && "CustomElementRegistry" === getConstructorName(instance) && (args[1] = defineCustomElement(winId, worker, args[1]));
|
||||
"insertRule" === previous && args[1] > len(instance.cssRules) && (args[1] = len(instance.cssRules));
|
||||
instance = instance[previous].apply(instance, args);
|
||||
if ("play" === previous) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (isLast) {
|
||||
throw err;
|
||||
}
|
||||
console.debug("Non-blocking setter error:", err);
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
};
|
||||
const readNextScript = (worker, winCtx) => {
|
||||
let $winId$ = winCtx.$winId$;
|
||||
let win = winCtx.$window$;
|
||||
let doc = win.document;
|
||||
let scriptSelector = 'script[type="text/partytown"]:not([data-ptid]):not([data-pterror])';
|
||||
let scriptElm;
|
||||
let $instanceId$;
|
||||
let scriptData;
|
||||
if (doc && doc.body) {
|
||||
scriptElm = doc.querySelector('script[type="text/partytown"]:not([data-ptid]):not([data-pterror]):not([async]):not([defer])');
|
||||
scriptElm || (scriptElm = doc.querySelector(scriptSelector));
|
||||
if (scriptElm) {
|
||||
scriptElm.dataset.ptid = $instanceId$ = getAndSetInstanceId(scriptElm, $winId$);
|
||||
scriptData = {
|
||||
$winId$: $winId$,
|
||||
$instanceId$: $instanceId$
|
||||
};
|
||||
if (scriptElm.src) {
|
||||
scriptData.$url$ = scriptElm.src;
|
||||
scriptData.$orgUrl$ = scriptElm.dataset.ptsrc || scriptElm.src;
|
||||
} else {
|
||||
scriptData.$content$ = scriptElm.innerHTML;
|
||||
}
|
||||
worker.postMessage([ 7, scriptData ]);
|
||||
} else {
|
||||
if (!winCtx.$isInitialized$) {
|
||||
winCtx.$isInitialized$ = 1;
|
||||
((worker, $winId$, win) => {
|
||||
let queuedForwardCalls = win._ptf;
|
||||
let forwards = (win.partytown || {}).forward || [];
|
||||
let i;
|
||||
let mainForwardFn;
|
||||
let forwardCall = ($forward$, args) => worker.postMessage([ 10, {
|
||||
$winId$: $winId$,
|
||||
$forward$: $forward$,
|
||||
$args$: serializeForWorker($winId$, Array.from(args))
|
||||
} ]);
|
||||
win._ptf = void 0;
|
||||
forwards.map((forwardProps => {
|
||||
mainForwardFn = win;
|
||||
forwardProps.split(".").map(((_, i, arr) => {
|
||||
mainForwardFn = mainForwardFn[arr[i]] = i + 1 < len(arr) ? mainForwardFn[arr[i]] || ("push" === arr[i + 1] ? [] : {}) : (...args) => forwardCall(arr, args);
|
||||
}));
|
||||
}));
|
||||
if (queuedForwardCalls) {
|
||||
for (i = 0; i < len(queuedForwardCalls); i += 2) {
|
||||
forwardCall(queuedForwardCalls[i], queuedForwardCalls[i + 1]);
|
||||
}
|
||||
}
|
||||
})(worker, $winId$, win);
|
||||
doc.dispatchEvent(new CustomEvent("pt0"));
|
||||
{
|
||||
const winType = win === win.top ? "top" : "iframe";
|
||||
logMain(`Executed ${winType} window ${normalizedWinId($winId$)} environment scripts in ${(performance.now() - winCtx.$startTime$).toFixed(1)}ms`);
|
||||
}
|
||||
}
|
||||
worker.postMessage([ 8, $winId$ ]);
|
||||
}
|
||||
} else {
|
||||
requestAnimationFrame((() => readNextScript(worker, winCtx)));
|
||||
}
|
||||
};
|
||||
const registerWindow = (worker, $winId$, $window$) => {
|
||||
if (!windowIds.has($window$)) {
|
||||
windowIds.set($window$, $winId$);
|
||||
const doc = $window$.document;
|
||||
const history = $window$.history;
|
||||
const $parentWinId$ = windowIds.get($window$.parent);
|
||||
let initialised = false;
|
||||
const onInitialisedQueue = [];
|
||||
const onInitialised = callback => {
|
||||
initialised ? callback() : onInitialisedQueue.push(callback);
|
||||
};
|
||||
const sendInitEnvData = () => {
|
||||
worker.postMessage([ 5, {
|
||||
$winId$: $winId$,
|
||||
$parentWinId$: $parentWinId$,
|
||||
$url$: doc.baseURI,
|
||||
$visibilityState$: doc.visibilityState
|
||||
} ]);
|
||||
setTimeout((() => {
|
||||
initialised = true;
|
||||
onInitialisedQueue.forEach((callback => {
|
||||
callback();
|
||||
}));
|
||||
}));
|
||||
};
|
||||
const pushState = history.pushState.bind(history);
|
||||
const replaceState = history.replaceState.bind(history);
|
||||
const onLocationChange = (type, state, newUrl, oldUrl) => () => {
|
||||
setTimeout((() => {
|
||||
worker.postMessage([ 13, {
|
||||
$winId$: $winId$,
|
||||
type: type,
|
||||
state: state,
|
||||
url: doc.baseURI,
|
||||
newUrl: newUrl,
|
||||
oldUrl: oldUrl
|
||||
} ]);
|
||||
}));
|
||||
};
|
||||
history.pushState = (state, _, newUrl) => {
|
||||
pushState(state, _, newUrl);
|
||||
onInitialised(onLocationChange(0, state, null == newUrl ? void 0 : newUrl.toString()));
|
||||
};
|
||||
history.replaceState = (state, _, newUrl) => {
|
||||
replaceState(state, _, newUrl);
|
||||
onInitialised(onLocationChange(1, state, null == newUrl ? void 0 : newUrl.toString()));
|
||||
};
|
||||
$window$.addEventListener("popstate", (event => {
|
||||
onInitialised(onLocationChange(2, event.state));
|
||||
}));
|
||||
$window$.addEventListener("hashchange", (event => {
|
||||
onInitialised(onLocationChange(3, {}, event.newURL, event.oldURL));
|
||||
}));
|
||||
$window$.addEventListener("ptupdate", (() => {
|
||||
readNextScript(worker, winCtxs[$winId$]);
|
||||
}));
|
||||
doc.addEventListener("visibilitychange", (() => worker.postMessage([ 14, $winId$, doc.visibilityState ])));
|
||||
winCtxs[$winId$] = {
|
||||
$winId$: $winId$,
|
||||
$window$: $window$
|
||||
};
|
||||
winCtxs[$winId$].$startTime$ = performance.now();
|
||||
{
|
||||
const winType = $winId$ === $parentWinId$ ? "top" : "iframe";
|
||||
logMain(`Registered ${winType} window ${normalizedWinId($winId$)}`);
|
||||
}
|
||||
"complete" === doc.readyState ? sendInitEnvData() : $window$.addEventListener("load", sendInitEnvData);
|
||||
}
|
||||
};
|
||||
const onMessageFromWebWorker = (worker, msg, winCtx) => {
|
||||
if (4 === msg[0]) {
|
||||
registerWindow(worker, randomId(), mainWindow);
|
||||
} else {
|
||||
winCtx = winCtxs[msg[1]];
|
||||
winCtx && (7 === msg[0] ? requestAnimationFrame((() => readNextScript(worker, winCtx))) : 6 === msg[0] && ((worker, winCtx, instanceId, errorMsg, scriptElm) => {
|
||||
scriptElm = winCtx.$window$.document.querySelector(`[data-ptid="${instanceId}"]`);
|
||||
if (scriptElm) {
|
||||
errorMsg ? scriptElm.dataset.pterror = errorMsg : scriptElm.type += "-x";
|
||||
delete scriptElm.dataset.ptid;
|
||||
}
|
||||
readNextScript(worker, winCtx);
|
||||
})(worker, winCtx, msg[2], msg[3]));
|
||||
}
|
||||
};
|
||||
const readMainInterfaces = () => {
|
||||
const elms = Object.getOwnPropertyNames(mainWindow).map((interfaceName => ((doc, interfaceName, r, tag) => {
|
||||
r = interfaceName.match(/^(HTML|SVG)(.+)Element$/);
|
||||
if (r) {
|
||||
tag = r[2];
|
||||
return "S" == interfaceName[0] ? doc.createElementNS("http://www.w3.org/2000/svg", svgConstructorTags[tag] || tag.slice(0, 2).toLowerCase() + tag.slice(2)) : doc.createElement(htmlConstructorTags[tag] || tag);
|
||||
}
|
||||
})(docImpl, interfaceName))).filter((elm => elm)).map((elm => [ elm ]));
|
||||
return readImplementations(elms, []);
|
||||
};
|
||||
const cstrs = new Set([ "Object" ]);
|
||||
const readImplementations = (impls, interfaces) => {
|
||||
const cstrImpls = impls.filter((implData => implData[0])).map((implData => {
|
||||
const impl = implData[0];
|
||||
const interfaceType = implData[1];
|
||||
const cstrName = getConstructorName(impl);
|
||||
const CstrPrototype = mainWindow[cstrName].prototype;
|
||||
return [ cstrName, CstrPrototype, impl, interfaceType ];
|
||||
}));
|
||||
cstrImpls.map((([cstrName, CstrPrototype, impl, intefaceType]) => readOwnImplementation(cstrs, interfaces, cstrName, CstrPrototype, impl, intefaceType)));
|
||||
return interfaces;
|
||||
};
|
||||
const readImplementation = (cstrName, impl, memberName) => {
|
||||
let interfaceMembers = [];
|
||||
let interfaceInfo = [ cstrName, "Object", interfaceMembers ];
|
||||
for (memberName in impl) {
|
||||
readImplementationMember(interfaceMembers, impl, memberName);
|
||||
}
|
||||
return interfaceInfo;
|
||||
};
|
||||
const readOwnImplementation = (cstrs, interfaces, cstrName, CstrPrototype, impl, interfaceType) => {
|
||||
if (!cstrs.has(cstrName)) {
|
||||
cstrs.add(cstrName);
|
||||
const SuperCstr = Object.getPrototypeOf(CstrPrototype);
|
||||
const superCstrName = getConstructorName(SuperCstr);
|
||||
const interfaceMembers = [];
|
||||
const propDescriptors = Object.getOwnPropertyDescriptors(CstrPrototype);
|
||||
readOwnImplementation(cstrs, interfaces, superCstrName, SuperCstr, impl, interfaceType);
|
||||
for (const memberName in propDescriptors) {
|
||||
readImplementationMember(interfaceMembers, impl, memberName);
|
||||
}
|
||||
interfaces.push([ cstrName, superCstrName, interfaceMembers, interfaceType, getNodeName(impl) ]);
|
||||
}
|
||||
};
|
||||
const readImplementationMember = (interfaceMembers, implementation, memberName, value, memberType, cstrName) => {
|
||||
try {
|
||||
if (isValidMemberName(memberName) && isNaN(memberName[0]) && "all" !== memberName) {
|
||||
value = implementation[memberName];
|
||||
memberType = typeof value;
|
||||
if ("function" === memberType) {
|
||||
(String(value).includes("[native") || Object.getPrototypeOf(implementation)[memberName]) && interfaceMembers.push([ memberName, 5 ]);
|
||||
} else if ("object" === memberType && null != value) {
|
||||
cstrName = getConstructorName(value);
|
||||
"Object" !== cstrName && self[cstrName] && interfaceMembers.push([ memberName, value.nodeType || cstrName ]);
|
||||
} else {
|
||||
"symbol" !== memberType && (memberName.toUpperCase() === memberName ? interfaceMembers.push([ memberName, 6, value ]) : interfaceMembers.push([ memberName, 6 ]));
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
};
|
||||
const readStorage = storageName => {
|
||||
let items = [];
|
||||
let i = 0;
|
||||
let l = len(mainWindow[storageName]);
|
||||
let key;
|
||||
for (;i < l; i++) {
|
||||
key = mainWindow[storageName].key(i);
|
||||
items.push([ key, mainWindow[storageName].getItem(key) ]);
|
||||
}
|
||||
return items;
|
||||
};
|
||||
const getGlobalConstructor = (mainWindow, cstrName) => void 0 !== mainWindow[cstrName] ? new mainWindow[cstrName](noop) : 0;
|
||||
const addGlobalConstructorUsingPrototype = ($interfaces$, mainWindow, cstrName) => {
|
||||
void 0 !== mainWindow[cstrName] && $interfaces$.push([ cstrName, "Object", Object.keys(mainWindow[cstrName].prototype).map((propName => [ propName, 6 ])), 12 ]);
|
||||
};
|
||||
let worker;
|
||||
(async receiveMessage => {
|
||||
const sharedDataBuffer = new SharedArrayBuffer(1073741824);
|
||||
const sharedData = new Int32Array(sharedDataBuffer);
|
||||
return (worker, msg) => {
|
||||
const msgType = msg[0];
|
||||
const accessReq = msg[1];
|
||||
if (0 === msgType) {
|
||||
const initData = (() => {
|
||||
const elm = docImpl.createElement("i");
|
||||
const textNode = docImpl.createTextNode("");
|
||||
const comment = docImpl.createComment("");
|
||||
const frag = docImpl.createDocumentFragment();
|
||||
const shadowRoot = docImpl.createElement("p").attachShadow({
|
||||
mode: "open"
|
||||
});
|
||||
const intersectionObserver = getGlobalConstructor(mainWindow, "IntersectionObserver");
|
||||
const mutationObserver = getGlobalConstructor(mainWindow, "MutationObserver");
|
||||
const resizeObserver = getGlobalConstructor(mainWindow, "ResizeObserver");
|
||||
const perf = mainWindow.performance;
|
||||
const screen = mainWindow.screen;
|
||||
const impls = [ [ mainWindow.history ], [ perf ], [ perf.navigation ], [ perf.timing ], [ screen ], [ screen.orientation ], [ mainWindow.visualViewport ], [ intersectionObserver, 12 ], [ mutationObserver, 12 ], [ resizeObserver, 12 ], [ textNode ], [ comment ], [ frag ], [ shadowRoot ], [ elm ], [ elm.attributes ], [ elm.classList ], [ elm.dataset ], [ elm.style ], [ docImpl ], [ docImpl.doctype ] ];
|
||||
const initialInterfaces = [ readImplementation("Window", mainWindow), readImplementation("Node", textNode) ];
|
||||
const $config$ = JSON.stringify(config, ((k, v) => {
|
||||
if ("function" == typeof v) {
|
||||
v = String(v);
|
||||
v.startsWith(k + "(") && (v = "function " + v);
|
||||
}
|
||||
return v;
|
||||
}));
|
||||
const initWebWorkerData = {
|
||||
$config$: $config$,
|
||||
$interfaces$: readImplementations(impls, initialInterfaces),
|
||||
$libPath$: new URL(libPath, mainWindow.location) + "",
|
||||
$origin$: origin,
|
||||
$localStorage$: readStorage("localStorage"),
|
||||
$sessionStorage$: readStorage("sessionStorage")
|
||||
};
|
||||
addGlobalConstructorUsingPrototype(initWebWorkerData.$interfaces$, mainWindow, "IntersectionObserverEntry");
|
||||
return initWebWorkerData;
|
||||
})();
|
||||
initData.$sharedDataBuffer$ = sharedDataBuffer;
|
||||
worker.postMessage([ 1, initData ]);
|
||||
} else {
|
||||
2 === msg[0] ? worker.postMessage([ 3, readMainInterfaces() ]) : 11 === msgType ? receiveMessage(accessReq, (accessRsp => {
|
||||
const stringifiedData = JSON.stringify(accessRsp);
|
||||
const stringifiedDataLength = stringifiedData.length;
|
||||
for (let i = 0; i < stringifiedDataLength; i++) {
|
||||
sharedData[i + 1] = stringifiedData.charCodeAt(i);
|
||||
}
|
||||
sharedData[0] = stringifiedDataLength;
|
||||
Atomics.notify(sharedData, 0);
|
||||
})) : onMessageFromWebWorker(worker, msg);
|
||||
}
|
||||
};
|
||||
})(((accessReq, responseCallback) => mainAccessHandler(worker, accessReq).then(responseCallback))).then((onMessageHandler => {
|
||||
if (onMessageHandler) {
|
||||
worker = new Worker(libPath + "partytown-ww-atomics.js?v=0.8.0", {
|
||||
name: "Partytown 🎉"
|
||||
});
|
||||
worker.onmessage = ev => {
|
||||
const msg = ev.data;
|
||||
12 === msg[0] ? mainAccessHandler(worker, msg[1]) : onMessageHandler(worker, msg);
|
||||
};
|
||||
logMain("Created Partytown web worker (0.8.0)");
|
||||
worker.onerror = ev => console.error("Web Worker Error", ev);
|
||||
mainWindow.addEventListener("pt1", (ev => registerWindow(worker, getAndSetInstanceId(ev.detail.frameElement), ev.detail)));
|
||||
}
|
||||
}));
|
||||
})(window);
|
||||
@@ -1,374 +0,0 @@
|
||||
/* Partytown 0.8.0 - MIT builder.io */
|
||||
(self => {
|
||||
const [getter, setter, callMethod, constructGlobal, definePrototypePropertyDescriptor, randomId, WinIdKey, InstanceIdKey, ApplyPathKey] = self.$bridgeToMedia$;
|
||||
delete self.$bridgeToMedia$;
|
||||
const ContextKey = Symbol();
|
||||
const MediaSourceKey = Symbol();
|
||||
const ReadyStateKey = Symbol();
|
||||
const SourceBuffersKey = Symbol();
|
||||
const SourceBufferTasksKey = Symbol();
|
||||
const TimeRangesKey = Symbol();
|
||||
const EMPTY_ARRAY = [];
|
||||
const defineCstr = (win, cstrName, Cstr) => win[cstrName] = defineCstrName(cstrName, Cstr);
|
||||
const defineCstrName = (cstrName, Cstr) => Object.defineProperty(Cstr, "name", {
|
||||
value: cstrName
|
||||
});
|
||||
const initCanvas = (WorkerBase, win) => {
|
||||
const HTMLCanvasDescriptorMap = {
|
||||
getContext: {
|
||||
value(contextType, contextAttributes) {
|
||||
this[ContextKey] || (this[ContextKey] = (contextType.includes("webgl") ? createContextWebGL : createContext2D)(this, contextType, contextAttributes));
|
||||
return this[ContextKey];
|
||||
}
|
||||
}
|
||||
};
|
||||
const WorkerCanvasGradient = defineCstr(win, "CanvasGradient", class extends WorkerBase {
|
||||
addColorStop(...args) {
|
||||
callMethod(this, [ "addColorStop" ], args, 2);
|
||||
}
|
||||
});
|
||||
const WorkerCanvasPattern = defineCstr(win, "CanvasPattern", class extends WorkerBase {
|
||||
setTransform(...args) {
|
||||
callMethod(this, [ "setTransform" ], args, 2);
|
||||
}
|
||||
});
|
||||
const createContext2D = (canvasInstance, contextType, contextAttributes) => {
|
||||
const winId = canvasInstance[WinIdKey];
|
||||
const ctxInstanceId = randomId();
|
||||
const ctxInstance = {
|
||||
[WinIdKey]: winId,
|
||||
[InstanceIdKey]: ctxInstanceId,
|
||||
[ApplyPathKey]: []
|
||||
};
|
||||
const ctx = callMethod(canvasInstance, [ "getContext" ], [ contextType, contextAttributes ], 1, ctxInstanceId);
|
||||
const ctx2dGetterMethods = "getContextAttributes,getImageData,getLineDash,getTransform,isPointInPath,isPointInStroke,measureText".split(",");
|
||||
const CanvasRenderingContext2D = {
|
||||
get: (target, propName) => "string" == typeof propName && propName in ctx ? "function" == typeof ctx[propName] ? (...args) => {
|
||||
if (propName.startsWith("create")) {
|
||||
const instanceId = randomId();
|
||||
callMethod(ctxInstance, [ propName ], args, 2, instanceId);
|
||||
if ("createImageData" === propName || "createPattern" === propName) {
|
||||
(api => {
|
||||
console.warn(`${api} not implemented`);
|
||||
})(`${propName}()`);
|
||||
return {
|
||||
setTransform: () => {}
|
||||
};
|
||||
}
|
||||
return new WorkerCanvasGradient(winId, instanceId);
|
||||
}
|
||||
const methodCallType = ctx2dGetterMethods.includes(propName) ? 1 : 2;
|
||||
return callMethod(ctxInstance, [ propName ], args, methodCallType);
|
||||
} : ctx[propName] : target[propName],
|
||||
set(target, propName, value) {
|
||||
if ("string" == typeof propName && propName in ctx) {
|
||||
ctx[propName] !== value && "function" != typeof value && setter(ctxInstance, [ propName ], value);
|
||||
ctx[propName] = value;
|
||||
} else {
|
||||
target[propName] = value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
return new Proxy(ctx, CanvasRenderingContext2D);
|
||||
};
|
||||
const createContextWebGL = (canvasInstance, contextType, contextAttributes) => {
|
||||
const winId = canvasInstance[WinIdKey];
|
||||
const ctxInstanceId = randomId();
|
||||
const ctxInstance = {
|
||||
[WinIdKey]: winId,
|
||||
[InstanceIdKey]: ctxInstanceId,
|
||||
[ApplyPathKey]: []
|
||||
};
|
||||
const ctx = callMethod(canvasInstance, [ "getContext" ], [ contextType, contextAttributes ], 1, ctxInstanceId);
|
||||
const WebGLRenderingContextHandler = {
|
||||
get: (target, propName) => "string" == typeof propName ? "function" != typeof ctx[propName] ? ctx[propName] : (...args) => callMethod(ctxInstance, [ propName ], args, getWebGlMethodCallType(propName)) : target[propName],
|
||||
set(target, propName, value) {
|
||||
if ("string" == typeof propName && propName in ctx) {
|
||||
ctx[propName] !== value && "function" != typeof value && setter(ctxInstance, [ propName ], value);
|
||||
ctx[propName] = value;
|
||||
} else {
|
||||
target[propName] = value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
return new Proxy(ctx, WebGLRenderingContextHandler);
|
||||
};
|
||||
const ctxWebGLGetterMethods = "checkFramebufferStatus,makeXRCompatible".split(",");
|
||||
const getWebGlMethodCallType = methodName => methodName.startsWith("create") || methodName.startsWith("get") || methodName.startsWith("is") || ctxWebGLGetterMethods.includes(methodName) ? 1 : 2;
|
||||
defineCstr(win, "CanvasGradient", WorkerCanvasGradient);
|
||||
defineCstr(win, "CanvasPattern", WorkerCanvasPattern);
|
||||
definePrototypePropertyDescriptor(win.HTMLCanvasElement, HTMLCanvasDescriptorMap);
|
||||
};
|
||||
const initMedia = (WorkerBase, WorkerEventTargetProxy, env, win) => {
|
||||
var _a, _b;
|
||||
win.Audio = defineCstrName("HTMLAudioElement", class {
|
||||
constructor(src) {
|
||||
const audio = env.$createNode$("audio", randomId());
|
||||
audio.src = src;
|
||||
return audio;
|
||||
}
|
||||
});
|
||||
const WorkerAudioTrack = class extends WorkerBase {
|
||||
get enabled() {
|
||||
return getter(this, [ "enabled" ]);
|
||||
}
|
||||
set enabled(value) {
|
||||
setter(this, [ "enabled" ], value);
|
||||
}
|
||||
get id() {
|
||||
return getter(this, [ "id" ]);
|
||||
}
|
||||
get kind() {
|
||||
return getter(this, [ "kind" ]);
|
||||
}
|
||||
get label() {
|
||||
return getter(this, [ "label" ]);
|
||||
}
|
||||
get language() {
|
||||
return getter(this, [ "language" ]);
|
||||
}
|
||||
get sourceBuffer() {
|
||||
return new WorkerSourceBuffer(this);
|
||||
}
|
||||
};
|
||||
const WorkerAudioTrackList = class {
|
||||
constructor(mediaElm) {
|
||||
const winId = mediaElm[WinIdKey];
|
||||
const instanceId = mediaElm[InstanceIdKey];
|
||||
const instance = {
|
||||
addEventListener(...args) {
|
||||
callMethod(mediaElm, [ "audioTracks", "addEventListener" ], args, 3);
|
||||
},
|
||||
getTrackById: (...args) => callMethod(mediaElm, [ "audioTracks", "getTrackById" ], args),
|
||||
get length() {
|
||||
return getter(mediaElm, [ "audioTracks", "length" ]);
|
||||
},
|
||||
removeEventListener(...args) {
|
||||
callMethod(mediaElm, [ "audioTracks", "removeEventListener" ], args, 3);
|
||||
}
|
||||
};
|
||||
return new Proxy(instance, {
|
||||
get: (target, propName) => "number" == typeof propName ? new WorkerAudioTrack(winId, instanceId, [ "audioTracks", propName ]) : target[propName]
|
||||
});
|
||||
}
|
||||
};
|
||||
const WorkerSourceBufferList = defineCstr(win, "SourceBufferList", class extends Array {
|
||||
constructor(mediaSource) {
|
||||
super();
|
||||
this[MediaSourceKey] = mediaSource;
|
||||
}
|
||||
addEventListener(...args) {
|
||||
callMethod(this[MediaSourceKey], [ "sourceBuffers", "addEventListener" ], args, 3);
|
||||
}
|
||||
removeEventListener(...args) {
|
||||
callMethod(this[MediaSourceKey], [ "sourceBuffers", "removeEventListener" ], args, 3);
|
||||
}
|
||||
});
|
||||
const WorkerSourceBuffer = defineCstr(win, "SourceBuffer", (_b = class extends WorkerEventTargetProxy {
|
||||
constructor(mediaSource) {
|
||||
super(mediaSource[WinIdKey], mediaSource[InstanceIdKey], [ "sourceBuffers" ]);
|
||||
this[_a] = [];
|
||||
this[MediaSourceKey] = mediaSource;
|
||||
}
|
||||
abort() {
|
||||
const sbIndex = getSourceBufferIndex(this);
|
||||
callMethod(this, [ sbIndex, "appendWindowStart" ], EMPTY_ARRAY, 1);
|
||||
}
|
||||
addEventListener(...args) {
|
||||
const sbIndex = getSourceBufferIndex(this);
|
||||
callMethod(this, [ sbIndex, "addEventListener" ], args, 3);
|
||||
}
|
||||
appendBuffer(buf) {
|
||||
this[SourceBufferTasksKey].push([ "appendBuffer", [ buf ], buf ]);
|
||||
drainSourceBufferQueue(this);
|
||||
}
|
||||
get appendWindowStart() {
|
||||
const sbIndex = getSourceBufferIndex(this);
|
||||
return getter(this, [ sbIndex, "appendWindowStart" ]);
|
||||
}
|
||||
set appendWindowStart(value) {
|
||||
const sbIndex = getSourceBufferIndex(this);
|
||||
setter(this, [ sbIndex, "appendWindowStart" ], value);
|
||||
}
|
||||
get appendWindowEnd() {
|
||||
const sbIndex = getSourceBufferIndex(this);
|
||||
return getter(this, [ sbIndex, "appendWindowEnd" ]);
|
||||
}
|
||||
set appendWindowEnd(value) {
|
||||
const sbIndex = getSourceBufferIndex(this);
|
||||
setter(this, [ sbIndex, "appendWindowEnd" ], value);
|
||||
}
|
||||
get buffered() {
|
||||
const mediaSource = this[MediaSourceKey];
|
||||
const sbIndex = getSourceBufferIndex(this);
|
||||
const timeRanges = new WorkerTimeRanges(mediaSource[WinIdKey], mediaSource[InstanceIdKey], [ "sourceBuffers", sbIndex, "buffered" ]);
|
||||
return timeRanges;
|
||||
}
|
||||
changeType(mimeType) {
|
||||
const sbIndex = getSourceBufferIndex(this);
|
||||
callMethod(this, [ sbIndex, "changeType" ], [ mimeType ], 2);
|
||||
}
|
||||
get mode() {
|
||||
const sbIndex = getSourceBufferIndex(this);
|
||||
return getter(this, [ sbIndex, "mode" ]);
|
||||
}
|
||||
set mode(value) {
|
||||
const sbIndex = getSourceBufferIndex(this);
|
||||
setter(this, [ sbIndex, "mode" ], value);
|
||||
}
|
||||
remove(start, end) {
|
||||
this[SourceBufferTasksKey].push([ "remove", [ start, end ] ]);
|
||||
drainSourceBufferQueue(this);
|
||||
}
|
||||
removeEventListener(...args) {
|
||||
const sbIndex = getSourceBufferIndex(this);
|
||||
callMethod(this, [ sbIndex, "removeEventListener" ], args, 3);
|
||||
}
|
||||
get timestampOffset() {
|
||||
const sbIndex = getSourceBufferIndex(this);
|
||||
return getter(this, [ sbIndex, "timestampOffset" ]);
|
||||
}
|
||||
set timestampOffset(value) {
|
||||
const sbIndex = getSourceBufferIndex(this);
|
||||
setter(this, [ sbIndex, "timestampOffset" ], value);
|
||||
}
|
||||
get updating() {
|
||||
const sbIndex = getSourceBufferIndex(this);
|
||||
return getter(this, [ sbIndex, "updating" ]);
|
||||
}
|
||||
}, _a = SourceBufferTasksKey, _b));
|
||||
const WorkerTimeRanges = defineCstr(win, "TimeRanges", class extends WorkerBase {
|
||||
start(...args) {
|
||||
return callMethod(this, [ "start" ], args);
|
||||
}
|
||||
end(...args) {
|
||||
return callMethod(this, [ "end" ], args);
|
||||
}
|
||||
get length() {
|
||||
return getter(this, [ "length" ]);
|
||||
}
|
||||
});
|
||||
const getSourceBufferIndex = sourceBuffer => {
|
||||
if (sourceBuffer) {
|
||||
const mediaSource = sourceBuffer[MediaSourceKey];
|
||||
const sourceBufferList = mediaSource[SourceBuffersKey];
|
||||
return sourceBufferList.indexOf(sourceBuffer);
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
const drainSourceBufferQueue = sourceBuffer => {
|
||||
if (sourceBuffer[SourceBufferTasksKey].length) {
|
||||
if (!sourceBuffer.updating) {
|
||||
const task = sourceBuffer[SourceBufferTasksKey].shift();
|
||||
if (task) {
|
||||
const sbIndex = getSourceBufferIndex(sourceBuffer);
|
||||
callMethod(sourceBuffer, [ sbIndex, task[0] ], task[1], 3, void 0, task[2]);
|
||||
}
|
||||
}
|
||||
setTimeout((() => drainSourceBufferQueue(sourceBuffer)), 50);
|
||||
}
|
||||
};
|
||||
const HTMLMediaDescriptorMap = {
|
||||
buffered: {
|
||||
get() {
|
||||
if (!this[TimeRangesKey]) {
|
||||
this[TimeRangesKey] = new WorkerTimeRanges(this[WinIdKey], this[InstanceIdKey], [ "buffered" ]);
|
||||
setTimeout((() => {
|
||||
this[TimeRangesKey] = void 0;
|
||||
}), 5e3);
|
||||
}
|
||||
return this[TimeRangesKey];
|
||||
}
|
||||
},
|
||||
readyState: {
|
||||
get() {
|
||||
if (4 === this[ReadyStateKey]) {
|
||||
return 4;
|
||||
}
|
||||
if ("number" != typeof this[ReadyStateKey]) {
|
||||
this[ReadyStateKey] = getter(this, [ "readyState" ]);
|
||||
setTimeout((() => {
|
||||
this[ReadyStateKey] = void 0;
|
||||
}), 1e3);
|
||||
}
|
||||
return this[ReadyStateKey];
|
||||
}
|
||||
}
|
||||
};
|
||||
defineCstr(win, "MediaSource", class extends WorkerEventTargetProxy {
|
||||
constructor() {
|
||||
super(env.$winId$);
|
||||
this[SourceBuffersKey] = new WorkerSourceBufferList(this);
|
||||
constructGlobal(this, "MediaSource", EMPTY_ARRAY);
|
||||
}
|
||||
get activeSourceBuffers() {
|
||||
return [];
|
||||
}
|
||||
addSourceBuffer(mimeType) {
|
||||
const sourceBuffer = new WorkerSourceBuffer(this);
|
||||
this[SourceBuffersKey].push(sourceBuffer);
|
||||
callMethod(this, [ "addSourceBuffer" ], [ mimeType ]);
|
||||
return sourceBuffer;
|
||||
}
|
||||
clearLiveSeekableRange() {
|
||||
callMethod(this, [ "clearLiveSeekableRange" ], EMPTY_ARRAY, 2);
|
||||
}
|
||||
get duration() {
|
||||
return getter(this, [ "duration" ]);
|
||||
}
|
||||
set duration(value) {
|
||||
setter(this, [ "duration" ], value);
|
||||
}
|
||||
endOfStream(endOfStreamError) {
|
||||
callMethod(this, [ "endOfStream" ], [ endOfStreamError ], 3);
|
||||
}
|
||||
get readyState() {
|
||||
return getter(this, [ "readyState" ]);
|
||||
}
|
||||
removeSourceBuffer(sourceBuffer) {
|
||||
const index = getSourceBufferIndex(sourceBuffer);
|
||||
if (index > -1) {
|
||||
this[SourceBuffersKey].splice(index, 1);
|
||||
callMethod(this, [ "removeSourceBuffer" ], [ index ], 1);
|
||||
}
|
||||
}
|
||||
setLiveSeekableRange(start, end) {
|
||||
callMethod(this, [ "setLiveSeekableRange" ], [ start, end ], 2);
|
||||
}
|
||||
get sourceBuffers() {
|
||||
return this[SourceBuffersKey];
|
||||
}
|
||||
static isTypeSupported(mimeType) {
|
||||
if (!isStaticTypeSupported.has(mimeType)) {
|
||||
const isSupported = callMethod(win, [ "MediaSource", "isTypeSupported" ], [ mimeType ]);
|
||||
isStaticTypeSupported.set(mimeType, isSupported);
|
||||
}
|
||||
return isStaticTypeSupported.get(mimeType);
|
||||
}
|
||||
});
|
||||
const winURL = win.URL = defineCstrName("URL", class extends URL {});
|
||||
const hasAudioTracks = "audioTracks" in win.HTMLMediaElement.prototype;
|
||||
if (hasAudioTracks) {
|
||||
defineCstr(win, "AudioTrackList", WorkerAudioTrackList);
|
||||
defineCstr(win, "AudioTrack", WorkerAudioTrack);
|
||||
HTMLMediaDescriptorMap.audioTracks = {
|
||||
get() {
|
||||
return new WorkerAudioTrackList(this);
|
||||
}
|
||||
};
|
||||
}
|
||||
definePrototypePropertyDescriptor(win.HTMLMediaElement, HTMLMediaDescriptorMap);
|
||||
winURL.createObjectURL = obj => callMethod(win, [ "URL", "createObjectURL" ], [ obj ]);
|
||||
winURL.revokeObjectURL = obj => callMethod(win, [ "URL", "revokeObjectURL" ], [ obj ]);
|
||||
};
|
||||
const isStaticTypeSupported = new Map;
|
||||
self.$bridgeFromMedia$ = (WorkerBase, WorkerEventTargetProxy, env, win, windowMediaConstructors) => {
|
||||
windowMediaConstructors.map((mediaCstrName => {
|
||||
delete win[mediaCstrName];
|
||||
}));
|
||||
initCanvas(WorkerBase, win);
|
||||
initMedia(WorkerBase, WorkerEventTargetProxy, env, win);
|
||||
};
|
||||
})(self);
|
||||
@@ -1,559 +0,0 @@
|
||||
/* Partytown 0.8.0 - MIT builder.io */
|
||||
(window => {
|
||||
const isPromise = v => "object" == typeof v && v && v.then;
|
||||
const noop = () => {};
|
||||
const len = obj => obj.length;
|
||||
const getConstructorName = obj => {
|
||||
var _a, _b, _c;
|
||||
try {
|
||||
const constructorName = null === (_a = null == obj ? void 0 : obj.constructor) || void 0 === _a ? void 0 : _a.name;
|
||||
if (constructorName) {
|
||||
return constructorName;
|
||||
}
|
||||
} catch (e) {}
|
||||
try {
|
||||
const zoneJsConstructorName = null === (_c = null === (_b = null == obj ? void 0 : obj.__zone_symbol__originalInstance) || void 0 === _b ? void 0 : _b.constructor) || void 0 === _c ? void 0 : _c.name;
|
||||
if (zoneJsConstructorName) {
|
||||
return zoneJsConstructorName;
|
||||
}
|
||||
} catch (e) {}
|
||||
return "";
|
||||
};
|
||||
const startsWith = (str, val) => str.startsWith(val);
|
||||
const isValidMemberName = memberName => !(startsWith(memberName, "webkit") || startsWith(memberName, "toJSON") || startsWith(memberName, "constructor") || startsWith(memberName, "toString") || startsWith(memberName, "_"));
|
||||
const getNodeName = node => 11 === node.nodeType && node.host ? "#s" : node.nodeName;
|
||||
const randomId = () => Math.round(Math.random() * Number.MAX_SAFE_INTEGER).toString(36);
|
||||
const defineConstructorName = (Cstr, value) => ((obj, memberName, descriptor) => Object.defineProperty(obj, memberName, {
|
||||
...descriptor,
|
||||
configurable: true
|
||||
}))(Cstr, "name", {
|
||||
value: value
|
||||
});
|
||||
const htmlConstructorTags = {
|
||||
Anchor: "a",
|
||||
DList: "dl",
|
||||
Image: "img",
|
||||
OList: "ol",
|
||||
Paragraph: "p",
|
||||
Quote: "q",
|
||||
TableCaption: "caption",
|
||||
TableCell: "td",
|
||||
TableCol: "colgroup",
|
||||
TableRow: "tr",
|
||||
TableSection: "tbody",
|
||||
UList: "ul"
|
||||
};
|
||||
const svgConstructorTags = {
|
||||
Graphics: "g",
|
||||
SVG: "svg"
|
||||
};
|
||||
const InstanceIdKey = Symbol();
|
||||
const CreatedKey = Symbol();
|
||||
const instances = new Map;
|
||||
const mainRefs = new Map;
|
||||
const winCtxs = {};
|
||||
const windowIds = new WeakMap;
|
||||
const getAndSetInstanceId = (instance, instanceId) => {
|
||||
if (instance) {
|
||||
if (instanceId = windowIds.get(instance)) {
|
||||
return instanceId;
|
||||
}
|
||||
(instanceId = instance[InstanceIdKey]) || setInstanceId(instance, instanceId = randomId());
|
||||
return instanceId;
|
||||
}
|
||||
};
|
||||
const getInstance = (winId, instanceId, win, doc, docId) => {
|
||||
if ((win = winCtxs[winId]) && win.$window$) {
|
||||
if (winId === instanceId) {
|
||||
return win.$window$;
|
||||
}
|
||||
doc = win.$window$.document;
|
||||
docId = instanceId.split(".").pop();
|
||||
if ("d" === docId) {
|
||||
return doc;
|
||||
}
|
||||
if ("e" === docId) {
|
||||
return doc.documentElement;
|
||||
}
|
||||
if ("h" === docId) {
|
||||
return doc.head;
|
||||
}
|
||||
if ("b" === docId) {
|
||||
return doc.body;
|
||||
}
|
||||
}
|
||||
return instances.get(instanceId);
|
||||
};
|
||||
const setInstanceId = (instance, instanceId, now) => {
|
||||
if (instance) {
|
||||
instances.set(instanceId, instance);
|
||||
instance[InstanceIdKey] = instanceId;
|
||||
instance[CreatedKey] = now = Date.now();
|
||||
if (now > lastCleanup + 5e3) {
|
||||
instances.forEach(((storedInstance, instanceId) => {
|
||||
storedInstance[CreatedKey] < lastCleanup && storedInstance.nodeType && !storedInstance.isConnected && instances.delete(instanceId);
|
||||
}));
|
||||
lastCleanup = now;
|
||||
}
|
||||
}
|
||||
};
|
||||
let lastCleanup = 0;
|
||||
const mainWindow = window.parent;
|
||||
const docImpl = document.implementation.createHTMLDocument();
|
||||
const config = mainWindow.partytown || {};
|
||||
const libPath = (config.lib || "/~partytown/") + "debug/";
|
||||
const logMain = msg => {
|
||||
console.debug.apply(console, [ "%cMain 🌎", "background: #717171; color: white; padding: 2px 3px; border-radius: 2px; font-size: 0.8em;", msg ]);
|
||||
};
|
||||
const winIds = [];
|
||||
const normalizedWinId = winId => {
|
||||
winIds.includes(winId) || winIds.push(winId);
|
||||
return winIds.indexOf(winId) + 1;
|
||||
};
|
||||
const defineCustomElement = (winId, worker, ceData) => {
|
||||
const Cstr = defineConstructorName(class extends winCtxs[winId].$window$.HTMLElement {}, ceData[0]);
|
||||
const ceCallbackMethods = "connectedCallback,disconnectedCallback,attributeChangedCallback,adoptedCallback".split(",");
|
||||
ceCallbackMethods.map((callbackMethodName => Cstr.prototype[callbackMethodName] = function(...args) {
|
||||
worker.postMessage([ 15, winId, getAndSetInstanceId(this), callbackMethodName, args ]);
|
||||
}));
|
||||
Cstr.observedAttributes = ceData[1];
|
||||
return Cstr;
|
||||
};
|
||||
const serializeForWorker = ($winId$, value, added, type, cstrName, prevInstanceId) => void 0 !== value && (type = typeof value) ? "string" === type || "number" === type || "boolean" === type || null == value ? [ 0, value ] : "function" === type ? [ 6 ] : (added = added || new Set) && Array.isArray(value) ? added.has(value) ? [ 1, [] ] : added.add(value) && [ 1, value.map((v => serializeForWorker($winId$, v, added))) ] : "object" === type ? serializedValueIsError(value) ? [ 14, {
|
||||
name: value.name,
|
||||
message: value.message,
|
||||
stack: value.stack
|
||||
} ] : "" === (cstrName = getConstructorName(value)) ? [ 2, {} ] : "Window" === cstrName ? [ 3, [ $winId$, $winId$ ] ] : "HTMLCollection" === cstrName || "NodeList" === cstrName ? [ 7, Array.from(value).map((v => serializeForWorker($winId$, v, added)[1])) ] : cstrName.endsWith("Event") ? [ 5, serializeObjectForWorker($winId$, value, added) ] : "CSSRuleList" === cstrName ? [ 12, Array.from(value).map(serializeCssRuleForWorker) ] : startsWith(cstrName, "CSS") && cstrName.endsWith("Rule") ? [ 11, serializeCssRuleForWorker(value) ] : "CSSStyleDeclaration" === cstrName ? [ 13, serializeObjectForWorker($winId$, value, added) ] : "Attr" === cstrName ? [ 10, [ value.name, value.value ] ] : value.nodeType ? [ 3, [ $winId$, getAndSetInstanceId(value), getNodeName(value), prevInstanceId ] ] : [ 2, serializeObjectForWorker($winId$, value, added, true, true) ] : void 0 : value;
|
||||
const serializeObjectForWorker = (winId, obj, added, includeFunctions, includeEmptyStrings, serializedObj, propName, propValue) => {
|
||||
serializedObj = {};
|
||||
if (!added.has(obj)) {
|
||||
added.add(obj);
|
||||
for (propName in obj) {
|
||||
if (isValidMemberName(propName)) {
|
||||
propValue = "path" === propName && getConstructorName(obj).endsWith("Event") ? obj.composedPath() : obj[propName];
|
||||
(includeFunctions || "function" != typeof propValue) && (includeEmptyStrings || "" !== propValue) && (serializedObj[propName] = serializeForWorker(winId, propValue, added));
|
||||
}
|
||||
}
|
||||
}
|
||||
return serializedObj;
|
||||
};
|
||||
const serializeCssRuleForWorker = cssRule => {
|
||||
let obj = {};
|
||||
let key;
|
||||
for (key in cssRule) {
|
||||
validCssRuleProps.includes(key) && (obj[key] = String(cssRule[key]));
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
const serializedValueIsError = value => value instanceof window.top.Error;
|
||||
const deserializeFromWorker = (worker, serializedTransfer, serializedType, serializedValue) => {
|
||||
if (serializedTransfer) {
|
||||
serializedType = serializedTransfer[0];
|
||||
serializedValue = serializedTransfer[1];
|
||||
return 0 === serializedType ? serializedValue : 4 === serializedType ? deserializeRefFromWorker(worker, serializedValue) : 1 === serializedType ? serializedValue.map((v => deserializeFromWorker(worker, v))) : 3 === serializedType ? getInstance(serializedValue[0], serializedValue[1]) : 5 === serializedType ? constructEvent(deserializeObjectFromWorker(worker, serializedValue)) : 2 === serializedType ? deserializeObjectFromWorker(worker, serializedValue) : 8 === serializedType ? serializedValue : 9 === serializedType ? new window[serializedTransfer[2]](serializedValue) : void 0;
|
||||
}
|
||||
};
|
||||
const deserializeRefFromWorker = (worker, {$winId$: $winId$, $instanceId$: $instanceId$, $refId$: $refId$}, ref) => {
|
||||
ref = mainRefs.get($refId$);
|
||||
if (!ref) {
|
||||
ref = function(...args) {
|
||||
worker.postMessage([ 9, {
|
||||
$winId$: $winId$,
|
||||
$instanceId$: $instanceId$,
|
||||
$refId$: $refId$,
|
||||
$thisArg$: serializeForWorker($winId$, this),
|
||||
$args$: serializeForWorker($winId$, args)
|
||||
} ]);
|
||||
};
|
||||
mainRefs.set($refId$, ref);
|
||||
}
|
||||
return ref;
|
||||
};
|
||||
const constructEvent = eventProps => new ("detail" in eventProps ? CustomEvent : Event)(eventProps.type, eventProps);
|
||||
const deserializeObjectFromWorker = (worker, serializedValue, obj, key) => {
|
||||
obj = {};
|
||||
for (key in serializedValue) {
|
||||
obj[key] = deserializeFromWorker(worker, serializedValue[key]);
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
const validCssRuleProps = "cssText,selectorText,href,media,namespaceURI,prefix,name,conditionText".split(",");
|
||||
const mainAccessHandler = async (worker, accessReq) => {
|
||||
let accessRsp = {
|
||||
$msgId$: accessReq.$msgId$
|
||||
};
|
||||
let totalTasks = len(accessReq.$tasks$);
|
||||
let i = 0;
|
||||
let task;
|
||||
let winId;
|
||||
let applyPath;
|
||||
let instance;
|
||||
let rtnValue;
|
||||
let isLast;
|
||||
for (;i < totalTasks; i++) {
|
||||
try {
|
||||
isLast = i === totalTasks - 1;
|
||||
task = accessReq.$tasks$[i];
|
||||
winId = task.$winId$;
|
||||
applyPath = task.$applyPath$;
|
||||
!winCtxs[winId] && winId.startsWith("f_") && await new Promise((resolve => {
|
||||
let check = 0;
|
||||
let callback = () => {
|
||||
winCtxs[winId] || check++ > 1e3 ? resolve() : requestAnimationFrame(callback);
|
||||
};
|
||||
callback();
|
||||
}));
|
||||
if (1 === applyPath[0] && applyPath[1] in winCtxs[winId].$window$) {
|
||||
setInstanceId(new winCtxs[winId].$window$[applyPath[1]](...deserializeFromWorker(worker, applyPath[2])), task.$instanceId$);
|
||||
} else {
|
||||
instance = getInstance(winId, task.$instanceId$);
|
||||
if (instance) {
|
||||
rtnValue = applyToInstance(worker, winId, instance, applyPath, isLast, task.$groupedGetters$);
|
||||
task.$assignInstanceId$ && ("string" == typeof task.$assignInstanceId$ ? setInstanceId(rtnValue, task.$assignInstanceId$) : winCtxs[task.$assignInstanceId$.$winId$] = {
|
||||
$winId$: task.$assignInstanceId$.$winId$,
|
||||
$window$: {
|
||||
document: rtnValue
|
||||
}
|
||||
});
|
||||
if (isPromise(rtnValue)) {
|
||||
rtnValue = await rtnValue;
|
||||
isLast && (accessRsp.$isPromise$ = true);
|
||||
}
|
||||
isLast && (accessRsp.$rtnValue$ = serializeForWorker(winId, rtnValue, void 0, void 0, void 0, task.$instanceId$));
|
||||
} else {
|
||||
accessRsp.$error$ = `Error finding instance "${task.$instanceId$}" on window ${normalizedWinId(winId)}`;
|
||||
console.error(accessRsp.$error$, task);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
isLast ? accessRsp.$error$ = String(e.stack || e) : console.error(e);
|
||||
}
|
||||
}
|
||||
return accessRsp;
|
||||
};
|
||||
const applyToInstance = (worker, winId, instance, applyPath, isLast, groupedGetters) => {
|
||||
let i = 0;
|
||||
let l = len(applyPath);
|
||||
let next;
|
||||
let current;
|
||||
let previous;
|
||||
let args;
|
||||
let groupedRtnValues;
|
||||
for (;i < l; i++) {
|
||||
current = applyPath[i];
|
||||
next = applyPath[i + 1];
|
||||
previous = applyPath[i - 1];
|
||||
try {
|
||||
if (!Array.isArray(next)) {
|
||||
if ("string" == typeof current || "number" == typeof current) {
|
||||
if (i + 1 === l && groupedGetters) {
|
||||
groupedRtnValues = {};
|
||||
groupedGetters.map((propName => groupedRtnValues[propName] = instance[propName]));
|
||||
return groupedRtnValues;
|
||||
}
|
||||
instance = instance[current];
|
||||
} else {
|
||||
if (0 === next) {
|
||||
instance[previous] = deserializeFromWorker(worker, current);
|
||||
return;
|
||||
}
|
||||
if ("function" == typeof instance[previous]) {
|
||||
args = deserializeFromWorker(worker, current);
|
||||
"define" === previous && "CustomElementRegistry" === getConstructorName(instance) && (args[1] = defineCustomElement(winId, worker, args[1]));
|
||||
"insertRule" === previous && args[1] > len(instance.cssRules) && (args[1] = len(instance.cssRules));
|
||||
instance = instance[previous].apply(instance, args);
|
||||
if ("play" === previous) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (isLast) {
|
||||
throw err;
|
||||
}
|
||||
console.debug("Non-blocking setter error:", err);
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
};
|
||||
const readNextScript = (worker, winCtx) => {
|
||||
let $winId$ = winCtx.$winId$;
|
||||
let win = winCtx.$window$;
|
||||
let doc = win.document;
|
||||
let scriptSelector = 'script[type="text/partytown"]:not([data-ptid]):not([data-pterror])';
|
||||
let scriptElm;
|
||||
let $instanceId$;
|
||||
let scriptData;
|
||||
if (doc && doc.body) {
|
||||
scriptElm = doc.querySelector('script[type="text/partytown"]:not([data-ptid]):not([data-pterror]):not([async]):not([defer])');
|
||||
scriptElm || (scriptElm = doc.querySelector(scriptSelector));
|
||||
if (scriptElm) {
|
||||
scriptElm.dataset.ptid = $instanceId$ = getAndSetInstanceId(scriptElm, $winId$);
|
||||
scriptData = {
|
||||
$winId$: $winId$,
|
||||
$instanceId$: $instanceId$
|
||||
};
|
||||
if (scriptElm.src) {
|
||||
scriptData.$url$ = scriptElm.src;
|
||||
scriptData.$orgUrl$ = scriptElm.dataset.ptsrc || scriptElm.src;
|
||||
} else {
|
||||
scriptData.$content$ = scriptElm.innerHTML;
|
||||
}
|
||||
worker.postMessage([ 7, scriptData ]);
|
||||
} else {
|
||||
if (!winCtx.$isInitialized$) {
|
||||
winCtx.$isInitialized$ = 1;
|
||||
((worker, $winId$, win) => {
|
||||
let queuedForwardCalls = win._ptf;
|
||||
let forwards = (win.partytown || {}).forward || [];
|
||||
let i;
|
||||
let mainForwardFn;
|
||||
let forwardCall = ($forward$, args) => worker.postMessage([ 10, {
|
||||
$winId$: $winId$,
|
||||
$forward$: $forward$,
|
||||
$args$: serializeForWorker($winId$, Array.from(args))
|
||||
} ]);
|
||||
win._ptf = void 0;
|
||||
forwards.map((forwardProps => {
|
||||
mainForwardFn = win;
|
||||
forwardProps.split(".").map(((_, i, arr) => {
|
||||
mainForwardFn = mainForwardFn[arr[i]] = i + 1 < len(arr) ? mainForwardFn[arr[i]] || ("push" === arr[i + 1] ? [] : {}) : (...args) => forwardCall(arr, args);
|
||||
}));
|
||||
}));
|
||||
if (queuedForwardCalls) {
|
||||
for (i = 0; i < len(queuedForwardCalls); i += 2) {
|
||||
forwardCall(queuedForwardCalls[i], queuedForwardCalls[i + 1]);
|
||||
}
|
||||
}
|
||||
})(worker, $winId$, win);
|
||||
doc.dispatchEvent(new CustomEvent("pt0"));
|
||||
{
|
||||
const winType = win === win.top ? "top" : "iframe";
|
||||
logMain(`Executed ${winType} window ${normalizedWinId($winId$)} environment scripts in ${(performance.now() - winCtx.$startTime$).toFixed(1)}ms`);
|
||||
}
|
||||
}
|
||||
worker.postMessage([ 8, $winId$ ]);
|
||||
}
|
||||
} else {
|
||||
requestAnimationFrame((() => readNextScript(worker, winCtx)));
|
||||
}
|
||||
};
|
||||
const registerWindow = (worker, $winId$, $window$) => {
|
||||
if (!windowIds.has($window$)) {
|
||||
windowIds.set($window$, $winId$);
|
||||
const doc = $window$.document;
|
||||
const history = $window$.history;
|
||||
const $parentWinId$ = windowIds.get($window$.parent);
|
||||
let initialised = false;
|
||||
const onInitialisedQueue = [];
|
||||
const onInitialised = callback => {
|
||||
initialised ? callback() : onInitialisedQueue.push(callback);
|
||||
};
|
||||
const sendInitEnvData = () => {
|
||||
worker.postMessage([ 5, {
|
||||
$winId$: $winId$,
|
||||
$parentWinId$: $parentWinId$,
|
||||
$url$: doc.baseURI,
|
||||
$visibilityState$: doc.visibilityState
|
||||
} ]);
|
||||
setTimeout((() => {
|
||||
initialised = true;
|
||||
onInitialisedQueue.forEach((callback => {
|
||||
callback();
|
||||
}));
|
||||
}));
|
||||
};
|
||||
const pushState = history.pushState.bind(history);
|
||||
const replaceState = history.replaceState.bind(history);
|
||||
const onLocationChange = (type, state, newUrl, oldUrl) => () => {
|
||||
setTimeout((() => {
|
||||
worker.postMessage([ 13, {
|
||||
$winId$: $winId$,
|
||||
type: type,
|
||||
state: state,
|
||||
url: doc.baseURI,
|
||||
newUrl: newUrl,
|
||||
oldUrl: oldUrl
|
||||
} ]);
|
||||
}));
|
||||
};
|
||||
history.pushState = (state, _, newUrl) => {
|
||||
pushState(state, _, newUrl);
|
||||
onInitialised(onLocationChange(0, state, null == newUrl ? void 0 : newUrl.toString()));
|
||||
};
|
||||
history.replaceState = (state, _, newUrl) => {
|
||||
replaceState(state, _, newUrl);
|
||||
onInitialised(onLocationChange(1, state, null == newUrl ? void 0 : newUrl.toString()));
|
||||
};
|
||||
$window$.addEventListener("popstate", (event => {
|
||||
onInitialised(onLocationChange(2, event.state));
|
||||
}));
|
||||
$window$.addEventListener("hashchange", (event => {
|
||||
onInitialised(onLocationChange(3, {}, event.newURL, event.oldURL));
|
||||
}));
|
||||
$window$.addEventListener("ptupdate", (() => {
|
||||
readNextScript(worker, winCtxs[$winId$]);
|
||||
}));
|
||||
doc.addEventListener("visibilitychange", (() => worker.postMessage([ 14, $winId$, doc.visibilityState ])));
|
||||
winCtxs[$winId$] = {
|
||||
$winId$: $winId$,
|
||||
$window$: $window$
|
||||
};
|
||||
winCtxs[$winId$].$startTime$ = performance.now();
|
||||
{
|
||||
const winType = $winId$ === $parentWinId$ ? "top" : "iframe";
|
||||
logMain(`Registered ${winType} window ${normalizedWinId($winId$)}`);
|
||||
}
|
||||
"complete" === doc.readyState ? sendInitEnvData() : $window$.addEventListener("load", sendInitEnvData);
|
||||
}
|
||||
};
|
||||
const onMessageFromWebWorker = (worker, msg, winCtx) => {
|
||||
if (4 === msg[0]) {
|
||||
registerWindow(worker, randomId(), mainWindow);
|
||||
} else {
|
||||
winCtx = winCtxs[msg[1]];
|
||||
winCtx && (7 === msg[0] ? requestAnimationFrame((() => readNextScript(worker, winCtx))) : 6 === msg[0] && ((worker, winCtx, instanceId, errorMsg, scriptElm) => {
|
||||
scriptElm = winCtx.$window$.document.querySelector(`[data-ptid="${instanceId}"]`);
|
||||
if (scriptElm) {
|
||||
errorMsg ? scriptElm.dataset.pterror = errorMsg : scriptElm.type += "-x";
|
||||
delete scriptElm.dataset.ptid;
|
||||
}
|
||||
readNextScript(worker, winCtx);
|
||||
})(worker, winCtx, msg[2], msg[3]));
|
||||
}
|
||||
};
|
||||
const readMainPlatform = () => {
|
||||
const elm = docImpl.createElement("i");
|
||||
const textNode = docImpl.createTextNode("");
|
||||
const comment = docImpl.createComment("");
|
||||
const frag = docImpl.createDocumentFragment();
|
||||
const shadowRoot = docImpl.createElement("p").attachShadow({
|
||||
mode: "open"
|
||||
});
|
||||
const intersectionObserver = getGlobalConstructor(mainWindow, "IntersectionObserver");
|
||||
const mutationObserver = getGlobalConstructor(mainWindow, "MutationObserver");
|
||||
const resizeObserver = getGlobalConstructor(mainWindow, "ResizeObserver");
|
||||
const perf = mainWindow.performance;
|
||||
const screen = mainWindow.screen;
|
||||
const impls = [ [ mainWindow.history ], [ perf ], [ perf.navigation ], [ perf.timing ], [ screen ], [ screen.orientation ], [ mainWindow.visualViewport ], [ intersectionObserver, 12 ], [ mutationObserver, 12 ], [ resizeObserver, 12 ], [ textNode ], [ comment ], [ frag ], [ shadowRoot ], [ elm ], [ elm.attributes ], [ elm.classList ], [ elm.dataset ], [ elm.style ], [ docImpl ], [ docImpl.doctype ] ];
|
||||
const initialInterfaces = [ readImplementation("Window", mainWindow), readImplementation("Node", textNode) ];
|
||||
const $config$ = JSON.stringify(config, ((k, v) => {
|
||||
if ("function" == typeof v) {
|
||||
v = String(v);
|
||||
v.startsWith(k + "(") && (v = "function " + v);
|
||||
}
|
||||
return v;
|
||||
}));
|
||||
const initWebWorkerData = {
|
||||
$config$: $config$,
|
||||
$interfaces$: readImplementations(impls, initialInterfaces),
|
||||
$libPath$: new URL(libPath, mainWindow.location) + "",
|
||||
$origin$: origin,
|
||||
$localStorage$: readStorage("localStorage"),
|
||||
$sessionStorage$: readStorage("sessionStorage")
|
||||
};
|
||||
addGlobalConstructorUsingPrototype(initWebWorkerData.$interfaces$, mainWindow, "IntersectionObserverEntry");
|
||||
return initWebWorkerData;
|
||||
};
|
||||
const readMainInterfaces = () => {
|
||||
const elms = Object.getOwnPropertyNames(mainWindow).map((interfaceName => ((doc, interfaceName, r, tag) => {
|
||||
r = interfaceName.match(/^(HTML|SVG)(.+)Element$/);
|
||||
if (r) {
|
||||
tag = r[2];
|
||||
return "S" == interfaceName[0] ? doc.createElementNS("http://www.w3.org/2000/svg", svgConstructorTags[tag] || tag.slice(0, 2).toLowerCase() + tag.slice(2)) : doc.createElement(htmlConstructorTags[tag] || tag);
|
||||
}
|
||||
})(docImpl, interfaceName))).filter((elm => elm)).map((elm => [ elm ]));
|
||||
return readImplementations(elms, []);
|
||||
};
|
||||
const cstrs = new Set([ "Object" ]);
|
||||
const readImplementations = (impls, interfaces) => {
|
||||
const cstrImpls = impls.filter((implData => implData[0])).map((implData => {
|
||||
const impl = implData[0];
|
||||
const interfaceType = implData[1];
|
||||
const cstrName = getConstructorName(impl);
|
||||
const CstrPrototype = mainWindow[cstrName].prototype;
|
||||
return [ cstrName, CstrPrototype, impl, interfaceType ];
|
||||
}));
|
||||
cstrImpls.map((([cstrName, CstrPrototype, impl, intefaceType]) => readOwnImplementation(cstrs, interfaces, cstrName, CstrPrototype, impl, intefaceType)));
|
||||
return interfaces;
|
||||
};
|
||||
const readImplementation = (cstrName, impl, memberName) => {
|
||||
let interfaceMembers = [];
|
||||
let interfaceInfo = [ cstrName, "Object", interfaceMembers ];
|
||||
for (memberName in impl) {
|
||||
readImplementationMember(interfaceMembers, impl, memberName);
|
||||
}
|
||||
return interfaceInfo;
|
||||
};
|
||||
const readOwnImplementation = (cstrs, interfaces, cstrName, CstrPrototype, impl, interfaceType) => {
|
||||
if (!cstrs.has(cstrName)) {
|
||||
cstrs.add(cstrName);
|
||||
const SuperCstr = Object.getPrototypeOf(CstrPrototype);
|
||||
const superCstrName = getConstructorName(SuperCstr);
|
||||
const interfaceMembers = [];
|
||||
const propDescriptors = Object.getOwnPropertyDescriptors(CstrPrototype);
|
||||
readOwnImplementation(cstrs, interfaces, superCstrName, SuperCstr, impl, interfaceType);
|
||||
for (const memberName in propDescriptors) {
|
||||
readImplementationMember(interfaceMembers, impl, memberName);
|
||||
}
|
||||
interfaces.push([ cstrName, superCstrName, interfaceMembers, interfaceType, getNodeName(impl) ]);
|
||||
}
|
||||
};
|
||||
const readImplementationMember = (interfaceMembers, implementation, memberName, value, memberType, cstrName) => {
|
||||
try {
|
||||
if (isValidMemberName(memberName) && isNaN(memberName[0]) && "all" !== memberName) {
|
||||
value = implementation[memberName];
|
||||
memberType = typeof value;
|
||||
if ("function" === memberType) {
|
||||
(String(value).includes("[native") || Object.getPrototypeOf(implementation)[memberName]) && interfaceMembers.push([ memberName, 5 ]);
|
||||
} else if ("object" === memberType && null != value) {
|
||||
cstrName = getConstructorName(value);
|
||||
"Object" !== cstrName && self[cstrName] && interfaceMembers.push([ memberName, value.nodeType || cstrName ]);
|
||||
} else {
|
||||
"symbol" !== memberType && (memberName.toUpperCase() === memberName ? interfaceMembers.push([ memberName, 6, value ]) : interfaceMembers.push([ memberName, 6 ]));
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
};
|
||||
const readStorage = storageName => {
|
||||
let items = [];
|
||||
let i = 0;
|
||||
let l = len(mainWindow[storageName]);
|
||||
let key;
|
||||
for (;i < l; i++) {
|
||||
key = mainWindow[storageName].key(i);
|
||||
items.push([ key, mainWindow[storageName].getItem(key) ]);
|
||||
}
|
||||
return items;
|
||||
};
|
||||
const getGlobalConstructor = (mainWindow, cstrName) => void 0 !== mainWindow[cstrName] ? new mainWindow[cstrName](noop) : 0;
|
||||
const addGlobalConstructorUsingPrototype = ($interfaces$, mainWindow, cstrName) => {
|
||||
void 0 !== mainWindow[cstrName] && $interfaces$.push([ cstrName, "Object", Object.keys(mainWindow[cstrName].prototype).map((propName => [ propName, 6 ])), 12 ]);
|
||||
};
|
||||
let worker;
|
||||
(receiveMessage => {
|
||||
const swContainer = window.navigator.serviceWorker;
|
||||
return swContainer.getRegistration().then((swRegistration => {
|
||||
swContainer.addEventListener("message", (ev => receiveMessage(ev.data, (accessRsp => swRegistration.active && swRegistration.active.postMessage(accessRsp)))));
|
||||
return (worker, msg) => {
|
||||
0 === msg[0] ? worker.postMessage([ 1, readMainPlatform() ]) : 2 === msg[0] ? worker.postMessage([ 3, readMainInterfaces() ]) : onMessageFromWebWorker(worker, msg);
|
||||
};
|
||||
}));
|
||||
})(((accessReq, responseCallback) => mainAccessHandler(worker, accessReq).then(responseCallback))).then((onMessageHandler => {
|
||||
if (onMessageHandler) {
|
||||
worker = new Worker(libPath + "partytown-ww-sw.js?v=0.8.0", {
|
||||
name: "Partytown 🎉"
|
||||
});
|
||||
worker.onmessage = ev => {
|
||||
const msg = ev.data;
|
||||
12 === msg[0] ? mainAccessHandler(worker, msg[1]) : onMessageHandler(worker, msg);
|
||||
};
|
||||
logMain("Created Partytown web worker (0.8.0)");
|
||||
worker.onerror = ev => console.error("Web Worker Error", ev);
|
||||
mainWindow.addEventListener("pt1", (ev => registerWindow(worker, getAndSetInstanceId(ev.detail.frameElement), ev.detail)));
|
||||
}
|
||||
}));
|
||||
})(window);
|
||||
@@ -1,59 +0,0 @@
|
||||
/* Partytown 0.8.0 - MIT builder.io */
|
||||
const resolves = new Map;
|
||||
|
||||
const swMessageError = (accessReq, $error$) => ({
|
||||
$msgId$: accessReq.$msgId$,
|
||||
$error$: $error$
|
||||
});
|
||||
|
||||
const httpRequestFromWebWorker = req => new Promise((async resolve => {
|
||||
const accessReq = await req.clone().json();
|
||||
const responseData = await (accessReq => new Promise((async resolve => {
|
||||
const clients = await self.clients.matchAll();
|
||||
const client = [ ...clients ].sort(((a, b) => a.url > b.url ? -1 : a.url < b.url ? 1 : 0))[0];
|
||||
if (client) {
|
||||
const timeout = 12e4;
|
||||
const msgResolve = [ resolve, setTimeout((() => {
|
||||
resolves.delete(accessReq.$msgId$);
|
||||
resolve(swMessageError(accessReq, "Timeout"));
|
||||
}), timeout) ];
|
||||
resolves.set(accessReq.$msgId$, msgResolve);
|
||||
client.postMessage(accessReq);
|
||||
} else {
|
||||
resolve(swMessageError(accessReq, "NoParty"));
|
||||
}
|
||||
})))(accessReq);
|
||||
resolve(response(JSON.stringify(responseData), "application/json"));
|
||||
}));
|
||||
|
||||
const response = (body, contentType) => new Response(body, {
|
||||
headers: {
|
||||
"content-type": contentType || "text/html",
|
||||
"Cache-Control": "no-store"
|
||||
}
|
||||
});
|
||||
|
||||
self.oninstall = () => self.skipWaiting();
|
||||
|
||||
self.onactivate = () => self.clients.claim();
|
||||
|
||||
self.onmessage = ev => {
|
||||
const accessRsp = ev.data;
|
||||
const r = resolves.get(accessRsp.$msgId$);
|
||||
if (r) {
|
||||
resolves.delete(accessRsp.$msgId$);
|
||||
clearTimeout(r[1]);
|
||||
r[0](accessRsp);
|
||||
}
|
||||
};
|
||||
|
||||
self.onfetch = ev => {
|
||||
const req = ev.request;
|
||||
const url = new URL(req.url);
|
||||
const pathname = url.pathname;
|
||||
if (pathname.endsWith("sw.html")) {
|
||||
ev.respondWith(response('<!DOCTYPE html><html><head><meta charset="utf-8"><script src="./partytown-sandbox-sw.js?v=0.8.0"><\/script></head></html>'));
|
||||
} else {
|
||||
pathname.endsWith("proxytown") && ev.respondWith(httpRequestFromWebWorker(req));
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,75 +0,0 @@
|
||||
/* Partytown 0.8.0 - MIT builder.io */
|
||||
!function(win, doc, nav, top, useAtomics, config, libPath, timeout, scripts, sandbox, mainForwardFn, isReady) {
|
||||
function ready() {
|
||||
if (!isReady) {
|
||||
isReady = 1;
|
||||
libPath = (config.lib || "/~partytown/") + (false !== config.debug ? "debug/" : "");
|
||||
if ("/" == libPath[0]) {
|
||||
scripts = doc.querySelectorAll('script[type="text/partytown"]');
|
||||
if (top != win) {
|
||||
top.dispatchEvent(new CustomEvent("pt1", {
|
||||
detail: win
|
||||
}));
|
||||
} else {
|
||||
timeout = setTimeout(fallback, 1e4);
|
||||
doc.addEventListener("pt0", clearFallback);
|
||||
useAtomics ? loadSandbox(1) : nav.serviceWorker ? nav.serviceWorker.register(libPath + (config.swPath || "partytown-sw.js"), {
|
||||
scope: libPath
|
||||
}).then((function(swRegistration) {
|
||||
if (swRegistration.active) {
|
||||
loadSandbox();
|
||||
} else if (swRegistration.installing) {
|
||||
swRegistration.installing.addEventListener("statechange", (function(ev) {
|
||||
"activated" == ev.target.state && loadSandbox();
|
||||
}));
|
||||
} else {
|
||||
console.warn(swRegistration);
|
||||
}
|
||||
}), console.error) : fallback();
|
||||
}
|
||||
} else {
|
||||
console.warn('Partytown config.lib url must start with "/"');
|
||||
}
|
||||
}
|
||||
}
|
||||
function loadSandbox(isAtomics) {
|
||||
sandbox = doc.createElement(isAtomics ? "script" : "iframe");
|
||||
if (!isAtomics) {
|
||||
sandbox.setAttribute("style", "display:block;width:0;height:0;border:0;visibility:hidden");
|
||||
sandbox.setAttribute("aria-hidden", !0);
|
||||
}
|
||||
sandbox.src = libPath + "partytown-" + (isAtomics ? "atomics.js?v=0.8.0" : "sandbox-sw.html?" + Date.now());
|
||||
doc.querySelector(config.sandboxParent || "body").appendChild(sandbox);
|
||||
}
|
||||
function fallback(i, script) {
|
||||
console.warn("Partytown script fallback");
|
||||
clearFallback();
|
||||
top == win && (config.forward || []).map((function(forwardProps) {
|
||||
delete win[forwardProps.split(".")[0]];
|
||||
}));
|
||||
for (i = 0; i < scripts.length; i++) {
|
||||
script = doc.createElement("script");
|
||||
script.innerHTML = scripts[i].innerHTML;
|
||||
doc.head.appendChild(script);
|
||||
}
|
||||
sandbox && sandbox.parentNode.removeChild(sandbox);
|
||||
}
|
||||
function clearFallback() {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
config = win.partytown || {};
|
||||
top == win && (config.forward || []).map((function(forwardProps) {
|
||||
mainForwardFn = win;
|
||||
forwardProps.split(".").map((function(_, i, forwardPropsArr) {
|
||||
mainForwardFn = mainForwardFn[forwardPropsArr[i]] = i + 1 < forwardPropsArr.length ? "push" == forwardPropsArr[i + 1] ? [] : mainForwardFn[forwardPropsArr[i]] || {} : function() {
|
||||
(win._ptf = win._ptf || []).push(forwardPropsArr, arguments);
|
||||
};
|
||||
}));
|
||||
}));
|
||||
if ("complete" == doc.readyState) {
|
||||
ready();
|
||||
} else {
|
||||
win.addEventListener("DOMContentLoaded", ready);
|
||||
win.addEventListener("load", ready);
|
||||
}
|
||||
}(window, document, navigator, top, window.crossOriginIsolated);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
||||
/* Partytown 0.8.0 - MIT builder.io */
|
||||
!function(t,e,n,i,r,o,a,d,s,c,l,p){function u(){p||(p=1,"/"==(a=(o.lib||"/~partytown/")+(o.debug?"debug/":""))[0]&&(s=e.querySelectorAll('script[type="text/partytown"]'),i!=t?i.dispatchEvent(new CustomEvent("pt1",{detail:t})):(d=setTimeout(f,1e4),e.addEventListener("pt0",w),r?h(1):n.serviceWorker?n.serviceWorker.register(a+(o.swPath||"partytown-sw.js"),{scope:a}).then((function(t){t.active?h():t.installing&&t.installing.addEventListener("statechange",(function(t){"activated"==t.target.state&&h()}))}),console.error):f())))}function h(t){c=e.createElement(t?"script":"iframe"),t||(c.setAttribute("style","display:block;width:0;height:0;border:0;visibility:hidden"),c.setAttribute("aria-hidden",!0)),c.src=a+"partytown-"+(t?"atomics.js?v=0.8.0":"sandbox-sw.html?"+Date.now()),e.querySelector(o.sandboxParent||"body").appendChild(c)}function f(n,r){for(w(),i==t&&(o.forward||[]).map((function(e){delete t[e.split(".")[0]]})),n=0;n<s.length;n++)(r=e.createElement("script")).innerHTML=s[n].innerHTML,e.head.appendChild(r);c&&c.parentNode.removeChild(c)}function w(){clearTimeout(d)}o=t.partytown||{},i==t&&(o.forward||[]).map((function(e){l=t,e.split(".").map((function(e,n,i){l=l[i[n]]=n+1<i.length?"push"==i[n+1]?[]:l[i[n]]||{}:function(){(t._ptf=t._ptf||[]).push(i,arguments)}}))})),"complete"==e.readyState?u():(t.addEventListener("DOMContentLoaded",u),t.addEventListener("load",u))}(window,document,navigator,top,window.crossOriginIsolated);
|
||||
@@ -1,17 +1,16 @@
|
||||
import adapter from '@sveltejs/adapter-vercel';
|
||||
import preprocess from 'svelte-preprocess';
|
||||
import adapter from '@sveltejs/adapter-auto';
|
||||
import { vitePreprocess } from '@sveltejs/kit/vite';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
// Consult https://github.com/sveltejs/svelte-preprocess
|
||||
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
||||
// for more information about preprocessors
|
||||
preprocess: [
|
||||
preprocess({
|
||||
postcss: true,
|
||||
preserve: ['partytown']
|
||||
})
|
||||
],
|
||||
preprocess: vitePreprocess(),
|
||||
|
||||
kit: {
|
||||
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
||||
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
|
||||
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
||||
adapter: adapter()
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||
theme: {
|
||||
extend: {}
|
||||
},
|
||||
plugins: []
|
||||
};
|
||||
@@ -8,10 +8,7 @@
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true
|
||||
"strict": true,
|
||||
"moduleResolution": "NodeNext"
|
||||
}
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
}
|
||||
|
||||
12
vercel.json
12
vercel.json
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"rewrites": [
|
||||
{
|
||||
"source": "/proxytown/gtm",
|
||||
"destination": "https://www.googletagmanager.com/gtag/js"
|
||||
},
|
||||
{
|
||||
"source": "/proxytown/ga",
|
||||
"destination": "https://www.google-analytics.com/analytics.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,26 +1,9 @@
|
||||
import { join } from 'path'
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import type { UserConfig } from 'vite';
|
||||
import { partytownVite } from '@builder.io/partytown/utils'
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
const config: UserConfig = {
|
||||
plugins: [sveltekit(),
|
||||
partytownVite({
|
||||
// `dest` specifies where files are copied to in production
|
||||
dest: join(process.cwd(), 'static', '~partytown')
|
||||
})
|
||||
],
|
||||
define: {
|
||||
_a: 'undefined'
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
external: ['@resvg/resvg-js']
|
||||
}
|
||||
},
|
||||
optimizeDeps: {
|
||||
exclude: ['@resvg/resvg-js']
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()],
|
||||
test: {
|
||||
include: ['src/**/*.{test,spec}.{js,ts}']
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user