原教程:
生成图标包及manifest
因为我们最终目的是要制作一个全平台的WEB APP
,所以对于图标的大小、类型适配显得格外重要。可以访问realfavicongenerator进行图标制作及manifest
的生成。
图文教程(可省略)
这一步可以省略,这个是店长的教程图片链接,我只是为了记录我的博客美化记录,后续出问题可以重新部署,各位请理解
选择图片
创建所有图标
调整Windows磁贴图标配色
设置图片相对于source目录的存放路径
设置Web App名称
生成README.md
选择生成
下载资源包
获取图标文件和manifest
配置PWA
- 在博客根目录
[Blogroot]
下打开终端,输入以下指令安装hexo-offline-popup
插件。
1 | npm install hexo-offline-popup --save |
- 修改站点配置文件
[Blogroot]/_config.yml
,在站点配置文件_config.yml
中增加以下内容:
1 | # hexo-offline-popup. |
- 将之前生成的图标包移入相应的目录,例如我是
/img/siteicon/
,所以放到[Blogroot]/source/img/siteicon/
目录下。 - 打开图标包内的
site.webmanifest
,建议修改文件名为manifest.json
并将其放到[Blogroot]/source
目录下,此时还不能直接用,需要添加一些内容,以下是我的manifest.json
配置内容,权且作为参考,其中的theme_color
建议用取色器取设计的图标的主色调,同时务必配置start_url和name的配置项,这关系到你之后能否看到浏览器的应用安装按钮。:
1 | { "lang": "en", |
json
中不要添加任何注释,不然会报错。注意最后一条内容后面不用加逗号”,” 。
- 打开主题配置文件
[Blogroot]/_config.butterfly.yml
,找到PWA
配置项。添加图标路径。这里的theme_color建议改成你图标的主色调,包括manifest.json
中的theme_color
也是如此。
1 | # PWA |
运行
hexo clean
之后hexo generate
,使用hexo server
本地查看或者hexo deploy
部署到网站上。可以通过Chrome插件Lighthouse
检查PWA
配置是否生效以及配置是否正确。在Chrome浏览器中打开站点,按F12打开控制台,在右上角找到Lighthouse
,可能没显示出来,在>>
里找找。使用
hexo-offline-popup
以后,如果还开启了pjax
,可能遇到页面URL带着长长的后缀。形似index.html?_sw-precache=fff6559539ab8f2d6043bcfa832ce38f
。此处感谢Android(矩阵)大佬提供的方案,把以下js引入即可,实质是劫持了pjax,并对其链接进行重定向:
1 | //重定向浏览器地址 |
而workbox
是通过设置 directoryIndex:null
来去掉index.html
的。这会导致PWA
无法加载索引文件,也就是说无法从PWA
加载index.html
,最终影响离线观看博客的体验。
安装必要插件
既然要使用gulp
配合workbox
实现PWA
,自然少不了安装这两个插件。1
2npm install --global gulp-cli # 全局安装gulp命令集
npm install workbox-build gulp --save # 安装workbox和gulp插件创建
gulpfile.js
在Hexo的根目录,创建一个gulpfile.js
文件,打开[Blogroot]/gulpfile.js
,
输入1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20const gulp = require("gulp");
const workbox = require("workbox-build");
gulp.task('generate-service-worker', () => {
return workbox.injectManifest({
swSrc: './sw-template.js',
swDest: './public/sw.js',
globDirectory: './public',
globPatterns: [
// 缓存所有以下类型的文件,极端不推荐
// "**/*.{html,css,js,json,woff2,xml}"
// 推荐只缓存404,主页和主要样式和脚本。
"404.html","index.html","js/main.js","css/index.css"
],
modifyURLPrefix: {
"": "./"
}
});
});
gulp.task("default", gulp.series("generate-service-worker"));创建在Hexo的根目录,创建一个
sw-template.js
文件,打开[Blogroot]/sw-template.js
,输入以下内容:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96const workboxVersion = '5.1.3';
importScripts(`https://storage.googleapis.com/workbox-cdn/releases/${workboxVersion}/workbox-sw.js`);
workbox.core.setCacheNameDetails({
prefix: "冰梦"
});
workbox.core.skipWaiting();
workbox.core.clientsClaim();
// 注册成功后要立即缓存的资源列表
// 具体缓存列表在gulpfile.js中配置,见下文
workbox.precaching.precacheAndRoute(self.__WB_MANIFEST,{
directoryIndex: null
});
// 清空过期缓存
workbox.precaching.cleanupOutdatedCaches();
// 图片资源(可选,不需要就注释掉)
workbox.routing.registerRoute(
/\.(?:png|jpg|jpeg|gif|bmp|webp|svg|ico)$/,
new workbox.strategies.CacheFirst({
cacheName: "images",
plugins: [
new workbox.expiration.ExpirationPlugin({
maxEntries: 1000,
maxAgeSeconds: 60 * 60 * 24 * 30
}),
new workbox.cacheableResponse.CacheableResponsePlugin({
statuses: [0, 200]
})
]
})
);
// 字体文件(可选,不需要就注释掉)
workbox.routing.registerRoute(
/\.(?:eot|ttf|woff|woff2)$/,
new workbox.strategies.CacheFirst({
cacheName: "fonts",
plugins: [
new workbox.expiration.ExpirationPlugin({
maxEntries: 1000,
maxAgeSeconds: 60 * 60 * 24 * 30
}),
new workbox.cacheableResponse.CacheableResponsePlugin({
statuses: [0, 200]
})
]
})
);
// 谷歌字体(可选,不需要就注释掉)
workbox.routing.registerRoute(
/^https:\/\/fonts\.googleapis\.com/,
new workbox.strategies.StaleWhileRevalidate({
cacheName: "google-fonts-stylesheets"
})
);
workbox.routing.registerRoute(
/^https:\/\/fonts\.gstatic\.com/,
new workbox.strategies.CacheFirst({
cacheName: 'google-fonts-webfonts',
plugins: [
new workbox.expiration.ExpirationPlugin({
maxEntries: 1000,
maxAgeSeconds: 60 * 60 * 24 * 30
}),
new workbox.cacheableResponse.CacheableResponsePlugin({
statuses: [0, 200]
})
]
})
);
// jsdelivr的CDN资源(可选,不需要就注释掉)
workbox.routing.registerRoute(
/^https:\/\/cdn\.jsdelivr\.net/,
new workbox.strategies.CacheFirst({
cacheName: "static-libs",
plugins: [
new workbox.expiration.ExpirationPlugin({
maxEntries: 1000,
maxAgeSeconds: 60 * 60 * 24 * 30
}),
new workbox.cacheableResponse.CacheableResponsePlugin({
statuses: [0, 200]
})
]
})
);
workbox.googleAnalytics.initialize();在
[Blogroot]\themes\butterfly\layout\includes\third-party\
目录下新建pwanotice.pug
文件,
打开[Blogroot]\themes\butterfly\layout\includes\third-party\pwanotice.pug
,输入:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43#app-refresh.app-refresh(style='position: fixed;top: -2.2rem;left: 0;right: 0;z-index: 99999;padding: 0 1rem;font-size: 15px;height: 2.2rem;transition: all 0.3s ease;')
.app-refresh-wrap(style=' display: flex;color: #fff;height: 100%;align-items: center;justify-content: center;')
label ✨ 冰梦更新啦! 👉
a(href='javascript:void(0)' onclick='location.reload()')
span(style='color: #fff;text-decoration: underline;cursor: pointer;') 🍭查看新品🍬
script.
if ('serviceWorker' in navigator) {
if (navigator.serviceWorker.controller) {
navigator.serviceWorker.addEventListener('controllerchange', function() {
showNotification()
})
}
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js')
})
}
function showNotification() {
if (GLOBAL_CONFIG.Snackbar) {
var snackbarBg =
document.documentElement.getAttribute('data-theme') === 'light' ?
GLOBAL_CONFIG.Snackbar.bgLight :
GLOBAL_CONFIG.Snackbar.bgDark
var snackbarPos = GLOBAL_CONFIG.Snackbar.position
Snackbar.show({
text: '✨ 冰梦更新啦! 👉',
backgroundColor: snackbarBg,
duration: 500000,
pos: snackbarPos,
actionText: '🍭查看新品🍬',
actionTextColor: '#fff',
onActionClick: function(e) {
location.reload()
},
})
} else {
var showBg =
document.documentElement.getAttribute('data-theme') === 'light' ?
'#49b1f5' :
'#1f1f1f'
var cssText = `top: 0; background: ${showBg};`
document.getElementById('app-refresh').style.cssText = cssText
}
}修改
[Blog]\themes\butterfly\layout\includes\additional-js.pug
,在文件底部添加以下内容。
1 | if theme.pjax.enable |
将之前生成的图标包移入相应的目录,例如我是
/img/siteicon/
,所以放到[Blog]/source/img/siteicon/
目录下。打开图标包内的
site.webmanifest
,建议修改文件名为manifest.json
并将其放到[Blog]/source
目录下,此时还不能直接用,需要添加一些内容,以下是我的manifest.json
配置内容,权且作为参考,其中的theme_color
建议用取色器取设计的图标的主色调,同时务必配置start_url和name的配置项,这关系到你之后能否看到浏览器的应用安装按钮:
1 | { "lang": "en", |
json
中不要添加任何注释,不然会报错。注意最后一条内容后面不用加逗号”,” 。
运行以下指令
1
2
3
4
5hexo clean # 清空缓存
hexo generate # 重新编译生成页面
gulp # hexo g之后必须运行gulp指令,不然PWA不会生效
hexo server # 打开本地预览
hexo deploy # 部署到网站上查看
运行hexo g
之后必须运行gulp
指令,不然PWA
不会生效!
可以通过Chrome
插件Lighthouse
检查PWA配置是否生效以及配置是否正确。在Chrome
浏览器中打开站点,按F12
打开控制台,在右上角找到Lighthouse
,可能没显示出来,在>>
里找找。
拓展内容,使用gulp压缩静态资源
BUG
御三家,考虑好再斟酌使用,用不用取决于你,现在回头用hexo-offline-popup
还来得及。
- 安装全套压缩插件
1 | # 上面有安装过,不需要重复安装 |
关于 font-min
的补充说明,在本文中,是通过读取所有编译好的 html 文件(./public/*/.html)
中的字符,然后匹配原有字体包内./public/fonts/.ttf
字体样式,输出压缩后的字体包到./public/fontsdest/
目录。所以最终引用字体的相对路径应该是/fontsdest/.ttf
。而本地测试时,如果没有运行 gulp
,自然也就不会输出压缩字体包到 public
目录,也就看不到字体样式。
gulp-terser
只会直接压缩 js
代码,所以不存在因为语法变动导致的错误。事实上,当我们使用 jsdelivr
的 CDN
服务时,只需要在 css
或者 js
的后缀前添加.min
,例如 example.js->example.min.js,JsDelivr
就会自动使用 terser
帮我们压缩好代码。
在 package.json
中添加
1 | "type": "module", |
将
[Blogroot]/gulpfile.js
里的内容修改为:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126import gulp from "gulp";
import cleanCSS from "gulp-clean-css";
import htmlmin from "gulp-htmlmin";
import htmlclean from "gulp-htmlclean";
import workbox from "workbox-build";
import fontmin from "gulp-fontmin";
// 若使用babel压缩js,则取消下方注释,并注释terser的代码
// var uglify = require('gulp-uglify');
// var babel = require('gulp-babel');
// 若使用terser压缩js
import terser from "gulp-terser";
//pwa
gulp.task("generate-service-worker", () => {
return workbox.injectManifest({
swSrc: "./sw-template.js",
swDest: "./public/sw.js",
globDirectory: "./public",
globPatterns: [
// 缓存所有以下类型的文件,极端不推荐
// "**/*.{html,css,js,json,woff2,xml}"
// 推荐只缓存404,主页和主要样式和脚本。
"404.html",
"index.html",
"js/main.js",
"css/index.css",
],
modifyURLPrefix: {
"": "./",
},
});
});
//minify js babel
// 若使用babel压缩js,则取消下方注释,并注释terser的代码
// gulp.task('compress', () =>
// gulp.src(['./public/**/*.js', '!./public/**/*.min.js'])
// .pipe(babel({
// presets: ['@babel/preset-env']
// }))
// .pipe(uglify().on('error', function(e){
// console.log(e);
// }))
// .pipe(gulp.dest('./public'))
// );
// minify js - gulp-tester
// 若使用terser压缩js
gulp.task("compress", () =>
gulp
.src([
"./public/**/*.js",
"!./public/**/*.min.js",
"!./public/js/custom/galmenu.js",
"!./public/js/custom/gitcalendar.js",
])
.pipe(terser())
.pipe(gulp.dest("./public"))
);
//css
gulp.task("minify-css", () => {
return gulp
.src("./public/**/*.css")
.pipe(
cleanCSS({
compatibility: "ie11",
})
)
.pipe(gulp.dest("./public"));
});
// 压缩 public 目录内 html
gulp.task("minify-html", () => {
return gulp
.src("./public/**/*.html")
.pipe(htmlclean())
.pipe(
htmlmin({
removeComments: true, //清除 HTML 註释
collapseWhitespace: true, //压缩 HTML
collapseBooleanAttributes: true, //省略布尔属性的值 <input checked="true"/> ==> <input />
removeEmptyAttributes: true, //删除所有空格作属性值 <input id="" /> ==> <input />
removeScriptTypeAttributes: true, //删除 <script> 的 type="text/javascript"
removeStyleLinkTypeAttributes: true, //删除 <style> 和 <link> 的 type="text/css"
minifyJS: true, //压缩页面 JS
minifyCSS: true, //压缩页面 CSS
minifyURLs: true,
})
)
.pipe(gulp.dest("./public"));
});
//压缩字体
function minifyFont(text, cb) {
gulp
.src("./public/fonts/*.ttf") //原字体所在目录
.pipe(
fontmin({
text: text,
})
)
.pipe(gulp.dest("./public/fontsdest/")) //压缩后的输出目录
.on("end", cb);
}
gulp.task("mini-font", cb => {
var buffers = [];
gulp
.src(["./public/**/*.html"]) //HTML文件所在目录请根据自身情况修改
.on("data", function (file) {
buffers.push(file.contents);
})
.on("end", function () {
var text = Buffer.concat(buffers).toString("utf-8");
minifyFont(text, cb);
});
});
// 执行 gulp 命令时执行的任务
gulp.task(
"default",
gulp.series("generate-service-worker", gulp.parallel("compress", "minify-html", "minify-css", "mini-font"))
);