我创建了一个Webpack插件来处理关键的CSS,你可以在这里查看:https://jsbin.com/qaveyeheta/edit?js,output
const fs = require( 'fs' )
const path = require( 'path' )
const puppeteer = require( 'puppeteer' )
const _ = require( 'lodash' )
const from = require( 'from' )
const config = {
viewport: {
width: 375,
height: 812
}
}
function CriticalCssPlugin( options ) {
this.options = options
this.finished = []
this.baseDir = `${this.options.views.src}/critical`
}
CriticalCssPlugin.prototype.apply = function( compiler ) {
compiler.plugin( 'done', () => {
let { views, base, isCritical } = this.options
this.content = this.getContent( views.src )
.filter( item => item.endsWith( '.html' ) )
this.content.forEach( ( content, index ) => {
this.createEmptyFolder( index )
this.createEmptyFile( index )
} )
if ( !isCritical ) {
return
}
this.sources = this.content.slice().map( item => `${isCritical ? base : `http://localhost`}${this.normalizeUrl( item )}` )
return puppeteer.launch( { headless: true, args: [ '--no-sandbox', '--disable-setuid-sandbox' ] } )
.then( browser => {
this.browser = browser
return browser.newPage()
} )
.then( page => {
return page.setViewport( { width: config.viewport.width, height: config.viewport.height } )
.then( () => this.load( page, this.sources[ 0 ], 0 ) )
} )
} )
}
CriticalCssPlugin.prototype.normalizeUrl = function( url ) {
return url.startsWith( '/' ) ? url : `/${url}`
}
CriticalCssPlugin.prototype.createEmptyFolder = function( fileIndex, file, base ) {
let dir = this.baseDir
base = base || dir
let pick = this.content.slice()[ fileIndex ]
let paths = ( file || pick.split( '/' ) ).filter( item => item )
let isDirectory = paths.length > 1 && !( paths[ 0 ].split( '.' ).length > 1 )
!fs.existsSync( dir ) && fs.mkdirSync( dir )
if ( isDirectory ) {
let folder = paths.shift()
let path = `${base}/${folder}`
!fs.existsSync( path ) && fs.mkdirSync( path )
this.createEmptyFolder( null, paths, path )
}
}
CriticalCssPlugin.prototype.createEmptyFile = function( fileIndex ) {
let dir = this.baseDir
let file = this.content.slice()[ fileIndex ]
let path = `${dir}/${file}`
fs.writeFileSync( path.replace( '.html', '.css' ), '', 'utf-8', err => {
if ( err ) throw err
} )
}
CriticalCssPlugin.prototype.load = function( page, src, fileIndex ) {
return page.goto( `${src}`, { waitUntil: 'load' } )
.then( () => page.waitFor( 100 ) )
.then( () => page.$$( '*' ) )
.then( elements =>
Promise.all( elements.map( element => element.boundingBox() ) )
.then( positions => {
let next = fileIndex + 1
if ( this.sources[ next ] ) {
this.load( page, this.sources[ next ], next )
}
let visible = positions.filter( ( rect, index ) => {
if ( !rect ) {
return
}
rect.element = elements[ index ]
return this.isAnyPartOfElementInViewport( rect, page.viewport() )
} )
this.getClasses( page, visible, fileIndex )
} )
)
.catch( err => console.error( `Failed loading ${err}` ) )
.then( () => this.finished.push( '' ) )
}
CriticalCssPlugin.prototype.getClasses = function( page, visibles, fileIndex ) {
let classes = visibles.map( visible => {
return visible.element._remoteObject.description
.replace( /#/g, ',#' )
.replace( /\./g, ',.' )
.split( ',' )
} )
let list = classes
.reduce( ( acc, val ) => acc.concat( val ), [] )
.filter( ( item, index, self ) => self.indexOf( item ) == index )
.filter( item => item.startsWith( '.' ) || item.startsWith( '#' ) )
return this.getParsedCss()
.then( data => {
let result = data.filter( rule => {
let includes = false
list.forEach( item => {
if ( includes || item.length < 2 ) {
return
}
includes = rule.includes( item )
} )
return includes
} )
this.writeStyle( result, fileIndex )
} )
.catch( console.error )
}
CriticalCssPlugin.prototype.writeStyle = function( css, fileIndex ) {
let dir = this.baseDir
let path = `${dir}${this.normalizeUrl( this.content[ fileIndex ] )}`
path = path.replace( 'html', 'css' )
!fs.existsSync( dir ) && fs.mkdirSync( dir )
let writeStream = fs.createWriteStream( path, { flags: 'w' } )
from( css ).pipe( writeStream )
this.verifyIfShouldCloseBrowser()
}
CriticalCssPlugin.prototype.verifyIfShouldCloseBrowser = function() {
let shouldClose = this.content.length == this.finished.length
if ( shouldClose && this.browser ) {
this.browser.close()
this.browser = null
console.log( 'Finished adding Critical CSS' )
}
}
CriticalCssPlugin.prototype.getParsedCss = function() {
let file = this.getContent( this.options.css.src )
.filter( item => item.startsWith( '/static' ) && item.endsWith( '.css' ) )
.map( item => `${this.options.css.src}${item}` )[ 0 ]
return new Promise( ( resolve, reject ) => {
fs.readFile( file, 'utf8', ( err, data ) => {
if ( err ) {
reject( err )
}
resolve( this.parseCss( data ) )
} )
} )
}
CriticalCssPlugin.prototype.parseCss = function( css ) {
let rules = []
let end = false
while ( css.length && !end ) {
let isMedia = css.startsWith( '@media' )
let selection = this.getRule( css, isMedia )
css = css.replace( selection.rule, '' )
rules.push( selection.rule )
end = selection.lineBreak
}
return rules
}
CriticalCssPlugin.prototype.getRule = function( css, isMedia ) {
let arr = css.split( '' )
let rule = ''
let finishedWithBrackets = false
let index = 0
let lineBreak = false
while ( !finishedWithBrackets ) {
let char = arr[ index ]
let lastChar = rule.slice( -1 )
lineBreak = char == '\n'
rule += char
finishedWithBrackets = ( char == '}' && ( !isMedia || ( isMedia && lastChar == '}' ) ) )
index++
}
return { rule, lineBreak }
}
CriticalCssPlugin.prototype.getContent = function( dir, filelist, path ) {
let files = fs.readdirSync( dir )
filelist = filelist || []
path = path || ''
files.forEach( file => {
if ( fs.statSync( `${dir}/${file}` ).isDirectory() ) {
filelist = this.getContent( `${dir}/${file}/`, filelist, file )
} else {
filelist.push( `${path}/${file}` )
}
} )
return filelist
}
CriticalCssPlugin.prototype.flattenDeep = function( arr ) {
return arr.reduce( ( acc, val ) => Array.isArray( val ) ? acc.concat( this.flattenDeep( val ) ) : acc.concat( val ), [] )
}
CriticalCssPlugin.prototype.isAnyPartOfElementInViewport = function( rect, viewport ) {
const windowHeight = viewport.height
const windowWidth = viewport.width
const vertInView = ( rect.y <= windowHeight ) && ( rect.height >= 0 )
const horInView = ( rect.x <= windowWidth ) && ( rect.width >= 0 )
return ( vertInView && horInView )
}
module.exports = CriticalCssPlugin
问题是:当它被添加到我的项目中时,它会编译,但是当我保存文件时,SCSS,例如,它不会重新编译,看起来它不是在看我的文件。我认为我忘记了“完成”插件流程的事情。 我想知道为什么我的插件会删除Webpack的观看/重新编译功能。
谢谢!