diff --git a/README.md b/README.md index 6751118..f721434 100644 --- a/README.md +++ b/README.md @@ -256,7 +256,7 @@ generate({ | inline | `boolean`\|`object` | `false` | Inline critical-path CSS using filamentgroup's loadCSS. Pass an object to configure [`inline-critical`](https://github.com/bezoerb/inline-critical#inlinehtml-styles-options) | | base | `string` | `path.dirname(src)` or `process.cwd()` | Base directory in which the source and destination are to be written | | html | `string` | | HTML source to be operated against. This option takes precedence over the `src` option. | -| css | `array` | `[]` | An array of paths to css files, file globs or [Vinyl](https://www.npmjs.com/package/vinyl) file objects. | +| css | `array` | `[]` | An array of paths to css files, file globs, [Vinyl](https://www.npmjs.com/package/vinyl) file objects or source CSS strings. | | src | `string` | | Location of the HTML source to be operated against | | target | `string` or `object` | | Location of where to save the output of an operation. Use an object with 'html' and 'css' props if you want to store both | | width | `integer` | `1300` | Width of the target viewport | diff --git a/package-lock.json b/package-lock.json index 734882b..410bf8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "7.0.0", "license": "Apache-2.0", "dependencies": { + "@adobe/css-tools": "^4.3.3", "clean-css": "^5.3.3", "common-tags": "^1.8.2", "css-url-parser": "^1.1.3", @@ -73,9 +74,9 @@ } }, "node_modules/@adobe/css-tools": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", - "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==" + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.3.tgz", + "integrity": "sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==" }, "node_modules/@ampproject/remapping": { "version": "2.2.1", @@ -12285,9 +12286,9 @@ "dev": true }, "@adobe/css-tools": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", - "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==" + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.3.tgz", + "integrity": "sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==" }, "@ampproject/remapping": { "version": "2.2.1", diff --git a/package.json b/package.json index 879d385..9891d7c 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "node": ">=18" }, "dependencies": { + "@adobe/css-tools": "^4.3.3", "clean-css": "^5.3.3", "common-tags": "^1.8.2", "css-url-parser": "^1.1.3", diff --git a/src/file.js b/src/file.js index 3872527..b430f7b 100644 --- a/src/file.js +++ b/src/file.js @@ -1,3 +1,4 @@ +/* eslint-disable complexity */ import {Buffer} from 'node:buffer'; import fs from 'node:fs'; import os from 'node:os'; @@ -10,6 +11,7 @@ import {dataUriToBuffer} from 'data-uri-to-buffer'; import debugBase from 'debug'; import {findUpMultiple} from 'find-up'; import {globby} from 'globby'; +import {parse} from '@adobe/css-tools'; import got from 'got'; import isGlob from 'is-glob'; import makeDir from 'make-dir'; @@ -408,6 +410,7 @@ function getStylesheetObjects(file, options) { throw new Error('Parameter file needs to be a vinyl object'); } + // Already computed stylesheetObjects if (file.stylesheetObjects) { return file.stylesheetObjects; } @@ -927,18 +930,40 @@ export async function getStylesheet(document, filepath, options = {}) { return file; } +const isCssSource = (string) => { + try { + parse(string); + return true; + } catch { + return false; + } +}; + /** * Get css for document * @param {Vinyl} document Vinyl representation of HTML document * @param {object} options Critical options * @returns {Promise} Css string unoptimized, Multiple stylesheets are concatenated with EOL */ -async function getCss(document, options = {}) { +export async function getCss(document, options = {}) { const {css} = options; let stylesheets = []; if (checkCssOption(css)) { - const files = await glob(css, options); + const cssArray = Array.isArray(css) ? css : [css]; + + // merge css files & css source strings passed as css option + const filesRaw = await Promise.all( + cssArray.map((value) => { + if (isCssSource(value)) { + return Buffer.from(value); + } + + return glob(value, options); + }) + ); + + const files = filesRaw.flat(); stylesheets = await mapAsync(files, (file) => getStylesheet(document, file, options)); debug('(getCss) css option set', files, stylesheets); } else { diff --git a/test/blackbox.test.js b/test/blackbox.test.js index 84f6be1..fee5489 100644 --- a/test/blackbox.test.js +++ b/test/blackbox.test.js @@ -316,6 +316,23 @@ describe('generate (local)', () => { ); }); + test('should evaluate css passed as source string', (done) => { + const expected = 'html{display:block}'; + const target = path.resolve('.source-string.css'); + + generate( + { + base: FIXTURES_DIR, + src: 'generate-default-nostyle.html', + css: ['html{display:block}.someclass{color:red}'], + target, + width: 1300, + height: 900, + }, + assertCritical(target, expected, done) + ); + }); + test('should inline relative images', (done) => { const expected = read('expected/generate-image.css'); const target = path.resolve('.image-relative.css'); diff --git a/test/file.test.js b/test/file.test.js index 8c0a51b..fd5c768 100644 --- a/test/file.test.js +++ b/test/file.test.js @@ -25,6 +25,7 @@ import { vinylize, normalizePath, getStylesheetHrefs, + getCss, getAssets, getDocumentPath, getStylesheetPath, @@ -663,3 +664,16 @@ test('Does not rebase when rebase is disabled via option', async () => { } } }); + +test('Handle css source option', async () => { + const document = await getDocument(path.join(__dirname, 'fixtures/generate-adaptive.html')); + + const source1 = 'html{display:block;}'; + const source2 = '.someclass{color:red}'; + const css = await getCss(document, { + css: [source1, path.join(__dirname, 'fixtures/styles/adaptive.css'), source2], + }); + + expect(css.startsWith(source1)).toBeTruthy(); + expect(css.endsWith(source2)).toBeTruthy(); +});