UNPKG

@elambro/extract-css-media-queries

Version:

A webpack plugin which extracts media queries from your css into separate files

209 lines (163 loc) 5.9 kB
const {splitRulesByMinSize, mergeDuplicateRules, sortRulesBySize, rescue} = require('./utils') const DEFAULT_MIN_WIDTH = 768; const css = require('css'); const printMessage = require('print-message') const createHash = require('webpack/lib/util/createHash'); class Breakpoint { constructor(options) { this.minHeight = options.minHeight; this.minWidth = options.minWidth; this.minify = options.minify; this.verbose = options.verbose; this.combined = options.combined; this.filename = options.filename; this.hash = options.hash } static parse(options) { return (options.breakpoints||[{}]).map( bp => { let isNum = typeof bp === 'string' || typeof bp === 'number'; let toNum = (n) => typeof n === 'number' ? n : n.match(/(\d+)/)[0]; let w = isNum ? bp : bp.minWidth; let h = isNum ? null : bp.minHeight let local = isNum ? {} : bp; let combined = typeof local.combined === 'boolean' ? local.combined : options.combined; return new Breakpoint({ minHeight: h ? toNum(h) : null, minWidth : w ? toNum(w) : (h ? null : DEFAULT_MIN_WIDTH), minify : typeof local.minify === 'boolean' ? local.minify : options.minify, verbose : typeof local.verbose === 'boolean' ? local.verbose : options.verbose, combined : typeof local.combined === 'boolean' ? local.combined : options.combined, filename : local.filename || options.filename, hash : local.hash || options.hash }) }) } } class StyleRules { constructor(rules, options, originalName) { this.rules = rules; this.options = options; this.originalName = originalName; this.rebuiltName = null; this.contenthash = null; this.hashedName = null; this.hash = null; this.fullhash = null; } static fromStylesheet(sheet, options, name) { let rules = sheet instanceof StyleRules ? sheet.rules : css.parse(sheet).stylesheet.rules return new StyleRules(rules, options, name); } split() { let {common,extracted,results} = splitRulesByMinSize(this.rules, this.options.minWidth, this.options.minHeight); this.options.verbose && rescue( () => printMessage( results.length ? [`Matches from ${this.originalName} for {min-width: ${this.options.minWidth}, min-height: ${this.options.minHeight}}:`, `---------------------`, ...results] : [`No matches from ${cssFileName}`])) this.rules = common; return new StyleRules(extracted, this.options, this.originalName) } dedupe() { this.rules = mergeDuplicateRules(this.rules); return this; } sort() { this.rules = sortRulesBySize(this.rules) return this; } getFilename() { let w = this.options.minWidth; let h = this.options.minHeight; let bp = `${w}${h?`-${h}`:``}`; const trimSlashes = str => str.replace(/^\/|\/$/g, '') // .replace(/^[\\/]|[\\/]$/g, '') // Or like this? let template; if (this.options.combined) { // Not based on individual sheet name template = this.options.filename || `extracted-[breakpoint].[ext]`; } else { // Needs original filename let dirs = this.originalName.split(`/`); // .filter(v => v !== '.' && v !== '..'); let basename = dirs.pop().replace('.css', '') let path = trimSlashes(dirs.join(`/`)) template = (this.options.filename || `${path}/[name]-[breakpoint].[ext]`).replace('[name]', basename); } let res = template .replace('[breakpoint]', bp) .replace('[ext]', 'css') if (!res.endsWith('.css')) { res += '.css'; } this.rebuiltName = res; return res; } /** * Create a stylesheet from the array of rules * @param {Array} rules * @return {String} The contents of a stylesheet */ toStylesheet() { let contents = css.stringify({ type: 'stylesheet', stylesheet: {rules: this.rules}, }) if (this.options.minify) { const CleanCSS = require('clean-css') contents = (new CleanCSS().minify(contents)).styles } let buffer = new Buffer(contents); this.getHashedName(buffer); return { contents, contenthash: this.contenthash, hashedName : this.hashedName, hash : this.hash, fullhash : this.fullhash, original : this.originalName || this.rebuiltName, size : () => Buffer.byteLength(contents, 'utf8'), source : () => buffer } } getHash(str, len=6) { this.hash = createHash('md4') this.hash.update(str) this.fullhash = this.hash.digest('hex'); return this.fullhash.substr(0, len) } getHashedName(buffer) { let filename = this.originalName || this.rebuiltName; let pattern = this.options.hash if (pattern) { // See https://github.com/scinos/webpack-plugin-hash-output/blob/master/src/OutputHash.js filename = filename.replace(/\\/g, '/'); let hashTemplate = typeof pattern === 'string' ? pattern : `[name].[contenthash:8].js`; const REGEXP_CONTENTHASH = /\[contenthash(?::(\d+))?\]/i const REGEXP_NAME = /\[name\]/i let match = hashTemplate.match(REGEXP_CONTENTHASH); let hashStr = match ? match[0] : false; let hashLen = match ? match[1] : false; var dirs = filename.split(`/`); var basename = dirs.pop().replace('.css', ''); this.contenthash = hashStr ? this.getHash(buffer, hashLen) : ''; let newFilename = hashTemplate .replace('[name]',basename) .replace('[ext]', 'css') .replace(hashStr, this.contenthash) .replace(/^[\\/]|[\\/]$/g, '') // remove leading & trailing slashes this.hashedName = `${dirs.join(`/`)}/${newFilename}`; } return this; } } module.exports = {StyleRules, Breakpoint};