assetgraph
Version:
An auto discovery dependency graph based optimization framework for web pages and applications
139 lines (119 loc) • 3.99 kB
JavaScript
const postcss = require('postcss');
const Css = require('../assets/Css');
const urlModule = require('url');
// http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/internet-explorer-stylesheet-rule-selector-import-sheet-limit-maximum.aspx
module.exports = (
queryObj,
{ minimumIeVersion = 1, rulesPerStylesheetLimit } = {}
) => {
if (typeof rulesPerStylesheetLimit === 'undefined') {
if (minimumIeVersion === null) {
rulesPerStylesheetLimit = Infinity;
} else if (minimumIeVersion <= 9) {
rulesPerStylesheetLimit = 4095;
} else {
rulesPerStylesheetLimit = 65534;
}
}
function countRules(node) {
let count = 0;
if (node.selector) {
count += node.selector.split(',').length;
}
if (Array.isArray(node.nodes)) {
// FIXME: Verify that this counting algorithm is identical to what IE does
for (const childNode of node.nodes) {
count += countRules(childNode);
}
}
return count;
}
function splitStyleSheet(cssAsset) {
const output = [];
let accumulatedRules = 0;
let offset = 0;
for (const [i, rule] of cssAsset.parseTree.nodes.entries()) {
const selectors = countRules(rule);
if (accumulatedRules + selectors <= rulesPerStylesheetLimit) {
accumulatedRules += selectors;
} else {
output.push({
type: cssAsset.type,
isPopulated: false,
parseTree: postcss
.root()
.append(
cssAsset.parseTree.nodes.slice(offset, i).map(Css.cloneWithRaws)
)
});
offset = i;
accumulatedRules = selectors;
}
}
output.push({
type: cssAsset.type,
isPopulated: false,
parseTree: postcss
.root()
.append(cssAsset.parseTree.nodes.slice(offset).map(Css.cloneWithRaws))
});
return output;
}
return function splitCssIfIeLimitIsReached(assetGraph) {
if (rulesPerStylesheetLimit === Infinity) {
return;
}
for (const largeAsset of assetGraph.findAssets(
Object.assign({ type: 'Css', isLoaded: true }, queryObj)
)) {
const count = countRules(largeAsset.parseTree);
let replacements;
if (count > rulesPerStylesheetLimit) {
replacements = splitStyleSheet(largeAsset);
const info = new Error(
`${count} CSS rules, ${count -
rulesPerStylesheetLimit} would be ignored by IE9 and below. Splitting into ${
replacements.length
} chunks to resolve the problem.`
);
info.asset = largeAsset;
assetGraph.info(info);
// Add replacement css assets to the graph
replacements = replacements.map((replacement, i) => {
const asset = assetGraph.addAsset(replacement);
if (!largeAsset.isInline) {
asset.url = new urlModule.URL(
`${largeAsset.fileName.split('.').shift()}-${i}${
largeAsset.extension
}`,
largeAsset.url
).toString();
}
assetGraph.addAsset(asset);
return asset;
});
// Add relations to new replacement assets to the graph
for (const oldRelation of largeAsset.incomingRelations) {
for (const replacement of replacements) {
oldRelation.from.addRelation(
{
type: oldRelation.type,
hrefType: oldRelation.hrefType,
to: replacement
},
'before',
oldRelation
);
}
oldRelation.detach();
}
// Remove all traces of the original to large Css asset
// We're reusing the parseTree of largeAsset, so make sure that
// the relations don't get detached from it as a side effect
// of AssetGraph#removeAsset:
largeAsset.outgoingRelations = [];
assetGraph.removeAsset(largeAsset);
}
}
};
};