目錄
vite插件生成骨架屏
利用現有樣式
#元件內渲染骨架螢幕
首屏渲染骨架屏
存在的一些问题
存在原始样式不生效的场景
骨架屏代码质量
解决方案
不使用scoped css
将骨架屏页面自动转成图片
复制一份独立的样式表
提取必要的布局信息生成骨架屏
postcss插件
小结
首頁 web前端 Vue.js 聊聊怎麼利用vite插件來實現骨架螢幕自動化

聊聊怎麼利用vite插件來實現骨架螢幕自動化

Oct 09, 2022 pm 07:19 PM
vue vite

骨架屏属于锦上添花的功能,理想状态下开发者应该是不需要过分关注的,因此从开发体验上来看,手动编写骨架屏并不是很好的解决方案。因此本文主要研究另外一种骨架屏自动生成方案:通过vite插件自动注入骨架屏。

聊聊怎麼利用vite插件來實現骨架螢幕自動化

【相关推荐:vuejs视频教程

骨架屏在SPA应用中有两个显著提升用户体验的作用

  • 避免页面初始化加载时的空白,体验介于SSR和完全等待页面初始化完成之间
  • 避免部分路由组件需要加载数据完成之后才渲染的空白

骨架屏会给用户一种内容已经返回的错觉,只要稍加等待就能看见完整内容了,因此骨架屏的定位就是真实内容准备好之前的替身。

之前研究过一种快速生成骨架屏的想法:使用Chrome扩展程序生成网页骨架屏,大概原理是通过Chrome扩展程序注入content.js修改页面DOM接口,最终导出带有骨架屏样式的HTML代码。

当时的这个想法并没有在生产中落地,最近在折腾用户体验相关的功能,发现还是有必要继续完善一下骨架屏相关的东西。

业界对于骨架屏的应用,也有好几种方案

  • 直接让设计师提供页面对应的骨架屏设计图
    • 导出svgbase64图片嵌入代码中,比较影响项目体积
    • 开发手动编写样式,工作量较大
  • 通过组件编写骨架屏
    • 诸如vue-content-loaderreact-content-loader等组件,可以通过svg快速编写骨架屏内容,但输出的产物与真实页面有一定差距,不容易实现定制化骨架屏需求。
    • 一些组件库,如vantvarlet也提供了skeleton组件,通过配置参数的形式控制生成骨架屏内容,其缺点也是定制化程度较差
  • 自动生成骨架屏
    • page-skeleton-webpack-plugin等比较成熟的自动骨架屏方案,甚至有专门的UI界面来控制生成不同一面的骨架屏,缺点是生成的骨架屏代码较大,影响项目体积
    • 借助puppeteer无头浏览器渲染出页面对应的骨架屏内容,依赖较大
    • 借助Chrome扩展程序生成骨架屏内容,本质上和无头浏览器原理相似

骨架屏属于锦上添花的功能,理想状态下开发者应该是不需要过分关注的,因此从开发体验上来看,手动编写骨架屏并不是很好的解决方案。因此本文主要研究另外一种骨架屏自动生成方案:通过vite插件自动注入骨架屏。

先预览一下效果

点击生成骨架屏

聊聊怎麼利用vite插件來實現骨架螢幕自動化

首屏访问

聊聊怎麼利用vite插件來實現骨架螢幕自動化

vite插件生成骨架屏

参考

首先需要探寻一种自动能够将设计图或真实页面转成骨架屏的方案。大概有下面几个思路

  • 透過編譯工具,解析程式碼中寫的HTML模板,產生骨架螢幕
  • 從設計圖來源出發,例如sketch、figma等,透過外掛匯出可以用骨架螢幕內容
  • 直接操作真實頁面的DOM,然後產生骨架螢幕內容

利用現有樣式

看起來第三種想法的實作成本最低,也最為熟悉。這也是使用Chrome擴充功能產生網頁骨架螢幕這個方案中採用的方案,因此具體的實作細節這裡不再贅述,簡單總結一下

  • 在開發環境下,透過手動觸發某個開關,開始產生某個頁面對應的骨架螢幕內容
  • 將頁面按節點類型拆分成不同區塊
  • 支援自訂節點類型、忽略或隱藏節點
  • 最後導出的是一段HTML程式碼,重複使用原始頁面的結構和CSS佈局程式碼

核心API只有一個,傳入對應的入口節點,輸出轉換後的骨架螢幕程式碼

const {name, content} = renderSkeleton(sel, defaultConfig)
登入後複製

例如下面這段結構

<div>
		<div>卡片标题</div>
	  <div>卡片内容卡片内容</div>
</div>
登入後複製
登入後複製

產生的骨架螢幕程式碼是

<div>
		<div>卡片标题</div>
	  <div>卡片内容卡片内容</div>
</div>
登入後複製
登入後複製

其中sk-blocksk- text等樣式類別都是在生成時追加上去的,用來覆寫原本的樣式,從而展示骨架螢幕的灰色背景,但同時保留原本的佈局樣式。

renderSkeleton的呼叫時機由開發者自己控制,我們可以向頁面注入一個按鈕,點擊時調用

function createTrigger() {
  const div: HTMLDivElement = document.createElement('div')
  div.setAttribute('style', 'position:fixed;right:0;bottom:20px;width:50px;height:50px;background:red;')
  div.addEventListener('click', function () {
    renderSkeleton('[data-skeleton-root]')
  })
  document.body.appendChild(div)
}

if(process.end.NODE_ENV ==='development'){
 createTrigger() 
}
登入後複製

在得到骨架螢幕程式碼之後,在業務程式碼中透過一個loading標誌位元控制展示的是骨架螢幕還是真實內容

<script>
import {ref, onMounted} from "vue";

const loading = ref(true);
const list = ref<number>([]);

async function fetchList() {
  await sleep(1000)
  list.value = [1, 2, 3, 4, 5]
  loading.value = false
}

onMounted(() => {
  fetchList()
})

</script>

<template>
  <div>
    <div>
      <!--这里的都是骨架屏代码-->
      <div>
          <div>卡片标题</div>
          <div>卡片内容卡片内容</div>
      </div>
   </div>
    <div>
     <div>
          <div>卡片标题</div>
          <div>卡片内容卡片内容</div>
      </div>
    </div>
  </div>

</template>

<style>
// 相关的样式
</style>
登入後複製

#可以看到,v-if="loading"標籤內部的程式碼,就是生成的骨架螢幕內容。要注意的是,既然骨架螢幕與業務程式碼在一起,也會參與Vue的SFC編譯,因此骨架螢幕標籤上面的一些動態屬性如scopeid等,需要移除。關於scopeid帶來的其他問題,後面的篇幅會提到,這也會影響整個renderSkeleton的實作。

如果每次在呼叫renderSkeleton拿到骨架螢幕程式碼之後,手動修改業務程式碼替換loading展示的內容,無疑非常麻煩,現在來研究如何自動化解決這個問題。

前面提到,骨架螢幕主要應用在首屏渲染需要和路由頁面切換時

  • #SPA首屏渲染優化
  • 路由組件切換時的佔位元內容

接下來看看這兩個場景下如何自動注入骨架螢幕程式碼

#元件內渲染骨架螢幕

我們可以透過佔位符來宣告目前元件對應骨架螢幕程式碼的地方,例如

<div>__SKELETON_APP_CONTENT__</div>
<div>真实业务代码</div>
登入後複製

在取得骨架螢幕程式碼之後,將__SKELETON_APP_CONTENT__這裡的內容替換成真實的骨架螢幕程式碼即可。

如何替換呢? vite外掛提供了一個transform的鉤子

const filename = './src/skeleton/content.json'

function SkeletonPlaceholderPlugin() {
  return {
    name: 'skeleton-placeholder-plugin',
    enforce: 'pre',
    transform(src, id) {
      if (/\.vue$/.test(id)) {
        const {content} = fs.readJsonSync(filename)
        // 约定对应的骨架屏占位符
        let code = src.replace(/__SKELETON_(.*?)_CONTENT__/igm, function (match) {
          return content
        })

        return {
          code,
        }
      }
      return src
    },
  } as Plugin
}
登入後複製

其中./skeleton.txt中的內容,就是在呼叫renderSkeleton後產生的骨架螢幕程式碼,透過transformpre,我們就可以在vue外掛程式解析SFC之前,先將骨架屏佔位符替換成真正的程式碼,再參與後續的編譯流程。

這裡還需要解決一個問題:renderSkeleton是在客戶端觸發的,而skeleton.txt是在開發伺服器環境下的,需要有一個通訊的機制將客戶端產生的骨架螢幕程式碼傳送到專案目錄下方。

vite外掛提供了一個configureServer鉤子,用來設定vite開發伺服器,我們可以加一個中間件,用來提供一個保存骨架螢幕程式碼的介面

function SkeletonApiPlugin() {
  async function saveSkeletonContent(name, content) {
    await fs.ensureFile(filename)
    const file = await fs.readJson(filename)
    file[name] = {
      content,
    }
    await fs.writeJson(filename, file)
  }

  return {
    name: 'skeleton-api-plugin',
    configureServer(server) {
      server.middlewares.use(bodyParser())
      server.middlewares.use('/update_skeleton', async (req, res, next) => {
        const {name, content, pathname} = req.body
        await saveSkeletonContent(name, content, pathname)
        // 骨架屏代码更新之后,重启服务
        server.restart()
        res.end('success')
      })
    },
  }
}
登入後複製

然後在renderSkeleton之後,呼叫這個介面上傳產生的骨架螢幕程式碼即可

async function sendContent(body: any) {
  const response = await fetch('/update_skeleton', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(body)
  })
  const data = await response.text()
}

const __renderSkeleton = async function () {
  const {name, content} = renderSkeleton(".card-list", {})

  await sendContent({
    name,
    content
  })
}
登入後複製

bingo!大功告成。梳理一下流程

  • 開發者在某個時候手動呼叫__renderSkeleton,就會自動產生目前頁面的骨架螢幕

  • 將骨架螢幕程式碼傳送給vite接口,更新本機skeleton/content.json中的骨架螢幕程式碼,

  • vite重新啟動服務後,重新觸發pre佇列中的skeleton-content-component插件,取代骨架螢幕佔位符,注入骨架螢幕程式碼,完成整個骨架螢幕的插入流程。

整個過程中,開發者只需要完成下面兩步驟操作即可

  • 宣告骨架螢幕在業務程式碼中的佔位符

  • 點擊按鈕,觸發產生骨架螢幕程式碼

#路由切換的骨架螢幕大多應用在路由元件上,可以考慮進一步封裝,統一管理loading和骨架螢幕展示,這裡比較細節,就不再一一展開了。

首屏渲染骨架屏

骨架屏对于SPA首屏渲染优化,需要在应用初始化之前就开始渲染,即需要在id="app"的组件内植入初始化的骨架屏代码

如果是服务端预渲染,可以直接返回填充后的代码;如果是客户端处理,可以通过document.write处理,我们这里只考虑纯SPA引用,由前端处理骨架屏的插入。

我们可以通过vite插件提供的transformIndexHtml钩子注入这段逻辑

function SkeletonApiPlugin() {
  return {
    name: 'skeleton-aip-plugin',
    transformIndexHtml(html) {
      let {content} = fs.readJsonSync(filename)
      const code = `
<script>
var map = ${JSON.stringify(content)}
var pathname = window.location.pathname
var target = Object.values(map).find(function (row){
  return row.pathname === pathname
})
var content = target && target.content || &#39;&#39;
document.write(content)
</script>
      `
      return html.replace(/__SKELETON_CONTENT__/, code)
    }

  }
}
登入後複製

对应的index.html代码为

<div>__SKELETON_CONTENT__</div>
登入後複製
登入後複製

根据用户当前访问的url,读取该url对应的骨架屏代码,然后通过document.write写入骨架屏代码。这里可以看出,在生成骨架屏代码时,我们还需要保留对应页面url的映射,甚至需要考虑动态化路由的匹配问题。这个也比较简单,在提交到服务端保存时,加个当前页面的路径参数就行了

  const {name, content} = renderSkeleton(sel, defaultConfig)
  // 如果是hash路由,就替换成fragment
  const {pathname} = window.location
  await sendContent({
    name,
    content,
    pathname // 保存骨架屏代码的时候顺道把pathname也保存了
  })
登入後複製

整理一下流程

  • 用户访问url
  • 根据页面url,加载对应的骨架屏代码,填充在根节点下
    • 如果是服务端预渲染,可以直接返回填充后的代码
    • 如果是客户端处理,可以通过document.write处理
  • 用户看见渲染的骨架屏内容
  • 初始化应用,加载页面数据,渲染出真实页面

开发者在点击生成当前页面的骨架屏时,保存的骨架屏代码,既可以用在路由组件切换时的骨架屏,也可以用在首屏渲染时的骨架屏,Nice~

存在的一些问题

利用vite插件注入骨架屏的代码,看起来是可行的,但在方案落地时,发现了一些需要解决的问题。

存在原始样式不生效的场景

由于生成的骨架屏代码是依赖原始样式的,

<div></div>
登入後複製
登入後複製

对应的骨架屏代码

<div></div>
登入後複製
登入後複製

其中的sk-block只会添加一些灰色背景和动画,至于整体的尺寸和布局,还是card这个类来控制的。

这么设计的主要原因是:即使card的尺寸布局发生了变化,对应的骨架屏样式也会一同更新。

但在某些场景下,原始样式类无法生效,最具有代表性的问题就Vue项目的的scoped css

我们知道,vue-loader@vitejs/plugin-vue等工具解析SFC文件时,会为对应组件生成scopeId(参考之前的源码分析:从vue-loader源码分析CSS-Scoped的实现),然后通过postcss插件,通过组合选择器实现了类似于css作用域的样式表

.card[data-v-xxx] {}
登入後複製
登入後複製
登入後複製

我们的生成骨架屏的时机是在开发环境下进行的,这就导致在生产环境下,看到的骨架屏并没有原始样式类对应的尺寸和布局。

下面是vite vue插件的源码

export function createDescriptor(
  filename: string,
  source: string,
  { root, isProduction, sourceMap, compiler }: ResolvedOptions
): SFCParseResult {
  const { descriptor, errors } = compiler.parse(source, {
    filename,
    sourceMap
  })

  const normalizedPath = slash(path.normalize(path.relative(root, filename)))
  descriptor.id = hash(normalizedPath + (isProduction ? source : ''))

  cache.set(filename, descriptor)
  return { descriptor, errors }
}
登入後複製

vue-loader中生成scopeid的方法类似,看了一下貌似并没有提供自定义scopeid的API。

因此对于同一个文件而言,生产环境和非生产环境参与生产hash的参数是不一样的,导致最后得到的scopeid 也不一样。

对于组件内渲染骨架屏这种场景,我们也许可以不考虑scopeid,因为在SFC编译之前,我们就已经通过transform钩子注入了对应的骨架屏模板,对于SFC编译器而言,骨架屏代码和业务代码都在同一个组件内,也就是说他们最后都会获得相同的scopeid,这也是为什么生成的骨架屏代码,要擦除HTML标签上面的scopeid的原因。

但如果骨架屏依赖的外部样式并不在同一个SFC文件内,也会导致原始的骨架屏样式不生效。

<template>
  <div>
    <div>
      <div>
          <div>卡片标题</div>
          <div>卡片内容卡片内容</div>
      </div>
   </div>
    <div>
     <card></card>
    </div>
  </div>

</template>

<style>
// card 样式不在这个SFC下面,但是插入的骨架屏代码却在这个SFC文件下
</style>
登入後複製

此外,对于首屏渲染骨架屏这种场景,就不得不考虑scopeid了。如果骨架屏依赖的原始样式是携带作用域的,那就必须要保证骨架屏代码与生产环境的样式表一致

.card[data-v-xxx] {}
登入後複製
登入後複製
登入後複製
<div></div>
登入後複製

这样,首屏渲染依赖的骨架屏和组件内渲染的骨架屏就产生了冲突,前者需要携带scopeid,而后者又需要擦除scopeid。

为了解决这个冲突,有两种办法

  • 在保存骨架屏代码时,同时保存对应的scopeid,并在首屏渲染时,为每个标签上手动添加scopeid。
  • 原始骨架屏代码就携带scopeid,而在替换组件内的骨架屏占位符时再擦除scopeid

但不论通过何种方式保证两个环境下生成的scopeid 一致(甚至是通过修改插件源码的方式),可能也会存在旧版本的骨架屏携带的scopeid和新版本对应的scopeid 不一致的问题,即旧版本的class和新版本的class不一致。

要解决这个问题,除非在每次修改源码之后,都更新一下骨架屏,由于生成骨架屏这一步是手动的,这与自动化的目的背道而驰了。

因此,看起来利用原始类同步真实DOM的布局和尺寸,在scoped css中并不是一个十分完善的设计。

骨架屏代码质量

第二个不是那么重要的问题是生成的骨架屏代码,相较于手动编写,不够精简。

虽然在源代码中,骨架屏代码被占位符替代,但在编译阶段,骨架屏会编译到render函数中,可能造成代码体积较大,甚至影响页面性能的问题。

这个问题并不是一个阻塞性问题,可以后面考虑如何优化,比如骨架屏仍旧保留v-for等指令,组件可以正常编译,而首屏渲染的骨架屏需要通过自己解析生成完整的HTML代码。

解决方案

上面这两个问题的本质都是因为骨架屏生成方案导致的,跟后续保存骨架屏代码并自动替换并没有多大关系,因此我们只需要优化骨架屏生成方案即可。

既然依赖于原始样式生成的骨架屏代码存在这些缺点,有没有什么解决办法呢?

事实上,我们对于骨架屏是否更真实内容结构的还原程度并没有那么高的要求,也并没有要求骨架屏要跟业务代码一直保持一致,既然导出HTML骨架屏代码比较冗余和繁琐,我们可以换一换思路。

不使用scoped css

其他比较常用的CSS方案如css moudlecss-in-js或者是全局原子类css如tailwindwindicss等,如果输出的是纯粹的CSS代码,且生产环境和线上保持一致,理论上是不会出现scopeid这个问题的。

但Vue项目中,scoped css方案应该占据了半壁江山,加上我自己也比较喜欢scoped css,因此这是一个绕不过去的问题。

将骨架屏页面自动转成图片

第一种思路将骨架屏页面保存为图片,这样就不用再依赖原始样式了。

大概思路就是:在解析当前页面获得骨架屏代码之后,再通过html2canvas等工具,将已经渲染的HTML内容转成canvas,再导出base64图片。

import html2canvas from 'html2canvas'

const __renderSkeleton = async function (sel = 'body') { 
  const {name, content} = renderSkeleton(sel, defaultConfig)
  const canvas = await html2canvas(document.querySelector(sel)!)
  document.body.appendChild(canvas);
  
  const imgData = canvas.toDataURL()
  // 保存<img  src="/static/imghw/default1.png" data-src="${imgData}" class="lazy" alt="聊聊怎麼利用vite插件來實現骨架螢幕自動化" >作为骨架屏代码
}
登入後複製

这种通过图片替代HTML骨架屏代码的优点在于兼容性好(对应的页面骨架屏甚至可以用在App或小程序中),容易迁移,不需要依赖项目代码中的样式类。

但是html2canvas生成的图片也不是百分百还原UI,需要足够纯净的代码原始结构才能生成符合要求的图片。此外图片也存在分辨率和清晰度等问题。

也许又要回到最初的起点,让设计大佬直接导出一张SVG?(开个玩笑,我们还是要走自动化的道路

复制一份独立的样式表

如果能够找到骨架屏代码中每个标签对应的class在样式表中定义的样式,类似于Chrome dev tools中的Elements Styles面板,我们就可以将这些样式复制一份,然后将scopeid替换成其他的选择器

聊聊怎麼利用vite插件來實現骨架螢幕自動化

开发环境下的样式都是通过style标签引入,因此可以拿到页面上所有的样式表对象,提取符合对应选择器的样式,包括.className.className[scopeId]这两类

写一个Demo

const { getClassStyle } = (() => {
    const styleNodes = document.querySelectorAll("style");
    const allRules = Array.from(styleNodes).reduce(
        (acc, styleNode) => {
            const rules = styleNode.sheet.cssRules;
            acc = acc.concat(Array.from(rules));
            return acc;
        },
        []
    );
    const getClassStyle = (selectorText) => {
        return allRules.filter(
            (row) => row.selectorText === selectorText
        );
    };

    return {
        getClassStyle,
    };
})();

const getNodeAttrByRegex = (node, re) => {
    const attr = Array.from(node.attributes).find((row) => {
        return re.test(row.name);
    });
    return attr && attr.name;
};

const parseNodeStyle = (node) => {
    const scopeId = getNodeAttrByRegex(node, /^data-v-/);
    return Array.from(myBox.classList).reduce((acc, row) => {
        const rules = getClassStyle(`.${row}`);

      	// 这里没有再考虑两个类.A.B之类的组合样式了,排列组合比较多
        return acc
            .concat(getClassStyle(`.${row}`))
            .concat(getClassStyle(`.${row}[${scopeId}]`));
    }, []);
};
const rules = parseNodeStyle(myBox);
console.log(rules);
登入後複製

这样就可以得到每个节点在scoped css的样式,然后替换成骨架屏依赖的样式。

但现在要保存的骨架屏代码的HTML结构之外,还需要保存对应的那份CSS代码,十分繁琐

提取必要的布局信息生成骨架屏

能否像html2canvas的思路一样,重新绘制一份骨架屏页面出来呢

通过getComputedStyle可以获取骨架屏每个节点的计算样式

const width = getComputedStyle(myBox,null).getPropertyValue('width');
登入後複製

复用页面结构,把所有布局和尺寸相关的属性都枚举出来,一一获取然后转成行内样式,看起来也是可行的。

但这个方案需要逐步尝试完善对应的属性列表,相当于复刻一下浏览器的布局规则,工作量较大,此外还需要考虑rem、postcss等问题,看起来也不是一个明智的选择。

postcss插件

既然scopeid是通过postcss插入的,能不能在对应的样式规则里面加一个分组选择器,额外支持一下骨架屏的呢

比如

.card[data-v-xxx] {}
登入後複製
登入後複製
登入後複製

修改为

.card[data-v-xxx], .sk-wrap .card {}
登入後複製

这样,只要解决生产环境和开发环境scopeid不一致的问题就可以了。

编写postcss插件可以参考官方文档:编写一个postcss 插件

vue/compuler-sfc源码中发现,scopedPlugin插件位于传入的postcssPlugins之后,而我们编写的插件需要位于scopedPlugin之后才行,

聊聊怎麼利用vite插件來實現骨架螢幕自動化

如果不能修改源码,只有继续从vite 插件的transform钩子入手了,在transform中手动执行postcss进行编译

继续编写一个SkeletonStylePlugin插件

const wrapSelector = '.sk-wrap'
export function SkeletonStylePlugin() {
  return {
    name: 'skeleton-style-plugin',
    transform(src: string, id: string) {
      const {query} = parseVueRequest(id)

      if (query.type === 'style') {
        const result = postcss([cssSkeletonGroupPlugin({wrapSelector})]).process(src)
        return result.css
      }
      return src
    }
  }
}
登入後複製

注意该插件要放在vue插件后面执行,因为此时得到的内容才是经过vue-compiler编译后的携带有scopeid 的样式。

其中cssSkeletonGroupPlugin是一个postcss插件

import {Rule} from 'postcss'

const processedRules = new WeakSet<rule>()

type PluginOptions = {
  wrapSelector: string
}
const plugin = (opts: PluginOptions) => {
  const {wrapSelector} = opts

  function processRule(rule: Rule) {
    if (processedRules.has(rule)) {
      return
    }
    processedRules.add(rule)
    rule.selector = rewriteSelector(rule)
  }

  function rewriteSelector(rule: Rule): string {
    const selector = rule.selector || ''

    const group: string[] = []
    selector.split(',').forEach(sel => {
      // todo 这里需要排除不在骨架屏中使用的样式
      const re = /\[data-v-.*?\]/igm
      if (re.test(sel)) {
        group.push(wrapSelector + ' ' + sel.replace(re, ''))
      }
    })

    if(!group.length) return selector
    return selector + ', ' + group.join(',')
  }

  return {
    postcssPlugin: 'skeleton-group-selector-plugin',
    Rule(rule: Rule) {
      processRule(rule)
    },
  }
}
plugin.postcss = true

export default plugin</rule>
登入後複製

这个插件写的比较粗糙,只考虑了常规的选择器,并依次追加分组选择器。测试一下

.test1[data-v-xxx] {}
登入後複製

成功编译成了

.test1[data-v-xxx], .sk-wrap .test1 {}
登入後複製

这样,只需要将骨架屏代码外边包一层sk-wrap,骨架屏中的样式就可以正常生效了!

content && document.write('<div>' +content+'</div>')
登入後複製

看起来解决了一个困扰我很久的问题。

小结

至此,一个借助于Vite插件实现自动骨架屏的方案就实现了,总结一下整体流程

首先初始化插件

import {SkeletonPlaceholderPlugin, SkeletonApiPlugin} from '../src/plugins/vitePlugin'

export default defineConfig({
  plugins: [
    SkeletonPlaceholderPlugin(),
    vue(),
    SkeletonApiPlugin(),
  ],
  build: {
    cssCodeSplit: false
  }
})
登入後複製

然后填写占位符,对于首屏渲染的骨架屏

<div>__SKELETON_CONTENT__</div>
登入後複製
登入後複製

对于组件内的骨架屏


__SKELETON_APP_CONTENT__
<div></div>
登入後複製

接着初始化客户端触发器,同时向页面插入一个可以点击生成骨架屏的按钮

import '../../src/style/skeleton.scss'
import {initInject} from '../../src/inject'

createApp(App).use(router).mount('#app')

// 开发环境下才注入
if (import.meta.env.DEV) {
  setTimeout(initInject)
}
登入後複製

点击触发器,自动将当前页面转换成骨架屏

通过HTTP将骨架屏代码发送到插件接口,通过fs写入本地文件./src/skeleton/content.json中,然后自动重启vite server

页面内组件的占位符会通过SkeletonPlaceholderPlugin替换对应占位符的骨架屏代码,loading生效时展示骨架屏

首屏渲染页面时,通过location.pathname插入当前路径对应的骨架屏代码,直接看见骨架屏代码

所有骨架屏依赖的当前样式通过cssSkeletonGroupPlugin解析,通过分组选择器输出在css文件,不再依赖scopeid。

这样,一个基本自动的骨架屏工具就集成到项目中,需要进行的手动工作包括

  • 配置插件
  • 定义组件的骨架屏占位符,以及骨架屏入口data-skeleton-root="APP"
  • 必要时在标签上声明data-skeleton-type,定制骨架屏节点

整个项目比较依赖vite插件开发知识,也参考了vite@vitejs/plugin-vue@vue/compile-sfc等源码的实现细节。

所有Demo已经放在github上面了,剩下要解决的就是优化生成骨架屏的效果和质量了,期待后续吧

(学习视频分享:web前端开发编程基础视频

以上是聊聊怎麼利用vite插件來實現骨架螢幕自動化的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它們
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

vue怎麼給按鈕添加函數 vue怎麼給按鈕添加函數 Apr 08, 2025 am 08:51 AM

可以通過以下步驟為 Vue 按鈕添加函數:將 HTML 模板中的按鈕綁定到一個方法。在 Vue 實例中定義該方法並編寫函數邏輯。

vue中怎麼用bootstrap vue中怎麼用bootstrap Apr 07, 2025 pm 11:33 PM

在 Vue.js 中使用 Bootstrap 分為五個步驟:安裝 Bootstrap。在 main.js 中導入 Bootstrap。直接在模板中使用 Bootstrap 組件。可選:自定義樣式。可選:使用插件。

vue.js怎麼引用js文件 vue.js怎麼引用js文件 Apr 07, 2025 pm 11:27 PM

在 Vue.js 中引用 JS 文件的方法有三種:直接使用 &lt;script&gt; 標籤指定路徑;利用 mounted() 生命週期鉤子動態導入;通過 Vuex 狀態管理庫進行導入。

vue中的watch怎麼用 vue中的watch怎麼用 Apr 07, 2025 pm 11:36 PM

Vue.js 中的 watch 選項允許開發者監聽特定數據的變化。當數據發生變化時,watch 會觸發一個回調函數,用於執行更新視圖或其他任務。其配置選項包括 immediate,用於指定是否立即執行回調,以及 deep,用於指定是否遞歸監聽對像或數組的更改。

vue返回上一頁的方法 vue返回上一頁的方法 Apr 07, 2025 pm 11:30 PM

Vue.js 返回上一頁有四種方法:$router.go(-1)$router.back()使用 &lt;router-link to=&quot;/&quot;&gt; 組件window.history.back(),方法選擇取決於場景。

vue多頁面開發是啥意思 vue多頁面開發是啥意思 Apr 07, 2025 pm 11:57 PM

Vue 多頁面開發是一種使用 Vue.js 框架構建應用程序的方法,其中應用程序被劃分為獨立的頁面:代碼維護性:將應用程序拆分為多個頁面可以使代碼更易於管理和維護。模塊化:每個頁面都可以作為獨立的模塊,便於重用和替換。路由簡單:頁面之間的導航可以通過簡單的路由配置來管理。 SEO 優化:每個頁面都有自己的 URL,這有助於搜索引擎優化。

怎樣查詢vue的版本 怎樣查詢vue的版本 Apr 07, 2025 pm 11:24 PM

可以通過以下方法查詢 Vue 版本:使用 Vue Devtools 在瀏覽器的控制台中查看“Vue”選項卡。使用 npm 運行“npm list -g vue”命令。在 package.json 文件的“dependencies”對像中查找 Vue 項。對於 Vue CLI 項目,運行“vue --version”命令。檢查 HTML 文件中引用 Vue 文件的 &lt;script&gt; 標籤中的版本信息。

vue的div怎麼跳轉 vue的div怎麼跳轉 Apr 08, 2025 am 09:18 AM

Vue 中 div 元素跳轉的方法有兩種:使用 Vue Router,添加 router-link 組件。添加 @click 事件監聽器,調用 this.$router.push() 方法跳轉。

See all articles