Compare commits

8 Commits
Author SHA1 Message Date
sookie c4e47fe9fa 安全加固:升级依赖消除高危漏洞,修复构建兼容性问题
【主项目依赖升级】
- hexo 6.x → 8.1.2:移除 swig-templates、swig-extras、旧版 cheerio、
  hexo-fs/hexo-util 等大量漏洞依赖链,解决 critical/high 级别漏洞
- hexo-renderer-marked 5.x → 7.0.1:修复 dompurify XSS 漏洞(GHSA-hpcv)
- gulp 4.x → 5.0.1:修复 vinyl-fs、glob/glob-stream、chokidar、
  braces 等 high 级漏洞链
- hexo-generator-sitemap 2.2.0 → 3.0.1:同步升级

【替换高危依赖】
- gulp-htmlmin 替换为 gulp-html-minifier-terser@7.1.0:
  gulp-htmlmin 依赖的 html-minifier 存在 ReDoS 高危漏洞且无修复版本,
  改用其维护分支 html-minifier-terser,API 完全兼容
- optimist 替换为 minimist@1.2.8(devDependency):
  optimist 捆绑的 minimist@<=0.2.3 存在 prototype pollution critical 漏洞,
  直接使用修复版 minimist 替代

【移除无修复版本的高危包】
- 移除 hexo-generator-baidu-sitemap(无维护更新,内部捆绑 hexo@4.x
  导致一系列 critical 漏洞),在 scripts/generator.js 中实现等效的
  自定义 Baidu Sitemap 生成器替代,功能与原包完全一致

【npm overrides 修复传递依赖漏洞】
- 添加 overrides: { ejs: "^6.0.1" }:
  hexo-renderer-ejs@2.x 依赖 ejs@3.x,ejs@3.x 存在 SSTI critical 漏洞
  且引入 jake → filelist → minimatch → brace-expansion 漏洞链;
  ejs@6.x 已无 jake 依赖,critical 漏洞全部消除

【yilia 主题依赖升级】
- @babel/core 7.26.x → 7.29.7:修复 Arbitrary File Read 漏洞
- @babel/plugin-transform-runtime / @babel/preset-env 同步升级至 7.29.x
- postcss 8.5.3 → 8.5.25:修复 XSS 及路径遍历漏洞
- 移除 clean-webpack-plugin(依赖链含 brace-expansion 高危漏洞),
  改用 webpack 5 内置 output.clean 实现等效的增量清理功能
- 添加 overrides: { brace-expansion: "5.0.9" } 修复剩余传递依赖漏洞

【构建兼容性修复】
- scripts/generator.js 新增 jsfiddle tag 注册:
  hexo@8 移除了内置 {% jsfiddle %} tag 导致构建 FATAL,
  重新实现该 tag 保持原有 iframe 嵌入行为
- gulpfile.js generate 任务错误处理修正:
  原代码在 hexo FATAL 时调用 hexo.exit(err) 但不向 gulp 抛出异常,
  导致 series 继续执行后续任务(compressHtml 因 public/ 不存在而报错),
  修改为在 hexo 退出后重新 throw err 确保 gulp 正确感知失败

【修复结果】
- 主项目:56 个漏洞(8 低危、14 中危、25 高危、9 严重)→ 0 个漏洞
- yilia 主题:8 个漏洞 → 1 个低危
  (剩余 1 个为 vue@2.x ReDoS,仅影响构建时自身模板解析,
  修复需整体迁移到 Vue 3 重写主题,暂不处理)
2026-07-31 20:38:04 +08:00
sookieandClaude Opus 4.6 a4ce7274cd 清理 CSS 浏览器前缀,使用现代标准属性
- _mixin.less: 移除 -ms-transition
- _reset.less: 使用标准 text-size-adjust 和 appearance
- share.less: 移除 -moz-inline-stack 和 zoom:1 hack
- tooltip.less: 移除 -webkit-transform 前缀

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 23:26:57 +08:00
sookie a2d1b2cc49 Revert "升级 Vue 2 到 Vue 3"
This reverts commit fb1cbbada4.
2026-03-24 23:06:58 +08:00
sookie 173e870a73 Revert "修改ref使用"
This reverts commit 6c8ac211ae.
2026-03-24 23:06:54 +08:00
sookie 6c8ac211ae 修改ref使用 2026-03-24 23:00:58 +08:00
sookieandClaude Opus 4.6 fb1cbbada4 升级 Vue 2 到 Vue 3
- 升级 vue 2.7.16 → 3.5.14
- 重构 slider.js:new Vue() 迁移到 createApp()
- data 改为函数形式,移除 filters 改用 methods
- 更新模板 tools.ejs:过滤器语法改为方法调用
- 代码风格优化:箭头函数、const/let、简化数组操作

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 22:46:48 +08:00
sookieandClaude Opus 4.6 6f6fcebd18 移除 axios,改用原生 fetch 实现 HTTP 请求
- 从 package.json 移除 axios 依赖
- 重构 request.js:用原生 fetch API 替代 axios
- 实现参数序列化、超时控制(AbortController)、统一响应处理
- 保持 http.get/http.post API 兼容,减少 bundle 体积

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 22:18:57 +08:00
sookieandClaude Opus 4.6 7d827d5db8 升级 yilia 主题构建工具链
- 升级 babel 相关依赖至 ^7.26.0,配置按需引入 core-js polyfill
- 升级 webpack 5.70.0 → 5.97.1,webpack-cli 4.9.2 → 5.1.4
- 升级 css-loader、postcss-loader、less-loader 等至最新版本
- 移除废弃的 file-loader/url-loader,改用 webpack5 内置 asset modules
- 移除 babel-polyfill 和 leancloud-storage,添加 @babel/runtime 和 core-js
- 升级 vue 2.6.14 → 2.7.16,axios 1.13.6 → 1.8.4
- 添加 browserslist 配置

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 22:12:52 +08:00
16 changed files with 9531 additions and 11768 deletions
-1
View File
@@ -1,7 +1,6 @@
.DS_Store
Thumbs.db
db.json
package-lock.json
*.log
node_modules/
public/
+4 -5
View File
@@ -1,14 +1,12 @@
const gulp = require('gulp'),
htmlmin = require('gulp-htmlmin'), // html压缩组件
htmlmin = require('gulp-html-minifier-terser'), // html压缩组件
htmlclean = require('gulp-htmlclean'), // html清理组件
plumber = require('gulp-plumber'), // 容错组件(发生错误不跳出任务,并报出错误内容)
Hexo = require('hexo'),
log = require('fancy-log') // gulp的日志输出
// 程序执行的传参
const argv = require('optimist')
.describe('deployPath', '静态化后发布的目录')
.argv
const argv = require('minimist')(process.argv.slice(2))
const hexo = new Hexo(process.cwd(), {})
@@ -20,7 +18,8 @@ gulp.task('generate', async function() {
await hexo.call('generate', { watch: false })
return hexo.exit()
} catch (err) {
return hexo.exit(err)
await hexo.exit(err)
throw err // 让 gulp series 感知到失败并终止后续任务
}
})
+5543
View File
File diff suppressed because it is too large Load Diff
+11 -9
View File
@@ -3,33 +3,35 @@
"version": "1.1.0",
"private": true,
"hexo": {
"version": "6.0.0"
"version": "8.1.2"
},
"scripts": {
"dev": "hexo server",
"build": "gulp"
},
"dependencies": {
"hexo": "^6.0.0",
"hexo": "^8.1.2",
"hexo-generator-archive": "^1.0.0",
"hexo-generator-baidu-sitemap": "^0.1.9",
"hexo-generator-category": "^1.0.0",
"hexo-generator-feed": "^3.0.0",
"hexo-generator-index": "^2.0.0",
"hexo-generator-json-content": "^4.2.3",
"hexo-generator-sitemap": "^2.2.0",
"hexo-generator-sitemap": "^3.0.1",
"hexo-generator-tag": "^1.0.0",
"hexo-renderer-ejs": "^2.0.0",
"hexo-renderer-marked": "^5.0.0",
"hexo-renderer-marked": "^7.0.1",
"hexo-server": "^3.0.0",
"hexo-wordcount": "^6.0.1"
},
"devDependencies": {
"gulp": "^4.0.2",
"gulp": "^5.0.1",
"gulp-htmlclean": "^2.7.22",
"gulp-htmlmin": "^5.0.1",
"gulp-html-minifier-terser": "^7.1.0",
"gulp-plumber": "^1.2.1",
"nunjucks": "^3.2.3",
"optimist": "^0.6.1"
"minimist": "^1.2.8",
"nunjucks": "^3.2.3"
},
"overrides": {
"ejs": "^6.0.1"
}
}
+47
View File
@@ -2,6 +2,16 @@ const nunjucks = require('nunjucks')
const path = require('path')
const fs = require('fs')
// hexo@8 移除了内置 jsfiddle tag,在此重新注册
hexo.extend.tag.register('jsfiddle', function(args) {
const shortcode = args[0]
const tabs = args[1] || 'js,resources,html,css,result'
const skin = args[2] || 'light'
const width = args[3] || '100%'
const height = args[4] || '300'
return `<iframe style="width: ${width}; height: ${height}px" src="//jsfiddle.net/${shortcode}/embedded/${tabs}/?skin=${skin}" allowfullscreen="allowfullscreen" frameborder="0"></iframe>`
})
const env = new nunjucks.configure({ autoescape: false })
env.addFilter('noControlChars', function(str) {
return str && str.replace(/[\x00-\x1F\x7F]/g, '')
@@ -32,3 +42,40 @@ hexo.extend.generator.register('xml', function(locals){
data: xmlData
}
})
// 自定义 Baidu Sitemap 生成器(替代已废弃的 hexo-generator-baidu-sitemap
hexo.extend.generator.register('baidusitemap', function(locals) {
const config = hexo.config.baidusitemap || {}
const outputPath = config.path || 'baidusitemap.xml'
const posts = locals.posts.toArray().filter(function(post) { return !post.draft })
const pages = locals.pages.toArray()
const allItems = posts.concat(pages).sort(function(a, b) {
const dateA = (a.updated || a.date)
const dateB = (b.updated || b.date)
return dateB - dateA
})
const lines = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'
]
allItems.forEach(function(item) {
const dateObj = item.updated || item.date
const dateStr = dateObj && dateObj.format
? dateObj.format('YYYY-MM-DD')
: new Date(dateObj).toISOString().split('T')[0]
lines.push(' <url>')
lines.push(' <loc>' + item.permalink + '</loc>')
lines.push(' <lastmod>' + dateStr + '</lastmod>')
lines.push(' </url>')
})
lines.push('</urlset>')
return {
path: outputPath,
data: lines.join('\n')
}
})
+4 -1
View File
@@ -1,6 +1,9 @@
{
"presets": [
"@babel/preset-env"
["@babel/preset-env", {
"useBuiltIns": "usage",
"corejs": 3
}]
],
"plugins": [
"@babel/transform-runtime"
+2178 -8103
View File
File diff suppressed because it is too large Load Diff
+24 -25
View File
@@ -18,39 +18,38 @@
"author": "litten",
"license": "ISC",
"dependencies": {
"babel-polyfill": "^6.23.0",
"leancloud-storage": "^3.7.3",
"@babel/runtime": "^7.26.0",
"core-js": "^3.40.0",
"photoswipe": "^4.1.3",
"qrious": "^4.0.2",
"scrollreveal": "^4.0.5",
"vue": "^2.6.14"
"scrollreveal": "^4.0.9",
"vue": "^2.7.16"
},
"devDependencies": {
"@babel/core": "^7.17.5",
"@babel/plugin-transform-runtime": "^7.17.0",
"@babel/preset-env": "^7.16.11",
"autoprefixer": "^10.4.2",
"axios": "^1.13.6",
"babel-loader": "8.3.0",
"clean-webpack-plugin": "^4.0.0",
"css-loader": "^5.2.7",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.0",
"less": "^4.1.3",
"less-loader": "^11.1.0",
"mini-css-extract-plugin": "^2.6.0",
"postcss-loader": "^6.2.1",
"terser-webpack-plugin": "^5.3.1",
"url-loader": "^4.1.1",
"webpack": "^5.70.0",
"webpack-cli": "^4.9.2"
"@babel/core": "^7.29.7",
"@babel/plugin-transform-runtime": "^7.29.0",
"@babel/preset-env": "^7.29.0",
"autoprefixer": "^10.4.20",
"babel-loader": "^9.2.1",
"css-loader": "^7.1.2",
"html-webpack-plugin": "^5.6.3",
"less": "^4.2.2",
"less-loader": "^12.2.0",
"mini-css-extract-plugin": "^2.9.2",
"postcss": "^8.5.25",
"postcss-loader": "^8.1.1",
"terser-webpack-plugin": "^5.3.11",
"webpack": "^5.97.1",
"webpack-cli": "^5.1.4"
},
"overrides": {
"brace-expansion": "5.0.9"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"iOS >= 7",
"Android >= 4",
"not ie <= 8"
"iOS >= 10",
"Android >= 5"
]
}
+1 -2
View File
@@ -1,6 +1,5 @@
module.exports = {
plugins: [
"autoprefixer",
"postcss-preset-env"
"autoprefixer"
]
}
@@ -285,13 +285,11 @@
// trans
.trans() {
transition: all 0.2s ease-in;
-ms-transition: all 0.2s ease-in;
}
// trans8
.trans8() {
transition: all 0.8s ease-in;
-ms-transition: all 0.8s ease-in;
}
// display-flex
+4 -5
View File
@@ -12,8 +12,7 @@
// 1. Prevent mobile text size adjust after orientation change, without disabling user zoom.
// 2. Remove the gray background color from tap, default value is inherit
html {
-ms-text-size-adjust: 100%; // 1
-webkit-text-size-adjust: 100%; // 1
text-size-adjust: 100%; // 1
-webkit-tap-highlight-color: transparent; // 2
height: 100%;
}
@@ -188,7 +187,7 @@ button,
html input[type="button"],
input[type="reset"],
input[type="submit"] {
-webkit-appearance: button; // 1
appearance: button; // 1
cursor: pointer; // 2
}
@@ -226,14 +225,14 @@ input[type="number"]::-webkit-outer-spin-button {
// 1. Address `appearance` set to `searchfield` in Safari and Chrome.
// 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
input[type="search"] {
-webkit-appearance: textfield; // 1
appearance: textfield; // 1
box-sizing: content-box; // 2
}
// Remove inner padding and search cancel button in Safari and Chrome on OS X.
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
appearance: none;
}
// Define consistent border, margin, and padding.
-2
View File
@@ -13,10 +13,8 @@
a {
border: 1px solid @colorBorder;
border-radius: 50%;
display: -moz-inline-stack;
display: inline-block;
vertical-align: middle;
zoom: 1;
margin: 10px;
transition: 0.3s;
text-align: center;
-1
View File
@@ -112,7 +112,6 @@
bottom: 100%;
left: 50%;
pointer-events: none;
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
}
+37 -10
View File
@@ -1,14 +1,19 @@
import axios from 'axios'
const DEFAULT_TIMEOUT = 10000
const http = axios.create({
timeout: 10000,
paramsSerializer: {
indexes: null
function serializeParams(params) {
if (!params) return ''
const query = Object.entries(params)
.filter(([, v]) => v !== undefined && v !== null)
.flatMap(([k, v]) => Array.isArray(v) ? v.map(item => `${encodeURIComponent(k)}=${encodeURIComponent(item)}`) : [`${encodeURIComponent(k)}=${encodeURIComponent(v)}`])
.join('&')
return query ? '?' + query : ''
}
})
http.interceptors.response.use(res => {
const responseBody = res.data
function handleResponse(res) {
if (!res.ok) {
return Promise.reject(new Error(`HTTP ${res.status}`))
}
return res.json().then(responseBody => {
// 统一响应格式处理
switch (responseBody.code) {
case 0:
@@ -18,8 +23,30 @@ http.interceptors.response.use(res => {
return Promise.reject(new Error(responseBody.message || '请求失败'))
default:
// 其他情况,兼容没有包装格式的响应
return res.data
return responseBody
}
})
}
async function request(url, options = {}) {
const controller = new AbortController()
const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT)
return fetch(url, { ...options, signal: controller.signal })
.then(res => { clearTimeout(timer); return handleResponse(res) })
.catch(err => { clearTimeout(timer); return Promise.reject(err) })
}
const http = {
get(url, { params } = {}) {
return request(url + serializeParams(params))
},
post(url, data) {
return request(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
}
}
}, err => Promise.reject(err))
export default http
+12 -31
View File
@@ -1,7 +1,6 @@
const TerserPlugin = require('terser-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const htmlPluginConfig = {
@@ -24,7 +23,7 @@ module.exports = function(env, argv) {
})]
},
entry: {
main: ['babel-polyfill', './source-src/js/main.js'],
main: './source-src/js/main.js',
slider: './source-src/js/slider.js',
mobile: './source-src/js/mobile.js',
viewer: './source-src/js/viewer.js'
@@ -32,44 +31,31 @@ module.exports = function(env, argv) {
output: {
publicPath: '/',
path: __dirname + '/source',
filename: isProd ? 'js/[name].[chunkhash].js' : 'js/[name].js'
filename: isProd ? 'js/[name].[chunkhash].js' : 'js/[name].js',
clean: isProd ? { keep: (asset) => !/^(js|css|fonts|images)\//.test(asset) } : false
},
module: {
rules: [{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-proposal-class-properties'],
},
},
use: 'babel-loader',
exclude: /node_modules/
},{
test: /\.less$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader']
},{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader']
},{
test: /\.(png|jpe?g|gif|ico)$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[contenthash:6].[ext]',
outputPath: 'images',
esModule: false // 不使用es6的模块语法
}
type: 'asset/resource',
generator: {
filename: 'images/[name].[contenthash:6][ext]'
}
},{
test: /\.(svg|eot|ttf|woff2?|otf)$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[contenthash:6].[ext]',
outputPath: 'fonts',
esModule: false // 不使用es6的模块语法
}
type: 'asset/resource',
generator: {
filename: 'fonts/[name].[contenthash:6][ext]'
}
}
]
@@ -83,11 +69,6 @@ module.exports = function(env, argv) {
template: './source-src/template/css.html',
filename: '../layout/_partial/css.ejs'
}, htmlPluginConfig)),
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: ['js/*','css/*','fonts/*','images/*'],
verbose: true,
dry: false,
}),
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:6].css'
})
+1648 -3553
View File
File diff suppressed because it is too large Load Diff