原理
监听打包完成事件
当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和style
const 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页面中调用全局方法 evaluate
await 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.Skeleton
var Skeleton = (function(){
// 转换原始元素为骨架dom元素
function genSkeleton(){
console.log('骨架')
}
// 获取附加dom 元素的html 和style
function genHtmlAndStyle(){
}
return {genSkeleton, genHtmlAndStyle}
})()