fix(依赖解析): 修复CSS依赖路径对上级目录的处理并增强CDN回退支持
- 修正CSS依赖路径中`..`上级目录的解析,确保文件正确落盘 - 针对Font Awesome的`../webfonts`场景进行特殊处理,迁移旧版本误存文件 - 增强CDN回退逻辑以支持`@scope`包名格式 - 更新.gitignore添加cache目录,修改seed.txt测试用例 - 更新README文档说明相关改进
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -5,4 +5,5 @@ node_modules
|
|||||||
.yarn/
|
.yarn/
|
||||||
dist/
|
dist/
|
||||||
build/
|
build/
|
||||||
*.log
|
*.log
|
||||||
|
cache
|
||||||
@@ -145,6 +145,7 @@
|
|||||||
- 当抓取 `CSS` 文件时,会自动解析其中的 `url(...)` 引用,并尝试下载相对路径的依赖(如字体、图片等),统一保存到 `cache/css/...` 对应目录下,保持与源路径相同的层级结构。
|
- 当抓取 `CSS` 文件时,会自动解析其中的 `url(...)` 引用,并尝试下载相对路径的依赖(如字体、图片等),统一保存到 `cache/css/...` 对应目录下,保持与源路径相同的层级结构。
|
||||||
- 这样,形如 `@font-face { src: url(fonts/element-icons.woff) }` 的引用将会在本地落盘为:`/css/.../fonts/element-icons.woff`,无需跨域请求第三方源。
|
- 这样,形如 `@font-face { src: url(fonts/element-icons.woff) }` 的引用将会在本地落盘为:`/css/.../fonts/element-icons.woff`,无需跨域请求第三方源。
|
||||||
- 失败的依赖抓取会被静默跳过,不影响主 `CSS` 的可用性。
|
- 失败的依赖抓取会被静默跳过,不影响主 `CSS` 的可用性。
|
||||||
|
- 针对使用 `../webfonts/...` 的场景(如 Font Awesome),已修正对上级目录的处理,确保依赖文件最终位于与 `css/` 同级的 `webfonts/` 目录;旧版本误存于 `css/webfonts/` 的文件会在后续抓取时自动迁移到正确位置。
|
||||||
|
|
||||||
## 去重策略
|
## 去重策略
|
||||||
|
|
||||||
@@ -186,4 +187,5 @@
|
|||||||
- 增强缓存管理:分页加载(20–50/页)、按名称/类型/更新时间过滤、按名称/大小/时间排序、轻量虚拟滚动与懒加载、元数据解析(库名/版本/扩展名/类别)
|
- 增强缓存管理:分页加载(20–50/页)、按名称/类型/更新时间过滤、按名称/大小/时间排序、轻量虚拟滚动与懒加载、元数据解析(库名/版本/扩展名/类别)
|
||||||
- 修复分段筛选:切换 `CSS/JS/全部` 时重置分页并重新加载
|
- 修复分段筛选:切换 `CSS/JS/全部` 时重置分页并重新加载
|
||||||
- 静态缓存优化:`/` 与 HTML 响应禁用缓存;为首页 CSS/JS 增加版本参数以避免浏览器缓存旧样式与脚本
|
- 静态缓存优化:`/` 与 HTML 响应禁用缓存;为首页 CSS/JS 增加版本参数以避免浏览器缓存旧样式与脚本
|
||||||
- 页面视觉细节:为 `header` 与 `main` 增加间距(≥30px),背景设置 `background-attachment: fixed` 并覆盖视窗(居中、等比、无重复)
|
- 页面视觉细节:为 `header` 与 `main` 增加间距(≥30px),背景设置 `background-attachment: fixed` 并覆盖视窗(居中、等比、无重复)
|
||||||
|
- 修复:CSS 依赖路径对 `..` 上级目录的正确解析与落盘(兼容 Font Awesome 的 `../webfonts`);增强 CDN 回退匹配支持 `@scope` 包名(jsDelivr / unpkg)
|
||||||
|
|||||||
10
seed.txt
10
seed.txt
@@ -7,4 +7,12 @@ https://cdn.jsdmirror.com/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js
|
|||||||
https://cdn.tailwindcss.com
|
https://cdn.tailwindcss.com
|
||||||
|
|
||||||
# Element UI 2.15.13 样式(用于验证字体依赖自动抓取)
|
# Element UI 2.15.13 样式(用于验证字体依赖自动抓取)
|
||||||
https://cdn.jsdmirror.com/npm/element-ui@2.15.13/lib/theme-chalk/index.css
|
https://dist.jicelue.com/css/npm/element-ui@2.15.13/lib/theme-chalk/index.css
|
||||||
|
|
||||||
|
# Font Awesome Free 6(验证 webfonts 自动抓取)
|
||||||
|
https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.5.1/css/all.min.css
|
||||||
|
https://s4.zstatic.net/ajax/libs/font-awesome/5.15.4/css/all.min.css
|
||||||
|
https://s4.zstatic.net/ajax/libs/font-awesome/5.15.4/css/v4-shims.min.css
|
||||||
|
https://s4.zstatic.net/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css
|
||||||
|
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css
|
||||||
|
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css
|
||||||
|
|||||||
59
server.js
59
server.js
@@ -195,17 +195,22 @@ async function fetchCssDependencies(baseUrl, cssBuf) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const rel of refs) {
|
for (const rel of refs) {
|
||||||
// 归一化相对路径,计算本地写入位置
|
// 归一化相对路径,保留 .. 以正确定位到上级目录;去掉查询与哈希
|
||||||
const relSafe = rel.replace(/\\+/g, '/').replace(/^\/+/, '')
|
const relNorm = rel.replace(/\\+/g, '/').replace(/^\s+|\s+$/g, '')
|
||||||
const relParts = relSafe.split('/').filter(p => p && p !== '..')
|
const noQuery = relNorm.split('#')[0].split('?')[0]
|
||||||
const localPath = path.join(targetDir, ...relParts)
|
const localPathCandidate = path.join(targetDir, noQuery)
|
||||||
|
const localPath = path.normalize(localPathCandidate)
|
||||||
const localDir = path.dirname(localPath)
|
const localDir = path.dirname(localPath)
|
||||||
|
// 防越权:确保写入路径仍在 CSS_DIR 根内
|
||||||
|
const rootResolved = path.resolve(CSS_DIR)
|
||||||
|
const resolved = path.resolve(localPath)
|
||||||
|
if (!resolved.startsWith(rootResolved)) continue
|
||||||
if (fs.existsSync(localPath)) continue
|
if (fs.existsSync(localPath)) continue
|
||||||
if (!fs.existsSync(localDir)) fs.mkdirSync(localDir, { recursive: true })
|
if (!fs.existsSync(localDir)) fs.mkdirSync(localDir, { recursive: true })
|
||||||
|
|
||||||
// 依次尝试:原始源、回退源
|
// 依次尝试:原始源、回退源
|
||||||
const primary = new URL(rel, baseUrl).toString()
|
const primary = new URL(relNorm, baseUrl).toString()
|
||||||
const candidates = [primary, ...buildFallbacks(primary)]
|
const candidates = [primary, ...buildScopedFallbacks(primary)]
|
||||||
let saved = false
|
let saved = false
|
||||||
for (const c of candidates) {
|
for (const c of candidates) {
|
||||||
try {
|
try {
|
||||||
@@ -220,6 +225,48 @@ async function fetchCssDependencies(baseUrl, cssBuf) {
|
|||||||
// no-op
|
// no-op
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 针对旧版本误保存在 packageRoot/css/webfonts 的情况,统一迁移到 packageRoot/webfonts
|
||||||
|
try {
|
||||||
|
const packageRoot = path.dirname(targetDir)
|
||||||
|
const wrongDir = path.join(packageRoot, 'css', 'webfonts')
|
||||||
|
const correctDir = path.join(packageRoot, 'webfonts')
|
||||||
|
if (fs.existsSync(wrongDir)) {
|
||||||
|
if (!fs.existsSync(correctDir)) fs.mkdirSync(correctDir, { recursive: true })
|
||||||
|
const entries = fs.readdirSync(wrongDir, { withFileTypes: true })
|
||||||
|
for (const e of entries) {
|
||||||
|
if (!e.isFile()) continue
|
||||||
|
const src = path.join(wrongDir, e.name)
|
||||||
|
const dst = path.join(correctDir, e.name)
|
||||||
|
if (!fs.existsSync(dst)) fs.renameSync(src, dst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 针对 npm/@scope 包名的CDN回退构造(jsDelivr/unpkg)
|
||||||
|
* 兼容路径:/npm/(css/)?[@scope/]<name>@<version>/...
|
||||||
|
* @param {string} absUrl 绝对URL
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
function buildScopedFallbacks(absUrl) {
|
||||||
|
try {
|
||||||
|
const u = new URL(absUrl)
|
||||||
|
const m = u.pathname.match(/\/(?:css\/)?npm\/(?:@([^/]+)\/)?([^/@]+)@([^/]+)\/(.+)/)
|
||||||
|
if (m) {
|
||||||
|
const scope = m[1]
|
||||||
|
const name = m[2]
|
||||||
|
const version = m[3]
|
||||||
|
const rest = m[4]
|
||||||
|
const pkg = scope ? `@${scope}/${name}@${version}` : `${name}@${version}`
|
||||||
|
return [
|
||||||
|
`https://cdn.jsdelivr.net/npm/${pkg}/${rest}`,
|
||||||
|
`https://unpkg.com/${pkg}/${rest}`
|
||||||
|
]
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user