├── .eslintignore
├── .gitignore
├── .husky
└── pre-commit
├── .npmignore
├── .prettierrc.yml
├── public
├── test1.html
├── test2.html
├── test3.html
└── index.html
├── LICENSE
├── rollup.config.js
├── package.json
├── .eslintrc.yml
├── types
└── index.d.ts
├── src
└── index.js
├── README.md
└── README_EN.md
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | public/dist
3 | node_modules
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npx lint-staged
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .husky
2 | node_modules
3 | public
4 | src
5 |
6 | .eslintignore
7 | .eslintrc.yml
8 | .gitignore
9 | .prettierrc.yml
10 | package-lock.json
11 | rollup.config.js
12 |
--------------------------------------------------------------------------------
/.prettierrc.yml:
--------------------------------------------------------------------------------
1 | # .prettierrc or .prettierrc.yaml
2 | trailingComma: none # 对象或数组最后是否添加逗号 es5|none|all
3 | tabWidth: 2 # 缩进
4 | printWidth: 160 # 多少个字符后换行
5 | semi: false # 结尾处是否添加分号
6 | singleQuote: true # 是否使用单引号
7 | # always:(x) => x
8 | # avoid:x => x
9 | arrowParens: always # 箭头函数在只有一个参数时
10 |
--------------------------------------------------------------------------------
/public/test1.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | test page 1
8 |
9 |
10 | test page 1
11 |
12 |
13 |
--------------------------------------------------------------------------------
/public/test2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | test page 2
8 |
9 |
10 | test page 2
11 |
12 |
13 |
--------------------------------------------------------------------------------
/public/test3.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | test page 3
8 |
9 |
10 | test page 3
11 |
12 |
13 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Prefetch-Page
8 |
14 |
15 |
20 |
21 |
22 | javascript:void(0);
23 | test page 1
24 | test page 2
25 | test page 3
26 |
27 |
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Lete乐特
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import serve from 'rollup-plugin-serve'
2 | import { terser } from 'rollup-plugin-terser'
3 | import { babel } from '@rollup/plugin-babel'
4 |
5 | const production = !process.env.ROLLUP_WATCH
6 | const plugins = [
7 | // Start a local service
8 | !production && serve({ contentBase: 'public', port: 6870 }),
9 |
10 | // building minify
11 | production && terser(),
12 |
13 | // building babel
14 | babel({ babelHelpers: 'bundled', presets: ['@babel/preset-env'] })
15 | ]
16 |
17 | export default [
18 | {
19 | input: 'src/index.js',
20 | output: {
21 | format: 'iife',
22 | name: 'prefetch',
23 | file: production ? 'dist/prefetch.js' : 'public/dist/prefetch.js'
24 | },
25 | plugins
26 | },
27 | {
28 | input: 'src/index.js',
29 | output: {
30 | format: 'esm',
31 | name: 'prefetch',
32 | file: production ? 'dist/prefetchm.js' : 'public/dist/prefetchm.js'
33 | }
34 | },
35 | {
36 | input: 'src/index.js',
37 | output: {
38 | exports: 'auto',
39 | format: 'cjs',
40 | name: 'prefetch',
41 | file: production ? 'dist/prefetch.cjs' : 'public/dist/prefetch.cjs'
42 | }
43 | }
44 | ]
45 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "prefetch-page",
3 | "version": "1.0.0-beta.6",
4 | "description": "浏览器在空闲时预加载可见区域的超链接,以加速后续页面的加载速度 | Browser prefetchs visible area hyperlinks at idle time to speed up subsequent page loads",
5 | "main": "dist/prefetch.cjs.js",
6 | "module": "dist/prefetch.esm.js",
7 | "unpkg": "dist/prefetch.js",
8 | "jsdelivr": "dist/prefetch.js",
9 | "typings": "types/index.d.ts",
10 | "scripts": {
11 | "dev": "rollup -c -w",
12 | "build": "rollup -c",
13 | "prepare": "husky install",
14 | "lint": "npx eslint src && npx prettier --check src",
15 | "lint:fix": "npx eslint --fix src && npx prettier --check --write src"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "git+https://github.com/Lete114/prefetch-page.git"
20 | },
21 | "keywords": [
22 | "precache",
23 | "prefetch",
24 | "prefetcher",
25 | "IntersectionObserver"
26 | ],
27 | "author": "Lete乐特",
28 | "license": "MIT",
29 | "bugs": {
30 | "url": "https://github.com/Lete114/prefetch-page/issues"
31 | },
32 | "homepage": "https://github.com/Lete114/prefetch-page#readme",
33 | "devDependencies": {
34 | "@babel/core": "^7.17.12",
35 | "@babel/preset-env": "^7.17.12",
36 | "@rollup/plugin-babel": "^5.3.1",
37 | "eslint": "^8.15.0",
38 | "husky": "^8.0.1",
39 | "lint-staged": "^12.4.1",
40 | "prettier": "^2.6.2",
41 | "rollup": "^2.73.0",
42 | "rollup-plugin-serve": "^1.1.0",
43 | "rollup-plugin-terser": "^7.0.2"
44 | },
45 | "lint-staged": {
46 | "*.js": "npx eslint --fix",
47 | "*": "npx prettier --check --write"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | env:
2 | node: true
3 | es6: true
4 | browser: true
5 | extends: eslint:recommended
6 | parserOptions:
7 | ecmaVersion: latest
8 | sourceType: module
9 |
10 | rules:
11 | # off 或 0 - 关闭规则
12 | # warn 或 1 - 开启规则, 使用警告 程序不会退出
13 | # error 或 2 - 开启规则, 使用错误 程序退出
14 |
15 | no-console: error # 禁用console
16 | no-var: error ## 禁止使用var
17 | quotes: [error, single, { avoidEscape: true }] ## 使用单引号
18 | indent: [error, 2, { SwitchCase: 1 }] ## 缩进必须为2格
19 | comma-dangle: [error, never] ## 禁止末尾逗号
20 | semi: [error, never] ## 禁止末尾有分号
21 | arrow-parens: [error, always] ## 箭头函数的参数使用圆括号
22 | array-bracket-spacing: [error, never] ## 禁止在数组括号内出现空格
23 | brace-style: error ## 强制 one true brace style
24 | camelcase: warn ## 强制属性名称为驼峰风格
25 | computed-property-spacing: [error, never] ## 禁止在计算属性内使用空格
26 | curly: [error, multi-line] ## https://cn.eslint.org/docs/rules/curly#multi
27 | eol-last: [error, always] ## 强制使用换行 (LF)
28 | eqeqeq: [error, smart] ## https://cn.eslint.org/docs/rules/eqeqeq#smart
29 | max-depth: [error, 3] ## 强制块语句的最大可嵌套深度
30 | max-len: [warn, 160] ## 强制行的最大长度
31 | max-statements: [warn, 20] ## 限制函数块中的语句的最大数量
32 | new-cap: [warn, { capIsNew: false }] ## 要求构造函数首字母大写
33 | no-extend-native: error ## 禁止扩展原生类型
34 | no-mixed-spaces-and-tabs: error ## 禁止空格和 tab 的混合缩进
35 | no-trailing-spaces: error ## 禁用行尾空格
36 | no-unused-vars: warn ## 禁止出现未使用过的变量
37 | no-use-before-define: [error, nofunc] ## 禁止在变量定义之前使用它们
38 | object-curly-spacing: [error, always] ## 不允许花括号中有空格
39 | keyword-spacing: [error, { before: true, after: true }] ## 强制在关键字前后使用一致的空格
40 | space-unary-ops: error ## 禁止在一元操作符之前或之后存在空格
41 |
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * https://github.com/Lete114/prefetch-page
3 | * Example:
4 | * prefetch({ el: '#box' }) // Select all a tag links under the box tag whose id attribute is
5 | * prefetch({ el: document.body })
6 | * prefetch({ origins: '*' }) // Allows all origins to be prefetched
7 | * prefetch({ origins: ['example.com', 'www.example.com', 'blog.example.com'] })
8 | * prefetch({ limit: 10 })
9 | * prefetch({ threshold: 25 }) // Trigger prefetching after entering 25% of the visible area
10 | * prefetch({ delay: 1000 }) // Prefetch is triggered after staying in the visible area for 1 second
11 | * prefetch({ customs: ['markdown.js', '404.html', 'https://www.example.com'] })
12 | * @param {String|Element} [options.el] - All a tag links under the specified selector (default whole page)
13 | * @param {Array} [options.origins] - Allowed origins to prefetch, Asterisk (*) allows all origins to be prefetched (default [location.hostname])
14 | * @param {Number} [options.limit] - The total number of prefetches to allow (default unlimited)
15 | * @param {Number} [options.threshold] - Percentage of access to the visible area (default 1 pixel appears in the visible area is prefetched)
16 | * @param {Number} [options.delay] - Time each link needs to stay inside viewport before prefetching (milliseconds) (default no delay)
17 | * @param {Array} [options.customs] - Custom prefetch resource (default [])
18 | * @returns {Function} - Executing this function will stop watching all the `` tag hyperlinks that are not prefetched
19 | */
20 | export default function prefetch(options?: Object): Function;
21 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | const { hostname, href } = location
2 |
3 | // Store the prefetched url
4 | let isFetch = []
5 |
6 | /**
7 | * Create a prefetched link tag to insert into the head
8 | * @param {String} href Prefetched url
9 | */
10 | function createLink(href) {
11 | const link = document.createElement('link')
12 | link.rel = 'prefetch'
13 | link.href = href
14 | document.head.appendChild(link)
15 | }
16 |
17 | /**
18 | * Perform prefetching
19 | * @param {String} url Prefetched url
20 | */
21 | function prefetch(url) {
22 | const nav = navigator
23 | const { saveData, effectiveType } = nav.connection || nav.mozConnection || nav.webkitConnection || {}
24 | // If 2G is used or the save-data function is enabled, prefetch is not performed
25 | if (saveData || /2g/.test(effectiveType)) return
26 | // Preventing duplicate prefetching
27 | if (!isFetch.includes(url)) {
28 | isFetch.push(url)
29 | createLink(url)
30 | }
31 | }
32 |
33 | /**
34 | * Whether to support prefetch and IntersectionObserver
35 | * @returns {Boolean}
36 | */
37 | function isSupports() {
38 | const link = document.createElement('link')
39 | return link.relList && link.relList.supports && link.relList.supports('prefetch') && IntersectionObserver
40 | }
41 |
42 | function getEl(el) {
43 | try {
44 | if (typeof el === 'string') {
45 | const _el = document.querySelector(el)
46 | if (_el.nodeType === 1) return _el
47 | }
48 | if (el.nodeType === 1) return el
49 | // eslint-disable-next-line no-empty
50 | } catch (error) {}
51 | return document
52 | }
53 |
54 | /**
55 | * https://github.com/Lete114/prefetch-page
56 | * Example:
57 | * prefetch({ el: '#box' }) // Select all a tag links under the box tag whose id attribute is
58 | * prefetch({ el: document.body })
59 | * prefetch({ origins: '*' }) // Allows all origins to be prefetched
60 | * prefetch({ origins: ['example.com', 'www.example.com', 'blog.example.com'] })
61 | * prefetch({ limit: 10 })
62 | * prefetch({ threshold: 25 }) // Trigger prefetching after entering 25% of the visible area
63 | * prefetch({ delay: 1000 }) // Prefetch is triggered after staying in the visible area for 1 second
64 | * prefetch({ customs: ['markdown.js', '404.html', 'https://www.example.com'] })
65 | * @param {String|Element} [options.el] - All a tag links under the specified selector (default whole page)
66 | * @param {Array} [options.origins] - Allowed origins to prefetch, Asterisk (*) allows all origins to be prefetched (default [location.hostname])
67 | * @param {Number} [options.limit] - The total number of prefetches to allow (default unlimited)
68 | * @param {Number} [options.threshold] - Percentage of access to the visible area (default 1 pixel appears in the visible area is prefetched)
69 | * @param {Number} [options.delay] - Time each link needs to stay inside viewport before prefetching (milliseconds) (default no delay)
70 | * @param {Array} [options.customs] - Custom prefetch resource (default [])
71 | * @returns {Function} - Executing this function will stop watching all the `` tag hyperlinks that are not prefetched
72 | */
73 | export default (options) => {
74 | if (!isSupports()) return
75 | if (!options) options = {}
76 | const el = getEl(options.el)
77 | const origins = options.origins || [hostname]
78 | const limit = options.limit || 1 / 0
79 | const threshold = (options.threshold || 0) / 100
80 | const delay = options.delay || 0
81 | const customs = options.customs || []
82 |
83 | const observer = new IntersectionObserver(observerCallback, { threshold })
84 |
85 | function setDelay(callback, delay) {
86 | if (!delay) return callback()
87 | return setTimeout(callback, delay)
88 | }
89 |
90 | function observerCallback(entries) {
91 | entries.forEach(({ isIntersecting, target }) => {
92 | // Delayed prefetching
93 | function delayFn() {
94 | observer.unobserve(target) // Stop watching
95 | if (isFetch.length < limit) prefetch(target.href)
96 | }
97 |
98 | // Whether the currently observed target appears in the visible window area
99 | // Clear the delay if you leave the visible window during the delay time
100 | if (isIntersecting) target.prefetch = setDelay(delayFn, delay)
101 | else clearTimeout(target.prefetch)
102 | })
103 | }
104 |
105 | el.querySelectorAll('a:not([target=_blank])').forEach((node) => {
106 | // If no origins are set, then all origins are prefetch
107 | // and vice versa only prefetch the set origins
108 | if (origins === '*' || origins.includes(node.hostname)) {
109 | // Avoid watching the current page
110 | if (node.href === href) return
111 | // Filtering hash, Avoid unnecessary observations
112 | const { origin, pathname } = new URL(node.href, href)
113 | origin + pathname === node.href ? observer.observe(node) : ''
114 | }
115 | })
116 | // custom prefetch
117 | for (let i = 0; i < customs.length; i++) prefetch(customs[i])
118 |
119 | return function () {
120 | isFetch = []
121 | observer.disconnect()
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 | 浏览器在空闲时预加载可见区域的超链接,以加速后续页面的速度
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | ## 安装
17 |
18 | 使用 npm:
19 |
20 | ```bash
21 | npm install prefetch-page --save
22 | ```
23 |
24 | 使用 CDN:
25 |
26 | ```html
27 |
28 | ```
29 |
30 | ## 使用方法
31 |
32 | 在浏览器中使用
33 |
34 | ```html
35 |
36 |
47 | ```
48 |
49 | ESModule 模块
50 |
51 | ```js
52 | import prefetch from 'prefetch-page'
53 |
54 | // 页面 dom 加载完成时触发
55 | addEventListener('DOMContentLoaded', function () {
56 | prefetch({ threshold: 25, delay: 3000, limit: 10 })
57 | })
58 |
59 | // 或者在浏览器空闲时执行
60 | requestIdleCallback(function () {
61 | prefetch({ threshold: 25, delay: 3000, limit: 10 })
62 | })
63 | ```
64 |
65 | CommonJS 模块
66 |
67 | ```js
68 | const prefetch = require('prefetch-page')
69 |
70 | // 页面 dom 加载完成时触发
71 | addEventListener('DOMContentLoaded', function () {
72 | prefetch({ threshold: 25, delay: 3000, limit: 10 })
73 | })
74 |
75 | // 或者在浏览器空闲时执行
76 | requestIdleCallback(function () {
77 | prefetch({ threshold: 25, delay: 3000, limit: 10 })
78 | })
79 | ```
80 |
81 | ## 选项 API
82 |
83 | ### prefetch(options)
84 |
85 | 返回类型: `Function`
86 |
87 | 返回一个函数,执行这个函数会停止观察所有未预加载的``标签超链接
88 |
89 | ```html
90 |
91 |
97 | ```
98 |
99 | ### options.el
100 |
101 | 类型: `String|Element`
102 |
103 | 默认值: `document`
104 |
105 | 监听指定 DOM 元素下的超链接
106 |
107 | ```html
108 |
113 | test page 4
114 |
115 |
123 | ```
124 |
125 | ### options.origins
126 |
127 | 类型: `Array`
128 |
129 | 默认值: `[location.hostname]`
130 |
131 | 只对指定的 origin 进行预加载,星号(\*)允许所有源预加载
132 |
133 | ```html
134 | test page 1
135 | test page 2
136 | test page 3
137 |
138 |
146 | ```
147 |
148 | ### options.limit
149 |
150 | 类型: `Number`
151 |
152 | 默认值: `Infinity`
153 |
154 | 限制预加载总数
155 |
156 | ```html
157 | test page 1
158 | test page 2
159 | test page 3
160 |
161 |
168 | ```
169 |
170 | ### options.threshold
171 |
172 | 类型: `Number`
173 |
174 | 默认值: `0`
175 |
176 | 当目标超链接出现在浏览器可视区域大于等于 25% 时触发预加载
177 |
178 | ### options.delay
179 |
180 | 类型: `Number`
181 |
182 | 默认值: `0`
183 |
184 | 当目标超链接出现在浏览器可视区域时延迟 3 秒触发预加载
185 |
186 | 如果未到 3 秒,假设在延迟到 2 秒的时候滚动到其它位置,导致目标超链接不再可视范围时,则终止触发预加载函数
187 |
188 | 如果目标元素再次出现唉可视区域,则重新延迟 3 秒
189 |
190 | ### options.customs
191 |
192 | 类型: `Array`
193 |
194 | 默认值: `undefined`
195 |
196 | 自定义预加载资源,可以是 img、mp3、mp4、json 任何资源
197 |
198 | ```html
199 |
200 |
205 | ```
206 |
--------------------------------------------------------------------------------
/README_EN.md:
--------------------------------------------------------------------------------
1 |
2 | Language:
3 | English
4 |
中文
5 |
6 |
7 |
8 | Browser prefetchs visible area hyperlinks at idle time to speed up subsequent page loads
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | ## Installing
18 |
19 | Using npm:
20 |
21 | ```bash
22 | npm install prefetch-page --save
23 | ```
24 |
25 | Using CDN:
26 |
27 | ````html
28 |
29 |
30 | ## Using Use in your browser ```html
31 |
32 |
43 | ````
44 |
45 | ESModule Modules
46 |
47 | ```js
48 | import prefetch from 'prefetch-page'
49 |
50 | // Triggered when page dom loading is complete
51 | addEventListener('DOMContentLoaded', function () {
52 | prefetch({ threshold: 25, delay: 3000, limit: 10 })
53 | })
54 |
55 | // Or execute it when the browser is idle
56 | requestIdleCallback(function () {
57 | prefetch({ threshold: 25, delay: 3000, limit: 10 })
58 | })
59 | ```
60 |
61 | CommonJS Modules
62 |
63 | ```js
64 | const prefetch = require('prefetch-page')
65 |
66 | // Triggered when page dom loading is complete
67 | addEventListener('DOMContentLoaded', function () {
68 | prefetch({ threshold: 25, delay: 3000, limit: 10 })
69 | })
70 |
71 | // Or execute it when the browser is idle
72 | requestIdleCallback(function () {
73 | prefetch({ threshold: 25, delay: 3000, limit: 10 })
74 | })
75 | ```
76 |
77 | ## Options API
78 |
79 | ### prefetch(options)
80 |
81 | ReturnType: `Function`
82 |
83 | Returns a function executing this function will stop watching all the `` tag hyperlinks that are not prefetched
84 |
85 | ```html
86 |
87 |
93 | ```
94 |
95 | ### options.el
96 |
97 | Type: `String|Element`
98 |
99 | Default: `document`
100 |
101 | Listens for hyperlinks under a given DOM element
102 |
103 | ```html
104 |
109 | test page 4
110 |
111 |
119 | ```
120 |
121 | ### options.origins
122 |
123 | Type: `Array`
124 |
125 | Default: `[location.hostname]`
126 |
127 | Prefetch only the specified origins, Asterisk (\*) allows all origins to be prefetched
128 |
129 | ```html
130 | test page 1
131 | test page 2
132 | test page 3
133 |
134 |
142 | ```
143 |
144 | ### options.limit
145 |
146 | Type: `Number`
147 |
148 | Default: `Infinity`
149 |
150 | Limit the total number of prefetch
151 |
152 | ```html
153 | test page 1
154 | test page 2
155 | test page 3
156 |
157 |
164 | ```
165 |
166 | ### options.threshold
167 |
168 | Type: `Number`
169 |
170 | Default: `0`
171 |
172 | Preload is triggered when the target hyperlink appears in more than 25% of the browser's viewable area
173 |
174 | ### options.delay
175 |
176 | Type: `Number`
177 |
178 | Default: `0`
179 |
180 | Delay 3 seconds to trigger the preload when the target hyperlink appears in the viewable area of the browser
181 |
182 | If the delay is less than 3 seconds, the preload function is terminated if the target hyperlink is no longer visible, assuming that it scrolls to another location when the delay reaches 2 seconds
183 |
184 | If the target element reappears in the visible area, the preload is delayed for 3 seconds again
185 |
186 | ### options.customs
187 |
188 | Type: `Array`
189 |
190 | Default: `undefined`
191 |
192 | Custom preloaded resources, can be img, mp3, mp4, json any resource
193 |
194 | ```html
195 |
196 |
201 | ```
202 |
--------------------------------------------------------------------------------