升级hexo相关依赖包
This commit is contained in:
parent
4d01aa2f10
commit
802bec24b5
@ -36,7 +36,8 @@ skip_render:
|
|||||||
new_post_name: :title.md # File name of new posts
|
new_post_name: :title.md # File name of new posts
|
||||||
default_layout: post
|
default_layout: post
|
||||||
titlecase: false # Transform title into titlecase
|
titlecase: false # Transform title into titlecase
|
||||||
external_link: true # Open external links in new tab
|
external_link:
|
||||||
|
enable: true # Open external links in new tab
|
||||||
filename_case: 0
|
filename_case: 0
|
||||||
render_drafts: false
|
render_drafts: false
|
||||||
post_asset_folder: false
|
post_asset_folder: false
|
||||||
|
|||||||
@ -1,136 +0,0 @@
|
|||||||
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 处理待删除文件的回调函数
|
|
||||||
*/
|
|
||||||
async _queryObjects(params, uploadCallback, deleteCallback) {
|
|
||||||
// 列出所有已存储的对象
|
|
||||||
const ret = await this.client.listObject(params)
|
|
||||||
// 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
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const crypto = require('crypto')
|
|
||||||
|
|
||||||
|
|
||||||
function sortName(item1, item2) {
|
|
||||||
if(item1.name > item2.name) {
|
|
||||||
return 1
|
|
||||||
} else if(item1.name < item2.name) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 递归遍历目录中的所有文件
|
|
||||||
* @param {String} imageFolderPath 文件夹路径
|
|
||||||
* @param {Array} images 图片列表
|
|
||||||
* @param {String} rootPath 根路径
|
|
||||||
*/
|
|
||||||
function readDirSync(imageFolderPath, images, rootPath, callback, count={fileCount:0, finishCount:0}){
|
|
||||||
var files = fs.readdirSync(imageFolderPath);
|
|
||||||
files.forEach(item => {
|
|
||||||
var fileInfo = fs.statSync(`${imageFolderPath}/${item}`)
|
|
||||||
if(fileInfo.isDirectory()){
|
|
||||||
// 该文件是一个目录, 则遍历该目录内容
|
|
||||||
readDirSync(`${imageFolderPath}/${item}`, images, rootPath, callback, count)
|
|
||||||
} else {
|
|
||||||
count.fileCount ++
|
|
||||||
var stream = fs.createReadStream(`${imageFolderPath}/${item}`)
|
|
||||||
var fsHash = crypto.createHash('md5')
|
|
||||||
|
|
||||||
stream.on('data', data => {
|
|
||||||
fsHash.update(data)
|
|
||||||
})
|
|
||||||
stream.on('end', () => {
|
|
||||||
count.finishCount ++
|
|
||||||
images.push({
|
|
||||||
name: `${imageFolderPath}/${item}`.replace(rootPath, ''),
|
|
||||||
md5: fsHash.digest('hex')
|
|
||||||
})
|
|
||||||
if(count.fileCount === count.finishCount && typeof callback === 'function') {
|
|
||||||
callback(images.sort(sortName))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = function (rootPath, imageFloder, callback) {
|
|
||||||
readDirSync(path.resolve(rootPath, imageFloder).replace(/\\/g,'/'), [], rootPath, callback)
|
|
||||||
}
|
|
||||||
89
gulpfile.js
89
gulpfile.js
@ -1,89 +0,0 @@
|
|||||||
const gulp = require('gulp'),
|
|
||||||
htmlmin = require('gulp-htmlmin'), // html压缩组件
|
|
||||||
htmlclean = require('gulp-htmlclean'), // html清理组件
|
|
||||||
plumber = require('gulp-plumber'), // 容错组件(发生错误不跳出任务,并报出错误内容)
|
|
||||||
Hexo = require('hexo'),
|
|
||||||
log = require('fancy-log') // gulp的日志输出
|
|
||||||
|
|
||||||
// 程序执行的传参
|
|
||||||
const argv = require('optimist')
|
|
||||||
// .demand(['accessKey', 'accessSecret', 'deployPath'])
|
|
||||||
.describe('accessKey', '网易云对象存储key')
|
|
||||||
.describe('accessSecret', '网易云对象存储secret')
|
|
||||||
.describe('deployPath', '静态化后发布的目录')
|
|
||||||
.argv
|
|
||||||
|
|
||||||
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中的空白
|
|
||||||
conservativeCollapse: false, //将多个空白折叠为1空白(永远不要完全移除), 必须与 collapseWhitespace=true 一起使用
|
|
||||||
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: false //替换页面URL
|
|
||||||
}
|
|
||||||
return gulp.src('./public/**/*.html')
|
|
||||||
.pipe(plumber())
|
|
||||||
.pipe(htmlclean(cleanOptions))
|
|
||||||
.pipe(htmlmin(minOption))
|
|
||||||
.pipe(gulp.dest('./public'))
|
|
||||||
})
|
|
||||||
|
|
||||||
// 同步图片到对象存储仓库
|
|
||||||
gulp.task('syncImages', () => {
|
|
||||||
const listImages = require('./deploy_utils/list_images')
|
|
||||||
if(!argv.accessKey || !argv.accessSecret) {
|
|
||||||
return Promise.resolve('未获得accessKey以及accessSecret, 跳过图片同步').then(log)
|
|
||||||
}
|
|
||||||
// 同步当前本地存在的所有图片
|
|
||||||
const rootPath = `${process.cwd()}/`.replace(/\\/g, '/')
|
|
||||||
return new Promise(resolve => {
|
|
||||||
listImages(rootPath, 'images/', resolve)
|
|
||||||
}).then(imagesList => {
|
|
||||||
const ImageSynchronizer = require('./deploy_utils/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, rootPath)
|
|
||||||
return imageSynchronizer.synchronize('images/')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
gulp.task('deploy', () => {
|
|
||||||
if(!argv.deployPath) {
|
|
||||||
return Promise.resolve('未获得deployPath, 跳过发布').then(log)
|
|
||||||
}
|
|
||||||
const deploy = require('./deploy_utils/deploy')
|
|
||||||
return deploy.exec('./public', argv.deployPath, false)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 默认任务
|
|
||||||
gulp.task('default',
|
|
||||||
gulp.series('generate', 'compressHtml', 'syncImages', 'deploy') // 串行执行任务
|
|
||||||
)
|
|
||||||
25
package.json
25
package.json
@ -3,34 +3,33 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"hexo": {
|
"hexo": {
|
||||||
"version": "4.2.1"
|
"version": "6.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "hexo server",
|
"dev": "hexo server",
|
||||||
"build": "gulp"
|
"build": "gulp"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hexo": "^4.2.1",
|
"hexo": "^6.0.0",
|
||||||
"hexo-generator-archive": "^1.0.0",
|
"hexo-generator-archive": "^1.0.0",
|
||||||
"hexo-generator-baidu-sitemap": "^0.1.6",
|
"hexo-generator-baidu-sitemap": "^0.1.9",
|
||||||
"hexo-generator-category": "^1.0.0",
|
"hexo-generator-category": "^1.0.0",
|
||||||
"hexo-generator-feed": "^2.2.0",
|
"hexo-generator-feed": "^3.0.0",
|
||||||
"hexo-generator-index": "^1.0.0",
|
"hexo-generator-index": "^2.0.0",
|
||||||
"hexo-generator-json-content": "^4.1.3",
|
"hexo-generator-json-content": "^4.2.3",
|
||||||
"hexo-generator-sitemap": "^2.0.0",
|
"hexo-generator-sitemap": "^2.2.0",
|
||||||
"hexo-generator-tag": "^1.0.0",
|
"hexo-generator-tag": "^1.0.0",
|
||||||
"hexo-renderer-ejs": "^1.0.0",
|
"hexo-renderer-ejs": "^2.0.0",
|
||||||
"hexo-renderer-marked": "^2.0.0",
|
"hexo-renderer-marked": "^5.0.0",
|
||||||
"hexo-server": "^1.0.0",
|
"hexo-server": "^3.0.0",
|
||||||
"hexo-wordcount": "^6.0.1"
|
"hexo-wordcount": "^6.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@xgheaven/nos-node-sdk": "^0.2.5",
|
|
||||||
"gulp": "^4.0.2",
|
"gulp": "^4.0.2",
|
||||||
"gulp-htmlclean": "^2.7.22",
|
"gulp-htmlclean": "^2.7.22",
|
||||||
"gulp-htmlmin": "^5.0.1",
|
"gulp-htmlmin": "^5.0.1",
|
||||||
"gulp-plumber": "^1.2.1",
|
"gulp-plumber": "^1.2.1",
|
||||||
"nunjucks": "^3.2.0",
|
"nunjucks": "^3.2.3",
|
||||||
"optimist": "^0.6.1"
|
"optimist": "^0.6.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
// 替换markdown中图片路径的正则
|
|
||||||
const mdImageRegex = /\]\s*\((?=(?!http).*?\))/gi
|
|
||||||
// 替换所有HTML标签的正则
|
|
||||||
hexo.extend.filter.register('before_post_render', function(article){
|
|
||||||
// article.raw 是原始的文件内容
|
|
||||||
// article.content 是处理过代码块语法高亮的内容
|
|
||||||
if(hexo.config.picture_cdn) {
|
|
||||||
article.content = article.content.replace(mdImageRegex, `](${hexo.config.picture_cdn}`)
|
|
||||||
}
|
|
||||||
return article
|
|
||||||
})
|
|
||||||
Loading…
x
Reference in New Issue
Block a user