添加自定义插件时,为什么我的Webpack手表会停止工作?

时间:2018-06-14 16:00:45

标签: node.js webpack puppeteer

我创建了一个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的观看/重新编译功能。

谢谢!

0 个答案:

没有答案