Skip to content

Commit

Permalink
Adds support for media attribute on link elements (#510)
Browse files Browse the repository at this point in the history
* Adds support for media attribute on link elements

* cleanup
  • Loading branch information
bezoerb authored Jul 19, 2021
1 parent 24e2266 commit f6aadc9
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 14 deletions.
85 changes: 71 additions & 14 deletions src/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ function urlParse(str = '') {
* @returns {string} file uri
*/
function getFileUri(file) {
if (!path.isAbsolute) {
if (!isAbsolute) {
throw new Error('Path must be absolute to compute file uri');
}

Expand All @@ -104,20 +104,24 @@ function urlResolve(from = '', to = '') {
return href;
}

if (path.isAbsolute(to)) {
if (isAbsolute(to)) {
return to;
}

return path.join(from.replace(/[^/]+$/, ''), to);
}

function isAbsolute(href) {
return !Buffer.isBuffer(href) && path.isAbsolute(href);
}

/**
* Check whether a resource is relative or not
* @param {string} href Path
* @returns {boolean} True if the path is relative
*/
function isRelative(href) {
return !Buffer.isBuffer(href) && !isRemote(href) && !path.isAbsolute(href);
return !Buffer.isBuffer(href) && !isRemote(href) && !isAbsolute(href);
}

/**
Expand Down Expand Up @@ -369,32 +373,77 @@ async function fetch(uri, options = {}, secure = true) {
* @param {Vinyl} file Vinyl file object (document)
* @returns {[string]} Stylesheet urls from document source
*/
function getStylesheetHrefs(file) {
function getStylesheetObjects(file) {
if (!isVinyl(file)) {
throw new Error('Parameter file needs to be a vinyl object');
}

if (file.stylesheetObjects) {
return file.stylesheetObjects;
}

const stylesheets = oust.raw(file.contents.toString(), ['stylesheets', 'preload', 'styles']);

const isNotPrint = (el) =>
el.attr('media') !== 'print' || (Boolean(el.attr('onload')) && el.attr('onload').includes('media'));

const hrefs = stylesheets
const isMediaQuery = (media) => typeof media === 'string' && !['all', 'print', 'screen'].includes(media);

const objects = stylesheets
.filter((link) => isNotPrint(link.$el) && Boolean(link.value))
.map((link) => {
const media = isMediaQuery(link.$el.attr('media')) ? link.$el.attr('media') : '';

// support base64 encoded styles
if (link.value.startsWith('data:')) {
return dataUriToBuffer(link.value);
return {
media,
value: dataUriToBuffer(link.value),
};
}

if (link.type === 'styles') {
return Buffer.from(link.value);
return {
media,
value: Buffer.from(link.value),
};
}

return link.value;
return {
media,
value: link.value,
};
});

return [...new Set(hrefs)];
const isEqual = (a, b) => Buffer.from(a).compare(Buffer.from(b)) === 0;
const compare = (a, b) => isEqual(a.media, b.media) && isEqual(a.value, b.value);
// Make objects unique
const stylesheetObjects = objects.filter((a, index, array) => {
return array.findIndex((b) => compare(a, b)) === index;
});

// cache them for later use
file.stylesheetObjects = stylesheetObjects;

return stylesheetObjects;
}

/**
* Extract stylesheet urls from html document
* @param {Vinyl} file Vinyl file object (document)
* @returns {[string]} Stylesheet urls from document source
*/
function getStylesheetHrefs(file) {
return getStylesheetObjects(file).map((object) => object.value);
}

/**
* Extract stylesheet urls from html document
* @param {Vinyl} file Vinyl file object (document)
* @returns {[string]} Stylesheet urls from document source
*/
function getStylesheetsMedia(file) {
return getStylesheetObjects(file).map((object) => object.media);
}

/**
Expand Down Expand Up @@ -441,8 +490,8 @@ async function getDocumentPath(file, options = {}) {

// Check local and assume base path based on relative stylesheets
if (file.stylesheets) {
const relativeRefs = file.stylesheets.filter((href) => !Buffer.isBuffer(href) && isRelative(href));
const absoluteRefs = file.stylesheets.filter((href) => !Buffer.isBuffer(href) && path.isAbsolute(href));
const relativeRefs = file.stylesheets.filter((href) => isRelative(href));
const absoluteRefs = file.stylesheets.filter((href) => isAbsolute(href));
// If we have no stylesheets inside, fall back to path relative to process cwd
if (relativeRefs.length === 0 && absoluteRefs.length === 0) {
process.stderr.write(BASE_WARNING);
Expand Down Expand Up @@ -720,7 +769,7 @@ async function vinylize(src, options = {}) {
* @returns {Promise<Vinyl>} Vinyl representation fo the stylesheet
*/
async function getStylesheet(document, filepath, options = {}) {
const {rebase = {}, css, strict} = options;
const {rebase = {}, css, strict, media} = options;
const originalPath = filepath;

const exists = await fileExists(filepath, options);
Expand All @@ -745,6 +794,9 @@ async function getStylesheet(document, filepath, options = {}) {
}

const file = await vinylize({filepath}, options);
if (media) {
file.contents = Buffer.from(`@media ${media} { ${file.contents.toString()} }`);
}

// Restore original path for local files referenced from document and not from options
if (!Buffer.isBuffer(originalPath) && !isRemote(originalPath) && !css) {
Expand Down Expand Up @@ -789,7 +841,7 @@ async function getStylesheet(document, filepath, options = {}) {
file.contents = await rebaseAssets(file.contents, rebase.from || stylepath, rebase.to || pathname);

// Make images absolute if we have an absolute positioned stylesheet
} else if (path.isAbsolute(stylepath)) {
} else if (isAbsolute(stylepath)) {
file.contents = await rebaseAssets(file.contents, rebase.from || stylepath, rebase.to || '/index.html', (asset) =>
normalizePath(asset.absolutePath)
);
Expand Down Expand Up @@ -817,7 +869,10 @@ async function getCss(document, options = {}) {
stylesheets = await mapAsync(files, (file) => getStylesheet(document, file, options));
debug('(getCss) css option set', files, stylesheets);
} else {
stylesheets = await mapAsync(document.stylesheets, (file) => getStylesheet(document, file, options));
stylesheets = await mapAsync(document.stylesheets, (file, index) => {
const media = (document.stylesheetsMedia || [])[index];
return getStylesheet(document, file, {...options, media});
});
debug('(getCss) extract from document', document.stylesheets, stylesheets);
}

Expand Down Expand Up @@ -896,6 +951,7 @@ async function getDocument(filepath, options = {}) {
const document = await vinylize({filepath}, options);

document.stylesheets = await getStylesheetHrefs(document);
document.stylesheetsMedia = await getStylesheetsMedia(document);
document.virtualPath = rebase.to || (await getDocumentPath(document, options));

document.cwd = base || process.cwd();
Expand Down Expand Up @@ -932,6 +988,7 @@ async function getDocumentFromSource(html, options = {}) {
const document = await vinylize({html}, options);

document.stylesheets = await getStylesheetHrefs(document);
document.stylesheetsMedia = await getStylesheetsMedia(document);
document.virtualPath = rebase.to || (await getDocumentPath(document, options));
document.cwd = base || process.cwd();

Expand Down
15 changes: 15 additions & 0 deletions test/file.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,21 @@ test('Get inline styles', async () => {
}
});

test('Get styles with media attribute', async () => {
const docs = await mapAsync(
[`http://localhost:${port}/media-attr.html`, path.join(__dirname, 'fixtures/media-attr.html')],
(filepath) => getDocument(filepath)
);

const expected = `@media (max-width: 1024px) { .header {
display: flex;
} }`;

for (const document of docs) {
expect(document.css.toString()).toMatch(expected);
}
});

test('Get base64 styles', async () => {
const docs = await mapAsync(
[
Expand Down
15 changes: 15 additions & 0 deletions test/fixtures/media-attr.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!doctype html>
<html class="no-js">
<head>
<meta charset="utf-8">
<title>critical css test</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles/media-attr.css" media="(max-width: 1024px)">
</head>
<body>
<div class="container">
<div class="header"></div>
</div>
</body>
</html>
3 changes: 3 additions & 0 deletions test/fixtures/styles/media-attr.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.header {
display: flex;
}

0 comments on commit f6aadc9

Please sign in to comment.