|
|
@@ -0,0 +1,95 @@
|
|
|
+#!/usr/bin/env node
|
|
|
+/**
|
|
|
+ * E 批:图片 lazy-load 治理
|
|
|
+ * 在长列表页的 <image> 标签上补 lazy-load(若尚未有 lazy-load 属性)
|
|
|
+ *
|
|
|
+ * 策略:
|
|
|
+ * - 对 <image ...> 不含 lazy-load 的标签补上 lazy-load
|
|
|
+ * - 跳过已有 lazy-load 的标签
|
|
|
+ * - 仅处理已识别的长列表页(discover / article-center / shop / activity / growth 等)
|
|
|
+ */
|
|
|
+const fs = require('fs')
|
|
|
+const path = require('path')
|
|
|
+
|
|
|
+const ROOT = path.resolve(__dirname, '..')
|
|
|
+const EXCLUDE_DIRS = new Set(['node_modules', 'dist', 'unpackage', '.git', 'docs'])
|
|
|
+
|
|
|
+// 需要治理的长列表页面/组件(按父目录分组,walk 时过滤)
|
|
|
+const TARGET_PATTERNS = [
|
|
|
+ 'pages/discover/',
|
|
|
+ 'pages/article-center/',
|
|
|
+ 'pages/shop/',
|
|
|
+ 'pages/activity/',
|
|
|
+ 'pages/growth/',
|
|
|
+ 'pages/promotion/',
|
|
|
+ 'components/DimensionActivities.vue',
|
|
|
+ 'components/DimensionProducts.vue',
|
|
|
+ 'components/RecommendedFeed.vue',
|
|
|
+ 'components/ArticleBookshelf.vue',
|
|
|
+ 'components/FamilyFeed.vue',
|
|
|
+ 'components/daily-task-card.vue',
|
|
|
+]
|
|
|
+
|
|
|
+// 模式:找到 <image ...> 中不含 lazy-load 的,加上 lazy-load
|
|
|
+// 匹配:<image ...attrs...>(不含 lazy-load),在后面某个位置补
|
|
|
+const IMG_TAG_RE = /<image\s([^>]*)>/gi
|
|
|
+
|
|
|
+function processFile(filePath) {
|
|
|
+ const src = fs.readFileSync(filePath, 'utf8')
|
|
|
+ // 只处理包含长列表 image 的文件(启发式:有 v-for 或 list/data 字样的文件优先)
|
|
|
+ const hasLoop = /v-for\s*=/.test(src) || /v_loop/.test(src)
|
|
|
+ if (!hasLoop) return { changed: false, count: 0 }
|
|
|
+
|
|
|
+ let result = src
|
|
|
+ let count = 0
|
|
|
+ result = result.replace(IMG_TAG_RE, (fullMatch, attrs) => {
|
|
|
+ if (/lazy-load/.test(attrs)) return fullMatch // 已有 lazy-load,跳过
|
|
|
+ // 在标签闭合 `>`(可能为 `/>`)前插入 lazy-load
|
|
|
+ if (fullMatch.endsWith('/>')) {
|
|
|
+ return fullMatch.slice(0, -2) + ' lazy-load />'
|
|
|
+ }
|
|
|
+ return fullMatch.slice(0, -1) + ' lazy-load>'
|
|
|
+ })
|
|
|
+
|
|
|
+ count = (result.match(/lazy-load/g) || []).length - (src.match(/lazy-load/g) || []).length
|
|
|
+ if (count > 0) {
|
|
|
+ fs.writeFileSync(filePath, result, 'utf8')
|
|
|
+ }
|
|
|
+ return { changed: count > 0, count }
|
|
|
+}
|
|
|
+
|
|
|
+function walk(dir, out = []) {
|
|
|
+ for (const name of fs.readdirSync(dir)) {
|
|
|
+ if (EXCLUDE_DIRS.has(name)) continue
|
|
|
+ const p = path.join(dir, name)
|
|
|
+ const st = fs.statSync(p)
|
|
|
+ if (st.isDirectory()) walk(p, out)
|
|
|
+ else if (name.endsWith('.vue')) out.push(p)
|
|
|
+ }
|
|
|
+ return out
|
|
|
+}
|
|
|
+
|
|
|
+function main() {
|
|
|
+ const allFiles = walk(ROOT)
|
|
|
+ const targets = allFiles.filter(f => {
|
|
|
+ const rel = path.relative(ROOT, f).split(path.sep).join('/')
|
|
|
+ return TARGET_PATTERNS.some(p => rel.startsWith(p))
|
|
|
+ })
|
|
|
+
|
|
|
+ let grandTotal = 0
|
|
|
+ const log = []
|
|
|
+ for (const f of targets) {
|
|
|
+ const rel = path.relative(ROOT, f)
|
|
|
+ const r = processFile(f)
|
|
|
+ if (r.changed) {
|
|
|
+ grandTotal += r.count
|
|
|
+ log.push(`${r.count.toString().padStart(3)} ${rel}`)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ console.log(`\n=== E 批 lazy-load 补全完成 ===`)
|
|
|
+ console.log(`新增 lazy-load: ${grandTotal} 处`)
|
|
|
+ log.sort((a, b) => parseInt(b.split(' ')[0]) - parseInt(a.split(' ')[0]))
|
|
|
+ for (const line of log.slice(0, 30)) console.log(line)
|
|
|
+}
|
|
|
+
|
|
|
+main()
|