Full Code of GoogleChromeLabs/quicklink for AI

main 7b4198bb9dee cached
119 files
246.7 KB
70.9k tokens
21 symbols
1 requests
Download .txt
Showing preview only (273K chars total). Download the full file or copy to clipboard to get everything.
Repository: GoogleChromeLabs/quicklink
Branch: main
Commit: 7b4198bb9dee
Files: 119
Total size: 246.7 KB

Directory structure:
gitextract_udyormir/

├── .babelrc.js
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── dependabot.yml
│   └── workflows/
│       ├── ci.yml
│       ├── codeql.yml
│       ├── lint.yml
│       ├── site.yml
│       └── size-limit.yml
├── .gitignore
├── .size-limit.json
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── demos/
│   ├── basic.html
│   ├── hrefFn/
│   │   ├── 2.html
│   │   ├── 2.json
│   │   └── hrefFn_demo.html
│   ├── network-idle.html
│   ├── network-idle.js
│   ├── news/
│   │   └── README.md
│   ├── news-workbox/
│   │   └── README.md
│   ├── spa/
│   │   └── README.md
│   └── sw.js
├── package.json
├── site/
│   ├── .browserslistrc
│   ├── .config/
│   │   ├── configstore/
│   │   │   └── update-notifier-pnpm.json
│   │   └── glitch-package-manager
│   ├── .eleventy.js
│   ├── .firebaserc
│   ├── .gitignore
│   ├── .stylelintignore
│   ├── .stylelintrc.json
│   ├── LICENSE
│   ├── README.md
│   ├── firebase.json
│   ├── index.njk
│   ├── package.json
│   ├── src/
│   │   ├── _data/
│   │   │   └── site.js
│   │   ├── _includes/
│   │   │   ├── components/
│   │   │   │   ├── chrome-extension.njk
│   │   │   │   ├── copy-snippet.njk
│   │   │   │   ├── download.njk
│   │   │   │   ├── github-fork.njk
│   │   │   │   ├── github-star.njk
│   │   │   │   ├── heading.njk
│   │   │   │   ├── installation.njk
│   │   │   │   ├── over-prefetching.njk
│   │   │   │   ├── react.njk
│   │   │   │   ├── trusted-by.njk
│   │   │   │   ├── usage.njk
│   │   │   │   ├── use-with.njk
│   │   │   │   ├── why-prefetch.njk
│   │   │   │   └── why-quicklink.njk
│   │   │   └── layouts/
│   │   │       ├── base.njk
│   │   │       ├── favicons.njk
│   │   │       ├── footer.njk
│   │   │       ├── head.njk
│   │   │       ├── header.njk
│   │   │       ├── highlighted-section-wrapper.njk
│   │   │       ├── normal-section-wrapper.njk
│   │   │       └── social.njk
│   │   ├── api.njk
│   │   ├── approach.njk
│   │   ├── assets/
│   │   │   ├── js/
│   │   │   │   └── script.js
│   │   │   └── styles/
│   │   │       ├── _copy-snippet.scss
│   │   │       ├── github-markdown.scss
│   │   │       ├── main.scss
│   │   │       └── vendor/
│   │   │           ├── _github-markdown.scss
│   │   │           └── _prism.scss
│   │   ├── demo.njk
│   │   ├── index.njk
│   │   ├── measure.njk
│   │   ├── robots.njk
│   │   ├── site.webmanifest
│   │   └── sitemap.njk
│   └── watch.json
├── src/
│   ├── chunks.mjs
│   ├── index.mjs
│   ├── prefetch.mjs
│   ├── prerender.mjs
│   ├── react-chunks.js
│   └── request-idle-callback.mjs
├── test/
│   ├── fixtures/
│   │   ├── 1.html
│   │   ├── 2.html
│   │   ├── 3.html
│   │   ├── 4.html
│   │   ├── index.html
│   │   ├── main.css
│   │   ├── rmanifest.json
│   │   ├── test-allow-origin-all.html
│   │   ├── test-allow-origin.html
│   │   ├── test-basic-usage.html
│   │   ├── test-custom-dom-source.html
│   │   ├── test-custom-href-function.html
│   │   ├── test-delay.html
│   │   ├── test-es-modules.html
│   │   ├── test-ignore-basic.html
│   │   ├── test-ignore-multiple.html
│   │   ├── test-limit.html
│   │   ├── test-node-list.html
│   │   ├── test-prefetch-chunks.html
│   │   ├── test-prefetch-duplicate-shared.html
│   │   ├── test-prefetch-duplicate.html
│   │   ├── test-prefetch-multiple.html
│   │   ├── test-prefetch-single.html
│   │   ├── test-prerender-andPrefetch.html
│   │   ├── test-prerender-only.html
│   │   ├── test-prerender-wrapper-multiple.html
│   │   ├── test-prerender-wrapper-single.html
│   │   ├── test-same-origin.html
│   │   ├── test-threshold.html
│   │   └── test-throttle.html
│   └── quicklink.spec.js
└── translations/
    └── zh-cn/
        └── README.md

================================================
FILE CONTENTS
================================================

================================================
FILE: .babelrc.js
================================================
'use strict';

module.exports = {
  presets: [
    '@babel/preset-react',
    [
      '@babel/preset-env',
      {
        loose: true,
        bugfixes: true,
      },
    ],
  ],
};


================================================
FILE: .editorconfig
================================================
# https://editorconfig.org/

root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false


================================================
FILE: .eslintignore
================================================
**/*.min.js
**/dist/
**/build/
**/vendor/
# explicitly include dot js files
!.*.js


================================================
FILE: .eslintrc.js
================================================
'use strict';

module.exports = {
  env: {
    browser: true,
    es6: true,
    node: true,
  },
  parserOptions: {
    sourceType: 'script',
    ecmaVersion: 2017,
  },
  extends: [
    'google',
    'plugin:react/recommended',
  ],
  settings: {
    react: {
      version: 'detect',
    },
  },
  rules: {
    'max-len': [
      'warn',
      {
        // 130 on GitHub, 80 on npmjs.org for README.md code blocks
        code: 130,
      },
    ],
    'arrow-parens': [
      'error',
      'as-needed',
    ],
    'space-before-function-paren': [
      'error',
      {
        anonymous: 'always',
        named: 'never',
      },
    ],
    'no-negated-condition': 'warn',
    'no-const-assign': 'error',
    'prefer-destructuring': [
      'off',
      {
        object: true,
        array: false,
      },
    ],
    'prefer-template': 'error',
    'strict': 'error',
    'spaced-comment': [
      'error',
      'always',
      {
        exceptions: [
          '/',
        ],
      },
    ],
  },
  overrides: [
    {
      files: [
        'src/**',
      ],
      parserOptions: {
        sourceType: 'module',
      },
    },
    {
      files: [
        'site/**',
      ],
      env: {
        node: false,
      },
      parserOptions: {
        sourceType: 'script',
      },
      rules: {
        'require-jsdoc': 'off',
        'strict': 'error',
      },
    },
  ],
};


================================================
FILE: .gitattributes
================================================
# Enforce Unix newlines
* text=auto eol=lf

*.njk linguist-language=js


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Something is not working as expected
labels:
---

**Before you start**

Please take a look at the [FAQ](https://github.com/GoogleChromeLabs/quicklink/wiki/FAQ) as well as the already opened issues! If nothing fits your problem, go ahead and fill out the following template:

**Describe the bug**

A clear and concise description of what the bug is.

**To Reproduce**

Steps to reproduce the behavior.

**Expected behavior**

A clear and concise description of what you expected to happen.

**Version:**

* OS w/ version: [e.g. iOS 12]
* Browser w/ version [e.g. Chrome 75]

**Additional context, screenshots, screencasts**

Add any other context about the problem here.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
labels:
---

**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.

**Additional context**
Add any other context or screenshots about the feature request here.


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: monthly
    groups:
      github-actions:
        patterns:
          - "*"


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on:
  push:
    branches:
      - main
  pull_request:
  workflow_dispatch:

env:
  FORCE_COLOR: 2
  NODE: 24

permissions:
  contents: read

jobs:
  run:
    runs-on: ubuntu-latest

    steps:
      - name: Clone repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: Set up Node.js
        uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
        with:
          node-version: ${{ env.NODE }}
          cache: npm

      - name: Disable AppArmor
        run: echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test


================================================
FILE: .github/workflows/codeql.yml
================================================
name: "CodeQL"

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
  schedule:
    - cron: "0 0 * * 0"
  workflow_dispatch:

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
      security-events: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: Initialize CodeQL
        uses: github/codeql-action/init@fe4161a26a8629af62121b670040955b330f9af2 # v4.31.6
        with:
          languages: "javascript"
          queries: +security-and-quality

      - name: Autobuild
        uses: github/codeql-action/autobuild@fe4161a26a8629af62121b670040955b330f9af2 # v4.31.6

      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@fe4161a26a8629af62121b670040955b330f9af2 # v4.31.6
        with:
          category: "/language:javascript"


================================================
FILE: .github/workflows/lint.yml
================================================
name: Lint

on:
  push:
    branches:
      - main
  pull_request:
  workflow_dispatch:

env:
  FORCE_COLOR: 2
  NODE: 24

permissions:
  contents: read

jobs:
  lint:
    runs-on: ubuntu-latest

    steps:
      - name: Clone repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: Set up Node.js
        uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
        with:
          node-version: "${{ env.NODE }}"
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Lint
        run: npm run lint


================================================
FILE: .github/workflows/site.yml
================================================
name: Site

on:
  push:
    branches:
      - main
  pull_request:
  workflow_dispatch:

env:
  FORCE_COLOR: 2
  NODE: 24

permissions:
  contents: read

defaults:
  run:
    working-directory: site

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Clone repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: Set up Node.js
        uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
        with:
          node-version: ${{ env.NODE }}
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test


================================================
FILE: .github/workflows/size-limit.yml
================================================
name: Size limit

on:
  push:
    branches:
      - main
  pull_request:
  workflow_dispatch:

env:
  FORCE_COLOR: 2
  NODE: 24

permissions:
  contents: read

jobs:
  size-limit:
    runs-on: ubuntu-latest

    steps:
      - name: Clone repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: Set up Node.js
        uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
        with:
          node-version: ${{ env.NODE }}
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build

      - name: Run size-limit
        run: npm run size


================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
dist
build
.DS_Store

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Coverage directory used by tools like istanbul
coverage

# Dependency directories
node_modules/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env

# Local Netlify folder
.netlify


================================================
FILE: .size-limit.json
================================================
[
  {
    "path": "dist/quicklink.js",
    "limit": "2.5 kB",
    "gzip": true
  },
  {
    "path": "dist/quicklink.mjs",
    "limit": "2.5 kB",
    "gzip": true
  },
  {
    "path": "dist/quicklink.modern.mjs",
    "limit": "2 kB",
    "gzip": true
  },
  {
    "path": "dist/quicklink.umd.js",
    "limit": "2.55 kB",
    "gzip": true
  }
]


================================================
FILE: CHANGELOG.md
================================================
## 2.3.0 (2022-08-05)

* 2.3.0 ([6189deb](https://github.com/GoogleChromeLabs/quicklink/commit/6189deb))
* Add support for same-site prerendering with Speculation Rules API (#258) ([3d26f40](https://github.com/GoogleChromeLabs/quicklink/commit/3d26f40)), closes [#258](https://github.com/GoogleChromeLabs/quicklink/issues/258)
* Create node.js.yml ([2f69b42](https://github.com/GoogleChromeLabs/quicklink/commit/2f69b42))
* Fix Markdown (#250) ([13e2f82](https://github.com/GoogleChromeLabs/quicklink/commit/13e2f82)), closes [#250](https://github.com/GoogleChromeLabs/quicklink/issues/250)
* Update node.js.yml ([312dbbb](https://github.com/GoogleChromeLabs/quicklink/commit/312dbbb))
* chore: Modify repository default languages (#264) ([5a0d396](https://github.com/GoogleChromeLabs/quicklink/commit/5a0d396)), closes [#264](https://github.com/GoogleChromeLabs/quicklink/issues/264)

## 2.2.0 (2021-06-18)

* [release] additions to changelog ([6ac410c](https://github.com/GoogleChromeLabs/quicklink/commit/6ac410c))
* [release] bump core to 2.2.0 ([418eb50](https://github.com/GoogleChromeLabs/quicklink/commit/418eb50))
* [release] bump site to 2.2.0 ([22a055c](https://github.com/GoogleChromeLabs/quicklink/commit/22a055c))
* [release] update changelog for 2.2.0 ([bb1a648](https://github.com/GoogleChromeLabs/quicklink/commit/bb1a648))
* [site] reorder logos ([09907bb](https://github.com/GoogleChromeLabs/quicklink/commit/09907bb))
* [site] update version ([7babbda](https://github.com/GoogleChromeLabs/quicklink/commit/7babbda))
* Add Magento Quicklink module (Readme + Site) (#216) ([c77e057](https://github.com/GoogleChromeLabs/quicklink/commit/c77e057)), closes [#216](https://github.com/GoogleChromeLabs/quicklink/issues/216)
* Added `threshold` option to allow users select the % of link areas that entered the viewport before ([d3746e1](https://github.com/GoogleChromeLabs/quicklink/commit/d3746e1)), closes [#214](https://github.com/GoogleChromeLabs/quicklink/issues/214)
* Instructions to debug Quicklink ([2b3dc21](https://github.com/GoogleChromeLabs/quicklink/commit/2b3dc21))

## 2.1.0 (2021-02-07)

* [docs] Add React SPA demos to repo and site (#179) ([179cb56](https://github.com/GoogleChromeLabs/quicklink/commit/179cb56)), closes [#179](https://github.com/GoogleChromeLabs/quicklink/issues/179)
* [docs] drop highlightjs reference and link up prism styles ([b91872b](https://github.com/GoogleChromeLabs/quicklink/commit/b91872b))
* [docs] Fix quicklink logo ([25829de](https://github.com/GoogleChromeLabs/quicklink/commit/25829de))
* [docs] minor tweak to header text ([cd31382](https://github.com/GoogleChromeLabs/quicklink/commit/cd31382))
* [docs] refactor api docs syntax highlighting ([59f7eca](https://github.com/GoogleChromeLabs/quicklink/commit/59f7eca))
* [docs] refactor copy-snippets syntax highlighting ([5445a8b](https://github.com/GoogleChromeLabs/quicklink/commit/5445a8b))
* [docs] refactor measure docs syntax highlighting ([eaa87ac](https://github.com/GoogleChromeLabs/quicklink/commit/eaa87ac))
* [docs] refactor over-prefetching docs syntax highlighting ([6ec1287](https://github.com/GoogleChromeLabs/quicklink/commit/6ec1287))
* [docs] refactor react docs syntax highlighting ([ba5984d](https://github.com/GoogleChromeLabs/quicklink/commit/ba5984d))
* [docs] refactor usage docs syntax highlighting ([25367f4](https://github.com/GoogleChromeLabs/quicklink/commit/25367f4))
* [docs] remainder of syntax highlighting fixes ([cf32a85](https://github.com/GoogleChromeLabs/quicklink/commit/cf32a85))
* [docs] remove highlightjs ([7ff8c8f](https://github.com/GoogleChromeLabs/quicklink/commit/7ff8c8f))
* [docs] Update CHANGELOG ([c4a4726](https://github.com/GoogleChromeLabs/quicklink/commit/c4a4726))
* [docs] various style theme improvements ([06786fb](https://github.com/GoogleChromeLabs/quicklink/commit/06786fb))
* [feat] (options): Add a `hrefFn` option to build the URL to prefetch. (#201) ([ee072d4](https://github.com/GoogleChromeLabs/quicklink/commit/ee072d4)), closes [#201](https://github.com/GoogleChromeLabs/quicklink/issues/201)
* [feat] `delay` option to reduce impact on CDNs and servers [alternative without data-attributes] (#2 ([5cdf569](https://github.com/GoogleChromeLabs/quicklink/commit/5cdf569)), closes [#217](https://github.com/GoogleChromeLabs/quicklink/issues/217)
* [infra] add eleventy syntax highlighting ([b48f80d](https://github.com/GoogleChromeLabs/quicklink/commit/b48f80d))
* [infra] Add site to firebase hosting config ([fe43486](https://github.com/GoogleChromeLabs/quicklink/commit/fe43486))
* [infra] bump version to 2.1.0 ([81232e8](https://github.com/GoogleChromeLabs/quicklink/commit/81232e8))
* Added Ray-Ban and Oakley from Luxxotica to trustedByLogos section (#202) ([b4494b0](https://github.com/GoogleChromeLabs/quicklink/commit/b4494b0)), closes [#202](https://github.com/GoogleChromeLabs/quicklink/issues/202)
* Correct typo, duplicate "passing" (#185) ([932f655](https://github.com/GoogleChromeLabs/quicklink/commit/932f655)), closes [#185](https://github.com/GoogleChromeLabs/quicklink/issues/185)
* Fix issues typo in 'network-idle.js' (#218) ([534e7b3](https://github.com/GoogleChromeLabs/quicklink/commit/534e7b3)), closes [#218](https://github.com/GoogleChromeLabs/quicklink/issues/218)
* New demo page (#205) ([5205d62](https://github.com/GoogleChromeLabs/quicklink/commit/5205d62)), closes [#205](https://github.com/GoogleChromeLabs/quicklink/issues/205)
* update homepage url in package.json (#184) ([172275b](https://github.com/GoogleChromeLabs/quicklink/commit/172275b)), closes [#184](https://github.com/GoogleChromeLabs/quicklink/issues/184)
* Updating broken link ([224df77](https://github.com/GoogleChromeLabs/quicklink/commit/224df77))
* Fix: Cannot read property 'then' of undefined (#188) ([a8872b8](https://github.com/GoogleChromeLabs/quicklink/commit/a8872b8)), closes [#188](https://github.com/GoogleChromeLabs/quicklink/issues/188)
* chore(deps): bump http-proxy from 1.18.0 to 1.18.1 (#200) ([0aa5157](https://github.com/GoogleChromeLabs/quicklink/commit/0aa5157)), closes [#200](https://github.com/GoogleChromeLabs/quicklink/issues/200)

## 2.0.0 (2020-05-07)

* [infra] Add site updates for 2.0.0 (#178) ([8aa512b](https://github.com/GoogleChromeLabs/quicklink/commit/8aa512b)), closes [#178](https://github.com/GoogleChromeLabs/quicklink/issues/178)
* [infra] Bump versions to 2.0.0 ([08d9a39](https://github.com/GoogleChromeLabs/quicklink/commit/08d9a39))
* 2.0.0 ([735caf6](https://github.com/GoogleChromeLabs/quicklink/commit/735caf6))

## 2.0.0-beta (2020-04-24)

* [core] Adds withQuicklink HOC (#172) ([89cd6a9](https://github.com/GoogleChromeLabs/quicklink/commit/89cd6a9)), closes [#172](https://github.com/GoogleChromeLabs/quicklink/issues/172) [#175](https://github.com/GoogleChromeLabs/quicklink/issues/175) [#176](https://github.com/GoogleChromeLabs/quicklink/issues/176) [#177](https://github.com/GoogleChromeLabs/quicklink/issues/177)
* [core] Introduce prefetch chunks build (#171) ([301aedb](https://github.com/GoogleChromeLabs/quicklink/commit/301aedb)), closes [#171](https://github.com/GoogleChromeLabs/quicklink/issues/171) [#168](https://github.com/GoogleChromeLabs/quicklink/issues/168) [#169](https://github.com/GoogleChromeLabs/quicklink/issues/169)
* [docs] Add initial site ([e934a2b](https://github.com/GoogleChromeLabs/quicklink/commit/e934a2b))
* [docs] Add notes on double-keyed caching ([03d3c97](https://github.com/GoogleChromeLabs/quicklink/commit/03d3c97))
* [docs] Added Newegg to trustedByLogos section (#153) ([453a661](https://github.com/GoogleChromeLabs/quicklink/commit/453a661)), closes [#153](https://github.com/GoogleChromeLabs/quicklink/issues/153)
* [docs] Bugfix/syntax highlighting site (#147) ([0f644e7](https://github.com/GoogleChromeLabs/quicklink/commit/0f644e7)), closes [#147](https://github.com/GoogleChromeLabs/quicklink/issues/147)
* [docs] Compress site resources w/ImageOptim ([179e18e](https://github.com/GoogleChromeLabs/quicklink/commit/179e18e))
* [docs] Measuring impact of QuickLink in sites guide (#146) ([2ce99e3](https://github.com/GoogleChromeLabs/quicklink/commit/2ce99e3)), closes [#146](https://github.com/GoogleChromeLabs/quicklink/issues/146)
* [docs] New section "Quicklink extension" for home page. (#150) ([468c231](https://github.com/GoogleChromeLabs/quicklink/commit/468c231)), closes [#150](https://github.com/GoogleChromeLabs/quicklink/issues/150)
* [docs] Over-prefetching section for home page (#148) ([75aa643](https://github.com/GoogleChromeLabs/quicklink/commit/75aa643)), closes [#148](https://github.com/GoogleChromeLabs/quicklink/issues/148)
* [docs] Update the Angular logo (#158) ([836f170](https://github.com/GoogleChromeLabs/quicklink/commit/836f170)), closes [#158](https://github.com/GoogleChromeLabs/quicklink/issues/158)
* [infra] Add firebase deployment ([89ab866](https://github.com/GoogleChromeLabs/quicklink/commit/89ab866))
* [infra] Fix tests (#142) ([6644860](https://github.com/GoogleChromeLabs/quicklink/commit/6644860)), closes [#142](https://github.com/GoogleChromeLabs/quicklink/issues/142)
* [infra] Publish dist directory (#98) ([9cdf06f](https://github.com/GoogleChromeLabs/quicklink/commit/9cdf06f)), closes [#98](https://github.com/GoogleChromeLabs/quicklink/issues/98)
* 2.0.0-beta ([185d1e8](https://github.com/GoogleChromeLabs/quicklink/commit/185d1e8))
* Add support for Quicklink 2.0.0-alpha ([7c7c917](https://github.com/GoogleChromeLabs/quicklink/commit/7c7c917))
* Add twitter metadata ([74d3224](https://github.com/GoogleChromeLabs/quicklink/commit/74d3224))
* Adding SPA section to README.md ([ea3229a](https://github.com/GoogleChromeLabs/quicklink/commit/ea3229a))
* Fix typo on README (#127) ([5acb27e](https://github.com/GoogleChromeLabs/quicklink/commit/5acb27e)), closes [#127](https://github.com/GoogleChromeLabs/quicklink/issues/127)
* package.json: bump version to 2.0.0-alpha ([d5d5ca5](https://github.com/GoogleChromeLabs/quicklink/commit/d5d5ca5))
* Updates to initial site (#144) ([3f796f6](https://github.com/GoogleChromeLabs/quicklink/commit/3f796f6)), closes [#144](https://github.com/GoogleChromeLabs/quicklink/issues/144)
* chore(deps): bump acorn from 6.4.0 to 6.4.1 (#167) ([8b62949](https://github.com/GoogleChromeLabs/quicklink/commit/8b62949)), closes [#167](https://github.com/GoogleChromeLabs/quicklink/issues/167)

## 2.0.0-alpha (2019-09-25)

* (docs) update to remove TODOs from README ([8cd1183](https://github.com/GoogleChromeLabs/quicklink/commit/8cd1183))
* Update docs with ad-related considerations (#122) ([7ac672f](https://github.com/GoogleChromeLabs/quicklink/commit/7ac672f)), closes [#122](https://github.com/GoogleChromeLabs/quicklink/issues/122)
* Major: Rework exports; Add `throttle` and `limit` options (#120) ([4044de0](https://github.com/GoogleChromeLabs/quicklink/commit/4044de0)), closes [#120](https://github.com/GoogleChromeLabs/quicklink/issues/120) [#1](https://github.com/GoogleChromeLabs/quicklink/issues/1)

## 1.0.1 (2019-08-17)

* (demo) Introduce new demos for basic + workbox usage ([9eb7fa0](https://github.com/GoogleChromeLabs/quicklink/commit/9eb7fa0))
* (demos) Add new demos to README ([85729aa](https://github.com/GoogleChromeLabs/quicklink/commit/85729aa))
* (docs) Update README: note on session stitching ([ba9795c](https://github.com/GoogleChromeLabs/quicklink/commit/ba9795c))
* (infra) Bump version to 1.0.1 ([d75188d](https://github.com/GoogleChromeLabs/quicklink/commit/d75188d))
* A few quick size optimizations ([201c217](https://github.com/GoogleChromeLabs/quicklink/commit/201c217))
* Add homepage and bugs links to package.json (#116) ([002645b](https://github.com/GoogleChromeLabs/quicklink/commit/002645b)), closes [#116](https://github.com/GoogleChromeLabs/quicklink/issues/116)
* Add note to README about Drupal module. ([d94ff80](https://github.com/GoogleChromeLabs/quicklink/commit/d94ff80))
* Check if `requestIdleCallback` exists in `window` (#112) ([089da91](https://github.com/GoogleChromeLabs/quicklink/commit/089da91)), closes [#112](https://github.com/GoogleChromeLabs/quicklink/issues/112)
* Create .editorconfig (#61) ([beae09b](https://github.com/GoogleChromeLabs/quicklink/commit/beae09b)), closes [#61](https://github.com/GoogleChromeLabs/quicklink/issues/61)
* Fail silently, don’t throw an error, when IntersectionObserver isn’t available (#113) ([32e5b61](https://github.com/GoogleChromeLabs/quicklink/commit/32e5b61)), closes [#113](https://github.com/GoogleChromeLabs/quicklink/issues/113)
* Fix ES Module import syntax ([a2b90ff](https://github.com/GoogleChromeLabs/quicklink/commit/a2b90ff))
* GitHub Issue Templates (#109) ([2e6401e](https://github.com/GoogleChromeLabs/quicklink/commit/2e6401e)), closes [#109](https://github.com/GoogleChromeLabs/quicklink/issues/109)
* HTML formatting tidy for Tests & Demos (#114) ([f4aef2e](https://github.com/GoogleChromeLabs/quicklink/commit/f4aef2e)), closes [#114](https://github.com/GoogleChromeLabs/quicklink/issues/114)
* HTTPS link to gruntjs.com (#100) ([47f49e7](https://github.com/GoogleChromeLabs/quicklink/commit/47f49e7)), closes [#100](https://github.com/GoogleChromeLabs/quicklink/issues/100)
* HTTPS link to nodejs.org (#110) ([cf9551e](https://github.com/GoogleChromeLabs/quicklink/commit/cf9551e)), closes [#110](https://github.com/GoogleChromeLabs/quicklink/issues/110)
* Mention instant.page as a related project ([0bc8aec](https://github.com/GoogleChromeLabs/quicklink/commit/0bc8aec))
* Mention Safari ≥ 12.1 working without polyfills (#111) ([b01e5bb](https://github.com/GoogleChromeLabs/quicklink/commit/b01e5bb)), closes [#111](https://github.com/GoogleChromeLabs/quicklink/issues/111)
* remove extraneous full stops / periods from comment (#105) ([23737af](https://github.com/GoogleChromeLabs/quicklink/commit/23737af)), closes [#105](https://github.com/GoogleChromeLabs/quicklink/issues/105)
* remove unneeded type="text/css" from demo (#106) ([4dc74f1](https://github.com/GoogleChromeLabs/quicklink/commit/4dc74f1)), closes [#106](https://github.com/GoogleChromeLabs/quicklink/issues/106)
* remove unneeded type="text/css" from demo page (#104) ([24919bd](https://github.com/GoogleChromeLabs/quicklink/commit/24919bd)), closes [#104](https://github.com/GoogleChromeLabs/quicklink/issues/104)
* Update link to Gatsby with Guess.js (#108) ([dc02d33](https://github.com/GoogleChromeLabs/quicklink/commit/dc02d33)), closes [#108](https://github.com/GoogleChromeLabs/quicklink/issues/108)
* Update microbundle to fix "missing JSX plugin" issue ([8f5cf22](https://github.com/GoogleChromeLabs/quicklink/commit/8f5cf22))
* Update repo path in package.json ([45e9bbd](https://github.com/GoogleChromeLabs/quicklink/commit/45e9bbd))
* Update the Readme and add a mention of the WordPress plugin ([0f15f45](https://github.com/GoogleChromeLabs/quicklink/commit/0f15f45))
* Use latest version of polyfill.io JS (#92) ([b15c8ba](https://github.com/GoogleChromeLabs/quicklink/commit/b15c8ba)), closes [#92](https://github.com/GoogleChromeLabs/quicklink/issues/92)
* fix: Attempt to address build issues (Travis) ([9755280](https://github.com/GoogleChromeLabs/quicklink/commit/9755280))
* fix: stop observing links once prefetched; ([ce0011c](https://github.com/GoogleChromeLabs/quicklink/commit/ce0011c))
* fix(README): use UMD file for `<script>` tags ([e735eb7](https://github.com/GoogleChromeLabs/quicklink/commit/e735eb7))
* docs(browser-support): add note for IE9-10 users (#67) ([aa40490](https://github.com/GoogleChromeLabs/quicklink/commit/aa40490)), closes [#67](https://github.com/GoogleChromeLabs/quicklink/issues/67)
* docs(README): add simplified Chinese version for README.md (#36) ([a3e0221](https://github.com/GoogleChromeLabs/quicklink/commit/a3e0221)), closes [#36](https://github.com/GoogleChromeLabs/quicklink/issues/36)
* docs(README): add URL fragments note about ignores (#52, #49) ([c28c002](https://github.com/GoogleChromeLabs/quicklink/commit/c28c002)), closes [#52](https://github.com/GoogleChromeLabs/quicklink/issues/52) [#49](https://github.com/GoogleChromeLabs/quicklink/issues/49)
* docs(README): address feedback (ignores) ([95ee730](https://github.com/GoogleChromeLabs/quicklink/commit/95ee730))
* docs(README): formatting -> recipe headings ([0073e2a](https://github.com/GoogleChromeLabs/quicklink/commit/0073e2a))
* docs(translated README): link to zh-CN MDN docs ([409cb2c](https://github.com/GoogleChromeLabs/quicklink/commit/409cb2c))
* ci: update Yarn with the recommended official commands ([1a92452](https://github.com/GoogleChromeLabs/quicklink/commit/1a92452))
* ci: use Yarn for all scripts ([4477ec6](https://github.com/GoogleChromeLabs/quicklink/commit/4477ec6))
* chore(release): add conventional-changelog-cli & changelog ([532b985](https://github.com/GoogleChromeLabs/quicklink/commit/532b985))

## 1.0.0 (2018-12-14)

* release(package.json): bump to 1.0.0 ([bd82a6c](https://github.com/GoogleChromeLabs/quicklink/commit/bd82a6c))
* infra(prefetch.mjs): add jsdoc for third-arg to prefetcher ([c5ed343](https://github.com/GoogleChromeLabs/quicklink/commit/c5ed343))
* infra(tests): fix linting for spec ([ba74f11](https://github.com/GoogleChromeLabs/quicklink/commit/ba74f11))
* docs: clarify Network Information API browser support ([ac5f7d9](https://github.com/GoogleChromeLabs/quicklink/commit/ac5f7d9))
* docs: describe “ignores” & add recipe ([26a126c](https://github.com/GoogleChromeLabs/quicklink/commit/26a126c))
* docs: describe new opts w/ recipes ([d2a7870](https://github.com/GoogleChromeLabs/quicklink/commit/d2a7870))
* docs(README.md): clarify layered support ([0025caa](https://github.com/GoogleChromeLabs/quicklink/commit/0025caa))
* test: add “ignores” suite ([b89faf0](https://github.com/GoogleChromeLabs/quicklink/commit/b89faf0))
* test: add origins & sameOrigin suites ([21f601b](https://github.com/GoogleChromeLabs/quicklink/commit/21f601b))
* fix: add "unpkg" entry ([3a0d97c](https://github.com/GoogleChromeLabs/quicklink/commit/3a0d97c))
* fix: drop `sameOrigin` but move to same origin default; ([11af301](https://github.com/GoogleChromeLabs/quicklink/commit/11af301))
* fix: remove `typeof document` check; ([c578c32](https://github.com/GoogleChromeLabs/quicklink/commit/c578c32))
* fix: rename “filter” -> “ignores” ([a1544da](https://github.com/GoogleChromeLabs/quicklink/commit/a1544da))
* fix: retain RegExp caller context ([53f5169](https://github.com/GoogleChromeLabs/quicklink/commit/53f5169))
* fix: revert `options` inlining; ([194881f](https://github.com/GoogleChromeLabs/quicklink/commit/194881f))
* feat: add “opt.origins” for inspecting before queue; ([1074d46](https://github.com/GoogleChromeLabs/quicklink/commit/1074d46))
* feat: add flexible `filter` option; ([4e64ca1](https://github.com/GoogleChromeLabs/quicklink/commit/4e64ca1))
* feat: add option to restrict same origin; ([2a31aee](https://github.com/GoogleChromeLabs/quicklink/commit/2a31aee))
* feat(index.mjs): normalize URLs being prefetched (#27) ([1da37f4](https://github.com/GoogleChromeLabs/quicklink/commit/1da37f4)), closes [#27](https://github.com/GoogleChromeLabs/quicklink/issues/27)
* add test & recipe for all origins ([ab6375a](https://github.com/GoogleChromeLabs/quicklink/commit/ab6375a))
* fixed polyfilling ([92e11ed](https://github.com/GoogleChromeLabs/quicklink/commit/92e11ed))
* new URL(...): add location.href as second parameter ([d8ed5f9](https://github.com/GoogleChromeLabs/quicklink/commit/d8ed5f9))
* tests(test-static-url-list): expand prefetch URL paths ([3027c72](https://github.com/GoogleChromeLabs/quicklink/commit/3027c72))
* golf: compress `support` helper; ([9df00ff](https://github.com/GoogleChromeLabs/quicklink/commit/9df00ff))
* golf: inline `options.priority` default; ([36cc199](https://github.com/GoogleChromeLabs/quicklink/commit/36cc199))
* golf: inline `options` defaults; ([fe5f735](https://github.com/GoogleChromeLabs/quicklink/commit/fe5f735))
* golf: inline `withCredentials` assignment; ([1c94262](https://github.com/GoogleChromeLabs/quicklink/commit/1c94262))
* golf: only use `document.head` for append; ([189984a](https://github.com/GoogleChromeLabs/quicklink/commit/189984a))
* golf: save `navigator.connection` to var; ([5f95309](https://github.com/GoogleChromeLabs/quicklink/commit/5f95309))

## 0.1.2 (2018-12-12)

* release(package.json): bump release ([de41a3c](https://github.com/GoogleChromeLabs/quicklink/commit/de41a3c))
* Fix wrong operator precedence: negation vs 'in' ([c2864b0](https://github.com/GoogleChromeLabs/quicklink/commit/c2864b0))
* fixes #5: localize and fix network-idle-callback deps ([807e8ad](https://github.com/GoogleChromeLabs/quicklink/commit/807e8ad)), closes [#5](https://github.com/GoogleChromeLabs/quicklink/issues/5)
* docs(README.md): add demo details (for #12) ([842f92a](https://github.com/GoogleChromeLabs/quicklink/commit/842f92a)), closes [#12](https://github.com/GoogleChromeLabs/quicklink/issues/12)
* docs(README.md): minor clean-up of demo text ([4c03801](https://github.com/GoogleChromeLabs/quicklink/commit/4c03801))
* docs(readme): correct a small typo ([93814e2](https://github.com/GoogleChromeLabs/quicklink/commit/93814e2))

## 0.1.1 (2018-12-12)

* release(package.json): bump version ([25b7cc7](https://github.com/GoogleChromeLabs/quicklink/commit/25b7cc7))
* Readme: Fix Guessjs website link ([0a32e19](https://github.com/GoogleChromeLabs/quicklink/commit/0a32e19))
* fix: amend incorrect `querySelector` call ([e330066](https://github.com/GoogleChromeLabs/quicklink/commit/e330066))

## 0.1.0 (2018-12-11)

* 0.1.0 ([a76eade](https://github.com/GoogleChromeLabs/quicklink/commit/a76eade))
* Clean up promises ([e4923ee](https://github.com/GoogleChromeLabs/quicklink/commit/e4923ee))
* feat(prefetch links scrolling into viewport) for #6 ([d1e825d](https://github.com/GoogleChromeLabs/quicklink/commit/d1e825d)), closes [#6](https://github.com/GoogleChromeLabs/quicklink/issues/6)
* fix typo~ ([0c791bf](https://github.com/GoogleChromeLabs/quicklink/commit/0c791bf))
* Improve basic demo ([04fa0d0](https://github.com/GoogleChromeLabs/quicklink/commit/04fa0d0))
* infra(lint-fixes) for index and prefetch ([c8c47f8](https://github.com/GoogleChromeLabs/quicklink/commit/c8c47f8))
* Make it proper IUU ([e6ffca9](https://github.com/GoogleChromeLabs/quicklink/commit/e6ffca9))
* Revert logic changes, but keep refactors ([a9d2216](https://github.com/GoogleChromeLabs/quicklink/commit/a9d2216))
* revert multi-if statement ([cbe616a](https://github.com/GoogleChromeLabs/quicklink/commit/cbe616a))
* docs(CONTRIBUTING.md): add contribution guidelines ([4a3da7b](https://github.com/GoogleChromeLabs/quicklink/commit/4a3da7b))
* docs(README): add link to gatsby guess plugin + prefetch notes ([d67a31a](https://github.com/GoogleChromeLabs/quicklink/commit/d67a31a))
* docs(README): add logo and badges ([ec2b87c](https://github.com/GoogleChromeLabs/quicklink/commit/ec2b87c))
* docs(README): changes for new boolean priority ([7ba57a6](https://github.com/GoogleChromeLabs/quicklink/commit/7ba57a6))
* docs(README): fix references to GoogleChomeLabs ([9056abe](https://github.com/GoogleChromeLabs/quicklink/commit/9056abe))
* docs(README): minor tweaks ([2c04988](https://github.com/GoogleChromeLabs/quicklink/commit/2c04988))
* release(package.json): bump due to priority now being Boolean ([d3c3806](https://github.com/GoogleChromeLabs/quicklink/commit/d3c3806))
* release(package.json): bump version ([5064745](https://github.com/GoogleChromeLabs/quicklink/commit/5064745))
* infra(eslint): bump to ecma 9 ([6336333](https://github.com/GoogleChromeLabs/quicklink/commit/6336333))
* infra(linting): fix linting issues from #15 ([e6c90d8](https://github.com/GoogleChromeLabs/quicklink/commit/e6c90d8)), closes [#15](https://github.com/GoogleChromeLabs/quicklink/issues/15)
* infra(tests): add test for links scrolled into viewport ([7b7e5ff](https://github.com/GoogleChromeLabs/quicklink/commit/7b7e5ff))
* infra(tests): minor rename ([62b60cd](https://github.com/GoogleChromeLabs/quicklink/commit/62b60cd))
* infra(travis): attempt to fix server perms ([fdbabff](https://github.com/GoogleChromeLabs/quicklink/commit/fdbabff))
* infra(travis): config for tests ([2f0631b](https://github.com/GoogleChromeLabs/quicklink/commit/2f0631b))
* infra(travis): revert ports ([972eacc](https://github.com/GoogleChromeLabs/quicklink/commit/972eacc))
* fix: match long ternary style ([06a9a0a](https://github.com/GoogleChromeLabs/quicklink/commit/06a9a0a))
* fix: move puppeteer to devdeps ([d98cec8](https://github.com/GoogleChromeLabs/quicklink/commit/d98cec8))
* golf: assert against `Map.get` existence; ([2cd1daf](https://github.com/GoogleChromeLabs/quicklink/commit/2cd1daf))
* golf: combine `nav.connection` if-statements; ([34e9718](https://github.com/GoogleChromeLabs/quicklink/commit/34e9718))
* golf: hoist shared “prefetcher” helper; ([2a5f63b](https://github.com/GoogleChromeLabs/quicklink/commit/2a5f63b))
* golf: inline `isIntersecting` filter; ([1037e38](https://github.com/GoogleChromeLabs/quicklink/commit/1037e38))
* golf: observe link & update Map in same loop; ([47b370d](https://github.com/GoogleChromeLabs/quicklink/commit/47b370d))
* golf: remove `return` within observer; ([0e569a3](https://github.com/GoogleChromeLabs/quicklink/commit/0e569a3))
* golf: remove duplicate `typeof document` check; ([1662c0c](https://github.com/GoogleChromeLabs/quicklink/commit/1662c0c))
* golf: skip `setAttribute` & assign directly; ([f7cb14e](https://github.com/GoogleChromeLabs/quicklink/commit/f7cb14e))
* golf: use `Array.from` to gather URL values; ([5def7dd](https://github.com/GoogleChromeLabs/quicklink/commit/5def7dd))
* golf: use `doc.head` & `doc.querySelector`; ([53b7d15](https://github.com/GoogleChromeLabs/quicklink/commit/53b7d15))
* golf: use Object.assign for defaults; ([11c4369](https://github.com/GoogleChromeLabs/quicklink/commit/11c4369))
* golf: use Promise instead of AsyncFunction; ([84a0468](https://github.com/GoogleChromeLabs/quicklink/commit/84a0468))
* golf: use Set & share `prefetch` caller; ([d221c51](https://github.com/GoogleChromeLabs/quicklink/commit/d221c51))
* golf: use ternary within prefetch functions; ([be441dc](https://github.com/GoogleChromeLabs/quicklink/commit/be441dc))
* golf(breaking): use Boolean for `priority` option; ([e478a47](https://github.com/GoogleChromeLabs/quicklink/commit/e478a47))

## 0.0.3 (2018-12-05)

* release(package.json) bump to 0.0.3 ([2d46f53](https://github.com/GoogleChromeLabs/quicklink/commit/2d46f53))
* Docs(README): add browser support and typo fix ([d2e18ad](https://github.com/GoogleChromeLabs/quicklink/commit/d2e18ad))
* Docs(README): minor revisions (why, support, projects) ([59d23d4](https://github.com/GoogleChromeLabs/quicklink/commit/59d23d4))
* docs(prefetch): add missing jsdoc comments ([cada9d4](https://github.com/GoogleChromeLabs/quicklink/commit/cada9d4))
* docs(README.md): link to APIs used ([a9af442](https://github.com/GoogleChromeLabs/quicklink/commit/a9af442))
* docs(README): add note about timeoutFn ([46b0874](https://github.com/GoogleChromeLabs/quicklink/commit/46b0874))
* docs(README): add notes on unpkg and initializing ([3609ac9](https://github.com/GoogleChromeLabs/quicklink/commit/3609ac9))
* docs(README): add why and related projects. ([8799aca](https://github.com/GoogleChromeLabs/quicklink/commit/8799aca))
* docs(README): further revisions to browser support ([2e49f1f](https://github.com/GoogleChromeLabs/quicklink/commit/2e49f1f))
* refactor(index.mjs): fix timeoutFn fallbacks ([bfa8917](https://github.com/GoogleChromeLabs/quicklink/commit/bfa8917))
* feat(bundlesize): add initial setup ([61012b4](https://github.com/GoogleChromeLabs/quicklink/commit/61012b4))
* demos(basic.html): add simplest usage demo ([e51781b](https://github.com/GoogleChromeLabs/quicklink/commit/e51781b))
* demos(network-idle): add network-idle-callback demo ([d4ae22d](https://github.com/GoogleChromeLabs/quicklink/commit/d4ae22d))
* core(index.mjs): add support for timeoutFn ([524b72e](https://github.com/GoogleChromeLabs/quicklink/commit/524b72e))
* infra(package.json): server->start, add demos to linting ([783a1b5](https://github.com/GoogleChromeLabs/quicklink/commit/783a1b5))

## 0.0.2 (2018-11-27)

* 0.0.1 ([cddf434](https://github.com/GoogleChromeLabs/quicklink/commit/cddf434))
* 0.0.2 ([eb5c15e](https://github.com/GoogleChromeLabs/quicklink/commit/eb5c15e))
* Drop private ([fadf8b3](https://github.com/GoogleChromeLabs/quicklink/commit/fadf8b3))
* fix test typos ([f3f3f9b](https://github.com/GoogleChromeLabs/quicklink/commit/f3f3f9b))
* release(pkg.json): bump ([200d528](https://github.com/GoogleChromeLabs/quicklink/commit/200d528))
* tests(bootstrap): extend timeout to 20000 ([e5bf3f3](https://github.com/GoogleChromeLabs/quicklink/commit/e5bf3f3))
* docs(README): add API, polyfills, expand recipes ([9205c20](https://github.com/GoogleChromeLabs/quicklink/commit/9205c20))
* docs(README): add installation instructions, some better jobs. ([b2ffa41](https://github.com/GoogleChromeLabs/quicklink/commit/b2ffa41))
* docs(README): fix typo ([43306cf](https://github.com/GoogleChromeLabs/quicklink/commit/43306cf))
* docs(README): minor tweaks. ([baa9ec0](https://github.com/GoogleChromeLabs/quicklink/commit/baa9ec0))
* feat(index.mjs): add support for rIC timeout customisation ([18bea81](https://github.com/GoogleChromeLabs/quicklink/commit/18bea81))
* feat(tests refactoring): add mocha, chai tests with puppeteer ([d0b8911](https://github.com/GoogleChromeLabs/quicklink/commit/d0b8911))
* feat(tests): add initial testing ([fd81b71](https://github.com/GoogleChromeLabs/quicklink/commit/fd81b71))
* feat(tests): improve test coverage ([3ee52bd](https://github.com/GoogleChromeLabs/quicklink/commit/3ee52bd))
* feat(tests): move /demo to tests directory ([9d8ff74](https://github.com/GoogleChromeLabs/quicklink/commit/9d8ff74))

## 0.0.1 (2018-11-24)

* (tidy) clean-up demo directory ([dc6bc58](https://github.com/GoogleChromeLabs/quicklink/commit/dc6bc58))
* (tidy) index.mjs: JSDoc comments ([e0d0afe](https://github.com/GoogleChromeLabs/quicklink/commit/e0d0afe))
* 0.0.1 ([9ad703b](https://github.com/GoogleChromeLabs/quicklink/commit/9ad703b))
* Add babelrc and travis configuration ([d417c9c](https://github.com/GoogleChromeLabs/quicklink/commit/d417c9c))
* Add demo directory ([67784f7](https://github.com/GoogleChromeLabs/quicklink/commit/67784f7))
* Add dist to gitignore ([3eb7f8c](https://github.com/GoogleChromeLabs/quicklink/commit/3eb7f8c))
* Add handling for effectiveconnectiontype ([47dbaf5](https://github.com/GoogleChromeLabs/quicklink/commit/47dbaf5))
* Add microbundle and configuration to package ([6571ff0](https://github.com/GoogleChromeLabs/quicklink/commit/6571ff0))
* Add package.json ([fd6149b](https://github.com/GoogleChromeLabs/quicklink/commit/fd6149b))
* Add saveData handling ([32902a7](https://github.com/GoogleChromeLabs/quicklink/commit/32902a7))
* Adds index: initial implementation ([ce0aa40](https://github.com/GoogleChromeLabs/quicklink/commit/ce0aa40))
* Clean-up source ([ecbd70f](https://github.com/GoogleChromeLabs/quicklink/commit/ecbd70f))
* clean(index, prefetch): move connection logic to prefetcher ([a7cafa4](https://github.com/GoogleChromeLabs/quicklink/commit/a7cafa4))
* clean(index.mjs, prefetch.mjs) move prefetching logic to one place ([6e5d9fb](https://github.com/GoogleChromeLabs/quicklink/commit/6e5d9fb))
* clean(src/index.mjs, src/prefetch.mjs): further reshuffling ([9fe6036](https://github.com/GoogleChromeLabs/quicklink/commit/9fe6036))
* docs(pkg, README): more tweaks. ([c1e66c5](https://github.com/GoogleChromeLabs/quicklink/commit/c1e66c5))
* feat(index, prefetch) Add support for higher prio fetches ([c4fb77a](https://github.com/GoogleChromeLabs/quicklink/commit/c4fb77a))
* fix(demo/index.html): Reference UMD build ([6e7a838](https://github.com/GoogleChromeLabs/quicklink/commit/6e7a838))
* Initial commit ([869ce69](https://github.com/GoogleChromeLabs/quicklink/commit/869ce69))
* Lots of ESLint fixes ([5d0af3d](https://github.com/GoogleChromeLabs/quicklink/commit/5d0af3d))
* minor(index, prefetch): renaming ([2c03bba](https://github.com/GoogleChromeLabs/quicklink/commit/2c03bba))
* tidy(demo/index.html): drop unused script reference ([4aecc0f](https://github.com/GoogleChromeLabs/quicklink/commit/4aecc0f))
* docs(package.json): get consistent with description ([c2f00ed](https://github.com/GoogleChromeLabs/quicklink/commit/c2f00ed))
* docs(README): add how it works, usage and recipes. ([0a26a65](https://github.com/GoogleChromeLabs/quicklink/commit/0a26a65))
* fix(src): Add license headers ([adf645e](https://github.com/GoogleChromeLabs/quicklink/commit/adf645e))


================================================
FILE: CONTRIBUTING.md
================================================
# How to Contribute

We'd love to accept your patches and contributions to this project. There are
just a few small guidelines you need to follow.

## Contributor License Agreement

Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution,
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to <https://cla.developers.google.com/> to see
your current agreements on file or to sign a new one.

You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again.

## Code reviews

All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult [GitHub
Help](https://help.github.com/articles/about-pull-requests/) for more
information on using pull requests.

## Community Guidelines

This project follows [Google's Open Source Community
Guidelines](https://opensource.google.com/conduct/).

## Debugging Quicklink

The [`test/fixtures/` folder](test/fixtures/) contains several test cases.
Make sure to create a new test when building a new feature.

Here's an example of how to debug the library by using one of these tests:
[test/fixtures/test-basic-usage.html](test/fixtures/test-basic-usage.html).

1. Comment the following block of code at `test/fixtures/test-basic-usage.html`:

    ```js
    <script src="../../dist/quicklink.umd.js"></script>
    <script>
      quicklink.listen();
    </script>
    ```

2. Add the following snippet in its place, to import the module from its
   source file:

    ```js
    <script type="module">
      import {listen} from '../../src/index.mjs';
      listen();
    </script>
    ```

3. Open [src/index.mjs](src/index.mjs) for edit and replace the following line:

    ```js
    import throttle from 'throttles';
    ```

    By:

    ```js
    import throttle from '../node_modules/throttles/dist/index.mjs'
    ```

4. Build the project: `npm run build`.

5. Start a local server: `npm start`. By default, this will start the local server at
   `https://localhost:8080`.

6. Open the file where the modifications where made:
   `http://localhost:8080/test/fixtures/test-basic-usage.html`.

7. Open Chrome DevTools and go the **Sources** tab.

8. Under `localhost:8080/src` you can find the unminified versions of the
   `Quicklink` files. Now you can use breakpoints and inspect variables to
   debug the library.


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
<p align="center">
  <img src="https://raw.githubusercontent.com/GoogleChromeLabs/quicklink/HEAD/assets/images/logos/banner-white-bg.png" alt="" width="640">
  <br>
  <a href="https://www.npmjs.com/package/quicklink">
    <img src="https://img.shields.io/npm/v/quicklink?style=flat&logo=npm&logoColor=fff" alt="npm">
  </a>
  <a href="https://unpkg.com/quicklink">
    <img src="https://img.shields.io/bundlephobia/minzip/quicklink" alt="gzip size">
  </a>
  <a href="https://github.com/GoogleChromeLabs/quicklink/actions/workflows/ci.yml?query=workflow%3ACI+branch%3Amain">
    <img src="https://img.shields.io/github/actions/workflow/status/GoogleChromeLabs/quicklink/ci.yml?branch=main&label=ci&logo=github" alt="ci">
  </a>
</p>

# quicklink

> Faster subsequent page-loads by prefetching or prerendering in-viewport links during idle time

## How it works

Quicklink attempts to make navigations to subsequent pages load faster. It:

- **Detects links within the viewport** (using [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API))
- **Waits until the browser is idle** (using [requestIdleCallback](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback))
- **Checks if the user isn't on a slow connection** (using `navigator.connection.effectiveType`) or has data-saver enabled (using `navigator.connection.saveData`)
- **Prefetches** (using [`<link rel=prefetch>`](https://www.w3.org/TR/resource-hints/#prefetch) or XHR) or **prerenders** (using [Speculation Rules API](https://github.com/WICG/nav-speculation/blob/main/triggers.md)) URLs to the links. Provides some control over the request priority (can switch to `fetch()` if supported).

## Why

This project aims to be a drop-in solution for sites to prefetch or prerender links based on what is in the user's viewport. It also aims to be small (**< 2KB minified/gzipped**).

## Multi page apps

### Installation

For use with [Node.js](https://nodejs.org/) and [npm](https://www.npmjs.com/):

```sh
npm install quicklink
```

You can also grab `quicklink` from [unpkg.com/quicklink](https://unpkg.com/quicklink).

### Usage

Once initialized, `quicklink` will automatically prefetch URLs for links that are in-viewport during idle time.

Quickstart:

```html
<!-- Include quicklink from dist -->
<script src="dist/quicklink.umd.js"></script>
<!-- Initialize (you can do this whenever you want) -->
<script>
  quicklink.listen();
</script>
```

For example, you can initialize after the `load` event fires:

```html
<script>
  window.addEventListener('load', () => {
    quicklink.listen();
  });
</script>
```

ES Module import:

```js
import {listen, prefetch} from 'quicklink';
```

## Single page apps (React)

### Installation

First, install the packages with [Node.js](https://nodejs.org/) and [npm](https://www.npmjs.com/):

```sh
npm install quicklink webpack-route-manifest --save-dev
```

Then, configure Webpack route manifest into your project, as explained [here](https://github.com/lukeed/webpack-route-manifest).
This will generate a map of routes and chunks called `rmanifest.json`. It can be obtained at:

- URL: `site_url/rmanifest.json`
- Window object: `window.__rmanifest`

### Usage

Import `quicklink` React HOC where want to add prefetching functionality.
Wrap your routes with the `withQuicklink()` HOC.

Example:

```jsx
import {withQuicklink} from 'quicklink/dist/react/hoc.js';

const options = {
  origins: [],
};

<Suspense fallback={<div>Loading...</div>}>
  <Route path='/' exact component={withQuicklink(Home, options)} />
  <Route path='/blog' exact component={withQuicklink(Blog, options)} />
  <Route path='/blog/:title' component={withQuicklink(Article, options)} />
  <Route path='/about' exact component={withQuicklink(About, options)} />
</Suspense>;
```

## API

### quicklink.listen(options)

Returns: `Function`

A "reset" function is returned, which will empty the active `IntersectionObserver` and the cache of URLs that have already been prefetched or prerendered. This can be used between page navigations and/or when significant DOM changes have occurred.

#### options.prerender

- Type: `Boolean`
- Default: `false`

Whether to switch from the default prefetching mode to the prerendering mode for the links inside the viewport.

> **Note:** The prerendering mode (when this option is set to true) will fallback to the prefetching mode if the browser does not support prerender.
> Once the element exits the viewport, the `speculationrules` script is removed from the DOM. This approach makes it possible to exceed the limit of 10 prerenders imposed for the 'immediate' and 'eager' settings for eagerness.

#### options.eagerness

- Type: `String`
- Default: `immediate`

Determines the mode to be used for prerendering specified within the speculation rules.

#### options.prerenderAndPrefetch

* Type: `Boolean`
* Default: `false`

Whether to activate both the prefetching and prerendering mode at the same time.

#### options.delay

- Type: `Number`
- Default: `0`

The _amount of time_ each link needs to stay inside the viewport before being prefetched, in milliseconds.

#### options.el

- Type: `HTMLElement|NodeList<A>`
- Default: `document.body`

The DOM element to observe for in-viewport links to prefetch or the NodeList of Anchor Elements.

#### options.limit

- Type: `Number`
- Default: `Infinity`

The _total_ requests that can be prefetched or prerendered while observing the `options.el` container.

#### options.threshold

- Type: `Number`
- Default: `0`

The _area percentage_ of each link that must have entered the viewport to be fetched, in its decimal form (e.g. 0.25 = 25%).

#### options.throttle

- Type: `Number`
- Default: `Infinity`

The _concurrency limit_ for simultaneous requests while observing the `options.el` container.

#### options.timeout

- Type: `Number`
- Default: `2000`

The `requestIdleCallback` timeout, in milliseconds.

> **Note:** The browser must be idle for the configured duration before prefetching.

#### options.timeoutFn

- Type: `Function`
- Default: `requestIdleCallback`

A function used for specifying a `timeout` delay.

This can be swapped out for a custom function like [networkIdleCallback](https://github.com/pastelsky/network-idle-callback) (see demos).

By default, this uses [`requestIdleCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback) or the embedded polyfill.

#### options.priority

- Type: `Boolean`
- Default: `false`

Whether or not the URLs within the `options.el` container should be treated as high priority.

When `true`, quicklink will attempt to use the `fetch()` API if supported (rather than `link[rel=prefetch]`).

#### options.origins

- Type: `Array<String>`
- Default: `[location.hostname]`

A static array of URL hostnames that are allowed to be prefetched.

Defaults to the same domain origin, which prevents _any_ cross-origin requests.

**Important:** An empty array (`[]`) allows **_all origins_** to be prefetched.

#### options.ignores

- Type: `RegExp` or `Function` or `Array`
- Default: `[]`

Determine if a URL should be prefetched.

When a `RegExp` tests positive, a `Function` returns `true`, or an `Array` contains the string, then the URL is _not_ prefetched.

> **Note:** An `Array` may contain `String`, `RegExp`, or `Function` values.

> **Important:** This logic is executed _after_ origin matching!

#### options.onError

- Type: `Function`
- Default: None

An optional error handler that will receive any errors from prefetched requests.

By default, these errors are silently ignored.

#### options.hrefFn

- Type: `Function`
- Default: None

An optional function to generate the URL to prefetch. It receives an [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) as the argument.

### quicklink.prefetch(urls, isPriority)

Returns: `Promise`

The `urls` provided are always passed through `Promise.all`, which means the result will always resolve to an Array.

> **Important:** You much `catch` you own request error(s).

#### urls

- Type: `String` or `Array<String>`
- Required: `true`

One or many URLs to be prefetched.

> **Note:** Each `url` value is resolved from the current location.

#### isPriority

- Type: `Boolean`
- Default: `false`

Whether or not the URL(s) should be treated as "high priority" targets.

By default, calls to `prefetch()` are low priority.

> **Note:** This behaves identically to `listen()`'s `priority` option.

### quicklink.prerender(urls, eagerness)

Returns: `Promise`

> **Important:** You much `catch` you own request error(s).

#### urls

- Type: `String` or `Array<String>`
- Required: `true`

One or many URLs to be prerendered.

> **Note:** Speculative Rules API supports same-site cross origin Prerendering with [opt-in header](https://bit.ly/ss-cross-origin-pre).

#### eagerness

- Type: `String`
- Default: `immediate`

Determines the mode to be used for prerendering specified within the speculation rules.

## Polyfills

`quicklink`:

- Includes a very small fallback for [requestIdleCallback](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback)
- Requires `IntersectionObserver` to be supported. This is [supported in all modern browsers](https://caniuse.com/intersectionobserver), however you can use the [Intersection Observer polyfill](https://github.com/GoogleChromeLabs/intersection-observer) to support legacy browsers if needed.

## Recipes

### Set a custom timeout for prefetching resources

Defaults to 2 seconds (via `requestIdleCallback`). Here we override it to 4 seconds:

```js
quicklink.listen({
  timeout: 4000,
});
```

### Set a specific Anchor Elements NodeList to observe for in-viewport links

Defaults to `document` otherwise.

```js
quicklink.listen({
  el: document.querySelectorAll('a.linksToPrefetch'),
});
```

### Set the DOM element to observe for in-viewport links

Defaults to `document` otherwise.

```js
quicklink.listen({
  el: document.getElementById('carousel'),
});
```

### Programmatically `prefetch()` URLs

If you would prefer to provide a static list of URLs to be prefetched, instead of detecting those in-viewport, customizing URLs is supported.

```js
// Single URL
quicklink.prefetch('2.html');

// Multiple URLs
quicklink.prefetch(['2.html', '3.html', '4.js']);

// Multiple URLs, with high priority
// Note: Can also be use with single URL!
quicklink.prefetch(['2.html', '3.html', '4.js'], true);
```

### Programmatically `prerender()` URLs

If you would prefer to provide a static list of URLs to be prerendered, instead of detecting those in-viewport, customizing URLs is supported.

```js
// Single URL
quicklink.prerender('2.html');

// Multiple URLs
quicklink.prerender(['2.html', '3.html', '4.js']);
```

### Set the request priority for prefetches while scrolling

Defaults to low-priority (`rel=prefetch` or XHR). For high-priority (`priority: true`), attempts to use `fetch()` or falls back to XHR.

> **Note:** This runs `prefetch(..., true)` with URLs found within the `options.el` container.

```js
quicklink.listen({priority: true});
```

### Specify a custom list of allowed origins

Provide a list of hostnames that should be prefetch-able. Only the same origin is allowed by default.

> **Important:** You must also include your own hostname!

```js
quicklink.listen({
  origins: [
    // add mine
    'my-website.com',
    'api.my-website.com',
    // add third-parties
    'other-website.com',
    'example.com',
    // ...
  ],
});
```

### Allow all origins

Enables all cross-origin requests to be made.

> **Note:** You may run into [CORB](https://chromium.googlesource.com/chromium/src/+/main/services/network/cross_origin_read_blocking_explainer.md) and [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) issues!

```js
quicklink.listen({
  origins: true,
  // or
  origins: [],
});
```

### Custom Ignore Patterns

These filters run _after_ the `origins` matching has run. Ignores can be useful for avoiding large file downloads or for responding to DOM attributes dynamically.

```js
// Same-origin restraint is enabled by default.
//
// This example will ignore all requests to:
//  - all "/api/*" pathnames
//  - all ".zip" extensions
//  - all <a> tags with "noprefetch" attribute
//
quicklink.listen({
  ignores: [
    /\/api\/?/,
    uri => uri.includes('.zip'),
    (uri, elem) => elem.hasAttribute('noprefetch'),
  ],
});
```

You may also wish to ignore prefetches to URLs which contain a URL fragment (e.g. `index.html#top`). This can be useful if you (1) are using anchors to headings in a page or (2) have URL fragments setup for a single-page application, and which to avoid firing prefetches for similar URLs.

Using `ignores` this can be achieved as follows:

```js
quicklink.listen({
  ignores: [
    uri => uri.includes('#'),
    // or RegExp: /#(.+)/
    // or element matching: (uri, elem) => !!elem.hash
  ],
});
```

### Custom URL to prefetch via hrefFn callback

The hrefFn method allows to build the URL to prefetch (e.g. API endpoint) on the fly instead of the prefetching the `href` attribute URL.

```js
quicklink.listen({
  hrefFn(element) {
    return element.href.replace('html', 'json');
  },
});
```

## Browser Support

The prefetching provided by `quicklink` can be viewed as a [progressive enhancement](https://www.smashingmagazine.com/2009/04/progressive-enhancement-what-it-is-and-how-to-use-it/). Cross-browser support is as follows:

- Without polyfills: Chrome, Safari ≥ 12.1, Firefox, Edge, Opera, Android Browser, Samsung Internet.
- With [Intersection Observer polyfill](https://github.com/GoogleChromeLabs/intersection-observer) ~6KB gzipped/minified: Safari ≤ 12.0, IE11
- With the above and a [Set()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) and [Array.from](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from) polyfill: IE9 and IE10. [Core.js](https://github.com/zloirock/core-js) provides both `Set()` and `Array.from()` shims. Projects like [es6-shim](https://github.com/paulmillr/es6-shim/blob/master/README.md) are an alternative you can consider.

Certain features have layered support:

- The [Network Information API](https://wicg.github.io/netinfo/), which is used to check if the user has a slow effective connection type (via `navigator.connection.effectiveType`) is only available in [Chrome 61+ and Opera 57+](https://caniuse.com/netinfo)
- If opting for `{priority: true}` and the [Fetch API](https://fetch.spec.whatwg.org/) isn't available, XHR will be used instead.

## Using the prefetcher directly

A `prefetch` method can be individually imported for use in other projects.

This method includes the logic to respect Data Saver and 2G connections. It also issues requests thru `fetch()`, XHRs, or `link[rel=prefetch]` depending on (a) the `isPriority` value and (b) the current browser's support.

After installing `quicklink` as a dependency, you can use it as follows:

```html
<script type="module">
  import {prefetch} from 'quicklink';
  prefetch(['1.html', '2.html']).catch(error => {
    // Handle own errors
  });
</script>
```

## Demo

### Glitch demos

- [Using Quicklink in a multi-page site](https://github.com/GoogleChromeLabs/quicklink/tree/main/demos/news)
- [Using Quicklink with Service Workers (via Workbox)](https://github.com/GoogleChromeLabs/quicklink/tree/main/demos/news-workbox)
- [Using Quicklink to prefetch API calls instead of `href` attribute](https://github.com/GoogleChromeLabs/quicklink/tree/main/demos/hrefFn)
- [Using Quicklink to prerender a specific page](https://uskay-prerender2.glitch.me/next.html)

### Research

Here's a [WebPageTest run](https://www.webpagetest.org/video/view.php?id=181212_4c294265117680f2636676721cc886613fe2eede&data=1) for our [demo](https://keyword-2-ecd7b.firebaseapp.com/) improving page-load performance by up to 4 seconds via quicklink's prefetching. A [video](https://youtu.be/rQ75YEbJicw) comparison of the before/after prefetching is on YouTube.

For demo purposes, we deployed a version of the [Google Blog](https://blog.google) on
Firebase hosting. We then deployed another version of it, adding quicklink to the homepage and benchmarked navigating from the homepage to an article that was
automatically prefetched. The prefetched version loaded faster.

Please note: this is by no means an exhaustive benchmark of the pros and cons of in-viewport link prefetching. Just a demo of the potential improvements the approach can offer. Your own mileage may heavily vary.

## Additional notes

### Session Stitching

Cross-origin prefetching (e.g `a.com/foo.html` prefetches `b.com/bar.html`) has a number of limitations. One such limitation is with session-stitching. `b.com` may expect `a.com`'s navigation requests to include session information (e.g a temporary ID - e.g `b.com/bar.html?hash=<>&timestamp=<>`), where this information is used to customize the experience or log information to analytics. If session-stitching requires a timestamp in the URL, what is prefetched and stored in the HTTP cache may not be the same as the one the user ultimately navigates to. This introduces a challenge as it can result in double prefetches.

To workaround this problem, you can consider passing along session information via the [ping attribute](https://caniuse.com/ping) (separately) so the origin can stitch a session together asynchronously.

### Ad-related considerations

Sites that rely on ads as a source of monetization should not prefetch ad-links, to avoid unintentionally counting clicks against those ad placements, which can lead to inflated Ad CTR (click-through-rate).

Ads appear on sites mostly in two ways:

- **Inside iframes:** By default, most ad-servers render ads within iframes. In these cases, those ad-links won't be prefetched by Quicklink, unless a developer explicitly passes in the URL of an ads iframe. The reason is that the library look-up for in-viewport elements is restricted to those of the top-level origin.

- **Outside iframes:**: In cases when the site shows same-origin ads, displayed in the top-level document (e.g. by hosting the ads themselves and by displaying the ads in the page directly), the developer needs to explicitly tell Quicklink to avoid prefetching these links. This can be achieved by passing the URL or subpath of the ad-link, or the element containing it to the [custom ignore patterns list](#custom-ignore-patterns).

## Related projects

- Using [Gatsby](https://gatsbyjs.org)? You already get most of this for free baked in. It uses `Intersection Observer` to prefetch all of the links that are in view and provided heavy inspiration for this project.
- Want a more data-driven approach? See [Guess.js](https://guess-js.github.io). It uses analytics and machine-learning to prefetch resources based on how users navigate your site. It also has plugins for [Webpack](https://www.npmjs.com/package/guess-webpack) and [Gatsby](https://www.gatsbyjs.org/docs/optimizing-site-performance-with-guessjs/).
- WordPress users can now get quicklink as a [WordPress Plugin from the plugin repository](https://wordpress.org/plugins/quicklink/).
- Drupal users can install the [Quicklink Drupal module](https://www.drupal.org/project/quicklink).
- Magento 2 users can install the [rafaelcg-magento2-quicklink](https://marketplace.magento.com/rafaelcg-magento2-quicklink.html) or [rangerz/magento2-module-quicklink](https://github.com/rangerz/magento2-module-quicklink).
- Want less aggressive prefetching? [instant.page](https://instant.page/) prefetches on mouseover and touchstart, right before a click.

## License

Licensed under the [Apache-2.0 license](LICENSE).


================================================
FILE: demos/basic.html
================================================
<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>Basic demo</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="../test/fixtures/main.css">
</head>

<body>
  <div class="screen">
    <h1>Basic demo</h1>
    <a href="../test/fixtures/1.html">Link 1</a>
    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
    tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
    quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
    consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
    cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
    proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
    <a href="../test/fixtures/2.html">Link 2</a>
  </div>
  <div class="screen">
    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eos, quos?
    <a href="../test/fixtures/3.html">Link 3</a>
    <section id="stuff">
      <a href="../test/fixtures/main.css">CSS</a>
    </section>
  </div>
  <div class="screen">
    <a href="../test/fixtures/4.html">Link 4</a>
  </div>
  <script src="../dist/quicklink.umd.js"></script>
  <script>
    quicklink.listen();
  </script>
</body>

</html>


================================================
FILE: demos/hrefFn/2.html
================================================
<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>Prefetch experiments</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="main.css">
</head>

<body>
  <h1>Page 2</h1>
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Soluta est sint assumenda corrupti minima aut, magnam
    totam beatae ullam ea iste voluptatum iusto expedita animi rem vitae rerum atque nemo!</p>
</body>

</html>


================================================
FILE: demos/hrefFn/2.json
================================================
{
  "title": "API Target to Prefetch Example",
  "description": "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Soluta est sint assumenda corrupti minima aut, magnam totam beatae ullam ea iste voluptatum iusto expedita animi rem vitae rerum atque nemo!"
}


================================================
FILE: demos/hrefFn/hrefFn_demo.html
================================================
<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>Basic demo</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="../../test/fixtures/main.css">
</head>

<body>
  <div class="screen">
    <h1>Basic demo</h1>
    <p>
      <a href="2.html">Link 1</a>
      Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
      tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
      quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
      consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
      cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
      proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
    </p>
  </div>
  <script src="../../dist/quicklink.umd.js"></script>
  <script>
    quicklink.listen({
      el: document.querySelector('div.screen'),
      // hrefFn will prevent quicklink to prefetch the "href" attribute URL
      // and the returned value will be prefetched as a result.
      hrefFn(element) {
        return element.href.replace('html', 'json');
      }
    });
  </script>

  <script>
    // Simple SPA-like page transition for demo purposes
    const container = document.querySelector('div.screen');
    const link = container.querySelector('a');
    const title = container.querySelector('h1');
    const paragraph = container.querySelector('p');

    link.addEventListener('click', event => {
      event.preventDefault();
      event.stopPropagation();
      const href = event.target.href.replace('html', 'json');
      fetch(href)
        .then(response => response.json())
        .then(data => {
          title.innerHTML = document.title = data.title;
          paragraph.innerHTML = data.description;
        });
    });
  </script>
</body>

</html>


================================================
FILE: demos/network-idle.html
================================================
<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>network-idle-callback demo</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="../test/fixtures/main.css">
  <script>
    if ('serviceWorker' in navigator) {
      window.addEventListener('load', () => {
        navigator.serviceWorker.register('./sw.js').then((registration) => {
          console.log('ServiceWorker registration successful with scope: ', registration.scope);
        }, (error) => {
          console.log('ServiceWorker registration failed: ', error);
        });
      });
    }
  </script>
</head>

<body>
  <p>This demo uses <a href="https://github.com/pastelsky/network-idle-callback">network-idle-callback</a> to only
    prefetch when network activity goes idle in the current tab.</p>
  <a href="../test/fixtures/1.html">Link 1</a>
  <a href="../test/fixtures/2.html">Link 2</a>
  <a href="../test/fixtures/3.html">Link 3</a>
  <section id="stuff">
    <a href="../test/fixtures/main.css">CSS</a>
  </section>
  <a href="../test/fixtures/4.html" style="position:absolute;margin-top:900px;">Link 4</a>
  <script src="network-idle.js"></script>
  <script type="module">
    import {listen} from "../dist/quicklink.mjs";
    listen({timeoutFn: networkIdleCallback});
  </script>
</body>

</html>


================================================
FILE: demos/network-idle.js
================================================
// This script is a localized version of the upstream
// https://github.com/pastelsky/network-idle-callback
// which fixes issues with browser importing of the
// above dependency. It is hopefully temporary.

'use strict';

const DOMContentLoad = new Promise(resolve => {
  document.addEventListener('DOMContentLoaded', resolve);
});

navigator.serviceWorker.getRegistration()
    .then(registration => {
      if (!registration) {
        console.warn('`networkIdleCallback` was called before a service worker was registered.');
        console.warn('`networkIdleCallback` is ineffective without a working service worker');
      }
    });

/**
 * networkIdleCallback works similar to requestIdleCallback,
 * detecting and notifying you when network activity goes idle
 * in your current tab.
 * @param {*} fn - A valid function
 * @param {*} options - An options object
 */
function networkIdleCallback(fn, options = {timeout: 0}) {
  // Call the function immediately if required features are absent
  if (
    !('MessageChannel' in window) ||
    !('serviceWorker' in navigator) ||
    !navigator.serviceWorker.controller
  ) {
    DOMContentLoad.then(() => fn({didTimeout: false}));
    return;
  }

  const messageChannel = new MessageChannel();
  navigator.serviceWorker.controller
      .postMessage(
          'NETWORK_IDLE_ENQUIRY',
          [messageChannel.port2],
      );

  const timeoutId = setTimeout(() => {
    const cbToPop = networkIdleCallback.__callbacks__
        .find(cb => cb.id === timeoutId);

    networkIdleCallback.__popCallback__(cbToPop, true);
  }, options.timeout);

  networkIdleCallback.__callbacks__.push({
    id: timeoutId,
    fn,
    timeout: options.timeout,
  });

  messageChannel.port1.addEventListener('message', handleMessage);
  messageChannel.port1.start();
}

/*
function cancelNetworkIdleCallback(callbackId) {
  clearTimeout(callbackId);

  networkIdleCallback.__callbacks__ = networkIdleCallback.__callbacks__
      .find(cb => cb.id === callbackId);
}
*/

networkIdleCallback.__popCallback__ = (callback, didTimeout) => {
  DOMContentLoad.then(() => {
    const cbToPop = networkIdleCallback.__callbacks__
        .find(cb => cb.id === callback.id);

    if (cbToPop) {
      cbToPop.fn({didTimeout});
      clearTimeout(cbToPop.id);
      networkIdleCallback.__callbacks__ = networkIdleCallback.__callbacks__.filter(
          cb => cb.id !== callback.id);
    }
  });
};

networkIdleCallback.__callbacks__ = [];

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.addEventListener('message', handleMessage);
}

/**
 * Handle message passing
 * @param {*} event - A valid event
 */
function handleMessage(event) {
  if (!event.data) {
    return;
  }

  switch (event.data) {
    case 'NETWORK_IDLE_ENQUIRY_RESULT_IDLE':
    case 'NETWORK_IDLE_CALLBACK':
      networkIdleCallback.__callbacks__.forEach(callback => {
        networkIdleCallback.__popCallback__(callback, false);
      });
      break;
  }
}


================================================
FILE: demos/news/README.md
================================================
# Demo: Quicklink basic usage

A demo showing how to use Quicklink on a simple multi-page website.

## Glitch Source

- [Link to Glitch App](https://anton-karlovskiy-quicklink-news.glitch.me)
- [Link to Project on Glitch](https://glitch.com/~anton-karlovskiy-quicklink-news)

## Installation

```sh
git clone https://api.glitch.com/git/anton-karlovskiy-quicklink-news
npm install
npm start
npm run build
```


================================================
FILE: demos/news-workbox/README.md
================================================
# Demo: Quicklink usage with workbox

A demo showing how to use Quicklink with Workbox for offline caching and links in the visible viewport.

## Glitch Source

- [Link to Glitch App](https://anton-karlovskiy-quicklink-news-workbox.glitch.me)
- [Link to Project on Glitch](https://glitch.com/~anton-karlovskiy-quicklink-news-workbox)

## Installation

```sh
git clone https://api.glitch.com/git/anton-karlovskiy-quicklink-news-workbox
npm install
npm start
npm run build
```


================================================
FILE: demos/spa/README.md
================================================
# Demo: Quicklink integration for create-react-app

A demo showing how to use Quicklink with in a create-react-app site.
To integrate your React SPA with Quicklink, follow the steps [here](https://github.com/GoogleChromeLabs/quicklink#single-page-apps-react).

## Glitch Source

- [Link to Glitch App](https://create-react-app-quicklink.glitch.me/)
- [Link to Project on Glitch](https://glitch.com/~create-react-app-quicklink)

## Installation

```sh
git clone https://api.glitch.com/git/create-react-app-quicklink
npm install
npm start
npm run build
```


================================================
FILE: demos/sw.js
================================================
/* eslint-env serviceworker */

'use strict';

importScripts('https://unpkg.com/network-idle-callback@1.0.1/lib/request-monitor.js');

self.addEventListener('install', event => {
  console.log('[ServiceWorker] Installed');
});

self.addEventListener('activate', event => {
  console.log('[ServiceWorker] Activated');
});

self.addEventListener('fetch', event => {
  console.log('[ServiceWorker] Fetch', event.request.url);
  self.requestMonitor.listen(event);

  const promise = fetch(event.request)
      .then(response => {
        console.log('done', event.clientId);
        self.requestMonitor.unlisten(event);
        return response;
      })
      .catch(error => {
        console.log('error');
        self.requestMonitor.unlisten(error);
      });

  event.respondWith(promise);
});


================================================
FILE: package.json
================================================
{
  "name": "quicklink",
  "version": "3.0.1",
  "description": "Faster subsequent page-loads by prefetching in-viewport links during idle time",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/GoogleChromeLabs/quicklink.git"
  },
  "homepage": "https://getquick.link/",
  "bugs": {
    "url": "https://github.com/GoogleChromeLabs/quicklink/issues"
  },
  "author": "addyosmani <addyosmani@gmail.com>",
  "license": "Apache-2.0",
  "main": "dist/quicklink.js",
  "module": "dist/quicklink.mjs",
  "umd:main": "dist/quicklink.umd.js",
  "unpkg": "dist/quicklink.umd.js",
  "files": [
    "dist"
  ],
  "scripts": {
    "lint": "eslint --report-unused-disable-directives --ext .js,.mjs .",
    "lint-fix": "npm run lint -- --fix",
    "fix": "npm run lint -- --fix",
    "start": "sirv --dev --no-clear --no-logs --host 127.0.0.1 --port 8080",
    "uvu": "uvu test",
    "test": "npm-run-all build-all --parallel --race start uvu",
    "build": "microbundle src/index.mjs --no-sourcemap --external none",
    "build-plugin": "microbundle src/chunks.mjs --no-sourcemap --external none -o dist/react",
    "build-react-chunks": "babel src/react-chunks.js --out-file dist/react/hoc.js",
    "build-all": "npm-run-all --parallel build build-plugin build-react-chunks",
    "prepublishonly": "npm run build-all",
    "size": "size-limit",
    "changelog": "npm run conventional-changelog -i CHANGELOG.md -s -r 0",
    "release": "cross-env-shell \"npm run build-all && git commit -am $npm_package_version && git tag $npm_package_version && git push --follow-tags\""
  },
  "keywords": [
    "prefetch",
    "performance",
    "fetch",
    "intersectionobserver",
    "background",
    "speed"
  ],
  "dependencies": {
    "route-manifest": "^1.0.0",
    "throttles": "^1.0.1"
  },
  "peerDependencies": {
    "react": "^16.8.0 || ^17 || ^18 || ^19",
    "react-dom": "^16.8.0 || ^17 || ^18 || ^19"
  },
  "peerDependenciesMeta": {
    "react": {
      "optional": true
    },
    "react-dom": {
      "optional": true
    }
  },
  "devDependencies": {
    "@babel/cli": "^7.28.6",
    "@babel/core": "^7.29.0",
    "@babel/preset-env": "^7.29.0",
    "@babel/preset-react": "^7.28.5",
    "@size-limit/file": "^12.0.0",
    "conventional-changelog": "^7.1.1",
    "cross-env": "^10.1.0",
    "eslint": "^8.57.1",
    "eslint-config-google": "^0.14.0",
    "eslint-plugin-react": "^7.37.5",
    "microbundle": "^0.15.1",
    "npm-run-all2": "^8.0.4",
    "puppeteer": "^24.37.2",
    "react": "^19.2.4",
    "react-dom": "^19.2.4",
    "sirv-cli": "^3.0.1",
    "size-limit": "^12.0.0",
    "uvu": "^0.5.6"
  }
}


================================================
FILE: site/.browserslistrc
================================================
# https://github.com/browserslist/browserslist#readme

defaults


================================================
FILE: site/.config/configstore/update-notifier-pnpm.json
================================================
{
  "optOut": false,
  "lastUpdateCheck": 1574808403652,
  "update": {
    "latest": "4.3.3",
    "current": "2.25.5",
    "type": "major",
    "name": "pnpm"
  }
}


================================================
FILE: site/.config/glitch-package-manager
================================================
pnpm


================================================
FILE: site/.eleventy.js
================================================
/* eslint-env node */

/* eslint-disable new-cap */

'use strict';

const {EleventyHtmlBasePlugin: htmlBasePlugin} = require('@11ty/eleventy');
const navigationPlugin = require('@11ty/eleventy-navigation');
const syntaxHighlight = require('@11ty/eleventy-plugin-syntaxhighlight');
const autoprefixer = require('autoprefixer');
const htmlminifier = require('html-minifier-terser');
const markdownIt = require('markdown-it');
const pluginRev = require('eleventy-plugin-rev');
const postcss = require('postcss');
const sass = require('eleventy-sass');

const IS_PRODUCTION = process.env.NODE_ENV === 'production';

const htmlminifierConfig = {
  collapseBooleanAttributes: true,
  collapseWhitespace: true,
  conservativeCollapse: false,
  decodeEntities: false,
  minifyCSS: true,
  minifyJS: true,
  minifyURLs: false,
  removeAttributeQuotes: true,
  removeComments: true,
  removeEmptyAttributes: false,
  removeOptionalAttributes: true,
  removeOptionalTags: true,
  removeRedundantAttributes: true,
  removeScriptTypeAttributes: true,
  removeStyleLinkTypeAttributes: true,
  removeTagWhitespace: false,
  sortAttributes: true,
  sortClassName: true,
};

module.exports = eleventyConfig => {
  eleventyConfig.addPlugin(htmlBasePlugin, {baseHref: '/'});
  eleventyConfig.addPlugin(navigationPlugin);
  eleventyConfig.addPlugin(syntaxHighlight);
  eleventyConfig.addPlugin(pluginRev);
  eleventyConfig.addPlugin(sass, [
    {
      postcss: postcss([autoprefixer]),
      sass: {
        style: 'expanded',
        sourceMap: true,
      },
      rev: false,
    },
    {
      sass: {
        style: 'compressed',
        sourceMap: false,
      },
      rev: true,
      when: [{NODE_ENV: 'production'}],
    },
  ]);

  eleventyConfig.addPassthroughCopy('src/assets/images');
  eleventyConfig.addPassthroughCopy('src/assets/js');
  eleventyConfig.addPassthroughCopy('src/site.webmanifest');

  eleventyConfig.addNunjucksFilter('markdown', string => {
    const md = new markdownIt();
    return md.render(string);
  });

  eleventyConfig.addPairedShortcode('markdownConvert', content => {
    const md = new markdownIt();
    return md.render(content);
  });

  eleventyConfig.addNunjucksShortcode('sectionTitle', title => {
    const md = new markdownIt();
    return md.render(`## ${title}`);
  });

  eleventyConfig.addTransform('htmlminifier', (content, outputPath) => {
    if (!outputPath.endsWith('.html')) return content;
    if (!IS_PRODUCTION) return content;

    return htmlminifier.minify(content, htmlminifierConfig);
  });

  return {
    dir: {
      input: 'src',
      output: 'build',
    },
  };
};


================================================
FILE: site/.firebaserc
================================================
{
  "projects": {
    "default": "quicklink-6a87b"
  }
}


================================================
FILE: site/.gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/

# Dependency directories
node_modules/

/_datacache


================================================
FILE: site/.stylelintignore
================================================
**/*.min.css
**/*.min.scss
**/dist/
**/vendor/
/build/


================================================
FILE: site/.stylelintrc.json
================================================
{
  "extends": [
    "stylelint-config-twbs-bootstrap"
  ],
  "reportInvalidScopeDisables": true,
  "reportNeedlessDisables": true,
  "rules": {
    "order/properties-order": null,
    "selector-class-pattern": null,
    "selector-no-qualifying-type": null
  },
  "overrides": [
    {
      "files": "**/*.scss",
      "rules": {
        "scss/selector-no-union-class-name": true
      }
    }
  ]
}


================================================
FILE: site/LICENSE
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: site/README.md
================================================
# eleventy-quicklink-website

Our canonical site source for Quicklink. This project uses [Eleventy](https://www.11ty.io/) as a static site generator. Templating uses [Nunjucks](https://mozilla.github.io/nunjucks/).

## Installation

```sh
git clone git@github.com:googlechromelabs/quicklink.git
cd site
npm install
```

## Commands

| Command         | Description                                                   |
| --------------- | ------------------------------------------------------------- |
| `npm start`     | Start a development server and watch for updates              |
| `npm run build` | Build templates, data, CSS, and JS for production environment |


================================================
FILE: site/firebase.json
================================================
{
  "hosting": {
    "public": "build",
    "site": "getquicklink",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "headers": [
      {
        "source": "/service-worker.js",
        "headers": [
          {
            "key": "Cache-Control",
            "value": "no-cache"
          }
        ]
      },
      {
        "source": "**/*.@(js)",
        "headers": [
          {
            "key": "Cache-Control",
            "value": "max-age=31536000"
          }
        ]
      },
      {
        "source": "**/*.@(css)",
        "headers": [
          {
            "key": "Cache-Control",
            "value": "max-age=31536000"
          }
        ]
      },
      {
        "source": "**/*.@(eot|otf|ttf|ttc|woff)",
        "headers": [
          {
            "key": "Access-Control-Allow-Origin",
            "value": "*"
          }
        ]
      },
      {
        "source": "**/*.@(jpg|jpeg|gif|png|svg)",
        "headers": [
          {
            "key": "Cache-Control",
            "value": "max-age=604800"
          }
        ]
      },
      {
        "source": "404.html",
        "headers": [
          {
            "key": "Cache-Control",
            "value": "max-age=300"
          }
        ]
      }
    ]
  }
}


================================================
FILE: site/index.njk
================================================
---
title: Quicklink
layout: layouts/base.njk
description: Faster subsequent page-loads by prefetching in-viewport links during idle time.
---

{% include "components/heading.njk" %}

{% include "components/why-quicklink.njk" %}

{% include "components/copy-snippet.njk" %}

{% include "components/download.njk" %}

{% include "components/trusted-by.njk" %}

{% include "components/installation.njk" %}

{% include "components/usage.njk" %}

{% include "components/over-prefetching.njk" %}

{% include "components/react.njk" %}

{% include "components/chrome-extension.njk" %}

{% include "components/why-prefetch.njk" %}

{% include "components/use-with.njk" %}


================================================
FILE: site/package.json
================================================
{
  "name": "eleventy-quicklink-website",
  "description": "",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "build": "rimraf build && cross-env NODE_ENV=production eleventy",
    "start": "rimraf build && cross-env NODE_ENV=development eleventy --serve",
    "deploy": "firebase deploy --project=quicklink-6a87b",
    "lint": "stylelint src/assets/styles",
    "test": "npm run lint && npm run build"
  },
  "license": "Apache-2.0",
  "devDependencies": {
    "@11ty/eleventy": "^2.0.1",
    "@11ty/eleventy-navigation": "^1.0.5",
    "@11ty/eleventy-plugin-syntaxhighlight": "^5.0.2",
    "autoprefixer": "^10.4.24",
    "cross-env": "^10.1.0",
    "eleventy-plugin-rev": "^2.0.0",
    "eleventy-sass": "^2.2.6",
    "html-minifier-terser": "^7.2.0",
    "markdown-it": "^14.1.0",
    "postcss": "^8.5.6",
    "rimraf": "^6.1.2",
    "stylelint": "^16.26.1",
    "stylelint-config-twbs-bootstrap": "^16.1.0"
  }
}


================================================
FILE: site/src/_data/site.js
================================================
/* eslint-env node */

'use strict';

const path = require('node:path');
const process = require('node:process');

const IS_NETLIFY = process.env.NETLIFY === 'true';
const {version: quicklinkVersion} = require(path.join(__dirname, '../../../package.json'));

module.exports = () => {
  return {
    title: 'Quicklink',
    subtitle: 'Instant next-page navigations',
    description: 'Faster subsequent page-loads by prefetching in-viewport links during idle time.',
    socialImage: '/assets/images/og-image.png',
    // If we are on Netlify, use the `DEPLOY_PRIME_URL` environment variable
    url: IS_NETLIFY ? process.env.DEPLOY_PRIME_URL : 'https://getquick.link',
    isNetlify: IS_NETLIFY,
    quicklinkGithubURL: 'https://github.com/GoogleChromeLabs/quicklink',
    quicklinkVersion,
    quicklinkSizeLimit: '1KB',
    bottomResource: {
      caption: 'View source on GitHub',
    },
    useWithFrameworks: [
      {
        title: 'wordpress',
        logoFileName: 'wordpress.svg',
        url: 'https://wordpress.org/plugins/quicklink/',
      },
      {
        title: 'drupal',
        logoFileName: 'drupal.svg',
        url: 'https://www.drupal.org/project/quicklink/',
      },
      {
        title: 'magento',
        logoFileName: 'magento.svg',
        url: 'https://marketplace.magento.com/rafaelcg-magento2-quicklink.html',
      },
      {
        title: 'react',
        logoFileName: 'react.svg',
        url: 'https://github.com/HOUCe/react-quicklink-component/',
      },
      {
        title: 'angular',
        logoFileName: 'angular.svg',
        url: 'https://github.com/mgechev/ngx-quicklink/',
      },
      {
        title: 'vue',
        logoFileName: 'vue.svg',
        url: 'https://nuxtjs.org/api/components-nuxt-link/',
      },
    ],
    trustedByLogos: [
      {
        websiteUrl: 'https://www.ray-ban.com/',
        logoFileName: 'rayban.com.png',
        companyName: 'Ray-Ban',
      },
      {
        websiteUrl: 'https://www.oakley.com/',
        logoFileName: 'oakley.com.png',
        companyName: 'Oakley',
      },
      {
        websiteUrl: 'https://www.syfy.com/',
        logoFileName: 'syfy.com.png',
        companyName: 'SYFY WIRE',
      },
      {
        websiteUrl: 'https://www.newegg.com/',
        logoFileName: 'newegg.com.png',
        companyName: 'Newegg',
      },
      {
        websiteUrl: 'https://www.barefootwine.ca/',
        logoFileName: 'barefootwine.ca.png',
        companyName: 'BAREFOOT',
      },
      {
        websiteUrl: 'https://hashnode.com/',
        logoFileName: 'hashnode.com.png',
        companyName: 'Hashnode',
      },
      {
        websiteUrl: 'https://www.hartfordwines.com/',
        logoFileName: 'hartfordwines.com.png',
        companyName: 'HARTFORD',
      },
      {
        websiteUrl: 'https://vinyla.com/',
        logoFileName: 'vinyla.com.png',
        companyName: 'Vinyla',
      },
      {
        websiteUrl: 'https://www.matsuda.com/',
        logoFileName: 'matsuda.com.png',
        companyName: 'MATSUDA',
      },
      {
        websiteUrl: 'https://paulrand.design/',
        logoFileName: 'paulrand.design.png',
        companyName: 'Paul Rand',
      },
      {
        websiteUrl: 'http://www.week.co.jp/',
        logoFileName: 'week.co.jp.png',
        companyName: 'Komachi',
      },
      {
        websiteUrl: 'https://www.quiply.com/',
        logoFileName: 'quiply.com.png',
        companyName: 'Quiply',
      },
      {
        websiteUrl: 'https://saintagnes.org/',
        logoFileName: 'saintagnes.org.png',
        companyName: 'St Agnes',
      },
    ],
  };
};


================================================
FILE: site/src/_includes/components/chrome-extension.njk
================================================
{% extends "layouts/normal-section-wrapper.njk" %}
{% block section %}
{% sectionTitle "Chrome Extension" %}
{{ "We've developed a [Chrome extension](https://chrome.google.com/webstore/detail/quicklink-chrome-extensio/epmplkdcjhgigmnjmjibilpmekhgkbeg) that injects and initializes `Quicklink` in every site you visit." | markdown | safe }}
{{ "The extension can be used with the following purposes:" | markdown | safe }}

{{ "* To navigate the web faster." | markdown | safe }}
{{ "* To estimate the potential impact of `Quicklink` on a site, before implementing it (see [impact measurement guide](/measure))." | markdown | safe }}

{{ "The extension comes with a default set of URL patterns [to ignore](https://github.com/GoogleChromeLabs/quicklink#optionsignores) (e.g. signin, logout, etc). You can add more patterns, by clicking on the extension icon, and picking 'Options' from the drop-down menu." | markdown | safe }}
{{ "The code of the extension can be found at [this repository](https://github.com/demianrenzulli/quicklink-chrome-extension). Contributions are welcomed!" | markdown | safe }}

{% endblock %}

================================================
FILE: site/src/_includes/components/copy-snippet.njk
================================================
{% extends "layouts/normal-section-wrapper.njk" %}
{% block section %}
<div class="copy-snippet-widget">
  <div class="tertiary-font-color">
    <strong>Place this snippet in your head or just before your close body tag:</strong>
  </div>

  <div class="snippet-for-copy">
  {% highlight "html" %}
<script defer src="https://cdn.jsdelivr.net/npm/quicklink@{{ site.quicklinkVersion }}/dist/quicklink.umd.js"></script>
<script>
  window.addEventListener('load', () => {
    quicklink.listen();
  });
</script>
  {% endhighlight %}
  </div>

  <div>
    <button id="copy-snippet-button" type="button" class="button button--copy-snippet" data-clipboard-target=".snippet-for-copy">Copy snippet</button>
  </div>

  <div class="primary-font-color notify-copied-snippet">
    <strong>Copied. Now place it just before &lt;/body&gt; on your pages.</strong>
  </div>
</div>
{% endblock %}


================================================
FILE: site/src/_includes/components/download.njk
================================================
{% extends "layouts/normal-section-wrapper.njk" %}
{% block section %}
{% sectionTitle "Download" %}
<ul class="no-bullet">
  <li class="list-icon">
    {% include "layouts/font-icons/download-solid.svg" %}
    <a href="https://cdn.jsdelivr.net/npm/quicklink@{{ site.quicklinkVersion }}/dist/quicklink.umd.js" target="_blank" rel="noopener noreferrer">Build (&lt; {{ site.quicklinkSizeLimit }} minified/gzipped)</a>
  </li>
  <li class="list-icon">
    {% include "layouts/font-icons/chain-solid.svg" %}
    <a href="https://www.jsdelivr.com/package/npm/quicklink" target="_blank" rel="noopener noreferrer">CDN copies</a>
  </li>
</ul>
{% endblock %}


================================================
FILE: site/src/_includes/components/github-fork.njk
================================================
<a class="github-button" href="{{ site.quicklinkGithubURL + '/fork' }}"{% if githubLarge === true %} data-size="large"{% endif %} data-icon="octicon-repo-forked" data-show-count="true" aria-label="Fork GoogleChromeLabs/quicklink on GitHub">Fork</a>


================================================
FILE: site/src/_includes/components/github-star.njk
================================================
<a class="github-button" href="{{ site.quicklinkGithubURL }}"{% if githubLarge === true %} data-size="large"{% endif %} data-icon="octicon-star" data-show-count="true" aria-label="Star GoogleChromeLabs/quicklink on GitHub">Star</a>


================================================
FILE: site/src/_includes/components/heading.njk
================================================
{% extends "layouts/highlighted-section-wrapper.njk" %}
{% block section %}
<h1 class="heading text-center">
"We implemented Quicklink and saw a <em class="secondary-font-color">50%</em> increase in conversions and <em class="secondary-font-color">4x</em> faster page transitions" - NewEgg
</h1>
{% endblock %}


================================================
FILE: site/src/_includes/components/installation.njk
================================================
{% extends "layouts/normal-section-wrapper.njk" %}
{% block section %}
{% sectionTitle "Installation" %}
{{ "For use with [Node.js](https://nodejs.org/) and [npm](https://www.npmjs.com/):" | markdown | safe }}
{% highlight "bash" %}
npm install quicklink
{% endhighlight %}
{{ "You can also grab `quicklink` from [unpkg.com/quicklink](https://unpkg.com/quicklink)." | markdown | safe }}
{% endblock %}


================================================
FILE: site/src/_includes/components/over-prefetching.njk
================================================
{% extends "layouts/normal-section-wrapper.njk" %}
{% block section %}
{% sectionTitle "Concerned about over-prefetching? We've got you covered" %}
{{ "By default `quicklink` observes all in-viewport links in `document.body`. There are different ways of telling `quicklink` to limit the number of links to prefetch." | markdown | safe }}

{{ "The most common approach is passing different `options` to configure prefetching when calling `quicklink.listen()`:" | markdown | safe }}

{{ "* Indicating a specific DOM element to observe, with the `options.el` parameter:" | markdown | safe }}

{% highlight "js" %}
quicklink.listen({
  el: document.getElementById('content')
});
{% endhighlight %}

{{ "* Passing an `options.limit` parameter, indicating the total number of requests that can be prefetched while observing the `options.el` container:" | markdown | safe }}

{% highlight "js" %}
quicklink.listen({
  limit: 5
});
{% endhighlight %}

{{ "* Using `options.throttle`, to establish a concurrency limit for simultaneous requests while observing the `options.el` container:" | markdown | safe }}

{% highlight "js" %}
quicklink.listen({
  throttle: 2
});
{% endhighlight %}

{{ "If none of these configuration options suits your needs, you can call `quicklink.prefetch()`, passing a single URL or an array of URLs to prefetch. Invoking `quicklink` this way, bypasses the `Intersection Observer` logic, giving you full control on the prefetch requests to be made:" | markdown | safe }}

{% highlight "js" %}
quicklink.prefetch(['2.html', '3.html', '4.js']);
{% endhighlight %}

{% endblock %}

================================================
FILE: site/src/_includes/components/react.njk
================================================
{% extends "layouts/normal-section-wrapper.njk" %}
{% block section %}
{% sectionTitle "Single page apps (React)" %}
{% markdownConvert %}

### Installation

First, install the packages with [Node.js](https://nodejs.org/) and [npm](https://www.npmjs.com/):
{% endmarkdownConvert %}

{% highlight "bash" %}
npm install quicklink webpack-route-manifest --save-dev
{% endhighlight %}

{% markdownConvert %}
Then, configure Webpack route manifest into your project, as explained [here](https://github.com/lukeed/webpack-route-manifest).
This will generate a map of routes and chunks called `rmanifest.json`. It can be obtained at:

* URL: `site_url/rmanifest.json`
* Window object: `window.__rmanifest`

### Usage

Import `quicklink` React HOC where want to add prefetching functionality.
Wrap your routes with the `withQuicklink()` HOC.

Example:
{% endmarkdownConvert %}

{% highlight "jsx" %}
import {withQuicklink} from 'quicklink/dist/react/hoc.js';

const options = {
  origins: [],
};

<Suspense fallback={<div>Loading...</div>}>
  <Route path='/' exact component={withQuicklink(Home, options)} />
  <Route path='/blog' exact component={withQuicklink(Blog, options)} />
  <Route path='/blog/:title' component={withQuicklink(Article, options)} />
  <Route path='/about' exact component={withQuicklink(About, options)} />
</Suspense>;
{% endhighlight %}

{% endblock %}


================================================
FILE: site/src/_includes/components/trusted-by.njk
================================================
{% extends "layouts/normal-section-wrapper.njk" %}
{% block section %}
{% sectionTitle "Trusted by" %}
<div class="flex-grid overflow-x-auto trusted-by">
  {%- for trustedByLogo in site.trustedByLogos %}
    <a class="flex-grid__item text-center" href="{{ trustedByLogo.websiteUrl }}" target="_blank" rel="noopener noreferrer">
      {# TODO: responsive sizing like "used-with" logos #}
      <img loading="lazy" src="/assets/images/quicklink-used-logos/{{ trustedByLogo.logoFileName }}" width="92" height="92" alt="">
      <span class="text-center"><strong>{{ trustedByLogo.companyName }}</strong></span>
    </a>
  {%- endfor %}
</div>
{% endblock %}


================================================
FILE: site/src/_includes/components/usage.njk
================================================
{% extends "layouts/normal-section-wrapper.njk" %}
{% block section %}
{% sectionTitle "Usage" %}
{{ "Once initialized, `quicklink` will automatically prefetch URLs for links that are in-viewport during idle time." | markdown | safe }}

{{ "Quickstart:" | markdown | safe }}

{% highlight "html" %}
<!-- Include quicklink from dist -->
<script defer src="dist/quicklink.umd.js"></script>
<!-- Initialize (you can do this whenever you want) -->
<script>
  quicklink.listen();
</script>
{% endhighlight %}

{{ "For example, you can initialize after the `load` event fires:" | markdown | safe }}
{% highlight "html" %}
<script>
  window.addEventListener('load', () => {
    quicklink.listen();
  });
</script>
{% endhighlight %}

{{ "ES Module import:" | markdown | safe }}
{% highlight "js" %}
import {listen} from 'quicklink/dist/quicklink.mjs';
listen();
{% endhighlight %}

{{ "The above options are best for multi-page sites. Single-page apps have a few options available for using quicklink with a router:" | markdown | safe }}
{{ "* Call `quicklink.listen()` once a navigation to a new route has completed" | markdown | safe }}
{{ "* Call `quicklink.listen()` against a specific DOM element / component" | markdown | safe }}
{{ "* Call `quicklink.prefetch()` with a custom set of URLs to prefetch" | markdown | safe }}
{% endblock %}


================================================
FILE: site/src/_includes/components/use-with.njk
================================================
{% extends "layouts/normal-section-wrapper.njk" %}
{% block section %}
{% sectionTitle "Use with" %}
<div class="center">
  <div class="flex-grid overflow-x-auto use-with">
    {%- for framework in site.useWithFrameworks %}
      <a class="flex-grid__item" href="{{ framework.url }}" target="_blank" rel="noopener noreferrer">
        <img loading="lazy" src="/assets/images/logos/{{ framework.logoFileName }}" alt="{{ framework.title }}" width="112" height="112">
      </a>
    {%- endfor %}
  </div>
</div>
{% endblock %}


================================================
FILE: site/src/_includes/components/why-prefetch.njk
================================================
{% extends "layouts/normal-section-wrapper.njk" %}
{% block section %}
{% sectionTitle "Why Quicklink\'s prefetch?" %}

<div class="center">
  <img loading="lazy" src="/assets/images/graphs/prefetch-improve-svgomg.svg" alt="Prefetch pages a user may need in the future to improve subsequent page loads" width="932" height="535">
</div>
{% endblock %}


================================================
FILE: site/src/_includes/components/why-quicklink.njk
================================================
{% extends "layouts/normal-section-wrapper.njk" %}
{% block section %}
<h2 class="flex-between-center">
<span>Why Quicklink?</span>

<span class="small-github">
{% set githubLarge = false %}
{% include "components/github-star.njk" %}
{% include "components/github-fork.njk" %}
</span>

<span class="large-github">
{% set githubLarge = true %}
{% include "components/github-star.njk" %}
{% include "components/github-fork.njk" %}
</span>
</h2>
{{ "This project aims to be a drop-in solution for sites to prefetch links based on what is in the user's viewport. It also aims to be small (**< 1KB minified/gzipped**)." | markdown | safe }}
{% endblock %}


================================================
FILE: site/src/_includes/layouts/base.njk
================================================
<!doctype html>
<html lang="en">
{% include "layouts/head.njk" %}

<body>
  {% include "layouts/header.njk" %}
  <main class="page-main markdown-body">
    {{ content | safe }}
    {% include "layouts/footer.njk" %}
    <div class="back-to-top hidden">&uarr;</div>
  </main>

  <script defer src="https://cdn.jsdelivr.net/npm/quicklink@{{ site.quicklinkVersion }}/dist/quicklink.umd.js"></script>
  <script>
    window.addEventListener('load', () => {
      quicklink.listen();
    });
  </script>
  <script defer src="https://buttons.github.io/buttons.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/clipboard@2.0.11/dist/clipboard.min.js" integrity="sha256-4XodgW4TwIJuDtf+v6vDJ39FVxI0veC/kSCCmnFp7ck=" crossorigin="anonymous"></script>
  <!-- Global site tag (gtag.js) - Google Analytics -->
  <script async src="https://www.googletagmanager.com/gtag/js?id=UA-153597376-1"></script>
  <script>
    window.dataLayer = window.dataLayer || [];
    function gtag() { dataLayer.push(arguments); }
    gtag('js', new Date());
    gtag('config', 'GA_MEASUREMENT_ID', { 'transport_type': 'beacon' });
    gtag('config', 'UA-153597376-1');
  </script>
  <script defer src="/assets/js/script.js"></script>
</body>

</html>


================================================
FILE: site/src/_includes/layouts/favicons.njk
================================================
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/icons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/icons/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<link rel="mask-icon" href="/assets/images/icons/safari-pinned-tab.svg" color="#5bbad5">
<meta name="theme-color" content="#fff">


================================================
FILE: site/src/_includes/layouts/footer.njk
================================================
<footer class="site-footer">
  <p class="text-center">
    <a href="{{ bottomResource.link if bottomResource.link else site.quicklinkGithubURL }}" target="_blank" rel="noopener noreferrer" class="link__blue">
      {{ bottomResource.caption if bottomResource.caption else site.bottomResource.caption }}
    </a>
  </p>
</footer>


================================================
FILE: site/src/_includes/layouts/head.njk
================================================
<head>
  <meta charset="utf-8">
  <title>{{ title + " | " + site.title if title else site.title }}</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="{{ description }}">
  <meta name="generator" content="{{ eleventy.generator }}">

  {{ extra_head | safe }}

  {% include "layouts/favicons.njk" -%}
  {% include "layouts/social.njk" -%}

  <link rel="preload" href="{{ '/assets/styles/main.css' | rev }}" as="style">
  <link rel="preload" href="{{ '/assets/styles/github-markdown.css' | rev }}" as="style">
  <link rel="stylesheet" href="{{ '/assets/styles/github-markdown.css' | rev }}">
  <link rel="stylesheet" href="{{ '/assets/styles/main.css' | rev }}">
  {# TODO: opt for theme and tweak the background color by avoiding github markdown #}
</head>


================================================
FILE: site/src/_includes/layouts/header.njk
================================================
<header class="page-header primary-font-color">
  <div class="page-header-content">
    <div class="page-header__title">
      <a href="{{ '/' }}" class="page-header__logo-link">
        <img class="page-header__logo-image" src="/assets/images/logos/quicklink.svg" alt="Quicklink" width="222" height="53">
      </a>
      <h3 class="page-header__subtitle">{{ site.subtitle }}</h3>
    </div>
    <nav class="page-header__navigation">
      {%- set navPages = collections.all | eleventyNavigation %}
      <ul>
        {%- for entry in navPages %}
          <li>
            <a href="{{ entry.url }}"{% if page.url === entry.url %} class="active" aria-current="page"{% endif %}>
              <strong class="primary-font-color">{{ entry.title }}</strong>
            </a>
          </li>
        {%- endfor %}
      </ul>
    </nav>
  </div>
</header>


================================================
FILE: site/src/_includes/layouts/highlighted-section-wrapper.njk
================================================
<div class="highlighted-section highlighted-section__text">
  {% block section %}
  {% endblock %}
</div>


================================================
FILE: site/src/_includes/layouts/normal-section-wrapper.njk
================================================
<div class="normal-section">
  {% block section %}
  {% endblock %}
</div>


================================================
FILE: site/src/_includes/layouts/social.njk
================================================
{% set socialImagePath = site.url + site.socialImage -%}

<meta name="og:title" content="{{ title }}">
<meta name="og:description" content="{{ description }}">
<meta name="og:url" content="{{ site.url + page.url }}">
<meta name="og:type" content="website">
<meta name="og:image" content="{{ socialImagePath }}">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="{{ title }}">
<meta name="twitter:description" content="{{ description }}">
<meta name="twitter:image" content="{{ socialImagePath }}">


================================================
FILE: site/src/api.njk
================================================
---
title: API
layout: layouts/base.njk
description: Faster subsequent page-loads by prefetching in-viewport links during idle time.
eleventyNavigation:
  key: API
  order: 1
bottomResource:
  caption: README on GitHub
  link: https://github.com/GoogleChromeLabs/quicklink#api
---

{% extends "layouts/normal-section-wrapper.njk" %}
{% block section %}
{% markdownConvert %}

## API

### quicklink.listen(options)

Returns: `Function`

A "reset" function is returned, which will empty the active `IntersectionObserver` and the cache of URLs that have already been prefetched or prerendered. This can be used between page navigations and/or when significant DOM changes have occurred.

#### options.prerender

- Type: `Boolean`
- Default: `false`

Whether to switch from the default prefetching mode to the prerendering mode for the links inside the viewport.

> **Note:** The prerendering mode (when this option is set to true) will fallback to the prefetching mode if the browser does not support prerender.
> Once the element exits the viewport, the `speculationrules` script is removed from the DOM. This approach makes it possible to exceed the limit of 10 prerenders imposed for the 'immediate' and 'eager' settings for eagerness.

#### options.eagerness

- Type: `String`
- Default: `immediate`

Determines the mode to be used for prerendering specified within the speculation rules.

#### options.prerenderAndPrefetch

* Type: `Boolean`
* Default: `false`

Whether to activate both the prefetching and prerendering mode at the same time.

#### options.delay

- Type: `Number`
- Default: `0`

The _amount of time_ each link needs to stay inside the viewport before being prefetched, in milliseconds.

#### options.el

- Type: `HTMLElement|NodeList<A>`
- Default: `document.body`

The DOM element to observe for in-viewport links to prefetch or the NodeList of Anchor Elements.

#### options.limit

- Type: `Number`
- Default: `Infinity`

The _total_ requests that can be prefetched or prerendered while observing the `options.el` container.

#### options.threshold

- Type: `Number`
- Default: `0`

The _area percentage_ of each link that must have entered the viewport to be fetched, in its decimal form (e.g. 0.25 = 25%).

#### options.throttle

- Type: `Number`
- Default: `Infinity`

The _concurrency limit_ for simultaneous requests while observing the `options.el` container.

#### options.timeout

- Type: `Number`
- Default: `2000`

The `requestIdleCallback` timeout, in milliseconds.

> **Note:** The browser must be idle for the configured duration before prefetching.

#### options.timeoutFn

- Type: `Function`
- Default: `requestIdleCallback`

A function used for specifying a `timeout` delay.

This can be swapped out for a custom function like [networkIdleCallback](https://github.com/pastelsky/network-idle-callback) (see demos).

By default, this uses [`requestIdleCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback) or the embedded polyfill.

#### options.priority

- Type: `Boolean`
- Default: `false`

Whether or not the URLs within the `options.el` container should be treated as high priority.

When `true`, quicklink will attempt to use the `fetch()` API if supported (rather than `link[rel=prefetch]`).

#### options.origins

- Type: `Array<String>`
- Default: `[location.hostname]`

A static array of URL hostnames that are allowed to be prefetched.

Defaults to the same domain origin, which prevents _any_ cross-origin requests.

**Important:** An empty array (`[]`) allows **_all origins_** to be prefetched.

#### options.ignores

- Type: `RegExp` or `Function` or `Array`
- Default: `[]`

Determine if a URL should be prefetched.

When a `RegExp` tests positive, a `Function` returns `true`, or an `Array` contains the string, then the URL is _not_ prefetched.

> **Note:** An `Array` may contain `String`, `RegExp`, or `Function` values.

> **Important:** This logic is executed _after_ origin matching!

#### options.onError

- Type: `Function`
- Default: None

An optional error handler that will receive any errors from prefetched requests.

By default, these errors are silently ignored.

#### options.hrefFn

- Type: `Function`
- Default: None

An optional function to generate the URL to prefetch. It receives an [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) as the argument.

### quicklink.prefetch(urls, isPriority)

Returns: `Promise`

The `urls` provided are always passed through `Promise.all`, which means the result will always resolve to an Array.

> **Important:** You much `catch` you own request error(s).

#### urls

- Type: `String` or `Array<String>`
- Required: `true`

One or many URLs to be prefetched.

> **Note:** Each `url` value is resolved from the current location.

#### isPriority

- Type: `Boolean`
- Default: `false`

Whether or not the URL(s) should be treated as "high priority" targets.

By default, calls to `prefetch()` are low priority.

> **Note:** This behaves identically to `listen()`'s `priority` option.

### quicklink.prerender(urls, eagerness)

Returns: `Promise`

> **Important:** You much `catch` you own request error(s).

#### urls

- Type: `String` or `Array<String>`
- Required: `true`

One or many URLs to be prerendered.

> **Note:** Speculative Rules API supports same-site cross origin Prerendering with [opt-in header](https://bit.ly/ss-cross-origin-pre).

#### eagerness

- Type: `String`
- Default: `immediate`

Determines the mode to be used for prerendering specified within the speculation rules.

## Polyfills

`quicklink`:

- Includes a very small fallback for [requestIdleCallback](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback)
- Requires `IntersectionObserver` to be supported. This is [supported in all modern browsers](https://caniuse.com/intersectionobserver), however you can use the [Intersection Observer polyfill](https://github.com/GoogleChromeLabs/intersection-observer) to support legacy browsers if needed.

## Recipes

### Set a custom timeout for prefetching resources

Defaults to 2 seconds (via `requestIdleCallback`). Here we override it to 4 seconds:
{% endmarkdownConvert %}
{% highlight "js" %}
quicklink.listen({
  timeout: 4000,
});
{% endhighlight %}

{% markdownConvert %}
### Set a specific Anchor Elements NodeList to observe for in-viewport links

Defaults to `document` otherwise.
{% endmarkdownConvert %}

{% highlight "js" %}
quicklink.listen({
  el: document.querySelectorAll('a.linksToPrefetch'),
});
{% endhighlight %}

{% markdownConvert %}
### Set the DOM element to observe for in-viewport links

Defaults to `document` otherwise.
{% endmarkdownConvert %}

{% highlight "js" %}
quicklink.listen({
  el: document.getElementById('carousel'),
});
{% endhighlight %}
{% markdownConvert %}
### Programmatically `prefetch()` URLs

If you would prefer to provide a static list of URLs to be prefetched, instead of detecting those in-viewport, customizing URLs is supported.
{% endmarkdownConvert %}
{% highlight "js" %}
// Single URL
quicklink.prefetch('2.html');

// Multiple URLs
quicklink.prefetch(['2.html', '3.html', '4.js']);

// Multiple URLs, with high priority
// Note: Can also be use with single URL!
quicklink.prefetch(['2.html', '3.html', '4.js'], true);
{% endhighlight %}
{% markdownConvert %}
### Programmatically `prerender()` URLs

If you would prefer to provide a static list of URLs to be prerendered, instead of detecting those in-viewport, customizing URLs is supported.

{% endmarkdownConvert %}
{% highlight "js" %}
// Single URL
quicklink.prerender('2.html');

// Multiple URLs
quicklink.prerender(['2.html', '3.html', '4.js']);
{% endhighlight %}
{% markdownConvert %}
### Set the request priority for prefetches while scrolling

Defaults to low-priority (`rel=prefetch` or XHR). For high-priority (`priority: true`), attempts to use `fetch()` or falls back to XHR.

> **Note:** This runs `prefetch(..., true)` with URLs found within the `options.el` container.
{% endmarkdownConvert %}
{% highlight "js" %}
quicklink.listen({priority: true});
{% endhighlight %}
{% markdownConvert %}
### Specify a custom list of allowed origins

Provide a list of hostnames that should be prefetch-able. Only the same origin is allowed by default.

> **Important:** You must also include your own hostname!
{% endmarkdownConvert %}
{% highlight "js" %}
quicklink.listen({
  origins: [
    // add mine
    'my-website.com',
    'api.my-website.com',
    // add third-parties
    'other-website.com',
    'example.com',
    // ...
  ],
});
{% endhighlight %}
{% markdownConvert %}
### Allow all origins

Enables all cross-origin requests to be made.

> **Note:** You may run into [CORB](https://chromium.googlesource.com/chromium/src/+/main/services/network/cross_origin_read_blocking_explainer.md) and [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) issues!

{% endmarkdownConvert %}
{% highlight "js" %}
quicklink.listen({
  origins: true,
  // or
  origins: [],
});
{% endhighlight %}
{% markdownConvert %}
### Custom Ignore Patterns

These filters run _after_ the `origins` matching has run. Ignores can be useful for avoiding large file downloads or for responding to DOM attributes dynamically.
{% endmarkdownConvert %}
{% highlight "js" %}
// Same-origin restraint is enabled by default.
//
// This example will ignore all requests to:
//  - all "/api/*" pathnames
//  - all ".zip" extensions
//  - all <a> tags with "noprefetch" attribute
//
quicklink.listen({
  ignores: [
    /\/api\/?/,
    uri => uri.includes('.zip'),
    (uri, elem) => elem.hasAttribute('noprefetch'),
  ],
});
{% endhighlight %}
{% markdownConvert %}
You may also wish to ignore prefetches to URLs which contain a URL fragment (e.g. `index.html#top`). This can be useful if you (1) are using anchors to headings in a page or (2) have URL fragments setup for a single-page application, and which to avoid firing prefetches for similar URLs.

Using `ignores` this can be achieved as follows:

{% endmarkdownConvert %}
{% highlight "js" %}
quicklink.listen({
  ignores: [
    uri => uri.includes('#'),
    // or RegExp: /#(.+)/
    // or element matching: (uri, elem) => !!elem.hash
  ],
});
{% endhighlight %}
{% markdownConvert %}
### Custom URL to prefetch via hrefFn callback

The hrefFn method allows to build the URL to prefetch (e.g. API endpoint) on the fly instead of the prefetching the `href` attribute URL.
{% endmarkdownConvert %}
{% highlight "js" %}
quicklink.listen({
  hrefFn(element) {
    return element.href.replace('html', 'json');
  },
});
{% endhighlight %}

{% markdownConvert %}

## Browser Support

The prefetching provided by `quicklink` can be viewed as a [progressive enhancement](https://www.smashingmagazine.com/2009/04/progressive-enhancement-what-it-is-and-how-to-use-it/). Cross-browser support is as follows:

- Without polyfills: Chrome, Safari ≥ 12.1, Firefox, Edge, Opera, Android Browser, Samsung Internet.
- With [Intersection Observer polyfill](https://github.com/GoogleChromeLabs/intersection-observer) ~6KB gzipped/minified: Safari ≤ 12.0, IE11
- With the above and a [Set()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) and [Array.from](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from) polyfill: IE9 and IE10. [Core.js](https://github.com/zloirock/core-js) provides both `Set()` and `Array.from()` shims. Projects like [es6-shim](https://github.com/paulmillr/es6-shim/blob/master/README.md) are an alternative you can consider.

Certain features have layered support:

- The [Network Information API](https://wicg.github.io/netinfo/), which is used to check if the user has a slow effective connection type (via `navigator.connection.effectiveType`) is only available in [Chrome 61+ and Opera 57+](https://caniuse.com/netinfo)
- If opting for `{priority: true}` and the [Fetch API](https://fetch.spec.whatwg.org/) isn't available, XHR will be used instead.

## Using the prefetcher directly

A `prefetch` method can be individually imported for use in other projects.

This method includes the logic to respect Data Saver and 2G connections. It also issues requests thru `fetch()`, XHRs, or `link[rel=prefetch]` depending on (a) the `isPriority` value and (b) the current browser's support.

After installing `quicklink` as a dependency, you can use it as follows:
{% endmarkdownConvert %}
{% highlight "html" %}
<script type="module">
  import {prefetch} from 'quicklink';
  prefetch(['1.html', '2.html']).catch(error => {
    // Handle own errors
  });
</script>
{% endhighlight %}

{% endblock %}


================================================
FILE: site/src/approach.njk
================================================
---
title: Approach
layout: layouts/base.njk
description: Faster subsequent page-loads by prefetching in-viewport links during idle time.
eleventyNavigation:
  key: Approach
  order: 2
sections:
  howItWorks:
    title: "How it works"
    summary: "Quicklink attempts to make navigations to subsequent pages load faster. It:"
---

{% extends "layouts/normal-section-wrapper.njk" %}
{% block section %}
{% markdownConvert %}

## How it works

{% endmarkdownConvert %}
<img class="article-image" src="/assets/images/graphs/what-is-prefetch-svgomg.svg" width="932" height="535" alt="Image shows how prefetching improves page load speed by fetching resources in advance">
{% markdownConvert %}

Quicklink attempts to make navigations to subsequent pages load faster. It:

* **Detects links within the viewport** (using [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API))
* **Waits until the browser is idle** (using [requestIdleCallback](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback))
* **Checks if the user isn't on a slow connection** (using `navigator.connection.effectiveType`) or has data-saver enabled (using `navigator.connection.saveData`)
* **Prefetches URLs to the links** (using [`<link rel=prefetch>`](https://www.w3.org/TR/resource-hints/#prefetch) or XHR). Provides some control over the request priority (can switch to `fetch()` if supported).

{% endmarkdownConvert %}
<img loading="lazy" class="article-image" src="/assets/images/graphs/prefetch-quicklink-svgomg.svg" width="932" height="397" alt="Image shows page load times comparison with and without Quicklink prefetching, showing faster performance when prefetch is enabled">
{% markdownConvert %}

## Future: Double-keyed HTTP Cache

Most Quicklink users use the library to prefetch links on the same origin. Some users do however care about cross-origin prefetches and this section outlines some relevant notes on double-keyed caching that may impact you.

Modern browsers are shifting towards a double-keyed HTTP cache. This partitions the HTTP cache roughly by the top-frame-origin that a request is made from. This breaks cross-origin resource prefetching, whose goal is to prefetch cross-origin resources and store them in the HTTP cache for later re-use. This [limits cache timing attacks](https://github.com/xsleaks/xsleaks/wiki/Browser-Side-Channels#cache-and-error-events) but does limit cross-origin prefetching from being effective.

Chrome [will](https://groups.google.com/a/chromium.org/g/blink-dev/c/bSMOY-evrV4/m/qT0gCByxBAAJ) allow cross-origin prefetched resources in the HTTP cache to be re-used for top-level navigations, which should be safe because by navigating, the user is explicitly sharing their interest with the destination site.

What this means for Quicklink is that the library will continue to work for same-origin navigations. If you choose to [control which origins can be prefetched](https://github.com/GoogleChromeLabs/quicklink#specify-a-custom-list-of-allowed-origins), these will be limited to prefetching same-origin use-cases only. As this is a browser-level limitation, it will impact any prefetching library.

## Session Stitching

Cross-origin prefetching (e.g `a.com/foo.html` prefetches `b.com/bar.html`) has a number of limitations. One such limitation is with session-stitching. `b.com` may expect `a.com`'s navigation requests to include session information (e.g a temporary ID - e.g `b.com/bar.html?hash=<>&timestamp=<>`), where this information is used to customize the experience or log information to analytics.

If session-stitching requires a timestamp in the URL, what is prefetched and stored in the HTTP cache may not be the same as the one the user ultimately navigates to. This introduces a challenge as it can result in double prefetches.

To workaround this problem, you can consider passing along session information via the [ping attribute](https://caniuse.com/ping) (separately) so the origin can stitch a session together asynchronously.

## Ad-related considerations

Sites that rely on ads as a source of monetization should not prefetch ad-links, to avoid unintentionally counting clicks against those ad placements, which can lead to inflated Ad CTR (click-through-rate).

Ads appear on sites mostly in two ways:

- **Inside iframes:** By default, most ad-servers render ads within iframes. In these cases, those ad-links won't be prefetched by Quicklink, unless a developer explicitly passes in the URL of an ads iframe. The reason is that the library look-up for in-viewport elements is restricted to those of the top-level origin.

- **Outside iframes:** In cases when the site shows same-origin ads, displayed in the top-level document (e.g. by hosting the ads themselves and by displaying the ads in the page directly), the developer needs to explicitly tell Quicklink to avoid prefetching these links. This can be achieved by passing the URL or subpath of the ad-link, or the element containing it to the [custom ignore patterns list](/#custom-ignore-patterns).

{% endmarkdownConvert %}
{% endblock %}


================================================
FILE: site/src/assets/js/script.js
================================================
window.addEventListener('load', () => {
  'use strict';

  function initGoToTopBtn() {
    const goTopBtn = document.querySelector('.back-to-top');

    function trackScroll() {
      const scrolled = window.pageYOffset;
      const threshold = 400;

      if (scrolled > threshold) {
        goTopBtn.classList.remove('hidden');
      }
      if (scrolled < threshold) {
        goTopBtn.classList.add('hidden');
      }
    }

    function scrollToTop() {
      const c = document.documentElement.scrollTop || document.body.scrollTop;
      if (c > 0) {
        window.requestAnimationFrame(scrollToTop);
        window.scrollTo(0, c - c / 8);
      }
    }

    function backToTop() {
      if (window.pageYOffset > 0) {
        scrollToTop();
      }
    }

    window.addEventListener('scroll', trackScroll, {passive: true});
    goTopBtn.addEventListener('click', backToTop);
  }

  initGoToTopBtn();

  const clipboard = new ClipboardJS('#copy-snippet-button', {
    text: trigger => trigger.parentNode.previousElementSibling.textContent.trim(),
  });

  clipboard.on('success', event => {
    event.clearSelection();
    event.trigger.blur();
    const notifyCopiedSnippet = document.querySelector('.notify-copied-snippet');
    notifyCopiedSnippet.classList.add('notify-copied-snippet--displayed');
  });

  clipboard.on('error', event => {
    console.error('[clipboard error] Action:', event.action);
    console.error('[clipboard error] Trigger:', event.trigger);
  });
});


================================================
FILE: site/src/assets/styles/_copy-snippet.scss
================================================
.copy-snippet-widget {
  background: linear-gradient(hsla(0deg, 0%, 100%, .775), hsla(0deg, 0%, 100%, .7));
  border-radius: 6px;
  padding: 10px 18px 17px;
  margin: 1.5em 0;
  box-shadow: inset 0 1px 0 hsla(0deg, 0%, 100%, 1), 0 3px 20px hsla(0deg, 0%, 0%, .1);
}

.snippet-for-copy {
  // border: 1px solid hsla(0, 0%, 0%, .15);
  border-radius: 1px;
  // background: hsla(0, 0%, 0%, .075);
  margin: 10px 0;
  font-size: .95em;
}

.copy-snippet-widget .snippet-for-copy pre {
  margin-bottom: 0;
}

.notify-copied-snippet {
  font-size: .8em;
  margin-top: .35em;
  margin-left: .35em;
  display: none;
}

.notify-copied-snippet--displayed {
  display: block;
}

.button {
  background: linear-gradient(hsla(0deg, 0%, 95%, 1), hsla(0deg, 0%, 90%, 1));
  border: 1px solid;
  --border-color-opacity: .25;
  --border-color: hsla(0deg, 0%, 25%, var(--border-color-opacity)) hsla(0deg, 0%, 10%, var(--border-color-opacity)) hsla(0deg, 0%, 0%, var(--border-color-opacity));
  border-color: var(--border-color);
  border-radius: 4px;
  padding: 4px 10px;
  --light-opacity: 1;
  --shadow-opacity: .05;
  --shadow-blur-radius: 1px;
  --box-shadow: inset 0 1px 0 hsla(0deg, 0%, 100%, var(--light-opacity)), 0 1px var(--shadow-blur-radius) hsla(0deg, 0%, 0%, var(--shadow-opacity)); // An intermediate variable is needed for Safari
  box-shadow: var(--box-shadow);
  cursor: pointer;
  font-size: 16px;
}

.button--copy-snippet {
  background: radial-gradient(ellipse at top, hsla(0deg, 0%, 100%, .25), transparent), linear-gradient(hsla(330deg, 73%, 49%, .65), hsla(330deg, 73%, 49%, 1));
  color: #fff;
  text-shadow: 0 1px 1px hsla(0deg, 0%, 0%, .25);
  --light-opacity: .2;
  --border-color-opacity: .25;
  vertical-align: top;

  font-size: .8em;
  padding: 7px 14px;
  border-radius: 7px;
  --shadow-opacity: .25;
  --shadow-blur-radius: 3px;
  font-weight: 700;
  letter-spacing: .01em;
}

@media (min-width: 600px) {
  .button--copy-snippet {
    font-size: 1em;
  }
}


================================================
FILE: site/src/assets/styles/github-markdown.scss
================================================
// stylelint-disable declaration-no-important

@use "vendor/github-markdown";

.markdown-body {
  box-sizing: border-box;
  min-width: 200px;
  max-width: 980px;
  margin: 0 auto;
  padding: 45px;
  // TODO: !important use does not look pefect, the github font looks better
  font-family: Arial, Helvetica, sans-serif !important;
  // TODO: opt for font color
  color: hsl(226deg, 52%, 27%) !important;
}

.markdown-body h1,
h2,
h3,
h4,
h5 {
  // Google Sans is for headlines only per Google guidelines
  font-family: Arial, Helvetica, sans-serif !important;
}

@media (max-width: 767px) {
  .markdown-body {
    padding: 15px;
  }

  .markdown-body h2 {
    font-size: 1.2em !important;
  }
}


================================================
FILE: site/src/assets/styles/main.scss
================================================
@use "vendor/prism";
@use "copy-snippet";

html {
  height: 100%;
}

html,
body {
  min-height: 100%;
}

body {
  margin: 0;
  font-family: Arial, Helvetica, sans-serif;
}

p,
ul {
  margin: 0 0 16px;
  font-style: normal;
}

.page-header {
  text-align: center;
  background: #fff;
  padding-top: 1rem;
  box-shadow: 0 0 6px rgba(57, 73, 76, .35);
}

.page-header__title {
  display: flex;
  justify-content: flex-start;
  align-items: center;
  padding: 7px;
}

.page-header__subtitle {
  font-size: 1em;
  margin: 0;
}

.page-header__logo-link {
  margin-right: 14px;
  border: none;
  border-right: 2px solid rgb(197, 197, 197);
}

.page-header__logo-image {
  width: 160px;
  height: auto;
  margin-right: 6px;
}

.page-header__navigation a {
  position: relative;
  display: inline-block;
  text-decoration: none;
  border: none;
  white-space: nowrap;
  padding: 8px 0;
  min-width: 40px;
  z-index: 1;
}

.page-header__navigation a::before {
  content: "";
  position: absolute;
  z-index: -1;
  top: 0;
  bottom: 0;
  left: -.5em;
  right: -.5em;
  background-color: #46aee8;
  transform-origin: 50% 100%;
  transform: scaleY(0);
  transition: transform .15s ease-in-out;
}

.page-header__navigation a:hover::before {
  transform: scaleY(1);
  transform-origin: 50% 100%;
}

.page-header__navigation a.active {
  pointer-events: none;
}

.page-header__navigation a.active::before {
  transform: scaleY(1);
  opacity: .5;
  transform-origin: 50% 100%;
}

.page-header__navigation ul {
  margin: 0;
  padding: .5rem .5rem 0;
}

.page-header__navigation li {
  display: inline;
  margin: 6px 0 0 15px;
}

.page-header__navigation li:last-child {
  margin-right: 0;
}

main.page-main {
  padding: 0;
  width: 96%;
  margin-top: 16px;
  margin-bottom: 16px;
}

.page-main > h1 {
  color: #fff;
}

.page-main > ul {
  margin: 0;
  padding: 0;
}

.hidden {
  display: none;
}

.text-center {
  text-align: center;
}

.center {
  display: flex;
  justify-content: center;
}

.back-to-top {
  opacity: 1;
  pointer-events: all;
  position: fixed;
  bottom: 3rem;
  right: 3rem;
  z-index: 9999;
  width: 30px;
  height: 30px;
  text-align: center;
  line-height: 30px;
  background: #fff;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .35);
  cursor: pointer;
  border-radius: 50%;
  transform: translate3d(0, 0, 0);
  transition: transform .3s ease;
}

.back-to-top:hover {
  transform: translate3d(0, -2px, 0);
}

.back-to-top.hidden {
  opacity: 0;
  pointer-events: none;
  transform: translate3d(300%, 0, 0);
  transition: transform .3s ease, opacity .3s ease;
}

// no bullet list
.page-main ul.no-bullet {
  list-style-type: none;
  padding-left: 0;
}

.list-icon svg {
  margin-right: 1rem;
}

// grid
.flex-grid {
  display: flex;
}

.overflow-x-auto {
  overflow-x: auto;
}

.flex-grid .flex-grid__item {
  margin-left: 24px;
}

.flex-grid .flex-grid__item:last-child {
  margin-right: 16px;
}

.normal-section {
  padding: 0 8px;
}

.trusted-by img {
  max-width: 92px;
}

.use-with img {
  width: 112px;
  min-width: 92px;
}

.highlighted-section {
  padding: 16px 24px;
  margin: 16px 0;
}

.highlighted-section__text {
  // TODO: opt for bg color
  // background-color: #d8217d;
  background-color: #283646;
  color: #fff;
}

.highlighted-section__text p {
  color: #fff;
}

.highlighted-section.highlighted-section__text h2 {
  border: 0;
}

// heading
.primary-font-color {
  // TODO: opt for font color
  color: hsl(226deg, 52%, 27%);
}

.secondary-font-color {
  color: #fe8ec6;
}

.tertiary-font-color {
  color: #d74b91;
}

main.page-main .heading {
  letter-spacing: -1px;
  font-size: 1.5em;
  font-weight: 900;
  margin: 0;
  border: 0;
}

main.page-main .heading em {
  font-style: normal;
  white-space: nowrap;
}

.flex-between-center {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.large-github {
  display: none;
}

.article-image {
  margin: 20px auto;
  display: block;
  max-width: 100%;
  height: auto;
}

@media (min-width: 600px) {
  main.page-main .heading {
    font-size: 2.1em;
    margin: 24px 0 18px;
  }

  .page-header__navigation a {
    padding: 16px 0;
    min-width: 50px;
  }

  .page-header__subtitle {
    margin: 1.7em 0;
  }

  .page-header__logo-image {
    width: 200px;
  }

  .small-github {
    display: none;
  }

  .large-github {
    display: block;
  }
}

@media (min-width: 992px) {
  body {
    background-color: #eee;
  }

  main.page-main {
    background-color: rgb(255, 255, 255);
    box-shadow: 0 1px 6px rgba(57, 73, 76, .35);
    margin-top: 3.2rem;
    margin-bottom: 3.2rem;
    padding-bottom: 1rem;
  }

  .page-header {
    padding-top: 0;
  }

  .page-header-content {
    display: flex;
    justify-content: space-between;
    align-items: center;
    min-width: 200px;
    max-width: 980px;
    margin: 0 auto;
  }

  .page-header__title {
    padding: 14px;
  }

  .page-header__logo-link {
    display: inline-block;
  }

  .page-header__logo-image {
    width: 222px;
  }

  .page-header nav {
    display: inline-block;
  }

  main.page-main .heading {
    margin: 24px 0;
  }

  .normal-section {
    padding: 0 24px;
  }

  .highlighted-section {
    padding: 16px 24px;
    margin: 16px 0;
  }
}

.site-footer {
  margin-top: 1rem;
  border-top: 1px dotted #cecece;
}

.site-footer p.text-center {
  margin-top: 16px;
}


================================================
FILE: site/src/assets/styles/vendor/_github-markdown.scss
================================================
.markdown-body .octicon {
  display: inline-block;
  fill: currentcolor;
  vertical-align: text-bottom;
}

.markdown-body .anchor {
  float: left;
  line-height: 1;
  margin-left: -20px;
  padding-right: 4px;
}

.markdown-body .anchor:focus {
  outline: 0;
}

.markdown-body h1 .octicon-link,
.markdown-body h2 .octicon-link,
.markdown-body h3 .octicon-link,
.markdown-body h4 .octicon-link,
.markdown-body h5 .octicon-link,
.markdown-body h6 .octicon-link {
  color: #1b1f23;
  vertical-align: middle;
  visibility: hidden;
}

.markdown-body h1:hover .anchor,
.markdown-body h2:hover .anchor,
.markdown-body h3:hover .anchor,
.markdown-body h4:hover .anchor,
.markdown-body h5:hover .anchor,
.markdown-body h6:hover .anchor {
  text-decoration: none;
}

.markdown-body h1:hover .anchor .octicon-link,
.markdown-body h2:hover .anchor .octicon-link,
.markdown-body h3:hover .anchor .octicon-link,
.markdown-body h4:hover .anchor .octicon-link,
.markdown-body h5:hover .anchor .octicon-link,
.markdown-body h6:hover .anchor .octicon-link {
  visibility: visible;
}

.markdown-body {
  text-size-adjust: 100%;
  color: #24292e;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
  font-size: 16px;
  line-height: 1.5;
  word-wrap: break-word;
}

.markdown-body .pl-c {
  color: #6a737d;
}

.markdown-body .pl-c1,
.markdown-body .pl-s .pl-v {
  color: #005cc5;
}

.markdown-body .pl-e,
.markdown-body .pl-en {
  color: #6f42c1;
}

.markdown-body .pl-s .pl-s1,
.markdown-body .pl-smi {
  color: #24292e;
}

.markdown-body .pl-ent {
  color: #22863a;
}

.markdown-body .pl-k {
  color: #d73a49;
}

.markdown-body .pl-pds,
.markdown-body .pl-s,
.markdown-body .pl-s .pl-pse .pl-s1,
.markdown-body .pl-sr,
.markdown-body .pl-sr .pl-cce,
.markdown-body .pl-sr .pl-sra,
.markdown-body .pl-sr .pl-sre {
  color: #032f62;
}

.markdown-body .pl-smw,
.markdown-body .pl-v {
  color: #e36209;
}

.markdown-body .pl-bu {
  color: #b31d28;
}

.markdown-body .pl-ii {
  background-color: #b31d28;
  color: #fafbfc;
}

.markdown-body .pl-c2 {
  background-color: #d73a49;
  color: #fafbfc;
}

.markdown-body .pl-c2::before {
  content: "^M";
}

.markdown-body .pl-sr .pl-cce {
  color: #22863a;
  font-weight: 700;
}

.markdown-body .pl-ml {
  color: #735c0f;
}

.markdown-body .pl-mh,
.markdown-body .pl-mh .pl-en,
.markdown-body .pl-ms {
  color: #005cc5;
  font-weight: 700;
}

.markdown-body .pl-mi {
  color: #24292e;
  font-style: italic;
}

.markdown-body .pl-mb {
  color: #24292e;
  font-weight: 700;
}

.markdown-body .pl-md {
  background-color: #ffeef0;
  color: #b31d28;
}

.markdown-body .pl-mi1 {
  background-color: #f0fff4;
  color: #22863a;
}

.markdown-body .pl-mc {
  background-color: #ffebda;
  color: #e36209;
}

.markdown-body .pl-mi2 {
  background-color: #005cc5;
  color: #f6f8fa;
}

.markdown-body .pl-mdr {
  color: #6f42c1;
  font-weight: 700;
}

.markdown-body .pl-ba {
  color: #586069;
}

.markdown-body .pl-sg {
  color: #959da5;
}

.markdown-body .pl-corl {
  color: #032f62;
  text-decoration: underline;
}

.markdown-body details {
  display: block;
}

.markdown-body summary {
  display: list-item;
}

.markdown-body a {
  background-color: transparent;
}

.markdown-body a:active,
.markdown-body a:hover {
  outline-width: 0;
}

.markdown-body strong {
  font-weight: inherit;
  font-weight: bolder;
}

.markdown-body h1 {
  font-size: 2em;
  margin: .67em 0;
}

.markdown-body img {
  border-style: none;
}

.markdown-body code,
.markdown-body kbd,
.markdown-body pre {
  font-family: monospace, monospace;
  font-size: 1em;
}

.markdown-body hr {
  box-sizing: content-box;
  height: 0;
  overflow: visible;
}

.markdown-body input {
  font: inherit;
  margin: 0;
}

.markdown-body input {
  overflow: visible;
}

.markdown-body [type="checkbox"] {
  box-sizing: border-box;
  padding: 0;
}

.markdown-body * {
  box-sizing: border-box;
}

.markdown-body input {
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
}

.markdown-body a {
  color: #0366d6;
  text-decoration: none;
}

.markdown-body a:hover {
  text-decoration: underline;
}

.markdown-body strong {
  font-weight: 600;
}

.markdown-body hr {
  background: 0 0;
  border: 0;
  border-bottom: 1px solid #dfe2e5;
  height: 0;
  margin: 15px 0;
  overflow: hidden;
}

.markdown-body hr::before {
  content: "";
  display: table;
}

.markdown-body hr::after {
  clear: both;
  content: "";
  display: table;
}

.markdown-body table {
  border-collapse: collapse;
  border-spacing: 0;
}

.markdown-body td,
.markdown-body th {
  padding: 0;
}

.markdown-body details summary {
  cursor: pointer;
}

.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
  margin-bottom: 0;
  margin-top: 0;
}

.markdown-body h1 {
  font-size: 32px;
}

.markdown-body h1,
.markdown-body h2 {
  font-weight: 600;
}

.markdown-body h2 {
  font-size: 24px;
}

.markdown-body h3 {
  font-size: 20px;
}

.markdown-body h3,
.markdown-body h4 {
  font-weight: 600;
}

.markdown-body h4 {
  font-size: 16px;
}

.markdown-body h5 {
  font-size: 14px;
}

.markdown-body h5,
.markdown-body h6 {
  font-weight: 600;
}

.markdown-body h6 {
  font-size: 12px;
}

.markdown-body p {
  margin-bottom: 10px;
  margin-top: 0;
}

.markdown-body blockquote {
  margin: 0;
}

.markdown-body ol,
.markdown-body ul {
  margin-bottom: 0;
  margin-top: 0;
  padding-left: 0;
}

.markdown-body ol ol,
.markdown-body ul ol {
  list-style-type: lower-roman;
}

.markdown-body ol ol ol,
.markdown-body ol ul ol,
.markdown-body ul ol ol,
.markdown-body ul ul ol {
  list-style-type: lower-alpha;
}

.markdown-body dd {
  margin-left: 0;
}

.markdown-body code,
.markdown-body pre {
  font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace;
  font-size: 12px;
}

.markdown-body pre {
  margin-bottom: 0;
  margin-top: 0;
}

.markdown-body input::-webkit-inner-spin-button,
.markdown-body input::-webkit-outer-spin-button {
  appearance: none;
  margin: 0;
}

.markdown-body .border {
  border: 1px solid #e1e4e8 !important;
}

.markdown-body .border-0 {
  border: 0 !important;
}

.markdown-body .border-bottom {
  border-bottom: 1px solid #e1e4e8 !important;
}

.markdown-body .rounded-1 {
  border-radius: 3px !important;
}

.markdown-body .bg-white {
  background-color: #fff !important;
}

.markdown-body .bg-gray-light {
  background-color: #fafbfc !important;
}

.markdown-body .text-gray-light {
  color: #6a737d !important;
}

.markdown-body .mb-0 {
  margin-bottom: 0 !important;
}

.markdown-body .my-2 {
  margin-bottom: 8px !important;
  margin-top: 8px !important;
}

.markdown-body .pl-0 {
  padding-left: 0 !important;
}

.markdown-body .py-0 {
  padding-bottom: 0 !important;
  padding-top: 0 !important;
}

.markdown-body .pl-1 {
  padding-left: 4px !important;
}

.markdown-body .pl-2 {
  padding-left: 8px !important;
}

.markdown-body .py-2 {
  padding-bottom: 8px !important;
  padding-top: 8px !important;
}

.markdown-body .pl-3,
.markdown-body .px-3 {
  padding-left: 16px !important;
}

.markdown-body .px-3 {
  padding-right: 16px !important;
}

.markdown-body .pl-4 {
  padding-left: 24px !important;
}

.markdown-body .pl-5 {
  padding-left: 32px !important;
}

.markdown-body .pl-6 {
  padding-left: 40px !important;
}

.markdown-body .f6 {
  font-size: 12px !important;
}

.markdown-body .lh-condensed {
  line-height: 1.25 !important;
}

.markdown-body .text-bold {
  font-weight: 600 !important;
}

.markdown-body::before {
  content: "";
  display: table;
}

.markdown-body::after {
  clear: both;
  content: "";
  display: table;
}

.markdown-body > :first-child {
  margin-top: 0 !important;
}

.markdown-body > :last-child {
  margin-bottom: 0 !important;
}

.markdown-body a:not([href]) {
  color: inherit;
  text-decoration: none;
}

.markdown-body blockquote,
.markdown-body dl,
.markdown-body ol,
.markdown-body p,
.markdown-body pre,
.markdown-body table,
.markdown-body ul {
  margin-bottom: 16px;
  margin-top: 0;
}

.markdown-body hr {
  background-color: #e1e4e8;
  border: 0;
  height: .25em;
  margin: 24px 0;
  padding: 0;
}

.markdown-body blockquote {
  border-left: .25em solid #dfe2e5;
  color: #6a737d;
  padding: 0 1em;
}

.markdown-body blockquote > :first-child {
  margin-top: 0;
}

.markdown-body blockquote > :last-child {
  margin-bottom: 0;
}

.markdown-body kbd {
  background-color: #fafbfc;
  border: 1px solid #c6cbd1;
  border-bottom-color: #959da5;
  border-radius: 3px;
  box-shadow: inset 0 -1px 0 #959da5;
  color: #444d56;
  display: inline-block;
  font-size: 11px;
  line-height: 10px;
  padding: 3px 5px;
  vertical-align: middle;
}

.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
  font-weight: 600;
  line-height: 1.25;
  margin-bottom: 16px;
  margin-top: 24px;
}

.markdown-body h1 {
  font-size: 2em;
}

.markdown-body h1,
.markdown-body h2 {
  border-bottom: 1px solid #eaecef;
  padding-bottom: .3em;
}

.markdown-body h2 {
  font-size: 1.5em;
}

.markdown-body h3 {
  font-size: 1.25em;
}

.markdown-body h4 {
  font-size: 1em;
}

.markdown-body h5 {
  font-size: .875em;
}

.markdown-body h6 {
  color: #6a737d;
  font-size: .85em;
}

.markdown-body ol,
.markdown-body ul {
  padding-left: 2em;
}

.markdown-body ol ol,
.markdown-body ol ul,
.markdown-body ul ol,
.markdown-body ul ul {
  margin-bottom: 0;
  margin-top: 0;
}

.markdown-body li {
  word-wrap: break-all;
}

.markdown-body li > p {
  margin-top: 16px;
}

.markdown-body li + li {
  margin-top: .3em;
}

.markdown-body dl {
  padding: 0;
}

.markdown-body dl dt {
  font-size: 1em;
  font-style: italic;
  font-weight: 600;
  margin-top: 16px;
  padding: 0;
}

.markdown-body dl dd {
  margin-bottom: 16px;
  padding: 0 16px;
}

.markdown-body table {
  display: block;
  overflow: auto;
  width: 100%;
}

.markdown-body table th {
  font-weight: 600;
}

.markdown-body table td,
.markdown-body table th {
  border: 1px solid #dfe2e5;
  padding: 6px 13px;
}

.markdown-body table tr {
  background-color: #fff;
  border-top: 1px solid #c6cbd1;
}

.markdown-body table tr:nth-child(2n) {
  background-color: #f6f8fa;
}

.markdown-body img {
  background-color: #fff;
  box-sizing: content-box;
  max-width: 100%;
  height: auto;
}

.markdown-body img[align="right"] {
  padding-left: 20px;
}

.markdown-body img[align="left"] {
  padding-right: 20px;
}

.markdown-body code {
  background-color: rgba(27, 31, 35, .05);
  border-radius: 3px;
  font-size: 85%;
  margin: 0;
  padding: .2em .4em;
}

.markdown-body pre {
  word-wrap: normal;
}

.markdown-body pre > code {
  background: 0 0;
  border: 0;
  font-size: 100%;
  margin: 0;
  padding: 0;
  white-space: pre;
  word-break: normal;
}

.markdown-body .highlight {
  margin-bottom: 16px;
}

.markdown-body .highlight pre {
  margin-bottom: 0;
  word-break: normal;
}

.markdown-body .highlight pre,
.markdown-body pre {
  background-color: #f6f8fa;
  border-radius: 3px;
  font-size: 85%;
  line-height: 1.45;
  overflow: auto;
  padding: 16px;
}

.markdown-body pre code {
  background-color: transparent;
  border: 0;
  display: inline;
  line-height: inherit;
  margin: 0;
  max-width: auto;
  overflow: visible;
  padding: 0;
  word-wrap: normal;
}

.markdown-body .commit-tease-sha {
  color: #444d56;
  display: inline-block;
  font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace;
  font-size: 90%;
}

.markdown-body .blob-wrapper {
  border-bottom-left-radius: 3px;
  border-bottom-right-radius: 3px;
  overflow-x: auto;
  overflow-y: hidden;
}

.markdown-body .blob-wrapper-embedded {
  max-height: 240px;
  overflow-y: auto;
}

.markdown-body .blob-num {
  color: rgba(27, 31, 35, .3);
  cursor: pointer;
  font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace;
  font-size: 12px;
  line-height: 20px;
  min-width: 50px;
  padding-left: 10px;
  padding-right: 10px;
  text-align: right;
  user-select: none;
  vertical-align: top;
  white-space: nowrap;
  width: 1%;
}

.markdown-body .blob-num:hover {
  color: rgba(27, 31, 35, .6);
}

.markdown-body .blob-num::before {
  content: attr(data-line-number);
}

.markdown-body .blob-code {
  line-height: 20px;
  padding-left: 10px;
  padding-right: 10px;
  position: relative;
  vertical-align: top;
}

.markdown-body .blob-code-inner {
  color: #24292e;
  font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace;
  font-size: 12px;
  overflow: visible;
  white-space: pre;
  word-wrap: normal;
}

.markdown-body .pl-token.active,
.markdown-body .pl-token:hover {
  background: #ffea7f;
  cursor: pointer;
}

.markdown-body kbd {
  background-color: #fafbfc;
  border: 1px solid #d1d5da;
  border-bottom-color: #c6cbd1;
  border-radius: 3px;
  box-shadow: inset 0 -1px 0 #c6cbd1;
  color: #444d56;
  display: inline-block;
  font: 11px SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace;
  line-height: 10px;
  padding: 3px 5px;
  vertical-align: middle;
}

.markdown-body :checked + .radio-label {
  border-color: #0366d6;
  position: relative;
  z-index: 1;
}

.markdown-body .tab-size[data-tab-size="1"] {
  tab-size: 1;
}

.markdown-body .tab-size[data-tab-size="2"] {
  tab-size: 2;
}

.markdown-body .tab-size[data-tab-size="3"] {
  tab-size: 3;
}

.markdown-body .tab-size[data-tab-size="4"] {
  tab-size: 4;
}

.markdown-body .tab-size[data-tab-size="5"] {
  tab-size: 5;
}

.markdown-body .tab-size[data-tab-size="6"] {
  tab-size: 6;
}

.markdown-body .tab-size[data-tab-size="7"] {
  tab-size: 7;
}

.markdown-body .tab-size[data-tab-size="8"] {
  tab-size: 8;
}

.markdown-body .tab-size[data-tab-size="9"] {
  tab-size: 9;
}

.markdown-body .tab-size[data-tab-size="10"] {
  tab-size: 10;
}

.markdown-body .tab-size[data-tab-size="11"] {
  tab-size: 11;
}

.markdown-body .tab-size[data-tab-size="12"] {
  tab-size: 12;
}

.markdown-body .task-list-item {
  list-style-type: none;
}

.markdown-body .task-list-item + .task-list-item {
  margin-top: 3px;
}

.markdown-body .task-list-item input {
  margin: 0 .2em .25em -1.6em;
  vertical-align: middle;
}

.markdown-body hr {
  border-bottom-color: #eee;
}

.markdown-body .pl-0 {
  padding-left: 0 !important;
}

.markdown-body .pl-1 {
  padding-left: 4px !important;
}

.markdown-body .pl-2 {
  padding-left: 8px !important;
}

.markdown-body .pl-3 {
  padding-left: 16px !important;
}

.markdown-body .pl-4 {
  padding-left: 24px !important;
}

.markdown-body .pl-5 {
  padding-left: 32px !important;
}

.markdown-body .pl-6 {
  padding-left: 40px !important;
}

.markdown-body .pl-7 {
  padding-left: 48px !important;
}

.markdown-body .pl-8 {
  padding-left: 64px !important;
}

.markdown-body .pl-9 {
  padding-left: 80px !important;
}

.markdown-body .pl-10 {
  padding-left: 96px !important;
}

.markdown-body .pl-11 {
  padding-left: 112px !important;
}

.markdown-body .pl-12 {
  padding-left: 128px !important;
}


================================================
FILE: site/src/assets/styles/vendor/_prism.scss
================================================
/**
 * prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML
 * Based on https://github.com/chriskempson/tomorrow-theme
 * @author Rose Pritchard
 */

code[class*="language-"],
pre[class*="language-"] {
	color: #ccc;
	background: none;
	font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
	font-size: 1em;
	text-align: left;
	white-space: pre;
	word-spacing: normal;
	word-break: normal;
	word-wrap: normal;
	line-height: 1.5;

	-moz-tab-size: 4;
	-o-tab-size: 4;
	tab-size: 4;

	-webkit-hyphens: none;
	-moz-hyphens: none;
	-ms-hyphens: none;
	hyphens: none;
}

/* Code blocks */
pre[class*="language-"] {
	padding: 1em;
	margin: .5em 0;
	overflow: auto;
}

:not(pre) > code[class*="language-"],
pre[class*="language-"] {
	background: #2d2d2d;
}

/* Inline code */
:not(pre) > code[class*="language-"] {
	padding: .1em;
	border-radius: .3em;
	white-space: normal;
}

.token.comment,
.token.block-comment,
.token.prolog,
.token.doctype,
.token.cdata {
	color: #999;
}

.token.punctuation {
	color: #ccc;
}

.token.tag,
.token.attr-name,
.token.namespace,
.token.deleted {
	color: #e2777a;
}

.token.function-name {
	color: #6196cc;
}

.token.boolean,
.token.number,
.token.function {
	color: #f08d49;
}

.token.property,
.token.class-name,
.token.constant,
.token.symbol {
	color: #f8c555;
}

.token.selector,
.token.important,
.token.atrule,
.token.keyword,
.token.builtin {
	color: #cc99cd;
}

.token.string,
.token.char,
.token.attr-value,
.token.regex,
.token.variable {
	color: #7ec699;
}

.token.operator,
.token.entity,
.token.url {
	color: #67cdcc;
}

.token.important,
.token.bold {
	font-weight: bold;
}
.token.italic {
	font-style: italic;
}

.token.entity {
	cursor: help;
}

.token.inserted {
	color: green;
}


================================================
FILE: site/src/demo.njk
================================================
---
title: Demo
layout: layouts/base.njk
description: Faster subsequent page-loads by prefetching in-viewport links during idle time.
eleventyNavigation:
  key: Demo
  order: 3
---

{% extends "layouts/normal-section-wrapper.njk" %}
{% block section %}
{% markdownConvert %}

## Demos

This page contains some demo sites that use Quicklink to improve navigation, grouped by architecture: **Multi Page Apps** / **Single Page Apps**.

If you like the library, and want to try them on your site, check out the **Installation** section of the [home page](/).

#### Multi Page Apps

In this demo you’ll compare an ecommerce site with and without Quicklink to see how navigation is improved thanks to the library.

The following waterfall shows a typical navigation for a site without Quicklink (top) vs. the same site using the library (bottom):

{% endmarkdownConvert %}

<img loading="lazy" class="article-image" src="/assets/images/screenshots/wpt-visual-comparison.png" height="320" width="615" alt="Image shows that use of quicklink improves navigation for a site by 1 second">

{% markdownConvert %}

To try the demo:

1. Open the [unoptimized site](https://mini-ecomm.glitch.me/) in Chrome.
1. Open **DevTools** and go to the **Network panel** to simulate a **Fast 3G** Connection.
1. Pick **Galaxy S5** as a simulated device.
1. Make sure **Disable cache** is not checked.
1. Reload the page.

{% endmarkdownConvert %}

<img loading="lazy" class="article-image" src="/assets/images/screenshots/devtools-unoptimized.png" height="130" width="600" alt="Image shows chrome dev tools inspection for site not using quicklink">

{% markdownConvert %}

Now, measure performance on the same site, that uses Quicklink:

1. Open the [optimized site](https://mini-ecomm-quicklink.glitch.me/) in Chrome.
1. Open **DevTools** and go to the **Network panel** to simulate a **Fast 3G** Connection.
1. Pick **Galaxy S5** as a simulated device.
1. Make sure **Disable cache** is not checked.
1. Reload the page.

Prefetched links can be identified in the **Network** panel by having `quicklink` as the **Initiator** and **Lowest** as the **Priority**:

{% endmarkdownConvert %}

<img loading="lazy" class="article-image" src="/assets/images/screenshots/devtools-optimized-1.png" height="130" width="600" alt="Image shows chrome dev tools inspection for site using quicklink">

{% markdownConvert %}

To measure the impact of `quicklink` on navigations:

1. Clear the **Network** trace.
1. Click on a list item.
1. Take a look at the **Network** panel.

{% endmarkdownConvert %}

<img loading="lazy" class="article-image" src="/assets/images/screenshots/devtools-optimized-2.png" height="130" width="600" alt="Image shows the site using quicklink along with prefetch cache improves data fetching time by 97 percent">

{% markdownConvert %}

In the **Size** column of the **Network** panel the trace shows that the product page was retrieved from the **prefetch cache** and now takes **3ms** to load: a **97% improvement** compared to the unoptimized version.

Here is a comparison video:

{% endmarkdownConvert %}

<img loading="lazy" class="article-image" src="/assets/images/screenshots/wpt-video-comparison.gif" height="320" width="450" alt="The gif shows comparison between same site being loaded with quicklink takes 1 second less than unoptimised version">

{% markdownConvert %}

### Single Page Apps

Quicklink 2.0 includes support for React-based single-page-apps. This has been covered to the detail in this [guide](https://web.dev/quicklink/).

To try the demo:

1. Open the [optimized site](https://create-react-app-quicklink.glitch.me/) in Chrome.
1. Open DevTools and go to the **Network** panel to simulate a **Fast 3G** Connection.
1. Pick **Galaxy S5** as a simulated device.
1. Make sure **Disable cache** is not checked.
1. Reload the page.

When the home page loads the chunks for that route are loaded. After that, `quicklink` prefetches the route's chunks for the in-viewport links:

{% endmarkdownConvert %}

<img loading="lazy" class="article-image" src="/assets/images/screenshots/spa-devtools-optimized-1.png" height="204" width="570" alt="Image shows that data for loading links available on the page being viewed are loaded beforehand by quicklink">

{% markdownConvert %}

Next:

1. Clear the **Network** log again.
1. Make sure **Disable** cache is not checked.
1. Click the Blog link to navigate to that page.

{% endmarkdownConvert %}

<img loading="lazy" class="article-image" src="/assets/images/screenshots/spa-devtools-optimized-2.png" height="76" width="643" alt="Image shows that quicklink takes only 2 milliseconds to load a site using prefetch cache">

{% markdownConvert %}

The Size column indicates that these chunks were retrieved from the "prefetch cache", instead of the network. Loading these chunks without a Quicklink takes approximately 580ms. Using the library it takes 2ms, which represents a 99% reduction!

{% endmarkdownConvert %}
{% endblock %}


================================================
FILE: site/src/index.njk
================================================
---
title: Home
layout: layouts/base.njk
description: Faster subsequent page-loads by prefetching in-viewport links during idle time.
eleventyNavigation:
  key: Home
  order: 0
extra_head: <link rel="dns-preconnect" href="https://api.github.com">
---

{% include "components/heading.njk" %}

{% include "components/why-quicklink.njk" %}

{% include "components/copy-snippet.njk" %}

{% include "components/download.njk" %}

{% include "components/trusted-by.njk" %}

{% include "components/installation.njk" %}

{% include "components/usage.njk" %}

{% include "components/over-prefetching.njk" %}

{% include "components/react.njk" %}

{% include "components/chrome-extension.njk" %}

{% include "components/why-prefetch.njk" %}

{% include "components/use-with.njk" %}


================================================
FILE: site/src/measure.njk
================================================
---
title: Measure
layout: layouts/base.njk
description: Faster subsequent page-loads by prefetching in-viewport links during idle time.
eleventyNavigation:
  key: Measure
  order: 4
---

{% extends "layouts/normal-section-wrapper.njk" %}
{% block section %}
{% markdownConvert %}
## Measuring impact of Quicklink in sites

Implementing Quicklink in sites can speed up navigations, by automatically prefetching in-viewport links during idle time.
Different metrics can be improved as a result of this, the most common ones being [Start Render](https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/quick-start-quide#TOC-Start-Render:) and [First Contentful Paint](https://developers.google.com/web/tools/lighthouse/audits/first-contentful-paint).

In this section, we explore different ways of measuring the impact of Quicklink in sites. To showcase that, we’ll use the following sites:
- An [unoptimized e-commerce demo](https://mini-ecomm.glitch.me/), consisting of a listing and a product page. The demo introduces a 1s delay before the product page response, to similate the backend processing time of a real e-commerce site. You can see the code, and make changes by remixing the [Glitch project](https://glitch.com/edit/#!/mini-ecomm?path=server.js:11:58).
- An [optimized version of the site](https://mini-ecomm-quicklink.glitch.me/), which is a copy of the original version, but this time, using Quicklink in the listing page, to prefetch links that come to the view. You can view and edit the code, in the [Glitch project](https://glitch.com/edit/#!/mini-ecomm-quicklink?path=server.js:1:0).

If you take a look at the code of the listing page, in the optimized version of the site, you'll find the code to initialize Quicklink:
{% endmarkdownConvert %}
  {% highlight "js" %}
<script defer src="https://cdn.jsdelivr.net/npm/quicklink@{{ site.quicklinkVersion }}/dist/quicklink.umd.js"></script>
<script>
  window.addEventListener('load', () => {
    quicklink.listen();
  });
</script>
  {% endhighlight %}
{% markdownConvert %}
## Using Chrome DevTools

The first tool you’ll use is [Chrome DevTools](https://developers.google.com/web/tools/chrome-devtools), which is useful both for local development, and also for production URLs.

First, measure performance before implementing Quicklink:

- Open the [unoptimized demo](https://mini-ecomm.glitch.me/) in Chrome.
- Open the **Network** panel and simulate a **Fast 3G** Connection.
- Pick **Galaxy S5** as simulated device.
- Make sure **Disable cache** is not checked.
- Reload the page.
- Click on the first product in the listing.

Take a look at the **Time** column: the product page takes approximately **2.5s** to load:

{% endmarkdownConvert %}

<img loading="lazy" class="article-image" src="/assets/images/screenshots/devtools-unoptimized.png" height="400" width="800" alt="Image shows chrome dev tools inspection for site not using quicklink">

{% markdownConvert %}

Now, measure performance after implementing Quicklink:

- Open the [optimized demo](https://mini-ecomm-quicklink.glitch.me/) in Chrome.
- Open the **Network** panel and simulate a Fast 3G Connection.
- Pick **Galaxy S5** as simulated device.
- Make sure **Disable cache** is not checked.
- Reload the page.

Prefetched links can be identified in the Network panel by having Quicklink as the **Initiator** and **Lowest** as the Priority:

{% endmarkdownConvert %}

<img loading="lazy" class="article-image" src="/assets/images/screenshots/devtools-optimized-1.png" height="400" width="800" alt="Image shows chrome dev tools inspection for site using quicklink">

{% markdownConvert %}

To measure the impact of Quicklink on navigations:

- Click on a list item.
- Take a look at the **Network** panel.

{% endmarkdownConvert %}

<img loading="lazy" class="article-image" src="/assets/images/screenshots/devtools-optimized-2.png" height="400" width="800" alt="Image shows the site using quicklink along with prefetch cache improves data fetching time by 97 percent">

{% markdownConvert %}

In the Size column of the **Network** panel the trace shows that the product page was retrieved from the **prefetch cache** and now takes **3ms** to load: a **97% improvement** compared to the unoptimized version.

## Using Webpagetest

Webpagetest can be used to measure impact on real devices and different connection types. You'll use [WPT Scripting](https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/scripting) to simulate a user arriving at the home page and clicking one of the product items.

Open to webpagetest.org.
Pick **Nexus 5** as Test Location.
In the Advanced Settings tab, pick **3GFast** in the connection type.
In the Script tab, place the following script:

```
logData 0
navigate https://mini-ecomm.glitch.me/
logData 1
execAndWait document.querySelector('a').click()
```

The script instructs WPT to open the [unoptimized demo](https://mini-ecomm.glitch.me/) and simulate a click on the first product of the listing. Metrics are captured only for the product page. Here is the [resultiing test](https://www.webpagetest.org/result/191103_TM_e68d81788d8744762301b44c6e3e72d2/).

Repeat the process on [the demo](https://mini-ecomm-quicklink.glitch.me/) that uses Quicklink. The script looks like:

```
logData 0
navigate https://mini-ecomm.glitch.me/
logData 1
execAndWait document.querySelector('a').click()
```

Here is the [resultiing test](https://www.webpagetest.org/result/191103_E3_f8217e45ad837ac084868d4f3b9a4a73/).

The following table compares the main metrics obtained for each of the sites:

{% endmarkdownConvert %}

<img loading="lazy" class="article-image" src="/assets/images/screenshots/wpt-metrics-comparison.png" height="105" width="700" alt="Image shows WPT results for site loaded with quicklink are much faster than without quicklink">

{% markdownConvert %}

Next, create a comparison between the tests.

- Open the [WPT test](https://www.webpagetest.org/result/191103_TM_e68d81788d8744762301b44c6e3e72d2/) for the unoptimized site.
- Click on the median run, that appears in the "First View" cell of the report.
- Repeat the same process in the [optimized test](https://www.webpagetest.org/result/191103_E3_f8217e45ad837ac084868d4f3b9a4a73/).

To create a comparison test, you need to append the IDs from the previous links as comma separated valuees, and send them as query params to `https://www.webpagetest.org/video/compare.php`:

```
https://www.webpagetest.org/video/compare.php?tests=test_id_1,test_id_2
```

The resulting comparison of the test ran previously can be found [here](https://www.webpagetest.org/video/compare.php?tests=191103_TM_e68d81788d8744762301b44c6e3e72d2-r%3A8-c%3A0%2C191103_E3_f8217e45ad837ac084868d4f3b9a4a73-r%3A7-c%3A0&thumbSize=200&ival=500&end=visual).

### Visual Comparison

The unoptimized site starts rendering approximately at **2.5s**, the demo that uses Quicklink, starts at **1.2s**.

{% endmarkdownConvert %}

<img loading="lazy" class="article-image" src="/assets/images/screenshots/wpt-visual-comparison.png" height="430" width="820" alt="Image shows that use of quicklink improves load time for a site by 1.3 seconds">

{% markdownConvert %}

### Video

A video can be generated from the [comparison page](https://www.webpagetest.org/video/compare.php?tests=191103_TM_e68d81788d8744762301b44c6e3e72d2-r%3A8-c%3A0%2C191103_E3_f8217e45ad837ac084868d4f3b9a4a73-r%3A7-c%3A0&thumbSize=200&ival=500&end=visual), by clicking on **Create Video**.

{% endmarkdownConvert %}

<img loading="lazy" class="article-image" src="/assets/images/screenshots/wpt-video-comparison.gif" height="435" width="600" alt="The gif shows comparison between same site being loaded with quicklink takes 1 second less than the unoptimised version">

{% markdownConvert %}

### Using RUM (Real user monitoring) tools

RUM tools, let you visualize how different metrics evolve in time for real users. If prefetching affects a large amount of pages, you might be able to see more page loads being loaded faster after implementing it, which can be reflected in metrics [First Contentful Paint](https://developers.google.com/web/tools/lighthouse/audits/first-contentful-paint).

For example, the [Chrome User Experience Report](https://developers.google.com/web/tools/chrome-user-experience-report/) provides performance metrics for how real-world Chrome users experience popular destinations on the web.
CrUX data is available in [PageSpeed Insights](https://developers.google.com/speed/pagespeed/insights/) and also in [BigQuery](https://bigquery.cloud.google.com/dataset/chrome-ux-report:all?pli=1), but you can obtain a quick visualization of the evolution of your metrics using the [CrUX dashboard](https://g.co/chromeuxdash) (refer to [this guide](https://web.dev/chrome-ux-report-data-studio-dashboard/) for more details).
The report contains a section for First Contentful Paint. If a large number of page views are prefetched as a result of implementing Quicklink, this graph could show a positive evolution in time.

**Note:** Even when checking the performance for real users is a general performance best practice, it’s usually hard to correlate the overall performance improvement of a site with a single optimization like this one. With that said, the best way to make sure you’re measuring exactly this change is to perform a before / after test with laboratory tools as explained in previous sections.

### Using Quicklink Chrome extension

[Quicklink Chrome Extension](https://chrome.google.com/webstore/detail/quicklink-chrome-extensio/epmplkdcjhgigmnjmjibilpmekhgkbeg) injects Quicklink in every site a user visits. You can use it to measure the potential impact of implementing the library on a site, before doing it.
Since the extension will simulate how the library would work when implemented, you can install the extension and then run the tests with DevTools, as described in the previous section.

### Conclusion

Quicklink can highly improve navigations by automatically prefetching in-viewport links, . We’ve explored different tools to measure the impact of implementing it in your site.
Metrics like [Start Render](https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/quick-start-quide#TOC-Start-Render:) and [First Contentful Paint](https://developers.google.com/web/tools/lighthouse/audits/first-contentful-paint) can be directly impacted by this change, but other metrics can be also improved as a result of this, as seen in the tests performed in this guide.
Laboratory testing tools, like Chrome DevTools and Webpagetest can help you have an accurate idea of the impact of this change, by running a before / after comparison. Also, If the number of pages affected by this change is large enough, you might be able to visualize the impact of the implementation on real user monitoring (RUM) tools as well.

{% endmarkdownConvert %}
{% endblock %}


================================================
FILE: site/src/robots.njk
================================================
---
permalink: /robots.txt
layout: null
eleventyExcludeFromCollections: true
---
# www.robotstxt.org

User-agent: *
Disallow:{% if site.isNetlify %} /{% endif %}
Sitemap: {{ site.url + "/sitemap.xml" }}


================================================
FILE: site/src/site.webmanifest
================================================
{
  "name": "Quicklink",
  "short_name": "Quicklink",
  "icons": [
    {
      "src": "/assets/images/icons/android-chrome-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/assets/images/icons/android-chrome-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "start_url": "/",
  "theme_color": "#fff",
  "background_color": "#fff",
  "display": "standalone"
}


================================================
FILE: site/src/sitemap.njk
================================================
---
permalink: /sitemap.xml
layout: null
eleventyExcludeFromCollections: true
---
<?xml version="1.0" encoding="utf-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  {% for page in collections.all -%}
  <url>
    <loc>{{ site.url + page.url }}</loc>
    <lastmod>{{ page.date.toISOString() }}</lastmod>
  </url>
  {%- endfor %}
</urlset>


================================================
FILE: site/watch.json
================================================
{
  "install": {
    "include": [
      "^package\\.json$",
      "^\\.env$"
    ]
  },
  "restart": {
    "exclude": [
      "^src/",
      "^dist/"
    ],
    "include": [
      "watch.json$",
      ".eleventy.js$"
    ]
  },
  "throttle": 1000
}

================================================
FILE: src/chunks.mjs
================================================
/**
 * Copyright 2018 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/

import throttle from 'throttles';
import {viaFetch, supported} from './prefetch.mjs';
import requestIdleCallback from './request-idle-callback.mjs';

// Cache of URLs we've prefetched
// Its `size` is compared against `opts.limit` value.
const toPrefetch = new Set();

/**
 * Determine if the anchor tag should be prefetched.
 * A filter can be a RegExp, Function, or Array of both.
 *   - Function receives `node.href, node` arguments
 *   - RegExp receives `node.href` only (the full URL)
 * @param  {Element}  node    The anchor (<a>) tag.
 * @param  {Mixed}    filter  The custom filter(s)
 * @return {Boolean}          If true, then it should be ignored
 */
function isIgnored(node, filter) {
  if (Array.isArray(filter)) {
    return filter.some(x => isIgnored(node, x));
  }

  return (filter.test || filter).call(filter, node.href, node);
}

/**
 * Prefetch an array of URLs if the user's effective
 * connection type and data-saver preferences suggests
 * it would be useful. By default, looks at in-viewport
 * links for `document`. Can also work off a supplied
 * DOM element or static array of URLs.
 * @param {Object} options - Configuration options for quicklink
 * @param {Object} [options.el] - DOM element to prefetch in-viewport links of
 * @param {Boolean} [options.priority] - Attempt higher priority fetch (low or high)
 * @param {Array} [options.origins] - Allowed origins to prefetch (empty allows all)
 * @param {Array|RegExp|Function} [options.ignores] - Custom filter(s) that run after origin checks
 * @param {Number} [options.timeout] - Timeout after which prefetching will occur
 * @param {Number} [options.throttle] - The concurrency limit for prefetching
 * @param {Number} [options.limit] - The total number of prefetches to allow
 * @param {Function} [options.timeoutFn] - Custom timeout function
 * @param {Function} [options.onError] - Error handler for failed `prefetch` requests
 * @param {Function} [options.prefetchChunks] - Function to prefetch chunks for route URLs (with route manifest for URL mapping)
 * @re
Download .txt
gitextract_udyormir/

├── .babelrc.js
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── dependabot.yml
│   └── workflows/
│       ├── ci.yml
│       ├── codeql.yml
│       ├── lint.yml
│       ├── site.yml
│       └── size-limit.yml
├── .gitignore
├── .size-limit.json
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── demos/
│   ├── basic.html
│   ├── hrefFn/
│   │   ├── 2.html
│   │   ├── 2.json
│   │   └── hrefFn_demo.html
│   ├── network-idle.html
│   ├── network-idle.js
│   ├── news/
│   │   └── README.md
│   ├── news-workbox/
│   │   └── README.md
│   ├── spa/
│   │   └── README.md
│   └── sw.js
├── package.json
├── site/
│   ├── .browserslistrc
│   ├── .config/
│   │   ├── configstore/
│   │   │   └── update-notifier-pnpm.json
│   │   └── glitch-package-manager
│   ├── .eleventy.js
│   ├── .firebaserc
│   ├── .gitignore
│   ├── .stylelintignore
│   ├── .stylelintrc.json
│   ├── LICENSE
│   ├── README.md
│   ├── firebase.json
│   ├── index.njk
│   ├── package.json
│   ├── src/
│   │   ├── _data/
│   │   │   └── site.js
│   │   ├── _includes/
│   │   │   ├── components/
│   │   │   │   ├── chrome-extension.njk
│   │   │   │   ├── copy-snippet.njk
│   │   │   │   ├── download.njk
│   │   │   │   ├── github-fork.njk
│   │   │   │   ├── github-star.njk
│   │   │   │   ├── heading.njk
│   │   │   │   ├── installation.njk
│   │   │   │   ├── over-prefetching.njk
│   │   │   │   ├── react.njk
│   │   │   │   ├── trusted-by.njk
│   │   │   │   ├── usage.njk
│   │   │   │   ├── use-with.njk
│   │   │   │   ├── why-prefetch.njk
│   │   │   │   └── why-quicklink.njk
│   │   │   └── layouts/
│   │   │       ├── base.njk
│   │   │       ├── favicons.njk
│   │   │       ├── footer.njk
│   │   │       ├── head.njk
│   │   │       ├── header.njk
│   │   │       ├── highlighted-section-wrapper.njk
│   │   │       ├── normal-section-wrapper.njk
│   │   │       └── social.njk
│   │   ├── api.njk
│   │   ├── approach.njk
│   │   ├── assets/
│   │   │   ├── js/
│   │   │   │   └── script.js
│   │   │   └── styles/
│   │   │       ├── _copy-snippet.scss
│   │   │       ├── github-markdown.scss
│   │   │       ├── main.scss
│   │   │       └── vendor/
│   │   │           ├── _github-markdown.scss
│   │   │           └── _prism.scss
│   │   ├── demo.njk
│   │   ├── index.njk
│   │   ├── measure.njk
│   │   ├── robots.njk
│   │   ├── site.webmanifest
│   │   └── sitemap.njk
│   └── watch.json
├── src/
│   ├── chunks.mjs
│   ├── index.mjs
│   ├── prefetch.mjs
│   ├── prerender.mjs
│   ├── react-chunks.js
│   └── request-idle-callback.mjs
├── test/
│   ├── fixtures/
│   │   ├── 1.html
│   │   ├── 2.html
│   │   ├── 3.html
│   │   ├── 4.html
│   │   ├── index.html
│   │   ├── main.css
│   │   ├── rmanifest.json
│   │   ├── test-allow-origin-all.html
│   │   ├── test-allow-origin.html
│   │   ├── test-basic-usage.html
│   │   ├── test-custom-dom-source.html
│   │   ├── test-custom-href-function.html
│   │   ├── test-delay.html
│   │   ├── test-es-modules.html
│   │   ├── test-ignore-basic.html
│   │   ├── test-ignore-multiple.html
│   │   ├── test-limit.html
│   │   ├── test-node-list.html
│   │   ├── test-prefetch-chunks.html
│   │   ├── test-prefetch-duplicate-shared.html
│   │   ├── test-prefetch-duplicate.html
│   │   ├── test-prefetch-multiple.html
│   │   ├── test-prefetch-single.html
│   │   ├── test-prerender-andPrefetch.html
│   │   ├── test-prerender-only.html
│   │   ├── test-prerender-wrapper-multiple.html
│   │   ├── test-prerender-wrapper-single.html
│   │   ├── test-same-origin.html
│   │   ├── test-threshold.html
│   │   └── test-throttle.html
│   └── quicklink.spec.js
└── translations/
    └── zh-cn/
        └── README.md
Download .txt
SYMBOL INDEX (21 symbols across 8 files)

FILE: demos/network-idle.js
  function networkIdleCallback (line 27) | function networkIdleCallback(fn, options = {timeout: 0}) {
  function handleMessage (line 95) | function handleMessage(event) {

FILE: site/.eleventy.js
  constant IS_PRODUCTION (line 17) | const IS_PRODUCTION = process.env.NODE_ENV === 'production';

FILE: site/src/_data/site.js
  constant IS_NETLIFY (line 8) | const IS_NETLIFY = process.env.NETLIFY === 'true';

FILE: site/src/assets/js/script.js
  function initGoToTopBtn (line 4) | function initGoToTopBtn() {

FILE: src/chunks.mjs
  function isIgnored (line 34) | function isIgnored(node, filter) {
  function listen (line 61) | function listen(options = {}) {
  function prefetch (line 128) | function prefetch(url, isPriority) {

FILE: src/index.mjs
  function isIgnored (line 40) | function isIgnored(node, filter) {
  function checkConnection (line 51) | function checkConnection(conn) {
  function listen (line 96) | function listen(options = {}) {
  function prefetch (line 237) | function prefetch(urls, isPriority, checkAccessControlAllowOrigin, check...
  function prerender (line 272) | function prerender(urls, eagerness = 'immediate') {

FILE: src/prefetch.mjs
  function hasPrefetch (line 26) | function hasPrefetch(link) {
  function viaDOM (line 37) | function viaDOM(url, hasCrossorigin) {
  function viaXHR (line 59) | function viaXHR(url, hasCredentials) {
  function viaFetch (line 89) | function viaFetch(url, hasModeCors, hasCredentials, isPriority) {
  function prefetchOnHover (line 112) | function prefetchOnHover(callback, url, onlyOnMouseover, ...args) {

FILE: src/prerender.mjs
  function addSpeculationRules (line 26) | function addSpeculationRules(urlsToPrerender, eagerness) {
  function removeSpeculationRule (line 57) | function removeSpeculationRule(specMap, url) {
  function hasSpecRulesSupport (line 74) | function hasSpecRulesSupport() {
Condensed preview — 119 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (270K chars).
[
  {
    "path": ".babelrc.js",
    "chars": 184,
    "preview": "'use strict';\n\nmodule.exports = {\n  presets: [\n    '@babel/preset-react',\n    [\n      '@babel/preset-env',\n      {\n     "
  },
  {
    "path": ".editorconfig",
    "chars": 217,
    "preview": "# https://editorconfig.org/\n\nroot = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 2\nindent_style = space\ninse"
  },
  {
    "path": ".eslintignore",
    "chars": 83,
    "preview": "**/*.min.js\n**/dist/\n**/build/\n**/vendor/\n# explicitly include dot js files\n!.*.js\n"
  },
  {
    "path": ".eslintrc.js",
    "chars": 1394,
    "preview": "'use strict';\n\nmodule.exports = {\n  env: {\n    browser: true,\n    es6: true,\n    node: true,\n  },\n  parserOptions: {\n   "
  },
  {
    "path": ".gitattributes",
    "chars": 71,
    "preview": "# Enforce Unix newlines\n* text=auto eol=lf\n\n*.njk linguist-language=js\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 698,
    "preview": "---\nname: Bug report\nabout: Something is not working as expected\nlabels:\n---\n\n**Before you start**\n\nPlease take a look a"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 430,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\nlabels:\n---\n\n**Is your feature request related to a pr"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 185,
    "preview": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: monthly\n   "
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 776,
    "preview": "name: CI\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n  workflow_dispatch:\n\nenv:\n  FORCE_COLOR: 2\n  NODE: 24\n"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "chars": 1007,
    "preview": "name: \"CodeQL\"\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n  schedule:\n    - cron"
  },
  {
    "path": ".github/workflows/lint.yml",
    "chars": 662,
    "preview": "name: Lint\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n  workflow_dispatch:\n\nenv:\n  FORCE_COLOR: 2\n  NODE: 2"
  },
  {
    "path": ".github/workflows/site.yml",
    "chars": 708,
    "preview": "name: Site\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n  workflow_dispatch:\n\nenv:\n  FORCE_COLOR: 2\n  NODE: 2"
  },
  {
    "path": ".github/workflows/size-limit.yml",
    "chars": 730,
    "preview": "name: Size limit\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n  workflow_dispatch:\n\nenv:\n  FORCE_COLOR: 2\n  N"
  },
  {
    "path": ".gitignore",
    "chars": 493,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\ndist\nbuild\n.DS_Store\n\n# Runtime data\npids\n*.pid\n*.seed\n"
  },
  {
    "path": ".size-limit.json",
    "chars": 343,
    "preview": "[\n  {\n    \"path\": \"dist/quicklink.js\",\n    \"limit\": \"2.5 kB\",\n    \"gzip\": true\n  },\n  {\n    \"path\": \"dist/quicklink.mjs\""
  },
  {
    "path": "CHANGELOG.md",
    "chars": 32718,
    "preview": "## 2.3.0 (2022-08-05)\n\n* 2.3.0 ([6189deb](https://github.com/GoogleChromeLabs/quicklink/commit/6189deb))\n* Add support f"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 2572,
    "preview": "# How to Contribute\n\nWe'd love to accept your patches and contributions to this project. There are\njust a few small guid"
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 19756,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/GoogleChromeLabs/quicklink/HEAD/assets/images/logos/ban"
  },
  {
    "path": "demos/basic.html",
    "chars": 1305,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Basic demo</title>\n  <meta name=\"viewport\" co"
  },
  {
    "path": "demos/hrefFn/2.html",
    "chars": 480,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch experiments</title>\n  <meta name=\"vi"
  },
  {
    "path": "demos/hrefFn/2.json",
    "chars": 267,
    "preview": "{\n  \"title\": \"API Target to Prefetch Example\",\n  \"description\": \"Lorem ipsum dolor sit amet, consectetur adipisicing eli"
  },
  {
    "path": "demos/hrefFn/hrefFn_demo.html",
    "chars": 1902,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Basic demo</title>\n  <meta name=\"viewport\" co"
  },
  {
    "path": "demos/network-idle.html",
    "chars": 1352,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>network-idle-callback demo</title>\n  <meta na"
  },
  {
    "path": "demos/network-idle.js",
    "chars": 2975,
    "preview": "// This script is a localized version of the upstream\n// https://github.com/pastelsky/network-idle-callback\n// which fix"
  },
  {
    "path": "demos/news/README.md",
    "chars": 408,
    "preview": "# Demo: Quicklink basic usage\n\nA demo showing how to use Quicklink on a simple multi-page website.\n\n## Glitch Source\n\n- "
  },
  {
    "path": "demos/news-workbox/README.md",
    "chars": 475,
    "preview": "# Demo: Quicklink usage with workbox\n\nA demo showing how to use Quicklink with Workbox for offline caching and links in "
  },
  {
    "path": "demos/spa/README.md",
    "chars": 555,
    "preview": "# Demo: Quicklink integration for create-react-app\n\nA demo showing how to use Quicklink with in a create-react-app site."
  },
  {
    "path": "demos/sw.js",
    "chars": 794,
    "preview": "/* eslint-env serviceworker */\n\n'use strict';\n\nimportScripts('https://unpkg.com/network-idle-callback@1.0.1/lib/request-"
  },
  {
    "path": "package.json",
    "chars": 2629,
    "preview": "{\n  \"name\": \"quicklink\",\n  \"version\": \"3.0.1\",\n  \"description\": \"Faster subsequent page-loads by prefetching in-viewport"
  },
  {
    "path": "site/.browserslistrc",
    "chars": 64,
    "preview": "# https://github.com/browserslist/browserslist#readme\n\ndefaults\n"
  },
  {
    "path": "site/.config/configstore/update-notifier-pnpm.json",
    "chars": 165,
    "preview": "{\n  \"optOut\": false,\n  \"lastUpdateCheck\": 1574808403652,\n  \"update\": {\n    \"latest\": \"4.3.3\",\n    \"current\": \"2.25.5\",\n "
  },
  {
    "path": "site/.config/glitch-package-manager",
    "chars": 5,
    "preview": "pnpm\n"
  },
  {
    "path": "site/.eleventy.js",
    "chars": 2623,
    "preview": "/* eslint-env node */\n\n/* eslint-disable new-cap */\n\n'use strict';\n\nconst {EleventyHtmlBasePlugin: htmlBasePlugin} = req"
  },
  {
    "path": "site/.firebaserc",
    "chars": 57,
    "preview": "{\n  \"projects\": {\n    \"default\": \"quicklink-6a87b\"\n  }\n}\n"
  },
  {
    "path": "site/.gitignore",
    "chars": 188,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Compiled binary addons (https://nodejs.org/api/addon"
  },
  {
    "path": "site/.stylelintignore",
    "chars": 55,
    "preview": "**/*.min.css\n**/*.min.scss\n**/dist/\n**/vendor/\n/build/\n"
  },
  {
    "path": "site/.stylelintrc.json",
    "chars": 400,
    "preview": "{\n  \"extends\": [\n    \"stylelint-config-twbs-bootstrap\"\n  ],\n  \"reportInvalidScopeDisables\": true,\n  \"reportNeedlessDisab"
  },
  {
    "path": "site/LICENSE",
    "chars": 11358,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "site/README.md",
    "chars": 669,
    "preview": "# eleventy-quicklink-website\n\nOur canonical site source for Quicklink. This project uses [Eleventy](https://www.11ty.io/"
  },
  {
    "path": "site/firebase.json",
    "chars": 1291,
    "preview": "{\n  \"hosting\": {\n    \"public\": \"build\",\n    \"site\": \"getquicklink\",\n    \"ignore\": [\n      \"firebase.json\",\n      \"**/.*\""
  },
  {
    "path": "site/index.njk",
    "chars": 663,
    "preview": "---\ntitle: Quicklink\nlayout: layouts/base.njk\ndescription: Faster subsequent page-loads by prefetching in-viewport links"
  },
  {
    "path": "site/package.json",
    "chars": 930,
    "preview": "{\n  \"name\": \"eleventy-quicklink-website\",\n  \"description\": \"\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"scripts\": {\n "
  },
  {
    "path": "site/src/_data/site.js",
    "chars": 3613,
    "preview": "/* eslint-env node */\n\n'use strict';\n\nconst path = require('node:path');\nconst process = require('node:process');\n\nconst"
  },
  {
    "path": "site/src/_includes/components/chrome-extension.njk",
    "chars": 1117,
    "preview": "{% extends \"layouts/normal-section-wrapper.njk\" %}\n{% block section %}\n{% sectionTitle \"Chrome Extension\" %}\n{{ \"We've d"
  },
  {
    "path": "site/src/_includes/components/copy-snippet.njk",
    "chars": 879,
    "preview": "{% extends \"layouts/normal-section-wrapper.njk\" %}\n{% block section %}\n<div class=\"copy-snippet-widget\">\n  <div class=\"t"
  },
  {
    "path": "site/src/_includes/components/download.njk",
    "chars": 651,
    "preview": "{% extends \"layouts/normal-section-wrapper.njk\" %}\n{% block section %}\n{% sectionTitle \"Download\" %}\n<ul class=\"no-bulle"
  },
  {
    "path": "site/src/_includes/components/github-fork.njk",
    "chars": 249,
    "preview": "<a class=\"github-button\" href=\"{{ site.quicklinkGithubURL + '/fork' }}\"{% if githubLarge === true %} data-size=\"large\"{%"
  },
  {
    "path": "site/src/_includes/components/github-star.njk",
    "chars": 232,
    "preview": "<a class=\"github-button\" href=\"{{ site.quicklinkGithubURL }}\"{% if githubLarge === true %} data-size=\"large\"{% endif %} "
  },
  {
    "path": "site/src/_includes/components/heading.njk",
    "chars": 311,
    "preview": "{% extends \"layouts/highlighted-section-wrapper.njk\" %}\n{% block section %}\n<h1 class=\"heading text-center\">\n\"We impleme"
  },
  {
    "path": "site/src/_includes/components/installation.njk",
    "chars": 402,
    "preview": "{% extends \"layouts/normal-section-wrapper.njk\" %}\n{% block section %}\n{% sectionTitle \"Installation\" %}\n{{ \"For use wit"
  },
  {
    "path": "site/src/_includes/components/over-prefetching.njk",
    "chars": 1596,
    "preview": "{% extends \"layouts/normal-section-wrapper.njk\" %}\n{% block section %}\n{% sectionTitle \"Concerned about over-prefetching"
  },
  {
    "path": "site/src/_includes/components/react.njk",
    "chars": 1371,
    "preview": "{% extends \"layouts/normal-section-wrapper.njk\" %}\n{% block section %}\n{% sectionTitle \"Single page apps (React)\" %}\n{% "
  },
  {
    "path": "site/src/_includes/components/trusted-by.njk",
    "chars": 654,
    "preview": "{% extends \"layouts/normal-section-wrapper.njk\" %}\n{% block section %}\n{% sectionTitle \"Trusted by\" %}\n<div class=\"flex-"
  },
  {
    "path": "site/src/_includes/components/usage.njk",
    "chars": 1338,
    "preview": "{% extends \"layouts/normal-section-wrapper.njk\" %}\n{% block section %}\n{% sectionTitle \"Usage\" %}\n{{ \"Once initialized, "
  },
  {
    "path": "site/src/_includes/components/use-with.njk",
    "chars": 525,
    "preview": "{% extends \"layouts/normal-section-wrapper.njk\" %}\n{% block section %}\n{% sectionTitle \"Use with\" %}\n<div class=\"center\""
  },
  {
    "path": "site/src/_includes/components/why-prefetch.njk",
    "chars": 351,
    "preview": "{% extends \"layouts/normal-section-wrapper.njk\" %}\n{% block section %}\n{% sectionTitle \"Why Quicklink\\'s prefetch?\" %}\n\n"
  },
  {
    "path": "site/src/_includes/components/why-quicklink.njk",
    "chars": 651,
    "preview": "{% extends \"layouts/normal-section-wrapper.njk\" %}\n{% block section %}\n<h2 class=\"flex-between-center\">\n<span>Why Quickl"
  },
  {
    "path": "site/src/_includes/layouts/base.njk",
    "chars": 1226,
    "preview": "<!doctype html>\n<html lang=\"en\">\n{% include \"layouts/head.njk\" %}\n\n<body>\n  {% include \"layouts/header.njk\" %}\n  <main c"
  },
  {
    "path": "site/src/_includes/layouts/favicons.njk",
    "chars": 462,
    "preview": "<link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/assets/images/icons/apple-touch-icon.png\">\n<link rel=\"icon\" type=\"im"
  },
  {
    "path": "site/src/_includes/layouts/footer.njk",
    "chars": 329,
    "preview": "<footer class=\"site-footer\">\n  <p class=\"text-center\">\n    <a href=\"{{ bottomResource.link if bottomResource.link else s"
  },
  {
    "path": "site/src/_includes/layouts/head.njk",
    "chars": 819,
    "preview": "<head>\n  <meta charset=\"utf-8\">\n  <title>{{ title + \" | \" + site.title if title else site.title }}</title>\n  <meta name="
  },
  {
    "path": "site/src/_includes/layouts/header.njk",
    "chars": 852,
    "preview": "<header class=\"page-header primary-font-color\">\n  <div class=\"page-header-content\">\n    <div class=\"page-header__title\">"
  },
  {
    "path": "site/src/_includes/layouts/highlighted-section-wrapper.njk",
    "chars": 106,
    "preview": "<div class=\"highlighted-section highlighted-section__text\">\n  {% block section %}\n  {% endblock %}\n</div>\n"
  },
  {
    "path": "site/src/_includes/layouts/normal-section-wrapper.njk",
    "chars": 75,
    "preview": "<div class=\"normal-section\">\n  {% block section %}\n  {% endblock %}\n</div>\n"
  },
  {
    "path": "site/src/_includes/layouts/social.njk",
    "chars": 529,
    "preview": "{% set socialImagePath = site.url + site.socialImage -%}\n\n<meta name=\"og:title\" content=\"{{ title }}\">\n<meta name=\"og:de"
  },
  {
    "path": "site/src/api.njk",
    "chars": 12653,
    "preview": "---\ntitle: API\nlayout: layouts/base.njk\ndescription: Faster subsequent page-loads by prefetching in-viewport links durin"
  },
  {
    "path": "site/src/approach.njk",
    "chars": 5128,
    "preview": "---\ntitle: Approach\nlayout: layouts/base.njk\ndescription: Faster subsequent page-loads by prefetching in-viewport links "
  },
  {
    "path": "site/src/assets/js/script.js",
    "chars": 1486,
    "preview": "window.addEventListener('load', () => {\n  'use strict';\n\n  function initGoToTopBtn() {\n    const goTopBtn = document.que"
  },
  {
    "path": "site/src/assets/styles/_copy-snippet.scss",
    "chars": 1972,
    "preview": ".copy-snippet-widget {\n  background: linear-gradient(hsla(0deg, 0%, 100%, .775), hsla(0deg, 0%, 100%, .7));\n  border-rad"
  },
  {
    "path": "site/src/assets/styles/github-markdown.scss",
    "chars": 694,
    "preview": "// stylelint-disable declaration-no-important\n\n@use \"vendor/github-markdown\";\n\n.markdown-body {\n  box-sizing: border-box"
  },
  {
    "path": "site/src/assets/styles/main.scss",
    "chars": 5329,
    "preview": "@use \"vendor/prism\";\n@use \"copy-snippet\";\n\nhtml {\n  height: 100%;\n}\n\nhtml,\nbody {\n  min-height: 100%;\n}\n\nbody {\n  margin"
  },
  {
    "path": "site/src/assets/styles/vendor/_github-markdown.scss",
    "chars": 15067,
    "preview": ".markdown-body .octicon {\n  display: inline-block;\n  fill: currentcolor;\n  vertical-align: text-bottom;\n}\n\n.markdown-bod"
  },
  {
    "path": "site/src/assets/styles/vendor/_prism.scss",
    "chars": 1765,
    "preview": "/**\n * prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML\n * Based on https://github.com/chrisk"
  },
  {
    "path": "site/src/demo.njk",
    "chars": 4985,
    "preview": "---\ntitle: Demo\nlayout: layouts/base.njk\ndescription: Faster subsequent page-loads by prefetching in-viewport links duri"
  },
  {
    "path": "site/src/index.njk",
    "chars": 771,
    "preview": "---\ntitle: Home\nlayout: layouts/base.njk\ndescription: Faster subsequent page-loads by prefetching in-viewport links duri"
  },
  {
    "path": "site/src/measure.njk",
    "chars": 10907,
    "preview": "---\ntitle: Measure\nlayout: layouts/base.njk\ndescription: Faster subsequent page-loads by prefetching in-viewport links d"
  },
  {
    "path": "site/src/robots.njk",
    "chars": 203,
    "preview": "---\npermalink: /robots.txt\nlayout: null\neleventyExcludeFromCollections: true\n---\n# www.robotstxt.org\n\nUser-agent: *\nDisa"
  },
  {
    "path": "site/src/site.webmanifest",
    "chars": 432,
    "preview": "{\n  \"name\": \"Quicklink\",\n  \"short_name\": \"Quicklink\",\n  \"icons\": [\n    {\n      \"src\": \"/assets/images/icons/android-chro"
  },
  {
    "path": "site/src/sitemap.njk",
    "chars": 356,
    "preview": "---\npermalink: /sitemap.xml\nlayout: null\neleventyExcludeFromCollections: true\n---\n<?xml version=\"1.0\" encoding=\"utf-8\"?>"
  },
  {
    "path": "site/watch.json",
    "chars": 248,
    "preview": "{\n  \"install\": {\n    \"include\": [\n      \"^package\\\\.json$\",\n      \"^\\\\.env$\"\n    ]\n  },\n  \"restart\": {\n    \"exclude\": [\n"
  },
  {
    "path": "src/chunks.mjs",
    "chars": 5387,
    "preview": "/**\n * Copyright 2018 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "src/index.mjs",
    "chars": 11718,
    "preview": "/**\n * Copyright 2018 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "src/prefetch.mjs",
    "chars": 4629,
    "preview": "/**\n * Portions copyright 2018 Google Inc.\n * Inspired by Gatsby's prefetching logic, with those portions\n * remaining M"
  },
  {
    "path": "src/prerender.mjs",
    "chars": 2419,
    "preview": "/**\n * Portions copyright 2018 Google Inc.\n * Inspired by Gatsby's prefetching logic, with those portions\n * remaining M"
  },
  {
    "path": "src/react-chunks.js",
    "chars": 2380,
    "preview": "/**\n * Copyright 2019-2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may n"
  },
  {
    "path": "src/request-idle-callback.mjs",
    "chars": 951,
    "preview": "/**\n * Copyright 2018 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "test/fixtures/1.html",
    "chars": 480,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch experiments</title>\n  <meta name=\"vi"
  },
  {
    "path": "test/fixtures/2.html",
    "chars": 480,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch experiments</title>\n  <meta name=\"vi"
  },
  {
    "path": "test/fixtures/3.html",
    "chars": 480,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch experiments</title>\n  <meta name=\"vi"
  },
  {
    "path": "test/fixtures/4.html",
    "chars": 480,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch experiments</title>\n  <meta name=\"vi"
  },
  {
    "path": "test/fixtures/index.html",
    "chars": 853,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch experiments</title>\n  <meta name=\"vie"
  },
  {
    "path": "test/fixtures/main.css",
    "chars": 81,
    "preview": "body {\n  font-family: Roboto, Arial, sans-serif;\n}\n\n.screen {\n  height: 100vh;\n}\n"
  },
  {
    "path": "test/fixtures/rmanifest.json",
    "chars": 1015,
    "preview": "{\n  \"/about\": [\n    {\n      \"type\": \"style\",\n      \"href\": \"/test/static/css/about.00ec0d84.chunk.css\"\n    },\n    {\n    "
  },
  {
    "path": "test/fixtures/test-allow-origin-all.html",
    "chars": 744,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch: Allow All Origins</title>\n  <meta n"
  },
  {
    "path": "test/fixtures/test-allow-origin.html",
    "chars": 737,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch: Allowed Origins</title>\n  <meta nam"
  },
  {
    "path": "test/fixtures/test-basic-usage.html",
    "chars": 664,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch: Basic Usage</title>\n  <meta name=\"v"
  },
  {
    "path": "test/fixtures/test-custom-dom-source.html",
    "chars": 639,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch: Custom DOM source</title>\n  <meta n"
  },
  {
    "path": "test/fixtures/test-custom-href-function.html",
    "chars": 570,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch: Custom function to build the URL.</"
  },
  {
    "path": "test/fixtures/test-delay.html",
    "chars": 599,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch: Basic Usage</title>\n  <meta name=\"v"
  },
  {
    "path": "test/fixtures/test-es-modules.html",
    "chars": 587,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch: ES Modules</title>\n  <meta name=\"vi"
  },
  {
    "path": "test/fixtures/test-ignore-basic.html",
    "chars": 749,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch: Ignore Basic</title>\n  <meta name=\""
  },
  {
    "path": "test/fixtures/test-ignore-multiple.html",
    "chars": 917,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch: Ignore Multiple</title>\n  <meta nam"
  },
  {
    "path": "test/fixtures/test-limit.html",
    "chars": 494,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch: Basic Usage</title>\n  <meta name=\"v"
  },
  {
    "path": "test/fixtures/test-node-list.html",
    "chars": 712,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch: NodeList case</title>\n  <meta name="
  },
  {
    "path": "test/fixtures/test-prefetch-chunks.html",
    "chars": 1701,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch: Chunk URL list</title>\n  <meta name"
  },
  {
    "path": "test/fixtures/test-prefetch-duplicate-shared.html",
    "chars": 654,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch: Static URL list</title>\n  <meta nam"
  },
  {
    "path": "test/fixtures/test-prefetch-duplicate.html",
    "chars": 619,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch: Static URL list</title>\n  <meta nam"
  },
  {
    "path": "test/fixtures/test-prefetch-multiple.html",
    "chars": 619,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch: Static URL list</title>\n  <meta nam"
  },
  {
    "path": "test/fixtures/test-prefetch-single.html",
    "chars": 597,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch: Static URL list</title>\n  <meta nam"
  },
  {
    "path": "test/fixtures/test-prerender-andPrefetch.html",
    "chars": 613,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch: Basic Usage</title>\n  <meta name=\"v"
  },
  {
    "path": "test/fixtures/test-prerender-only.html",
    "chars": 602,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch: Basic Usage</title>\n  <meta name=\"v"
  },
  {
    "path": "test/fixtures/test-prerender-wrapper-multiple.html",
    "chars": 626,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch: Basic Usage</title>\n  <meta name=\"v"
  },
  {
    "path": "test/fixtures/test-prerender-wrapper-single.html",
    "chars": 594,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch: Basic Usage</title>\n  <meta name=\"v"
  },
  {
    "path": "test/fixtures/test-same-origin.html",
    "chars": 668,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch: Same Origin</title>\n  <meta name=\"v"
  },
  {
    "path": "test/fixtures/test-threshold.html",
    "chars": 886,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch: Basic Usage</title>\n  <meta name=\"v"
  },
  {
    "path": "test/fixtures/test-throttle.html",
    "chars": 497,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Prefetch: Basic Usage</title>\n  <meta name=\"v"
  },
  {
    "path": "test/quicklink.spec.js",
    "chars": 12116,
    "preview": "'use strict';\n\nconst puppeteer = require('puppeteer');\nconst {suite} = require('uvu');\nconst assert = require('uvu/asser"
  },
  {
    "path": "translations/zh-cn/README.md",
    "chars": 6723,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/GoogleChromeLabs/quicklink/HEAD/assets/images/logos/ban"
  }
]

About this extraction

This page contains the full source code of the GoogleChromeLabs/quicklink GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 119 files (246.7 KB), approximately 70.9k tokens, and a symbol index with 21 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!