├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── release.yml │ └── test-and-benchmark.yml ├── .gitignore ├── .gitpod.Dockerfile ├── .gitpod.yml ├── .husky └── commit-msg ├── .nvmrc ├── .typedoc ├── tsconfig.json ├── typedoc.css ├── typedoc.json ├── typedoc.mjs └── typedoc.tsx ├── .vscode ├── extensions.json └── launch.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── TODO.md ├── media ├── _headers ├── assets │ └── favicon.svg ├── favicon.ico ├── fonts │ ├── FluentSystemIcons-Regular.ttf │ └── FluentSystemIcons-Regular.woff2 ├── measure.js └── robots.txt ├── package.json ├── pnpm-lock.yaml ├── src ├── constants.ts ├── index.ts ├── polyfill.ts └── ponyfill.ts ├── tsconfig.json ├── tsup.config.ts ├── vite-env.d.ts └── vite.config.ts /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.187.0/containers/typescript-node/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] Node.js version: 16, 14, 12 4 | ARG VARIANT="16-buster" 5 | FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:0-${VARIANT} 6 | 7 | # [Optional] Uncomment this section to install additional OS packages. 8 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 9 | # && apt-get -y install --no-install-recommends 10 | 11 | # [Optional] Uncomment if you want to install an additional version of node using nvm 12 | # ARG EXTRA_NODE_VERSION=10 13 | # RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" 14 | 15 | # [Optional] Uncomment if you want to install more global node packages 16 | # RUN su node -c "npm install -g " 17 | 18 | RUN su node -c "npm install -g pnpm" -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.187.0/containers/typescript-node 3 | { 4 | "name": "Node.js & TypeScript", 5 | "build": { 6 | "dockerfile": "Dockerfile", 7 | // Update 'VARIANT' to pick a Node version: 12, 14, 16 8 | "args": { 9 | "VARIANT": "16" 10 | } 11 | }, 12 | 13 | // Set *default* container specific settings.json values on container create. 14 | "settings": {}, 15 | 16 | 17 | // Add the IDs of extensions you want installed when the container is created. 18 | "extensions": [ 19 | "bierner.jsdoc-markdown-highlighting", 20 | "github.github-vscode-theme", 21 | "shd101wyy.markdown-preview-enhanced" 22 | ], 23 | 24 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 25 | // "forwardPorts": [], 26 | 27 | // Use 'postCreateCommand' to run commands after the container is created. 28 | "postCreateCommand": "pnpm install", 29 | 30 | // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 31 | "remoteUser": "node" 32 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG] An error occurs between pages...on browser..." 5 | labels: bug 6 | assignees: okikio 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | 40 | 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE] How about the multipage SharedWorker do...as well" 5 | labels: enhancement 6 | assignees: okikio 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: workflow_dispatch 3 | 4 | jobs: 5 | release: 6 | name: Release 7 | runs-on: ubuntu-latest 8 | if: "!contains(github.event.head_commit.message, '[skip ci]')" 9 | permissions: 10 | contents: write # to be able to publish a GitHub release 11 | issues: write # to be able to comment on released issues 12 | pull-requests: write # to be able to comment on released pull requests 13 | packages: write # to be able to publish a GitHub package 14 | id-token: write 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token 20 | fetch-depth: 0 # otherwise, you will failed to push refs to dest repo 21 | 22 | - name: Setup Node.js environment 23 | uses: actions/setup-node@v4 24 | with: 25 | # File containing the version Spec of the version to use. Examples: .nvmrc, .node-version, .tool-versions. 26 | node-version-file: '.nvmrc' # optional 27 | 28 | - name: Setup pnpm 29 | uses: pnpm/action-setup@v4 30 | with: 31 | # Version of pnpm to install 32 | version: latest # optional 33 | # If specified, run `pnpm install` 34 | run_install: true # optional, default is null 35 | 36 | - name: Get pnpm store directory 37 | id: pnpm-cache 38 | shell: bash 39 | run: | 40 | echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT 41 | 42 | - uses: actions/cache@v4 43 | name: Setup pnpm cache 44 | with: 45 | path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} 46 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 47 | restore-keys: | 48 | ${{ runner.os }}-pnpm-store- 49 | 50 | - name: Install dependencies 51 | run: pnpm install 52 | 53 | - name: Build 54 | run: pnpm pre-release 55 | 56 | - name: Release 57 | run: pnpm semantic-release 58 | env: 59 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 60 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 61 | 62 | # - name: Push changes 63 | # if: ${{ github.ref == 'refs/heads/main' }} 64 | # uses: ad-m/github-push-action@master 65 | # with: 66 | # github_token: ${{ secrets.GITHUB_TOKEN }} 67 | # branch: ${{ github.ref }} -------------------------------------------------------------------------------- /.github/workflows/test-and-benchmark.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Test & Benchmark CI 5 | 6 | on: 7 | push: 8 | branches: [main] 9 | paths: 10 | - '*.ts' 11 | - '*.js' 12 | - '*.json' 13 | - '*.jsonc' 14 | - '*.yaml' 15 | - '.github/**/test-and-benchmark.yml' 16 | - '.typedoc/**/*' 17 | - 'media/**/*' 18 | - 'tests/**/*' 19 | - 'scripts/**/*' 20 | workflow_dispatch: 21 | 22 | jobs: 23 | build: 24 | runs-on: ubuntu-latest 25 | if: "!contains(github.event.head_commit.message, '[skip ci]')" 26 | permissions: 27 | contents: read 28 | 29 | steps: 30 | - uses: actions/checkout@v4 31 | with: 32 | persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token 33 | fetch-depth: 0 # otherwise, you will failed to push refs to dest repo 34 | 35 | - name: Setup Node.js environment 36 | uses: actions/setup-node@v4 37 | with: 38 | # File containing the version Spec of the version to use. Examples: .nvmrc, .node-version, .tool-versions. 39 | node-version-file: '.nvmrc' # optional 40 | 41 | - name: Setup pnpm 42 | uses: pnpm/action-setup@v4 43 | with: 44 | # Version of pnpm to install 45 | version: latest # optional 46 | # If specified, run `pnpm install` 47 | run_install: false # optional, default is null 48 | 49 | - name: Get pnpm store directory 50 | id: pnpm-cache 51 | shell: bash 52 | run: | 53 | echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT 54 | 55 | - uses: actions/cache@v4 56 | name: Setup pnpm cache 57 | with: 58 | path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} 59 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 60 | restore-keys: | 61 | ${{ runner.os }}-pnpm-store- 62 | 63 | - name: Install dependencies 64 | run: pnpm install 65 | 66 | - name: Build 67 | run: pnpm pre-release -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # Project specific files 107 | /lib 108 | /docs 109 | /@types 110 | 111 | # build output 112 | dist/ 113 | docs/ 114 | 115 | # generated types 116 | .astro/ 117 | 118 | # dependencies 119 | node_modules/ 120 | 121 | # logs 122 | npm-debug.log* 123 | yarn-debug.log* 124 | yarn-error.log* 125 | pnpm-debug.log* 126 | 127 | # environment variables 128 | .env 129 | .env.production 130 | 131 | # macOS-specific files 132 | .DS_Store 133 | -------------------------------------------------------------------------------- /.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gitpod/workspace-full:latest 2 | 3 | # Install custom tools, runtime, etc. using apt-get 4 | # For example, the command below would install "bastet" - a command line tetris clone: 5 | # 6 | # RUN sudo apt-get -q update && # sudo apt-get install -yq bastet && # sudo rm -rf /var/lib/apt/lists/* 7 | # 8 | # More information: https://www.gitpod.io/docs/config-docker/ -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: .gitpod.Dockerfile 3 | 4 | # List the ports you want to expose and what to do when they are served. See https://www.gitpod.io/docs/43_config_ports/ 5 | ports: 6 | - port: 3000 7 | onOpen: open-preview 8 | - port: 3001 9 | onOpen: ignore 10 | 11 | github: 12 | prebuilds: 13 | # enable for the master/default branch (defaults to true) 14 | master: true 15 | # enable for all branches in this repo (defaults to false) 16 | branches: true 17 | # enable for pull requests coming from this repo (defaults to true) 18 | pullRequests: true 19 | # enable for pull requests coming from forks (defaults to false) 20 | pullRequestsFromForks: true 21 | # add a "Review in Gitpod" button as a comment to pull requests (defaults to true) 22 | addComment: true 23 | # add a "Review in Gitpod" button to pull requests (defaults to false) 24 | addBadge: false 25 | # add a label once the prebuild is ready to pull requests (defaults to false) 26 | addLabel: prebuilt-in-gitpod 27 | 28 | # List the start up tasks. You can start them in parallel in multiple terminals. See https://www.gitpod.io/docs/44_config_start_tasks/ 29 | tasks: 30 | - init: > 31 | npm install -g pnpm && 32 | pnpm install 33 | command: > 34 | npm install -g pnpm && 35 | pnpm build -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | pnpm commitlint "$1" 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 21 -------------------------------------------------------------------------------- /.typedoc/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "skipLibCheck": true, 5 | "jsxFactory": "JSX.createElement", 6 | "jsxFragmentFactory": "JSX.Fragment" 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /.typedoc/typedoc.css: -------------------------------------------------------------------------------- 1 | /* @import url("https://fonts.googleapis.com/css2?family=Lexend:wght@300;400;500;700&display=swap"); */ 2 | 3 | @font-face { 4 | font-family: "FluentSystemIcons-Regular"; 5 | src: 6 | url("./media/fonts/FluentSystemIcons-Regular.woff2") format("woff2"), 7 | url("./media/fonts/FluentSystemIcons-Regular.ttf") format("truetype"); 8 | } 9 | 10 | :root { 11 | --light-color-background-secondary: #fff; 12 | --dark-color-background: #1b1c1f; 13 | --dark-color-background-secondary: #36393f; 14 | 15 | --light-color-panel-divider: #dadce0; 16 | --dark-color-panel-divider: #47474d; 17 | 18 | --color-panel-divider: var(--light-color-panel-divider); 19 | --dark-color-text-aside: #8486b4; 20 | /* --light-color-background: #f8f9fa; 21 | --light-color-panel-divider: #dadce0; 22 | --light-link-color: #1155cc; 23 | --dark-color-panel-divider: #47474d; 24 | --dark-color-menu-divider-focus: #d73a49; 25 | --dark-color-background: #1b1c1f; 26 | --dark-color-secondary-background: #2d2f34; */ 27 | } 28 | 29 | @media (prefers-color-scheme: light) { 30 | :root { 31 | --light-color-background-secondary: #fff; 32 | --light-color-panel-divider: #dadce0; 33 | --color-panel-divider: var(--light-color-panel-divider); 34 | } 35 | } 36 | 37 | @media (prefers-color-scheme: dark) { 38 | :root { 39 | --dark-color-background: #1b1c1f; 40 | --dark-color-background-secondary: #36393f; 41 | --dark-color-panel-divider: #47474d; 42 | --color-panel-divider: var(--dark-color-panel-divider); 43 | --dark-color-text-aside: #8486b4; 44 | } 45 | } 46 | 47 | html, 48 | body { 49 | font-family: "Lexend", "Manrope", "Century Gothic", sans-serif; 50 | font-weight: 400; 51 | } 52 | 53 | h1 { 54 | font-weight: 300; 55 | padding-bottom: 0.9rem; 56 | } 57 | 58 | h2 { 59 | font-weight: 400; 60 | color: var(--color-ts); 61 | } 62 | 63 | h3 { 64 | font-weight: 500; 65 | } 66 | 67 | h4 { 68 | font-weight: 500; 69 | } 70 | 71 | a :is(h1, h2, h3, h4, h5, h6):after { 72 | font-family: FluentSystemIcons-Regular !important; 73 | font-style: normal; 74 | font-weight: normal !important; 75 | font-variant: normal; 76 | text-transform: none; 77 | line-height: 1; 78 | -webkit-font-smoothing: antialiased; 79 | -moz-osx-font-smoothing: grayscale; 80 | content: "\f4e5"; 81 | 82 | visibility: hidden; 83 | font-weight: 800; 84 | font-size: 1em; 85 | padding-left: 0.25em; 86 | vertical-align: middle; 87 | color: var(--color-link); 88 | } 89 | 90 | a :is(h1, h2, h3, h4, h5, h6):hover:after { 91 | visibility: visible; 92 | } 93 | 94 | a :is(h1, h2, h3, h4, h5, h6):is(:hover, :focus):after { 95 | visibility: visible; 96 | } 97 | 98 | a .external-icon svg { 99 | vertical-align: middle; 100 | display: inline-block; 101 | stroke-width: 2; 102 | opacity: .8; 103 | width: 1em; 104 | height: 1em; 105 | fill: currentColor; 106 | } 107 | 108 | details { 109 | cursor: pointer; 110 | } 111 | 112 | pre { 113 | padding: 14px; 114 | } 115 | 116 | pre, 117 | code { 118 | border-radius: 6px; 119 | border: 1px solid rgb(20 20 20 / 0.25); 120 | } 121 | 122 | pre code { 123 | border: none; 124 | } 125 | 126 | blockquote { 127 | padding: 0.2em 0.8em 0.2em 1em; 128 | border-left: 5px solid gray; 129 | margin-bottom: 2.5rem; 130 | background-color: var(--color-background-secondary); 131 | border-radius: 0.45em; 132 | } 133 | 134 | a[href^="https://bundlejs.com/"] .external-icon { 135 | display: none; 136 | } 137 | 138 | img[src^="https://bundlejs.com/"] { 139 | overflow: hidden; 140 | border-radius: 20em; 141 | border: 1px solid rgb(20 20 20 / 0.25); 142 | } 143 | 144 | .container { 145 | max-width: 1600px; 146 | margin-inline: auto; 147 | } 148 | 149 | .container .col-content { 150 | max-width: 800px; 151 | margin-inline: auto; 152 | } 153 | 154 | .container.tsd-generator { 155 | max-width: 100%; 156 | } 157 | 158 | .container.tsd-generator p { 159 | line-height: 28px; 160 | max-width: 1200px; 161 | width: 100%; 162 | margin: auto; 163 | } 164 | 165 | header.tsd-page-toolbar { 166 | border-bottom: 1px solid var(--color-panel-divider); 167 | } 168 | 169 | .tsd-signature { 170 | border-radius: 0.5rem; 171 | } 172 | 173 | .tsd-breadcrumb { 174 | padding-block-start: 0.5rem; 175 | } 176 | 177 | .tsd-typography p, .tsd-typography ul, .tsd-typography ol { 178 | line-height: 1.85em; 179 | font-weight: 300; 180 | } 181 | .tsd-navigation { 182 | padding-inline-end: 0.25rem; 183 | } 184 | 185 | .tsd-accordion-details { 186 | padding-inline-start: 1.35rem; 187 | } 188 | 189 | .tsd-accordion-details > ul > li > ul { 190 | padding-left: 0; 191 | } 192 | 193 | .container-main { 194 | --mask-image: linear-gradient(0deg, transparent 0%, white 2%, white 50%, white 98%, transparent 100%); 195 | } 196 | 197 | @media (min-width: 769px) { 198 | .container-main { 199 | grid-template-columns: minmax(0, 15rem) minmax(0, 2.5fr); 200 | } 201 | 202 | .col-sidebar { 203 | border-right: 1px solid var(--color-panel-divider); 204 | mask-image: var(--mask-image); 205 | -webkit-mask-image: var(--mask-image); 206 | padding-block: 1rem; 207 | } 208 | } 209 | 210 | @media (min-width: 1200px) { 211 | .container-main { 212 | grid-template-columns: minmax(0, 18rem) minmax(0, 2.5fr) minmax(0, 15.5rem); 213 | } 214 | 215 | .page-menu { 216 | border-left: 1px solid var(--color-panel-divider); 217 | } 218 | 219 | .site-menu { 220 | border-right: 1px solid var(--color-panel-divider); 221 | mask-image: var(--mask-image); 222 | -webkit-mask-image: var(--mask-image); 223 | padding-block: 1rem; 224 | } 225 | } 226 | 227 | .tsd-accordion-details ul { 228 | padding-left: 1rem; 229 | } 230 | 231 | .tsd-accordion-details > ul { 232 | padding-left: 0; 233 | } 234 | 235 | :is(.tsd-page-navigation, .tsd-navigation) svg { 236 | vertical-align: middle; 237 | } 238 | 239 | .tsd-navigation a.current, .tsd-page-navigation a.current { 240 | border-radius: 0.45rem; 241 | padding-block: 0.3rem; 242 | padding-inline: 0.3rem; 243 | } 244 | 245 | .col-menu { 246 | border-left: 1px solid var(--color-panel-divider); 247 | } 248 | 249 | .tsd-widget:is(.search, .options, .menu) svg { 250 | display: none; 251 | } 252 | 253 | .tsd-widget:is(.search, .options, .menu):after { 254 | font-family: FluentSystemIcons-Regular !important; 255 | font-style: normal; 256 | font-weight: normal !important; 257 | font-variant: normal; 258 | text-transform: none; 259 | line-height: 1; 260 | -webkit-font-smoothing: antialiased; 261 | -moz-osx-font-smoothing: grayscale; 262 | 263 | font-size: 24px; 264 | color: var(--color-toolbar-text); 265 | width: 100%; 266 | height: 100%; 267 | 268 | position: absolute; 269 | text-align: center; 270 | display: flex; 271 | top: 0; 272 | left: 0; 273 | align-items: center; 274 | justify-content: center; 275 | } 276 | 277 | .tsd-widget.search:after { 278 | content: "\f690"; 279 | } 280 | 281 | .tsd-widget.options:after { 282 | content: "\f407"; 283 | } 284 | 285 | .tsd-widget.menu { 286 | position: relative; 287 | height: 40px; 288 | vertical-align: bottom; 289 | } 290 | 291 | .tsd-widget.menu:after { 292 | content: "\f561"; 293 | } -------------------------------------------------------------------------------- /.typedoc/typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://typedoc.org/schema.json", 3 | "exclude": [ 4 | "../lib/**/*", 5 | "../tests/**/*", 6 | "../**/test.ts", 7 | "../**/@types/*.d.ts" 8 | ], 9 | "entryPoints": ["../src/index.ts"], 10 | "name": "@okikio/sharedworker", 11 | "out": "../docs", 12 | "lightHighlightTheme": "github-light", 13 | "darkHighlightTheme": "github-dark", 14 | "includeVersion": true, 15 | "readme": "../README.md", 16 | "customCss": "./typedoc.css", 17 | "tsconfig": "./tsconfig.json", 18 | "plugin": [ 19 | "./typedoc.mjs", 20 | "typedoc-plugin-extras", 21 | "typedoc-plugin-mdn-links", 22 | "typedoc-plugin-inline-sources" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.typedoc/typedoc.mjs: -------------------------------------------------------------------------------- 1 | import { Application, ParameterType, JSX } from "typedoc"; 2 | function load(app) { 3 | app.options.addDeclaration({ 4 | name: "keywords", 5 | type: ParameterType.Array, 6 | help: "Website keywords", 7 | defaultValue: [ 8 | "sharedworker", 9 | "polyfill", 10 | "ponyfill", 11 | "framework agnostic", 12 | "es2023", 13 | "web" 14 | ] 15 | }); 16 | app.renderer.hooks.on("head.begin", (ctx) => { 17 | const keywords = ctx.options.getValue("keywords"); 18 | return /* @__PURE__ */ JSX.createElement(JSX.Fragment, null, /* @__PURE__ */ JSX.createElement("link", { rel: "preconnect", href: "https://fonts.googleapis.com" }), /* @__PURE__ */ JSX.createElement("link", { rel: "preconnect", href: "https://fonts.gstatic.com", crossorigin: true }), /* @__PURE__ */ JSX.createElement( 19 | "link", 20 | { 21 | href: "https://fonts.googleapis.com/css2?family=Lexend:wght@300;400;500;700&display=swap", 22 | rel: "stylesheet" 23 | } 24 | ), /* @__PURE__ */ JSX.createElement("meta", { name: "keyword", content: keywords.join(", ") }), /* @__PURE__ */ JSX.createElement("meta", { name: "color-scheme", content: "dark light" }), /* @__PURE__ */ JSX.createElement("link", { rel: "shortcut icon", href: "/media/favicon.ico" }), /* @__PURE__ */ JSX.createElement( 25 | "link", 26 | { 27 | rel: "icon", 28 | type: "image/svg+xml", 29 | href: "./media/assets/favicon.svg" 30 | } 31 | ), /* @__PURE__ */ JSX.createElement("meta", { name: "web-author", content: "Okiki Ojo" }), /* @__PURE__ */ JSX.createElement("meta", { name: "robots", content: "index, follow" }), /* @__PURE__ */ JSX.createElement("meta", { name: "twitter:url", content: "https://sharedworker.okikio.dev" }), /* @__PURE__ */ JSX.createElement("meta", { name: "twitter:site", content: "@okikio_dev" }), /* @__PURE__ */ JSX.createElement("meta", { name: "twitter:creator", content: "@okikio_dev" }), /* @__PURE__ */ JSX.createElement("link", { href: "https://twitter.com/okikio_dev", rel: "me" }), /* @__PURE__ */ JSX.createElement( 32 | "link", 33 | { 34 | rel: "webmention", 35 | href: "https://webmention.io/sharedworker.okikio.dev/webmention" 36 | } 37 | ), /* @__PURE__ */ JSX.createElement( 38 | "link", 39 | { 40 | rel: "pingback", 41 | href: "https://webmention.io/sharedworker.okikio.dev/xmlrpc" 42 | } 43 | ), /* @__PURE__ */ JSX.createElement( 44 | "link", 45 | { 46 | rel: "pingback", 47 | href: "https://webmention.io/webmention?forward=https://sharedworker.okikio.dev/endpoint" 48 | } 49 | )); 50 | }); 51 | } 52 | export { 53 | load 54 | }; 55 | -------------------------------------------------------------------------------- /.typedoc/typedoc.tsx: -------------------------------------------------------------------------------- 1 | /** @jsxFactory JSX.createElement */ 2 | /** @jsxFragmentFactory JSX.Fragment */ 3 | import { Application, ParameterType, JSX } from "typedoc"; 4 | 5 | export function load(app: Application) { 6 | app.options.addDeclaration({ 7 | name: "keywords", 8 | type: ParameterType.Array, 9 | help: "Website keywords", 10 | defaultValue: [ 11 | "sharedworker", 12 | "polyfill", 13 | "ponyfill", 14 | "framework agnostic", 15 | "es2023", 16 | "web" 17 | ], 18 | }); 19 | 20 | app.renderer.hooks.on("head.begin", (ctx) => { 21 | const keywords = ctx.options.getValue("keywords") as string[]; 22 | 23 | return ( 24 | <> 25 | 26 | 27 | 31 | 32 | 33 | 34 | 35 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 53 | 57 | 61 | 62 | ); 63 | }); 64 | } 65 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [1.1.0](https://github.com/okikio/sharedworker/compare/v1.0.7...v1.1.0) (2024-11-20) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * fix broken typedoc ([e850d55](https://github.com/okikio/sharedworker/commit/e850d55f81b5fe4a58d03712e79acb102cec550b)) 7 | 8 | 9 | ### Features 10 | 11 | * add SharedPonyfill class to address [#9](https://github.com/okikio/sharedworker/issues/9) ([2a7a143](https://github.com/okikio/sharedworker/commit/2a7a14347de8cf5a663f7d512acf77c0f6d90cb5)) 12 | 13 | ## [1.0.7](https://github.com/okikio/sharedworker/compare/v1.0.6...v1.0.7) (2024-2-1) 14 | 15 | 16 | ### Bug Fixes 17 | 18 | * add back support for umd [#7](https://github.com/okikio/sharedworker/issues/7) ([3f1a376](https://github.com/okikio/sharedworker/commit/3f1a376e6c0f8ba1b49560f356c67d667b6c3c9b)) 19 | 20 | ## [1.0.6](https://github.com/okikio/sharedworker/compare/v1.0.5...v1.0.6) (2023-12-13) 21 | 22 | 23 | ### Bug Fixes 24 | 25 | * release the fixed version to npm ([04fc9ce](https://github.com/okikio/sharedworker/commit/04fc9ce02e086d94045e5ad2eaffc4ae35872875)) 26 | 27 | ## [1.0.5](https://github.com/okikio/sharedworker/compare/v1.0.4...v1.0.5) (2023-12-13) 28 | 29 | 30 | ### Bug Fixes 31 | 32 | * fix types not working with typescript bundler config ([be0d607](https://github.com/okikio/sharedworker/commit/be0d607bc338c507d31e41bf690657c024adbe79)) 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Okiki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @okikio/sharedworker 2 | 3 | [![Open Bundle](https://bundlejs.com/badge-light.svg)](https://bundlejs.com/?q=@okikio/sharedworker&bundle) 4 | 5 | [NPM](https://www.npmjs.com/package/@okikio/sharedworker) | [Github](https://github.com/okikio/sharedworker#readme) | [Docs](https://sharedworker.okikio.dev) | [Licence](./LICENSE) 6 | 7 | 8 | A small mostly spec. compliant polyfill/ponyfill for `SharedWorkers`, it acts as a drop in replacement for normal `Workers`, and supports a similar API surface that matches normal `Workers`. 9 | 10 | > * [Ponyfills](https://github.com/sindresorhus/ponyfill) are seperate modules that are included to replicate the functionality of the original API, but are not required to be used. 11 | > * [Polyfills](https://developer.mozilla.org/en-US/docs/Glossary/Polyfill) update the original API on the global scope if it isn't supported in that specific environment or it's feature set is lacking compared to modern variations. 12 | 13 | > Check out the [blog post](https://blog.okikio.dev/sharedworker), created for it's launch. 14 | 15 | ## Installation 16 | ```bash 17 | npm install @okikio/sharedworker 18 | ``` 19 | 20 |
21 | Others 22 | 23 | ```bash 24 | yarn add @okikio/sharedworker 25 | ``` 26 | 27 | or 28 | 29 | ```bash 30 | pnpm install @okikio/sharedworker 31 | ``` 32 |
33 | 34 | ## Usage 35 | 36 | ```ts 37 | import { SharedWorkerPolyfill as SharedWorker } from "@okikio/sharedworker"; 38 | // or 39 | import SharedWorker from "@okikio/sharedworker"; 40 | ``` 41 | 42 | You can also use it directly through a script tag: 43 | ```html 44 | 45 | 49 | ``` 50 | 51 | You can also use it via a CDN, e.g. 52 | ```ts 53 | import SharedWorker from "https://cdn.skypack.dev/@okikio/sharedworker"; 54 | // or 55 | import SharedWorker from "https://cdn.jsdelivr.net/npm/@okikio/sharedworker"; 56 | // or 57 | import SharedWorker from "https://esm.sh/@okikio/sharedworker"; 58 | // or any number of other CDN's 59 | ``` 60 | 61 | For vite and other bundlers you can also use the new `SharedWorkerPonyfill` like so ([#9](https://github.com/okikio/sharedworker/issues/9)), to address one-off issues with workers 62 | 63 | ```ts 64 | import { SharedWorkerPonyfill, SharedWorkerSupported } from "@okikio/sharedworker"; 65 | 66 | let worker: SharedWorkerPonyfill; 67 | 68 | if (SharedWorkerSupported) { 69 | worker = new SharedWorkerPonyfill(new SharedWorker(new URL("./../worker.ts", import.meta.url), { name: "position-sync", type: "module" })); 70 | } else { 71 | worker = new SharedWorkerPonyfill(new Worker(new URL("./../worker.ts", import.meta.url), { name: "position-sync", type: "module" })); 72 | } 73 | ``` 74 | 75 | `@okikio/sharedworker` supports the same API surfaces as `SharedWorker` and `Worker`, except it adds some none spec. compliant properties and methods to the `SharedWorkerPolyfill` class, that enables devs to use `SharedWorker`'s on browsers that don't support it. 76 | 77 | In order to support browsers that don't natively support `SharedWorker`'s, the actual worker file needs to be tweaked slightly, 78 | 79 | ```ts 80 | /* 81 | * All variables and values outside the `start(...)` function are shared between all pages, this behavior can cause unexpected bugs if you're not careful 82 | */ 83 | const start = (port) => { 84 | // All your normal Worker and SharedWorker stuff can be placed here and should just work, with no extra setup required 85 | 86 | /** 87 | * All variables and values inside the `start(...)` function are isolated to each page, and will be allocated seperately per page. 88 | */ 89 | port.onmessage = ({ data }) => { 90 | console.log("Cool") 91 | }; 92 | }; 93 | 94 | self.onconnect = e => { 95 | let [port] = e.ports; 96 | start(port); 97 | }; 98 | 99 | // This is the fallback for WebWorkers, in case the browser doesn't support SharedWorkers natively 100 | if (!("SharedWorkerGlobalScope" in self)) 101 | start(self); 102 | ``` 103 | 104 | > _**Note**: make sure to read the comments in the above code carefully to avoid unexpected bugs._ 105 | 106 | ## Showcase 107 | 108 | A couple sites that use `@okikio/sharedworker`: 109 | * [astro.build/play](https://astro.build/play) - [GitHub](https://github.com/snowpackjs/astro-repl) 110 | * [bundlejs.com](https://bundlejs.com) - [GitHub](https://github.com/okikio/bundle) 111 | * Your site here... 112 | 113 | ## API 114 | 115 | The API of `@okikio/sharedworker` closely match the web `SharedWorker` API, except that all the major methods and properties of `SharedWorker.prototype.port` are available directly on `SharedWorker.prototype` including `addEventListener` and `removeEventListener`. 116 | 117 | > _**Note:** the normal functionality of the methods and properties that are normally available on `SharedWorker.prototype` will still be kept intact, in `@okikio/sharedworker`._ 118 | 119 | In addition, the `terminate()` method was added to `@okikio/sharedworker`, this allows both the `close()` method (this is from `SharedWorker.prototype.port`) and the `terminate()` method to manually close workers. 120 | 121 | Check out the [API site](https://sharedworker.okikio.dev) for detailed API documentation. 122 | 123 | ## Browser Support 124 | 125 | | Chrome | Edge | Firefox | Safari | IE | 126 | | ------ | ---- | ------- | ------ | --- | 127 | | 4+ | 12+ | 4+ | 4+ | 10+ | 128 | 129 | Native support for `SharedWorker` is not supported at all on Safari and IE, as well as all mobile browsers (excluding Firefox For Android). 130 | 131 | > _**Note:** some features of `Workers` appeared at later versions of the spec., so, I suggest looking into the feature support table for [Workers](https://developer.mozilla.org/en-US/docs/Web/API/Worker#browser_compatibility) and [SharedWorkers](https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker#browser_compatibility)._ 132 | 133 | 134 | ## Contributing 135 | 136 | I encourage you to use [pnpm](https://pnpm.io/configuring) to contribute to this repo, but you can also use [yarn](https://classic.yarnpkg.com/lang/en/) or [npm](https://npmjs.com) if you prefer. 137 | 138 | Install all necessary packages 139 | ```bash 140 | npm install 141 | ``` 142 | 143 | Then run tests (WIP) 144 | ```bash 145 | npm test 146 | ``` 147 | 148 | Build project 149 | ```bash 150 | npm run build 151 | ``` 152 | 153 | Preview API Docs 154 | ```bash 155 | npm run typedoc && npm run preview 156 | ``` 157 | 158 | > _**Note**: this project uses [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) standard for commits, so, please format your commits using the rules it sets out._ 159 | 160 | ## Licence 161 | See the [LICENSE](./LICENSE) file for license rights and limitations (MIT). 162 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # Todo 2 | 3 | - [ ] Maintain the project 4 | - [ ] Create better documentation 5 | - [ ] Research browser compatibility 6 | - [ ] Create better tests 7 | - [ ] Enhance build tool support 8 | -------------------------------------------------------------------------------- /media/_headers: -------------------------------------------------------------------------------- 1 | /* 2 | X-Frame-Options: SAMEORIGIN 3 | X-Content-Type-Options: nosniff 4 | X-XSS-Protection: 1; mode=block 5 | Referrer-Policy: strict-origin-when-cross-origin 6 | Strict-Transport-Security: max-age=63072000; includeSubDomains; preload 7 | Cache-Control: max-age=1360, stale-while-revalidate=480, public 8 | Accept-CH: DPR, Viewport-Width, Width 9 | X-UA-Compatible: IE=edge 10 | Content-Security-Policy: default-src 'self'; font-src 'self' https://fonts.gstatic.com; style-src 'self' https://fonts.googleapis.com/ 'unsafe-inline'; img-src 'self' https://gitpod.io/ https://bundlejs.com data:; script-src 'self' https://bundlejs.com https://*.bundlejs.com 'unsafe-inline' 'unsafe-eval'; connect-src 'self' https:; block-all-mixed-content; upgrade-insecure-requests; base-uri 'self'; object-src 'none'; worker-src 'self'; manifest-src 'self'; media-src 'self' https://res.cloudinary.com/; form-action 'self'; frame-ancestors 'self' https:; 11 | Permissions-Policy: geolocation=(), microphone=(), usb=(), sync-xhr=(self), camera=(), interest-cohort=() 12 | Link: ; rel=preload; as=font; type=font/ttf; crossorigin=anonymous 13 | Link: ; rel=preload; as=style 14 | Link: ; rel=preload; as=font; type=font/woff2; crossorigin=anonymous 15 | # Link: ; rel=preload; as=style 16 | # Link: ; rel=preload; as=font; type=font/woff2; crossorigin=anonymous 17 | 18 | /*.css 19 | Cache-Control: public, max-age=604800, stale-while-revalidate=480 20 | Content-Type: text/css 21 | 22 | /*.ttf 23 | Cache-Control: public, max-age=31536000, stale-while-revalidate=23480 24 | Content-Type: font/ttf 25 | 26 | /*.woff2 27 | Cache-Control: public, max-age=31536000, stale-while-revalidate=23480 28 | Content-Type: font/woff2 29 | 30 | /*.js 31 | Cache-Control: public, max-age=604800, stale-while-revalidate=480 32 | Content-Type: text/javascript 33 | 34 | /manifest.json 35 | Cache-Control: public, max-age=604800, stale-while-revalidate=480 36 | Content-Type: application/manifest+json 37 | 38 | /assets/* 39 | Cache-Control: public, max-age=31536000, stale-while-revalidate=23480 40 | 41 | /*.svg 42 | Cache-Control: public, max-age=31536000, stale-while-revalidate=23480 43 | Content-Type: image/svg+xml 44 | -------------------------------------------------------------------------------- /media/assets/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /media/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okikio/sharedworker/15957a09089d150356972a452ce4135124136d60/media/favicon.ico -------------------------------------------------------------------------------- /media/fonts/FluentSystemIcons-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okikio/sharedworker/15957a09089d150356972a452ce4135124136d60/media/fonts/FluentSystemIcons-Regular.ttf -------------------------------------------------------------------------------- /media/fonts/FluentSystemIcons-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okikio/sharedworker/15957a09089d150356972a452ce4135124136d60/media/fonts/FluentSystemIcons-Regular.woff2 -------------------------------------------------------------------------------- /media/measure.js: -------------------------------------------------------------------------------- 1 | (window => { 2 | const { 3 | screen: { width, height }, 4 | navigator: { language }, 5 | location, 6 | localStorage, 7 | document, 8 | history, 9 | } = window; 10 | const { hostname, pathname, search } = location; 11 | const { currentScript } = document; 12 | 13 | if (!currentScript) return; 14 | 15 | const assign = (a, b) => { 16 | Object.keys(b).forEach(key => { 17 | if (b[key] !== undefined) a[key] = b[key]; 18 | }); 19 | return a; 20 | }; 21 | 22 | const hook = (_this, method, callback) => { 23 | const orig = _this[method]; 24 | 25 | return (...args) => { 26 | callback.apply(null, args); 27 | 28 | return orig.apply(_this, args); 29 | }; 30 | }; 31 | 32 | const doNotTrack = () => { 33 | const { doNotTrack, navigator, external } = window; 34 | 35 | const msTrackProtection = 'msTrackingProtectionEnabled'; 36 | const msTracking = () => { 37 | return external && msTrackProtection in external && external[msTrackProtection](); 38 | }; 39 | 40 | const dnt = doNotTrack || navigator.doNotTrack || navigator.msDoNotTrack || msTracking(); 41 | 42 | return dnt == '1' || dnt === 'yes'; 43 | }; 44 | 45 | const trackingDisabled = () => 46 | (localStorage && localStorage.getItem('umami.disabled')) || 47 | (dnt && doNotTrack()) || 48 | (domain && !domains.includes(hostname)); 49 | 50 | const _data = 'data-'; 51 | const _false = 'false'; 52 | const attr = currentScript.getAttribute.bind(currentScript); 53 | const website = attr(_data + 'website-id'); 54 | const hostUrl = attr(_data + 'host-url'); 55 | const autoTrack = attr(_data + 'auto-track') !== _false; 56 | const dnt = attr(_data + 'do-not-track'); 57 | const cssEvents = attr(_data + 'css-events') !== _false; 58 | const domain = attr(_data + 'domains') || ''; 59 | const domains = domain.split(',').map(n => n.trim()); 60 | const root = hostUrl 61 | ? hostUrl.replace(/\/$/, '') 62 | : currentScript.src.split('/').slice(0, -1).join('/'); 63 | const endpoint = `${root}/take-measurement`; // "/api/collect"; 64 | const screen = `${width}x${height}`; 65 | const eventClass = /^umami--([a-z]+)--([\w]+[\w-]*)$/; 66 | const eventSelect = "[class*='umami--']"; 67 | 68 | let listeners = {}; 69 | let currentUrl = `${pathname}${search}`; 70 | let currentRef = document.referrer; 71 | let cache; 72 | 73 | /* Collect metrics */ 74 | 75 | const getPayload = () => ({ 76 | website, 77 | hostname, 78 | screen, 79 | language, 80 | url: currentUrl, 81 | }); 82 | 83 | const collect = (type, payload) => { 84 | if (trackingDisabled()) return; 85 | 86 | return fetch(endpoint, { 87 | method: 'POST', 88 | body: JSON.stringify({ type, payload }), 89 | headers: assign({ 'Content-Type': 'application/json' }, { ['x-umami-cache']: cache }), 90 | }) 91 | .then(res => res.text()) 92 | .then(text => (cache = text)); 93 | }; 94 | 95 | const trackView = (url = currentUrl, referrer = currentRef, websiteUuid = website) => 96 | collect( 97 | 'pageview', 98 | assign(getPayload(), { 99 | website: websiteUuid, 100 | url, 101 | referrer, 102 | }), 103 | ); 104 | 105 | const trackEvent = (eventName, eventData, url = currentUrl, websiteUuid = website) => 106 | collect( 107 | 'event', 108 | assign(getPayload(), { 109 | website: websiteUuid, 110 | url, 111 | event_name: eventName, 112 | event_data: eventData, 113 | }), 114 | ); 115 | 116 | /* Handle events */ 117 | 118 | const addEvents = node => { 119 | const elements = node.querySelectorAll(eventSelect); 120 | Array.prototype.forEach.call(elements, addEvent); 121 | }; 122 | 123 | const addEvent = element => { 124 | const get = element.getAttribute.bind(element); 125 | (get('class') || '').split(' ').forEach(className => { 126 | if (!eventClass.test(className)) return; 127 | 128 | const [, event, name] = className.split('--'); 129 | 130 | const listener = listeners[className] 131 | ? listeners[className] 132 | : (listeners[className] = e => { 133 | if ( 134 | event === 'click' && 135 | element.tagName === 'A' && 136 | !( 137 | e.ctrlKey || 138 | e.shiftKey || 139 | e.metaKey || 140 | (e.button && e.button === 1) || 141 | get('target') 142 | ) 143 | ) { 144 | e.preventDefault(); 145 | trackEvent(name).then(() => { 146 | const href = get('href'); 147 | if (href) { 148 | location.href = href; 149 | } 150 | }); 151 | } else { 152 | trackEvent(name); 153 | } 154 | }); 155 | 156 | element.addEventListener(event, listener, true); 157 | }); 158 | }; 159 | 160 | /* Handle history changes */ 161 | 162 | const handlePush = (state, title, url) => { 163 | if (!url) return; 164 | 165 | currentRef = currentUrl; 166 | const newUrl = url.toString(); 167 | 168 | if (newUrl.substring(0, 4) === 'http') { 169 | currentUrl = '/' + newUrl.split('/').splice(3).join('/'); 170 | } else { 171 | currentUrl = newUrl; 172 | } 173 | 174 | if (currentUrl !== currentRef) { 175 | trackView(); 176 | } 177 | }; 178 | 179 | const observeDocument = () => { 180 | const monitorMutate = mutations => { 181 | mutations.forEach(mutation => { 182 | const element = mutation.target; 183 | addEvent(element); 184 | addEvents(element); 185 | }); 186 | }; 187 | 188 | const observer = new MutationObserver(monitorMutate); 189 | observer.observe(document, { childList: true, subtree: true }); 190 | }; 191 | 192 | /* Global */ 193 | 194 | if (!window.umami) { 195 | const umami = eventValue => trackEvent(eventValue); 196 | umami.trackView = trackView; 197 | umami.trackEvent = trackEvent; 198 | 199 | window.umami = umami; 200 | } 201 | 202 | /* Start */ 203 | 204 | if (autoTrack && !trackingDisabled()) { 205 | history.pushState = hook(history, 'pushState', handlePush); 206 | history.replaceState = hook(history, 'replaceState', handlePush); 207 | 208 | const update = () => { 209 | if (document.readyState === 'complete') { 210 | trackView(); 211 | 212 | if (cssEvents) { 213 | addEvents(document); 214 | observeDocument(); 215 | } 216 | } 217 | }; 218 | 219 | document.addEventListener('readystatechange', update, true); 220 | 221 | update(); 222 | } 223 | })(window); 224 | 225 | 226 | 227 | // (window => { 228 | // const apiRoute = "/take-measurement"; // "/api/collect"; 229 | // const { screen: { width, height }, navigator: { language }, location: { hostname, pathname, search }, localStorage, document, history, } = window; 230 | // const script = document.querySelector('script[data-website-id]'); 231 | // if (!script) 232 | // return; 233 | // const attr = script.getAttribute.bind(script); 234 | // const website = attr('data-website-id'); 235 | // const hostUrl = attr('data-host-url'); 236 | // const autoTrack = attr('data-auto-track') !== 'false'; 237 | // const dnt = attr('data-do-not-track'); 238 | // const cssEvents = attr('data-css-events') !== 'false'; 239 | // const domain = attr('data-domains') || ''; 240 | // const domains = domain.split(',').map(n => n.trim()); 241 | // const eventClass = /^umami--([a-z]+)--([\w]+[\w-]*)$/; 242 | // const eventSelect = "[class*='umami--']"; 243 | // const trackingDisabled = () => (localStorage && localStorage.getItem('umami.disabled')) || 244 | // (dnt && doNotTrack()) || 245 | // (domain && !domains.includes(hostname)); 246 | // const root = hostUrl 247 | // ? removeTrailingSlash(hostUrl) 248 | // : ""; // script.src.split('/').slice(0, -1).join('/'); 249 | // const screen = `${width}x${height}`; 250 | // const listeners = {}; 251 | // let currentUrl = `${pathname}${search}`; 252 | // let currentRef = document.referrer; 253 | // let cache; 254 | // /* Collect metrics */ 255 | // const post = (url, data, callback) => { 256 | // const req = new XMLHttpRequest(); 257 | // req.open('POST', url, true); 258 | // req.setRequestHeader('Content-Type', 'application/json'); 259 | // if (cache) 260 | // req.setRequestHeader('x-umami-cache', cache); 261 | // req.onreadystatechange = () => { 262 | // if (req.readyState === 4) { 263 | // callback(req.response); 264 | // } 265 | // }; 266 | // req.send(JSON.stringify(data)); 267 | // }; 268 | // const getPayload = () => ({ 269 | // website, 270 | // hostname, 271 | // screen, 272 | // language, 273 | // url: currentUrl, 274 | // }); 275 | // const assign = (a, b) => { 276 | // Object.keys(b).forEach(key => { 277 | // a[key] = b[key]; 278 | // }); 279 | // return a; 280 | // }; 281 | // const collect = (type, payload) => { 282 | // if (trackingDisabled()) 283 | // return; 284 | // post(`${root}${apiRoute}`, { 285 | // type, 286 | // payload, 287 | // }, res => (cache = res)); 288 | // }; 289 | // const trackView = (url = currentUrl, referrer = currentRef, uuid = website) => { 290 | // collect('pageview', assign(getPayload(), { 291 | // website: uuid, 292 | // url, 293 | // referrer, 294 | // })); 295 | // }; 296 | // const trackEvent = (event_value, event_type = 'custom', url = currentUrl, uuid = website) => { 297 | // collect('event', assign(getPayload(), { 298 | // website: uuid, 299 | // url, 300 | // event_type, 301 | // event_value, 302 | // })); 303 | // }; 304 | // /* Handle events */ 305 | // const sendEvent = (value, type) => { 306 | // const payload = getPayload(); 307 | // // @ts-ignore 308 | // payload.event_type = type; 309 | // // @ts-ignore 310 | // payload.event_value = value; 311 | // const data = JSON.stringify({ 312 | // type: 'event', 313 | // payload, 314 | // }); 315 | // navigator.sendBeacon(`${root}${apiRoute}`, data); 316 | // }; 317 | // const addEvents = node => { 318 | // const elements = node.querySelectorAll(eventSelect); 319 | // Array.prototype.forEach.call(elements, addEvent); 320 | // }; 321 | // const addEvent = element => { 322 | // (element.getAttribute('class') || '').split(' ').forEach(className => { 323 | // if (!eventClass.test(className)) 324 | // return; 325 | // const [, type, value] = className.split('--'); 326 | // const listener = listeners[className] 327 | // ? listeners[className] 328 | // : (listeners[className] = () => { 329 | // if (element.tagName === 'A') { 330 | // sendEvent(value, type); 331 | // } 332 | // else { 333 | // trackEvent(value, type); 334 | // } 335 | // }); 336 | // element.addEventListener(type, listener, true); 337 | // }); 338 | // }; 339 | // /* Handle history changes */ 340 | // const handlePush = (state, title, url) => { 341 | // if (!url) 342 | // return; 343 | // currentRef = currentUrl; 344 | // const newUrl = url.toString(); 345 | // if (newUrl.substring(0, 4) === 'http') { 346 | // currentUrl = '/' + newUrl.split('/').splice(3).join('/'); 347 | // } 348 | // else { 349 | // currentUrl = newUrl; 350 | // } 351 | // if (currentUrl !== currentRef) { 352 | // trackView(); 353 | // } 354 | // }; 355 | // const observeDocument = () => { 356 | // const monitorMutate = mutations => { 357 | // mutations.forEach(mutation => { 358 | // const element = mutation.target; 359 | // addEvent(element); 360 | // addEvents(element); 361 | // }); 362 | // }; 363 | // const observer = new MutationObserver(monitorMutate); 364 | // observer.observe(document, { childList: true, subtree: true }); 365 | // }; 366 | // /* Global */ 367 | // // @ts-ignore 368 | // if (!window.umami) { 369 | // const umami = eventValue => trackEvent(eventValue); 370 | // umami.trackView = trackView; 371 | // umami.trackEvent = trackEvent; 372 | // // @ts-ignore 373 | // window.umami = umami; 374 | // } 375 | // /* Start */ 376 | // if (autoTrack && !trackingDisabled()) { 377 | // history.pushState = hook(history, 'pushState', handlePush); 378 | // history.replaceState = hook(history, 'replaceState', handlePush); 379 | // const update = () => { 380 | // if (document.readyState === 'complete') { 381 | // trackView(); 382 | // if (cssEvents) { 383 | // addEvents(document); 384 | // observeDocument(); 385 | // } 386 | // } 387 | // }; 388 | // document.addEventListener('readystatechange', update, true); 389 | // update(); 390 | // } 391 | // })(window); 392 | 393 | -------------------------------------------------------------------------------- /media/robots.txt: -------------------------------------------------------------------------------- 1 | Sitemap: https://sharedworker.okikio.dev/sitemap.xml 2 | User-agent: * 3 | Allow: / 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@okikio/sharedworker", 3 | "version": "1.1.0", 4 | "type": "module", 5 | "sideEffects": false, 6 | "description": "A small mostly spec. compliant polyfill/ponyfill for SharedWorkers, it acts as a drop in replacement for normal Workers, and supports an API surface that matches normal Workers.", 7 | "umd": "sharedworker", 8 | "access": "public", 9 | "legacy": "lib/index.cjs", 10 | "main": "lib/index.cjs", 11 | "browser": "lib/index.umd.js", 12 | "module": "lib/index.js", 13 | "exports": { 14 | ".": { 15 | "require": { 16 | "types": "./lib/index.d.cts", 17 | "default": "./lib/index.cjs" 18 | }, 19 | "import": { 20 | "types": "./lib/index.d.ts", 21 | "default": "./lib/index.js" 22 | } 23 | }, 24 | "./polyfill": { 25 | "require": { 26 | "types": "./lib/polyfill.d.cts", 27 | "default": "./lib/polyfill.cjs" 28 | }, 29 | "import": { 30 | "types": "./lib/polyfill.d.ts", 31 | "default": "./lib/polyfill.js" 32 | } 33 | }, 34 | "./ponyfill": { 35 | "require": { 36 | "types": "./lib/ponyfill.d.cts", 37 | "default": "./lib/ponyfill.cjs" 38 | }, 39 | "import": { 40 | "types": "./lib/ponyfill.d.ts", 41 | "default": "./lib/ponyfill.js" 42 | } 43 | }, 44 | "./constants": { 45 | "require": { 46 | "types": "./lib/constants.d.cts", 47 | "default": "./lib/constants.cjs" 48 | }, 49 | "import": { 50 | "types": "./lib/constants.d.ts", 51 | "default": "./lib/constants.js" 52 | } 53 | }, 54 | "./lib/*": "./lib/*", 55 | "./src/*": "./src/*", 56 | "./package.json": "./package.json" 57 | }, 58 | "directories": { 59 | "lib": "./lib", 60 | "src": "./src" 61 | }, 62 | "files": [ 63 | "lib", 64 | "src" 65 | ], 66 | "publishConfig": { 67 | "provenance": true 68 | }, 69 | "scripts": { 70 | "typedoc": "esbuild ./.typedoc/typedoc.tsx --format=esm --outfile=./.typedoc/typedoc.mjs && typedoc --options ./.typedoc/typedoc.json", 71 | "vite": "vite --config vite.config.ts", 72 | "dev": "vite dev --config vite.config.ts", 73 | "start": "vite dev --config vite.config.ts", 74 | "build": "tsup", 75 | "commitlint": "commitlint --edit", 76 | "pre-release": "pnpm build && pnpm typedoc", 77 | "prepare": "[ \"$NODE_ENV\" = \"production\" ] || [ -n \"$CI\" ] && echo \"Skipping Husky Install...\" || husky", 78 | "semantic-release": "semantic-release" 79 | }, 80 | "release": { 81 | "branches": [ 82 | "main" 83 | ], 84 | "plugins": [ 85 | "@semantic-release/commit-analyzer", 86 | "@semantic-release/release-notes-generator", 87 | "@semantic-release/changelog", 88 | "@semantic-release/npm", 89 | "@semantic-release/git", 90 | [ 91 | "@semantic-release/github", 92 | { 93 | "assets": [ 94 | "lib/**", 95 | "src/**" 96 | ] 97 | } 98 | ] 99 | ] 100 | }, 101 | "changelog": { 102 | "repo": "sharedworker", 103 | "labels": { 104 | "breaking": ":boom: Breaking Change", 105 | "enhancement": ":rocket: Enhancement", 106 | "bug": ":bug: Bug Fix", 107 | "documentation": ":memo: Documentation", 108 | "internal": ":house: Internal" 109 | }, 110 | "cacheDir": ".changelog" 111 | }, 112 | "commitlint": { 113 | "extends": [ 114 | "@commitlint/config-conventional" 115 | ] 116 | }, 117 | "repository": { 118 | "type": "git", 119 | "url": "https://github.com/okikio/sharedworker.git" 120 | }, 121 | "keywords": [ 122 | "sharedworker", 123 | "typescript", 124 | "worker", 125 | "web", 126 | "polyfill", 127 | "ponyfill", 128 | "es2023" 129 | ], 130 | "author": { 131 | "name": "Okiki Ojo", 132 | "email": "hey@okikio.dev", 133 | "url": "https://okikio.dev" 134 | }, 135 | "license": "MIT", 136 | "bugs": { 137 | "url": "https://github.com/okikio/sharedworker/issues" 138 | }, 139 | "homepage": "https://sharedworker.okikio.dev", 140 | "devDependencies": { 141 | "@commitlint/cli": "^19.6.0", 142 | "@commitlint/config-conventional": "^19.6.0", 143 | "@semantic-release/changelog": "^6.0.3", 144 | "@semantic-release/commit-analyzer": "^13.0.0", 145 | "@semantic-release/git": "^10.0.1", 146 | "@semantic-release/github": "^11.0.1", 147 | "@semantic-release/npm": "^12.0.1", 148 | "@semantic-release/release-notes-generator": "^14.0.1", 149 | "@swc/core": "^1.9.2", 150 | "@types/node": "^22.9.1", 151 | "@types/web": "^0.0.180", 152 | "esbuild": "^0.24.0", 153 | "esbuild-plugin-umd-wrapper": "^3.0.0", 154 | "husky": "^9.1.7", 155 | "pnpm": "^9.14.1", 156 | "semantic-release": "^24.2.0", 157 | "tsup": "^8.3.5", 158 | "typedoc": "^0.26.11", 159 | "typedoc-plugin-extras": "^3.1.0", 160 | "typedoc-plugin-inline-sources": "^1.1.0", 161 | "typedoc-plugin-mdn-links": "^3.3.8", 162 | "typedoc-plugin-missing-exports": "^3.0.2", 163 | "typescript": "^5.6.3", 164 | "vite": "^5.4.11" 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * indicates if SharedWorker is supported, in the global scope 3 | */ 4 | export const SharedWorkerSupported = "SharedWorker" in globalThis; -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // Adapted from https://github.com/okikio/bundle/blob/main/src/ts/util/WebWorker.ts, which is licensed under the MIT license. 2 | // If the above file is removed or modified, you can access the original state in the following GitHub Gist: https://gist.github.com/okikio/6809cfc0cdbf1df4c0573addaaf7e259 3 | 4 | export * from "./constants.ts"; 5 | export * from "./polyfill.ts"; 6 | export * from "./ponyfill.ts"; 7 | 8 | export { default } from "./polyfill.ts"; -------------------------------------------------------------------------------- /src/polyfill.ts: -------------------------------------------------------------------------------- 1 | // Adapted from https://github.com/okikio/bundle/blob/main/src/ts/util/WebWorker.ts, which is licensed under the MIT license. 2 | // If the above file is removed or modified, you can access the original state in the following GitHub Gist: https://gist.github.com/okikio/6809cfc0cdbf1df4c0573addaaf7e259 3 | 4 | import { SharedWorkerSupported } from "./constants.ts"; 5 | import { SharedWorkerPonyfill } from "./ponyfill.ts"; 6 | 7 | /** 8 | * A polyfill class for `SharedWorker`, it accepts a URL/string as well as any other options the spec. allows for `SharedWorker`. It supports all the same methods and properties as the original, except it adds compatibility methods and properties for older browsers that don't support `SharedWorker`, so, it can switch to normal `Workers` instead. 9 | */ 10 | export class SharedWorkerPolyfill extends SharedWorkerPonyfill { 11 | constructor(url: string | URL, opts?: WorkerOptions) { 12 | let worker: SharedWorker | Worker; 13 | if (SharedWorkerSupported) { 14 | worker = new SharedWorker(url, opts); 15 | } else { 16 | worker = new Worker(url, opts); 17 | } 18 | 19 | super(worker); 20 | } 21 | } 22 | 23 | export default SharedWorkerPolyfill; -------------------------------------------------------------------------------- /src/ponyfill.ts: -------------------------------------------------------------------------------- 1 | // Adapted from https://github.com/okikio/bundle/blob/main/src/ts/util/WebWorker.ts, which is licensed under the MIT license. 2 | // If the above file is removed or modified, you can access the original state in the following GitHub Gist: https://gist.github.com/okikio/6809cfc0cdbf1df4c0573addaaf7e259 3 | 4 | import { SharedWorkerSupported } from "./constants.ts"; 5 | 6 | /** 7 | * A polyfill class for `SharedWorker`, it accepts a URL/string as well as any other options the spec. allows for `SharedWorker`. It supports all the same methods and properties as the original, except it adds compatibility methods and properties for older browsers that don't support `SharedWorker`, so, it can switch to normal `Workers` instead. 8 | */ 9 | export class SharedWorkerPonyfill implements SharedWorker, EventTarget, AbstractWorker { 10 | /** 11 | * The actual worker that is used, depending on browser support it can be either a `SharedWorker` or a normal `Worker`. 12 | */ 13 | public ActualWorker: SharedWorker | Worker; 14 | constructor(worker: SharedWorker | Worker) { 15 | this.ActualWorker = worker; 16 | } 17 | 18 | /** 19 | * An EventListener called when MessageEvent of type message is fired on the port—that is, when the port receives a message. 20 | */ 21 | public get onmessage() { 22 | if (SharedWorkerSupported) { 23 | return (this.ActualWorker as SharedWorker)?.port.onmessage; 24 | } else { 25 | return (this.ActualWorker as Worker).onmessage as unknown as MessagePort["onmessage"]; 26 | } 27 | } 28 | 29 | public set onmessage(value: MessagePort["onmessage"] | Worker["onmessage"]) { 30 | if (SharedWorkerSupported) { 31 | (this.ActualWorker as SharedWorker).port.onmessage = value as MessagePort["onmessage"]; 32 | } else { 33 | (this.ActualWorker as Worker).onmessage = value as Worker["onmessage"]; 34 | } 35 | } 36 | 37 | /** 38 | * An EventListener called when a MessageEvent of type MessageError is fired—that is, when it receives a message that cannot be deserialized. 39 | */ 40 | public get onmessageerror() { 41 | if (SharedWorkerSupported) { 42 | return (this.ActualWorker as SharedWorker)?.port.onmessageerror; 43 | } else { 44 | return (this.ActualWorker as Worker).onmessageerror; 45 | } 46 | } 47 | 48 | public set onmessageerror(value: MessagePort["onmessageerror"] | Worker["onmessageerror"]) { 49 | if (SharedWorkerSupported) { 50 | (this.ActualWorker as SharedWorker).port.onmessageerror = value as MessagePort["onmessageerror"]; 51 | } else { 52 | (this.ActualWorker as Worker).onmessageerror = value as Worker["onmessageerror"]; 53 | } 54 | } 55 | 56 | /** 57 | * Starts the sending of messages queued on the port (only needed when using EventTarget.addEventListener; it is implied when using MessagePort.onmessage.) 58 | */ 59 | public start() { 60 | if (SharedWorkerSupported) { 61 | return (this.ActualWorker as SharedWorker)?.port.start(); 62 | } 63 | } 64 | 65 | /** 66 | * Clones message and transmits it to worker's global environment. transfer can be passed as a list of objects that are to be transferred rather than cloned. 67 | */ 68 | public postMessage(message: any, transfer?: Transferable[] | StructuredSerializeOptions) { 69 | if (SharedWorkerSupported) { 70 | return (this.ActualWorker as SharedWorker)?.port.postMessage(message, transfer as Transferable[]); 71 | } else { 72 | return (this.ActualWorker as Worker).postMessage(message, transfer as Transferable[]); 73 | } 74 | } 75 | 76 | /** 77 | * Immediately terminates the worker. This does not let worker finish its operations; it is halted at once. ServiceWorker instances do not support this method. 78 | */ 79 | public terminate() { 80 | if (SharedWorkerSupported) { 81 | return (this.ActualWorker as SharedWorker)?.port.close(); 82 | } else { 83 | return (this.ActualWorker as Worker).terminate(); 84 | } 85 | } 86 | 87 | /** 88 | * Disconnects the port, so it is no longer active. 89 | */ 90 | public close() { 91 | return this.terminate(); 92 | } 93 | 94 | /** 95 | * Returns a MessagePort object used to communicate with and control the shared worker. 96 | */ 97 | public get port() { 98 | return (SharedWorkerSupported ? (this.ActualWorker as SharedWorker).port : this.ActualWorker) as MessagePort; 99 | } 100 | 101 | /** 102 | * Is an EventListener that is called whenever an ErrorEvent of type error event occurs. 103 | */ 104 | public get onerror() { return this.ActualWorker.onerror; } 105 | public set onerror(value: ((this: AbstractWorker, ev: ErrorEvent) => any) | null) { 106 | this.ActualWorker.onerror = value; 107 | } 108 | 109 | /** 110 | * Registers an event handler of a specific event type on the EventTarget 111 | */ 112 | public addEventListener(type: K, listener: (this: Worker, ev: WorkerEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; 113 | public addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; 114 | public addEventListener(type: K, listener: (this: MessagePort, ev: MessagePortEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; 115 | public addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void { 116 | if (SharedWorkerSupported && type !== "error") { 117 | return (this.ActualWorker as SharedWorker)?.port.addEventListener(type, listener, options) 118 | } else { 119 | return this.ActualWorker.addEventListener(type, listener, options); 120 | } 121 | } 122 | 123 | /** 124 | * Removes an event listener from the EventTarget. 125 | */ 126 | public removeEventListener(type: K, listener: (this: Worker, ev: WorkerEventMap[K]) => any, options?: boolean | EventListenerOptions): void; 127 | public removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; 128 | public removeEventListener(type: K, listener: (this: MessagePort, ev: MessagePortEventMap[K]) => any, options?: boolean | EventListenerOptions): void; 129 | public removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void { 130 | if (SharedWorkerSupported && type !== "error") { 131 | return (this.ActualWorker as SharedWorker)?.port.removeEventListener(type, listener, options) 132 | } else { 133 | return this.ActualWorker.removeEventListener(type, listener, options); 134 | } 135 | } 136 | 137 | /** 138 | * Dispatches an event to this EventTarget. 139 | */ 140 | public dispatchEvent(event: Event) { 141 | return this.ActualWorker.dispatchEvent(event); 142 | } 143 | } 144 | 145 | export default SharedWorkerPonyfill; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "bundler", 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | "lib": ["DOM"], 7 | "sourceMap": true, 8 | "outDir": "lib", 9 | "declaration": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "esModuleInterop": true, 13 | "allowArbitraryExtensions": true, 14 | "allowImportingTsExtensions": true, 15 | "verbatimModuleSyntax": true, 16 | "noEmit": true 17 | }, 18 | "include": ["src"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'tsup'; 2 | import { defineConfig } from 'tsup'; 3 | import { umdWrapper } from 'esbuild-plugin-umd-wrapper'; 4 | 5 | import { umd } from "./package.json"; 6 | 7 | const GLOBAL_NAME = umd; 8 | 9 | const baseConfig: Options = { 10 | target: ["es2022", "node21", "chrome105"], 11 | entry: ['src/index.ts', 'src/polyfill.ts', 'src/ponyfill.ts', 'src/constants.ts'], 12 | format: ["esm", "cjs"], 13 | sourcemap: true, 14 | clean: true, 15 | dts: true, 16 | outDir: "lib", 17 | platform: 'browser', 18 | globalName: GLOBAL_NAME, 19 | 20 | outExtension({ format, options }) { 21 | const ext = ({ "esm": "js", "cjs": "cjs", "umd": "umd.js" })[format] 22 | const outputExtension = options.minify ? `min.${ext}` : `${ext}` 23 | return { 24 | js: `.${outputExtension}`, 25 | } 26 | }, 27 | } 28 | 29 | export default defineConfig([ 30 | { ...baseConfig }, 31 | { 32 | ...baseConfig, 33 | target: 'es5', 34 | format: ['cjs'], 35 | 36 | outExtension({ format, options }) { 37 | const ext = "umd.js" 38 | const outputExtension = options.minify ? `min.${ext}` : `${ext}` 39 | return { 40 | js: `.${outputExtension}`, 41 | } 42 | }, 43 | esbuildPlugins: [umdWrapper({ libraryName: GLOBAL_NAME, external: 'inherit' })], 44 | }, 45 | ]) -------------------------------------------------------------------------------- /vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | // https://astro.build/config 4 | export default defineConfig({ 5 | root: "./docs" 6 | }); --------------------------------------------------------------------------------