原理
监听打包完成事件
当webpack编译完成后会将打包好的文件输出到我们打包的目录中,我们可以启动一个服务,通过puppeteer 自动化访问生成的页面,生成快照,抓取骨架内容,将页面中的元素通过css 替换成我们想要的效果,比如背景色生成浅灰,页面中的所有button按钮生成深灰色,以及图片、文字等等都有对应的css替换。最后将我们在 html 中的占位替换成骨架屏内容就可以。

创建插件
const PLUGIN_NAME = 'SkeletonPlugin'const Skeleton = require('./Skeleton')const Server = require('./server')class SkeletonPlugin{constructor(options){this.options = options}// compiler 代表了整个webpack配置apply(compiler){// tap :以同步方式触发钩子;compiler.hooks.done.tap(PLUGIN_NAME, async () =>{// 启动服务,预览项目dist,拿到dom结构,转成骨架屏页面await this.startServer()// 用puptier 抓取内容// 生成骨架屏内容this.skeleton = new Skeleton(this.options)// 启动一个chrome 无头浏览器await this.skeleton.initialize()// 生成骨架屏html和styleconst skeletonHtml = await this.skeleton.genHtml(this.options.origin)console.log(skeletonHtml)// 销毁无头浏览器// await this.skeleton.destroy()// 关闭服务// await this.server.close()})}async startServer(){// 创建服务this.server = new Server(this.options)// 启动这个服务await this.server.listen()}}module.exports = SkeletonPlugin;
创建服务,打开浏览器
const puppeteer = require('puppeteer')class Skeleton{constructor(options){this.options = options}// 打开一个浏览器async initialize(){this.browser = await puppeteer.launch({headless: false})}// 打开新的页签async newPage(){let { device } = this.options// 打开一个新的页签let page = await this.browser.newPage()// 选择一个适配设备await page.emulate(puppeteer.devices[device])return page}async genHtml(url){let page = await this.newPage()let res = await page.goto(url, { waitUntil: "networkidle2" })if(res && !res.ok()){// 访问失败 抛出异常throw new Error(`${res.status} on ${url}`)}return 'html'}async destory(){if(this.browser){await this.browser.close()this.browser = null}}}module.exports = Skeleton
生成骨架屏dom结构
async makeSkeleton(page){const {defer = 5000} = this.options// 读取要生成骨架屏DoM 结构的脚本const scriptContent = await readFileSync(resolve(__dirname, '../script/index.js'), 'utf8')// 向页面中注入脚本await page.addScriptTag({content: scriptContent})// 执行脚本需要花时间await sleep(defer)// 脚本执行完就创建骨架屏DOM结构 在puppteer页面中调用全局方法 evaluateawait page.evaluate((options) =>{Skeleton.genSkeleton(options)}, this.options)}async genHtml(url){let page = await this.newPage()let res = await page.goto(url, { waitUntil: "networkidle2" })if(res && !res.ok()){// 访问失败 抛出异常throw new Error(`${res.status} on ${url}`)}// 创建骨架屏dom结构await this.makeSkeleton(page)return 'html'}
处理元素的脚本
// 创建全局变量 用var 或者 window.Skeletonvar Skeleton = (function(){// 转换原始元素为骨架dom元素function genSkeleton(){console.log('骨架')}// 获取附加dom 元素的html 和stylefunction genHtmlAndStyle(){}return {genSkeleton, genHtmlAndStyle}})()
