html压缩+图片同步
This commit is contained in:
parent
c8baba77fe
commit
a0a5800024
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,10 +1,12 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
db.json
|
db.json
|
||||||
|
package-lock.json
|
||||||
*.log
|
*.log
|
||||||
node_modules/
|
node_modules/
|
||||||
public/
|
public/
|
||||||
.deploy*/
|
.deploy*/
|
||||||
|
|
||||||
# 主题构建生成的目标文件
|
# 主题构建生成的目标文件
|
||||||
themes/yilia/source/css/
|
themes/yilia/source/css/
|
||||||
themes/yilia/source/js/
|
themes/yilia/source/js/
|
||||||
|
|||||||
71
gulpfile.js
Normal file
71
gulpfile.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
const gulp = require('gulp'),
|
||||||
|
htmlmin = require('gulp-htmlmin'), //html压缩组件
|
||||||
|
htmlclean = require('gulp-htmlclean'), //html清理组件
|
||||||
|
plumber = require('gulp-plumber'), //容错组件(发生错误不跳出任务,并报出错误内容)
|
||||||
|
Hexo = require('hexo')
|
||||||
|
|
||||||
|
const hexo = new Hexo(process.cwd(), {})
|
||||||
|
|
||||||
|
// 创建静态页面 (等同 hexo generate)
|
||||||
|
gulp.task('generate', async function() {
|
||||||
|
try {
|
||||||
|
await hexo.init()
|
||||||
|
await hexo.call('clean')
|
||||||
|
await hexo.call('generate', { watch: false })
|
||||||
|
return hexo.exit()
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return hexo.exit(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 压缩public目录下的html文件
|
||||||
|
gulp.task('compressHtml', () => {
|
||||||
|
const cleanOptions = {
|
||||||
|
protect: /<\!--%fooTemplate\b.*?%-->/g, //忽略处理
|
||||||
|
unprotect: /<script [^>]*\btype="text\/x-handlebars-template"[\s\S]+?<\/script>/ig //特殊处理
|
||||||
|
}
|
||||||
|
const minOption = {
|
||||||
|
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"
|
||||||
|
removeComments: true, //清除HTML注释
|
||||||
|
minifyJS: true, //压缩页面JS
|
||||||
|
minifyCSS: true, //压缩页面CSS
|
||||||
|
minifyURLs: true //替换页面URL
|
||||||
|
}
|
||||||
|
return gulp.src('./public/**/*.html')
|
||||||
|
.pipe(plumber())
|
||||||
|
.pipe(htmlclean(cleanOptions))
|
||||||
|
.pipe(htmlmin(minOption))
|
||||||
|
.pipe(gulp.dest('./public'))
|
||||||
|
})
|
||||||
|
|
||||||
|
// 同步图片到对象存储仓库
|
||||||
|
gulp.task('syncImages', () => {
|
||||||
|
// 程序执行的传参
|
||||||
|
var argv = require('optimist')
|
||||||
|
.usage('$0 --accessKey [string] --accessSecret [string]')
|
||||||
|
.demand(['accessKey', 'accessSecret'])
|
||||||
|
.argv
|
||||||
|
|
||||||
|
const listImages = require('./image_sync/list_images')
|
||||||
|
// 当前本地存在的所有图片
|
||||||
|
const imagesList = listImages(`${process.cwd()}/source/`, 'images/')
|
||||||
|
const ImageSynchronizer = require('./image_sync/image_synchronize')
|
||||||
|
const nosSetting = {
|
||||||
|
defaultBucket: 'blog-cdn',
|
||||||
|
endpoint: 'http://nos-eastchina1.126.net',
|
||||||
|
accessKey: argv.accessKey,
|
||||||
|
accessSecret: argv.accessSecret
|
||||||
|
}
|
||||||
|
const imageSynchronizer = new ImageSynchronizer(nosSetting, imagesList, `${process.cwd()}/source/`)
|
||||||
|
return imageSynchronizer.synchronize('images/')
|
||||||
|
})
|
||||||
|
|
||||||
|
// 默认任务
|
||||||
|
gulp.task('default',
|
||||||
|
gulp.series('generate', 'compressHtml', 'syncImages') // 串行执行任务
|
||||||
|
)
|
||||||
135
image_sync/image_synchronize.js
Normal file
135
image_sync/image_synchronize.js
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
const fs = require('fs'),
|
||||||
|
path = require('path'),
|
||||||
|
nos = require('@xgheaven/nos-node-sdk')
|
||||||
|
|
||||||
|
class ImageSynchronizer {
|
||||||
|
/**
|
||||||
|
* 构造方法
|
||||||
|
* @param {Object} setting NosClient的设置项
|
||||||
|
* @param {Array} imagesList 本地图片的列表
|
||||||
|
* @param {String} rootPath 本地文件根路径
|
||||||
|
*/
|
||||||
|
constructor(setting, imagesList, rootPath) {
|
||||||
|
// 网易云对象存储调用接口client
|
||||||
|
this.client = new nos.NosClient(setting)
|
||||||
|
this.imagesList = imagesList
|
||||||
|
this.rootPath = rootPath
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 执行文件同步
|
||||||
|
* @param {String} prefix 图片目录前缀
|
||||||
|
*/
|
||||||
|
synchronize(prefix) {
|
||||||
|
return this._queryObjects({limit: this.imagesList.length+1, prefix}, function(pendingUploadFiles){
|
||||||
|
this._uploadObject(pendingUploadFiles)
|
||||||
|
}, function(pendingDeleteFiles){
|
||||||
|
this._deleteObjects(pendingDeleteFiles)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 查询所有对象存储库已存在的文件
|
||||||
|
* @param {Object} params 查询的参数
|
||||||
|
* @param {Function} uploadCallback 处理待上传文件的回调函数
|
||||||
|
* @param {Function} deleteCallback 处理待删除文件的回调函数
|
||||||
|
*/
|
||||||
|
_queryObjects(params, uploadCallback, deleteCallback) {
|
||||||
|
// 列出所有已存储的对象
|
||||||
|
return this.client.listObject(params).then(ret => {
|
||||||
|
// ret 包括 items(元素),limit(请求的数量),nextMarker(下一个标记)
|
||||||
|
let storageItems = ret.items.filter((item) => {
|
||||||
|
return /^images.+?\.(png|jpe?g|gif)$/.test(item.key)
|
||||||
|
}).sort((item1, item2) => {
|
||||||
|
if(item1.key > item2.key) {
|
||||||
|
return 1
|
||||||
|
} else if(item1.key < item2.key) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
// 待上传的文件列表
|
||||||
|
let pendingUploadFiles = this.imagesList.filter(item => {
|
||||||
|
let index = this._binarySearch(storageItems, item.name, 'key', 0, storageItems.length-1)
|
||||||
|
if(index === -1) {
|
||||||
|
// 文件名不存在, 代表是新文件
|
||||||
|
item.type = 'new'
|
||||||
|
return true
|
||||||
|
} else if(storageItems[index].eTag !== item.md5) {
|
||||||
|
// 文件名存在, 但是hash值不同, 代表有变化
|
||||||
|
item.type = 'change'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
// 处理待上传的文件
|
||||||
|
uploadCallback.call(this, pendingUploadFiles)
|
||||||
|
// 待删除的文件列表( 仓库中存在, 本地不存在 )
|
||||||
|
let pendingDeleteFiles = storageItems.filter(item => {
|
||||||
|
return this._binarySearch(this.imagesList, item.key, 'name', 0, this.imagesList.length-1) === -1
|
||||||
|
})
|
||||||
|
// 处理待删除的文件
|
||||||
|
deleteCallback.call(this, pendingDeleteFiles.map(item => item.key))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 上传文件对象
|
||||||
|
* @param {Array} filesList 待上传的文件列表
|
||||||
|
* @param {Number} index 索引值
|
||||||
|
*/
|
||||||
|
_uploadObject(filesList, index=0) {
|
||||||
|
if(index >= filesList.length) return
|
||||||
|
|
||||||
|
this.client.putObject({
|
||||||
|
objectKey: filesList[index].name,
|
||||||
|
body: fs.createReadStream(path.resolve(this.rootPath, filesList[index].name)), // 支持 Buffer/Readable/string
|
||||||
|
}).then(result => {
|
||||||
|
// eTag是上传后远端校验的md5值, 用于和本地进行比对
|
||||||
|
let eTag = result.eTag.replace(/"/g,'')
|
||||||
|
if(filesList[index].md5 === eTag) {
|
||||||
|
console.log(`${filesList[index].name} 上传成功, md5:${eTag} 类型: ${filesList[index].type}`)
|
||||||
|
} else {
|
||||||
|
console.warn(`${filesList[index].name} 上传出错, md5值不一致`)
|
||||||
|
console.warn(`===> 本地文件: ${filesList[index].md5}, 接口返回: ${eTag}`)
|
||||||
|
}
|
||||||
|
this._uploadObject(filesList, ++index)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 批量删除文件
|
||||||
|
* @param {Array} fileNamesList 文件名数组
|
||||||
|
*/
|
||||||
|
_deleteObjects(fileNamesList) {
|
||||||
|
if(!Array.isArray(fileNamesList) || !fileNamesList.length) return
|
||||||
|
|
||||||
|
this.client.deleteMultiObject({
|
||||||
|
objectKeys: fileNamesList
|
||||||
|
}).then(err => {
|
||||||
|
console.log('===> 文件删除成功')
|
||||||
|
fileNamesList.forEach(item => console.log(item))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 二分法查找
|
||||||
|
* @param {Array} arr 执行查找的数组
|
||||||
|
* @param {Object} target 要找到的目标元素
|
||||||
|
* @param {String} key 数组元素上的键
|
||||||
|
* @param {Number} start 查找的范围 起点
|
||||||
|
* @param {Number} end 查找的范围 终点
|
||||||
|
*/
|
||||||
|
_binarySearch(arr, target, key, start, end) {
|
||||||
|
if(!Array.isArray(arr) || !arr.length) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if(start >= end) {
|
||||||
|
return arr[start][key] === target ? start : -1
|
||||||
|
}
|
||||||
|
let index = Math.ceil((start + end)/2)
|
||||||
|
if(arr[index][key] === target) {
|
||||||
|
return index
|
||||||
|
} else if(arr[index][key] > target) {
|
||||||
|
return this._binarySearch(arr, target, key, start, index-1)
|
||||||
|
} else {
|
||||||
|
return this._binarySearch(arr, target, key, index+1, end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
module.exports = ImageSynchronizer
|
||||||
41
image_sync/list_images.js
Normal file
41
image_sync/list_images.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
const crypto = require('crypto')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递归遍历目录中的所有文件
|
||||||
|
* @param {String} imageFolderPath 文件夹路径
|
||||||
|
* @param {Array} images 图片列表
|
||||||
|
* @param {String} rootPath 根路径
|
||||||
|
*/
|
||||||
|
function readDirSync(imageFolderPath, images, rootPath){
|
||||||
|
var files = fs.readdirSync(imageFolderPath);
|
||||||
|
files.forEach(item => {
|
||||||
|
var fileInfo = fs.statSync(`${imageFolderPath}/${item}`)
|
||||||
|
if(fileInfo.isDirectory()){
|
||||||
|
// 该文件是一个目录, 则遍历该目录内容
|
||||||
|
readDirSync(`${imageFolderPath}/${item}`, images, rootPath)
|
||||||
|
}else{
|
||||||
|
//读取一个Buffer
|
||||||
|
let buffer = fs.readFileSync(`${imageFolderPath}/${item}`)
|
||||||
|
let fsHash = crypto.createHash('md5')
|
||||||
|
fsHash.update(buffer)
|
||||||
|
images.push({
|
||||||
|
name: `${imageFolderPath}/${item}`.replace(rootPath, ''),
|
||||||
|
md5: fsHash.digest('hex')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return images
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function (rootPath, imageFloder) {
|
||||||
|
return readDirSync(path.resolve(rootPath, imageFloder), [], rootPath).sort(function(item1, item2){
|
||||||
|
if(item1.name > item2.name) {
|
||||||
|
return 1
|
||||||
|
} else if(item1.name < item2.name) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
}
|
||||||
3923
package-lock.json
generated
3923
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -7,7 +7,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "hexo server",
|
"dev": "hexo server",
|
||||||
"build": "hexo clean && hexo generate"
|
"build": "gulp"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hexo": "^3.8.0",
|
"hexo": "^3.8.0",
|
||||||
@ -25,6 +25,11 @@
|
|||||||
"hexo-wordcount": "^6.0.1"
|
"hexo-wordcount": "^6.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"hexo-cli": "^1.1.0"
|
"@xgheaven/nos-node-sdk": "^0.2.5",
|
||||||
|
"gulp": "^4.0.2",
|
||||||
|
"gulp-htmlclean": "^2.7.22",
|
||||||
|
"gulp-htmlmin": "^5.0.1",
|
||||||
|
"gulp-plumber": "^1.2.1",
|
||||||
|
"optimist": "^0.6.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -158,19 +158,19 @@ svg:not(:root) {
|
|||||||
|
|
||||||
// 1. Contain overflow in all browsers.
|
// 1. Contain overflow in all browsers.
|
||||||
// 2. Improve readability of pre-formatted text in all browsers.
|
// 2. Improve readability of pre-formatted text in all browsers.
|
||||||
pre {
|
// pre {
|
||||||
overflow: auto; // 1
|
// overflow: auto; // 1
|
||||||
white-space: pre; // 2
|
// white-space: pre; // 2
|
||||||
white-space: pre-wrap; // 2
|
// white-space: pre-wrap; // 2
|
||||||
word-wrap: break-word; // 2
|
// word-wrap: break-word; // 2
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 1. Address odd `em`-unit font size rendering in all browsers.
|
// 1. Address odd `em`-unit font size rendering in all browsers.
|
||||||
code,
|
code,
|
||||||
kbd,
|
kbd,
|
||||||
pre,
|
pre,
|
||||||
samp {
|
samp {
|
||||||
font-family: monospace, monospace; // 1
|
font-family: monospace; // 1
|
||||||
font-size: 1em; // 2
|
font-size: 1em; // 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,10 @@
|
|||||||
.article-entry pre,
|
.article-entry pre,
|
||||||
.article-entry .highlight {
|
.article-entry .highlight {
|
||||||
background: #272822;
|
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
padding: 10px 10px;
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
line-height: 22.400000000000002px;
|
line-height: 22.4px;
|
||||||
}
|
}
|
||||||
.article-entry .highlight .gutter pre,
|
.article-entry .highlight .gutter pre,
|
||||||
.article-entry .highlight .gutter pre .line,
|
.article-entry .highlight .gutter pre .line,
|
||||||
@ -31,47 +29,52 @@
|
|||||||
|
|
||||||
.article-entry .highlight {
|
.article-entry .highlight {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
pre {
|
||||||
.article-entry .highlight pre {
|
border: none;
|
||||||
border: none;
|
margin: 0;
|
||||||
margin: 0;
|
padding: 0;
|
||||||
padding: 0;
|
}
|
||||||
}
|
table {
|
||||||
.article-entry .highlight table {
|
margin: 0;
|
||||||
margin: 0;
|
width: auto;
|
||||||
width: auto;
|
}
|
||||||
}
|
td {
|
||||||
.article-entry .highlight td {
|
border: none;
|
||||||
border: none;
|
padding: 0;
|
||||||
padding: 0;
|
&.gutter {
|
||||||
|
background-color: #000;
|
||||||
|
pre {
|
||||||
|
text-align: right;
|
||||||
|
padding: 0 10px;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.code {
|
||||||
|
background-color: #363636;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.line {
|
||||||
|
min-height: 19px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.article-entry .highlight figcaption {
|
.article-entry .highlight figcaption {
|
||||||
color: highlight-comment;
|
color: highlight-comment;
|
||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
|
&::after {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.article-entry .highlight figcaption:before,
|
.article-entry .highlight figcaption::before,
|
||||||
.article-entry .highlight figcaption:after {
|
.article-entry .highlight figcaption::after {
|
||||||
content: "";
|
content: "";
|
||||||
display: table;
|
display: table;
|
||||||
}
|
}
|
||||||
.article-entry .highlight figcaption:after {
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
.article-entry .highlight figcaption a {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
.article-entry .highlight .gutter pre {
|
|
||||||
text-align: right;
|
|
||||||
padding-right: 20px;
|
|
||||||
}
|
|
||||||
.article-entry .highlight .gutter pre .line {
|
|
||||||
text-shadow: none;
|
|
||||||
}
|
|
||||||
.article-entry .highlight .line {
|
|
||||||
color: #fff;
|
|
||||||
min-height: 19px;
|
|
||||||
}
|
|
||||||
.article-entry .gist {
|
.article-entry .gist {
|
||||||
margin: 0 -20px;
|
margin: 0 -20px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user