├── .editorconfig
├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── .tape.js
├── CHANGELOG.md
├── CONTRIBUTING.md
├── INSTALL.md
├── LICENSE.md
├── README.md
├── index.js
├── lib
├── get-comma.js
├── get-image.js
├── get-media.js
├── handle-invalidation.js
└── process-image-set.js
├── package.json
└── test
├── basic.css
├── basic.expect.css
├── basic.no-preserve.expect.css
└── invalid.css
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_style = tab
7 | insert_final_newline = true
8 | trim_trailing_whitespace = true
9 |
10 | [*.md]
11 | trim_trailing_whitespace = false
12 |
13 | [*.{json,md,yml}]
14 | indent_size = 2
15 | indent_style = space
16 |
17 | [test/*.expect.css]
18 | insert_final_newline = false
19 | trim_trailing_whitespace = false
20 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test
2 | on:
3 | push:
4 |
5 | jobs:
6 | test:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | matrix:
10 | node: [12, 14, 16]
11 | steps:
12 | - uses: actions/checkout@v2
13 | - uses: actions/setup-node@v2
14 | with:
15 | node-version: ${{ matrix.node }}
16 |
17 | - run: npm install --ignore-scripts
18 | - run: npm run test
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | yarn.lock
4 | *.log*
5 | *.result.css
6 | .*
7 | !.editorconfig
8 | !.gitignore
9 | !.tape.js
10 | !.github
11 |
--------------------------------------------------------------------------------
/.tape.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'basic': {
3 | message: 'supports basic usage',
4 | warnings: 2,
5 | },
6 | 'basic:no-preserve': {
7 | message: 'supports { preserve: false } usage',
8 | warnings: 2,
9 | options: {
10 | preserve: false
11 | }
12 | },
13 | 'invalid': {
14 | message: 'ignores invalid usage',
15 | expect: 'invalid.css',
16 | result: 'invalid.css'
17 | },
18 | 'invalid:warn': {
19 | message: 'warns invalid usage when { onvalid: "warn" }',
20 | options: {
21 | oninvalid: 'warn'
22 | },
23 | expect: 'invalid.css',
24 | result: 'invalid.css',
25 | warnings: 10
26 | },
27 | 'invalid:throw': {
28 | message: 'throws invalid usage when { onvalid: "throw" }',
29 | options: {
30 | oninvalid: 'throw'
31 | },
32 | expect: 'invalid.css',
33 | result: 'invalid.css',
34 | error: {
35 | message: /unexpected image/
36 | }
37 | }
38 | };
39 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changes to PostCSS image-set() Function
2 |
3 | ### 4.0.2 (November 19, 2021)
4 |
5 | - Updated: `postcss-value-parser` to 6.0.1 (patch)
6 |
7 | ### 4.0.1 (November 18, 2021)
8 |
9 | - Added: Safeguards against postcss-values-parser potentially throwing an error.
10 |
11 | ### 4.0.0 (September 17, 2021)
12 |
13 | - Updated: Support for PostCS 8+ (major).
14 | - Updated: Support for Node 12+ (major).
15 |
16 | ### 3.0.1 (September 18, 2018)
17 |
18 | - Updated: PostCSS Values Parser 2
19 |
20 | ### 3.0.0 (September 17, 2018)
21 |
22 | - Updated: Support for PostCSS 7+
23 | - Updated: Support for Node 6+
24 |
25 | ### 2.0.0 (May 7, 2018)
26 |
27 | - Sort images by DPR and use the lowest as the default
28 |
29 | ### 1.0.0 (May 2, 2018)
30 |
31 | - Initial version
32 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to PostCSS image-set() Function
2 |
3 | You want to help? You rock! Now, take a moment to be sure your contributions
4 | make sense to everyone else.
5 |
6 | ## Reporting Issues
7 |
8 | Found a problem? Want a new feature?
9 |
10 | - See if your issue or idea has [already been reported].
11 | - Provide a [reduced test case] or a [live example].
12 |
13 | Remember, a bug is a _demonstrable problem_ caused by _our_ code.
14 |
15 | ## Submitting Pull Requests
16 |
17 | Pull requests are the greatest contributions, so be sure they are focused in
18 | scope and avoid unrelated commits.
19 |
20 | 1. To begin; [fork this project], clone your fork, and add our upstream.
21 | ```bash
22 | # Clone your fork of the repo into the current directory
23 | git clone git@github.com:YOUR_USER/postcss-image-set-function.git
24 |
25 | # Navigate to the newly cloned directory
26 | cd postcss-image-set-function
27 |
28 | # Assign the original repo to a remote called "upstream"
29 | git remote add upstream git@github.com:jonathantneal/postcss-image-set-function.git
30 |
31 | # Install the tools necessary for testing
32 | npm install
33 | ```
34 |
35 | 2. Create a branch for your feature or fix:
36 | ```bash
37 | # Move into a new branch for your feature
38 | git checkout -b feature/thing
39 | ```
40 | ```bash
41 | # Move into a new branch for your fix
42 | git checkout -b fix/something
43 | ```
44 |
45 | 3. If your code follows our practices, then push your feature branch:
46 | ```bash
47 | # Test current code
48 | npm test
49 | ```
50 | ```bash
51 | # Push the branch for your new feature
52 | git push origin feature/thing
53 | ```
54 | ```bash
55 | # Or, push the branch for your update
56 | git push origin update/something
57 | ```
58 |
59 | That’s it! Now [open a pull request] with a clear title and description.
60 |
61 | [already been reported]: issues
62 | [fork this project]: fork
63 | [live example]: https://codepen.io/pen
64 | [open a pull request]: https://help.github.com/articles/using-pull-requests/
65 | [reduced test case]: https://css-tricks.com/reduced-test-cases/
66 |
--------------------------------------------------------------------------------
/INSTALL.md:
--------------------------------------------------------------------------------
1 | # Installing PostCSS image-set() Function
2 |
3 | [PostCSS image-set() Function] runs in all Node environments, with special instructions for:
4 |
5 | | [Node](#node) | [PostCSS CLI](#postcss-cli) | [Webpack](#webpack) | [Create React App](#create-react-app) | [Gulp](#gulp) | [Grunt](#grunt) |
6 | | --- | --- | --- | --- | --- | --- |
7 |
8 | ## Node
9 |
10 | Add [PostCSS image-set() Function] to your project:
11 |
12 | ```bash
13 | npm install postcss-image-set-function --save-dev
14 | ```
15 |
16 | Use [PostCSS image-set() Function] to process your CSS:
17 |
18 | ```js
19 | const postcssImageSetFunction = require('postcss-image-set-function');
20 |
21 | postcssImageSetFunction.process(YOUR_CSS /*, processOptions, pluginOptions */);
22 | ```
23 |
24 | Or use it as a [PostCSS] plugin:
25 |
26 | ```js
27 | const postcss = require('postcss');
28 | const postcssImageSetFunction = require('postcss-image-set-function');
29 |
30 | postcss([
31 | postcssImageSetFunction(/* pluginOptions */)
32 | ]).process(YOUR_CSS /*, processOptions */);
33 | ```
34 |
35 | ## PostCSS CLI
36 |
37 | Add [PostCSS CLI] to your project:
38 |
39 | ```bash
40 | npm install postcss-cli --save-dev
41 | ```
42 |
43 | Use [PostCSS image-set() Function] in your `postcss.config.js` configuration
44 | file:
45 |
46 | ```js
47 | const postcssImageSetFunction = require('postcss-image-set-function');
48 |
49 | module.exports = {
50 | plugins: [
51 | postcssImageSetFunction(/* pluginOptions */)
52 | ]
53 | }
54 | ```
55 |
56 | ## Webpack
57 |
58 | Add [PostCSS Loader] to your project:
59 |
60 | ```bash
61 | npm install postcss-loader --save-dev
62 | ```
63 |
64 | Use [PostCSS image-set() Function] in your Webpack configuration:
65 |
66 | ```js
67 | const postcssImageSetFunction = require('postcss-image-set-function');
68 |
69 | module.exports = {
70 | module: {
71 | rules: [
72 | {
73 | test: /\.css$/,
74 | use: [
75 | 'style-loader',
76 | { loader: 'css-loader', options: { importLoaders: 1 } },
77 | { loader: 'postcss-loader', options: {
78 | ident: 'postcss',
79 | plugins: () => [
80 | postcssImageSetFunction(/* pluginOptions */)
81 | ]
82 | } }
83 | ]
84 | }
85 | ]
86 | }
87 | }
88 | ```
89 |
90 | ## Create React App
91 |
92 | Add [React App Rewired] and [React App Rewire PostCSS] to your project:
93 |
94 | ```bash
95 | npm install react-app-rewired react-app-rewire-postcss --save-dev
96 | ```
97 |
98 | Use [React App Rewire PostCSS] and [PostCSS image-set() Function] in your
99 | `config-overrides.js` file:
100 |
101 | ```js
102 | const reactAppRewirePostcss = require('react-app-rewire-postcss');
103 | const postcssImageSetFunction = require('postcss-image-set-function');
104 |
105 | module.exports = config => reactAppRewirePostcss(config, {
106 | plugins: () => [
107 | postcssImageSetFunction(/* pluginOptions */)
108 | ]
109 | });
110 | ```
111 |
112 | ## Gulp
113 |
114 | Add [Gulp PostCSS] to your project:
115 |
116 | ```bash
117 | npm install gulp-postcss --save-dev
118 | ```
119 |
120 | Use [PostCSS image-set() Function] in your Gulpfile:
121 |
122 | ```js
123 | const postcss = require('gulp-postcss');
124 | const postcssImageSetFunction = require('postcss-image-set-function');
125 |
126 | gulp.task('css', () => gulp.src('./src/*.css').pipe(
127 | postcss([
128 | postcssImageSetFunction(/* pluginOptions */)
129 | ])
130 | ).pipe(
131 | gulp.dest('.')
132 | ));
133 | ```
134 |
135 | ## Grunt
136 |
137 | Add [Grunt PostCSS] to your project:
138 |
139 | ```bash
140 | npm install grunt-postcss --save-dev
141 | ```
142 |
143 | Use [PostCSS image-set() Function] in your Gruntfile:
144 |
145 | ```js
146 | const postcssImageSetFunction = require('postcss-image-set-function');
147 |
148 | grunt.loadNpmTasks('grunt-postcss');
149 |
150 | grunt.initConfig({
151 | postcss: {
152 | options: {
153 | use: [
154 | postcssImageSetFunction(/* pluginOptions */)
155 | ]
156 | },
157 | dist: {
158 | src: '*.css'
159 | }
160 | }
161 | });
162 | ```
163 |
164 | [Gulp PostCSS]: https://github.com/postcss/gulp-postcss
165 | [Grunt PostCSS]: https://github.com/nDmitry/grunt-postcss
166 | [PostCSS]: https://github.com/postcss/postcss
167 | [PostCSS CLI]: https://github.com/postcss/postcss-cli
168 | [PostCSS Loader]: https://github.com/postcss/postcss-loader
169 | [PostCSS image-set() Function]: https://github.com/jonathantneal/postcss-image-set-function
170 | [React App Rewire PostCSS]: https://github.com/csstools/react-app-rewire-postcss
171 | [React App Rewired]: https://github.com/timarney/react-app-rewired
172 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # CC0 1.0 Universal
2 |
3 | ## Statement of Purpose
4 |
5 | The laws of most jurisdictions throughout the world automatically confer
6 | exclusive Copyright and Related Rights (defined below) upon the creator and
7 | subsequent owner(s) (each and all, an “owner”) of an original work of
8 | authorship and/or a database (each, a “Work”).
9 |
10 | Certain owners wish to permanently relinquish those rights to a Work for the
11 | purpose of contributing to a commons of creative, cultural and scientific works
12 | (“Commons”) that the public can reliably and without fear of later claims of
13 | infringement build upon, modify, incorporate in other works, reuse and
14 | redistribute as freely as possible in any form whatsoever and for any purposes,
15 | including without limitation commercial purposes. These owners may contribute
16 | to the Commons to promote the ideal of a free culture and the further
17 | production of creative, cultural and scientific works, or to gain reputation or
18 | greater distribution for their Work in part through the use and efforts of
19 | others.
20 |
21 | For these and/or other purposes and motivations, and without any expectation of
22 | additional consideration or compensation, the person associating CC0 with a
23 | Work (the “Affirmer”), to the extent that he or she is an owner of Copyright
24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and
25 | publicly distribute the Work under its terms, with knowledge of his or her
26 | Copyright and Related Rights in the Work and the meaning and intended legal
27 | effect of CC0 on those rights.
28 |
29 | 1. Copyright and Related Rights. A Work made available under CC0 may be
30 | protected by copyright and related or neighboring rights (“Copyright and
31 | Related Rights”). Copyright and Related Rights include, but are not limited
32 | to, the following:
33 | 1. the right to reproduce, adapt, distribute, perform, display, communicate,
34 | and translate a Work;
35 | 2. moral rights retained by the original author(s) and/or performer(s);
36 | 3. publicity and privacy rights pertaining to a person’s image or likeness
37 | depicted in a Work;
38 | 4. rights protecting against unfair competition in regards to a Work,
39 | subject to the limitations in paragraph 4(i), below;
40 | 5. rights protecting the extraction, dissemination, use and reuse of data in
41 | a Work;
42 | 6. database rights (such as those arising under Directive 96/9/EC of the
43 | European Parliament and of the Council of 11 March 1996 on the legal
44 | protection of databases, and under any national implementation thereof,
45 | including any amended or successor version of such directive); and
46 | 7. other similar, equivalent or corresponding rights throughout the world
47 | based on applicable law or treaty, and any national implementations
48 | thereof.
49 |
50 | 2. Waiver. To the greatest extent permitted by, but not in contravention of,
51 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
52 | unconditionally waives, abandons, and surrenders all of Affirmer’s Copyright
53 | and Related Rights and associated claims and causes of action, whether now
54 | known or unknown (including existing as well as future claims and causes of
55 | action), in the Work (i) in all territories worldwide, (ii) for the maximum
56 | duration provided by applicable law or treaty (including future time
57 | extensions), (iii) in any current or future medium and for any number of
58 | copies, and (iv) for any purpose whatsoever, including without limitation
59 | commercial, advertising or promotional purposes (the “Waiver”). Affirmer
60 | makes the Waiver for the benefit of each member of the public at large and
61 | to the detriment of Affirmer’s heirs and successors, fully intending that
62 | such Waiver shall not be subject to revocation, rescission, cancellation,
63 | termination, or any other legal or equitable action to disrupt the quiet
64 | enjoyment of the Work by the public as contemplated by Affirmer’s express
65 | Statement of Purpose.
66 |
67 | 3. Public License Fallback. Should any part of the Waiver for any reason be
68 | judged legally invalid or ineffective under applicable law, then the Waiver
69 | shall be preserved to the maximum extent permitted taking into account
70 | Affirmer’s express Statement of Purpose. In addition, to the extent the
71 | Waiver is so judged Affirmer hereby grants to each affected person a
72 | royalty-free, non transferable, non sublicensable, non exclusive,
73 | irrevocable and unconditional license to exercise Affirmer’s Copyright and
74 | Related Rights in the Work (i) in all territories worldwide, (ii) for the
75 | maximum duration provided by applicable law or treaty (including future time
76 | extensions), (iii) in any current or future medium and for any number of
77 | copies, and (iv) for any purpose whatsoever, including without limitation
78 | commercial, advertising or promotional purposes (the “License”). The License
79 | shall be deemed effective as of the date CC0 was applied by Affirmer to the
80 | Work. Should any part of the License for any reason be judged legally
81 | invalid or ineffective under applicable law, such partial invalidity or
82 | ineffectiveness shall not invalidate the remainder of the License, and in
83 | such case Affirmer hereby affirms that he or she will not (i) exercise any
84 | of his or her remaining Copyright and Related Rights in the Work or (ii)
85 | assert any associated claims and causes of action with respect to the Work,
86 | in either case contrary to Affirmer’s express Statement of Purpose.
87 |
88 | 4. Limitations and Disclaimers.
89 | 1. No trademark or patent rights held by Affirmer are waived, abandoned,
90 | surrendered, licensed or otherwise affected by this document.
91 | 2. Affirmer offers the Work as-is and makes no representations or warranties
92 | of any kind concerning the Work, express, implied, statutory or
93 | otherwise, including without limitation warranties of title,
94 | merchantability, fitness for a particular purpose, non infringement, or
95 | the absence of latent or other defects, accuracy, or the present or
96 | absence of errors, whether or not discoverable, all to the greatest
97 | extent permissible under applicable law.
98 | 3. Affirmer disclaims responsibility for clearing rights of other persons
99 | that may apply to the Work or any use thereof, including without
100 | limitation any person’s Copyright and Related Rights in the Work.
101 | Further, Affirmer disclaims responsibility for obtaining any necessary
102 | consents, permissions or other rights required for any use of the Work.
103 | 4. Affirmer understands and acknowledges that Creative Commons is not a
104 | party to this document and has no duty or obligation with respect to this
105 | CC0 or use of the Work.
106 |
107 | For more information, please see
108 | http://creativecommons.org/publicdomain/zero/1.0/.
109 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
3 |
4 | # PostCSS image-set() Function [
][postcss]
5 |
6 | [![NPM Version][npm-img]][npm-url]
7 | [![CSS Standard Status][css-img]][css-url]
8 | [![Build Status][cli-img]][cli-url]
9 | [![Support Chat][git-img]][git-url]
10 |
11 | [PostCSS image-set() Function] lets you display resolution-dependent images
12 | using the `image-set()` function in CSS, following the [CSS Images]
13 | specification.
14 |
15 | [](https://caniuse.com/#feat=css-image-set)
16 |
17 | ```pcss
18 | .example {
19 | background-image: image-set(
20 | url(img.png) 1x,
21 | url(img@2x.png) 2x,
22 | url(img@print.png) 600dpi
23 | );
24 | }
25 |
26 | /* becomes */
27 |
28 | .example {
29 | background-image: url(img.png);
30 | }
31 |
32 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
33 | .example {
34 | background-image: url(img@2x.png);
35 | }
36 | }
37 |
38 |
39 | @media (-webkit-min-device-pixel-ratio: 6.25), (min-resolution: 600dpi) {
40 | .example {
41 | background-image: url(my@print.png);
42 | }
43 | }
44 |
45 | .example {
46 | background-image: image-set(
47 | url(img.png) 1x,
48 | url(img@2x.png) 2x,
49 | url(img@print.png) 600dpi
50 | );
51 | }
52 | ```
53 |
54 | ## Usage
55 |
56 | Add [PostCSS image-set() Function] to your project:
57 |
58 | ```bash
59 | npm install postcss-image-set-function --save-dev
60 | ```
61 |
62 | Use [PostCSS image-set() Function] to process your CSS:
63 |
64 | ```js
65 | const postcssImageSetFunction = require('postcss-image-set-function');
66 |
67 | postcssImageSetFunction.process(YOUR_CSS /*, processOptions, pluginOptions */);
68 | ```
69 |
70 | Or use it as a [PostCSS] plugin:
71 |
72 | ```js
73 | const postcss = require('postcss');
74 | const postcssImageSetFunction = require('postcss-image-set-function');
75 |
76 | postcss([
77 | postcssImageSetFunction(/* pluginOptions */)
78 | ]).process(YOUR_CSS /*, processOptions */);
79 | ```
80 |
81 | [PostCSS image-set() Function] runs in all Node environments, with special
82 | instructions for:
83 |
84 | | [Node](INSTALL.md#node) | [PostCSS CLI](INSTALL.md#postcss-cli) | [Webpack](INSTALL.md#webpack) | [Create React App](INSTALL.md#create-react-app) | [Gulp](INSTALL.md#gulp) | [Grunt](INSTALL.md#grunt) |
85 | | --- | --- | --- | --- | --- | --- |
86 |
87 | ## Options
88 |
89 | ### preserve
90 |
91 | The `preserve` option determines whether the original declaration using
92 | `image-set()` is preserved. By default, it is preserved.
93 |
94 | ```js
95 | postcssImageSetFunction({ preserve: false })
96 | ```
97 |
98 | ```pcss
99 | .example {
100 | background-image: image-set(
101 | url(img.png) 1x,
102 | url(img@2x.png) 2x,
103 | url(img@print.png) 600dpi
104 | );
105 | }
106 |
107 | /* becomes */
108 |
109 | .example {
110 | background-image: url(img.png);
111 | }
112 |
113 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
114 | .example {
115 | background-image: url(img@2x.png);
116 | }
117 | }
118 |
119 |
120 | @media (-webkit-min-device-pixel-ratio: 6.25), (min-resolution: 600dpi) {
121 | .example {
122 | background-image: url(my@print.png);
123 | }
124 | }
125 | ```
126 |
127 | ### onvalid
128 |
129 | The `oninvalid` option determines how invalid usage of `image-set()` should be
130 | handled. By default, invalid usages of `image-set()` are ignored. They can be
131 | configured to display a `warning` or `throw` an error.
132 |
133 | ```js
134 | postcssImageSetFunction({ oninvalid: 'warning' }) // warn on invalid usages
135 | ```
136 |
137 | ```js
138 | postcssImageSetFunction({ oninvalid: 'throw' }) // throw on invalid usages
139 | ```
140 |
141 | ## Image Resolution
142 |
143 | The `image-set()` function allows an author to provide multiple resolutions of
144 | an image and let the browser decide which is most appropriate in a given
145 | situation. The `image-set()` also never fails to choose an image; the
146 | `` just helps determine which of the images is chosen.
147 |
148 | Since this plugin is not a browser, the image options are sorted by device
149 | pixel ratio and the lowest ratio is used as the default, while the remaining
150 | images are pushed behind media queries.
151 |
152 | Therefore, this plugin can only approximate native browser behavior. While
153 | images should typically match the resolution as the device they’re being viewed
154 | in, other factors can affect the chosen image. For example, if the user is on a
155 | slow mobile connection, the browser may prefer to select a lower-res image
156 | rather than wait for a larger, resolution-matching image to load.
157 |
158 | [cli-img]: https://github.com/csstools/postcss-image-set-function/workflows/test/badge.svg
159 | [cli-url]: https://github.com/csstools/postcss-image-set-function/actions/workflows/test.yml?query=workflow/test
160 | [css-img]: https://cssdb.org/badge/image-set-function.svg
161 | [css-url]: https://cssdb.org/#image-set-function
162 | [git-img]: https://img.shields.io/badge/support-chat-blue.svg
163 | [git-url]: https://gitter.im/postcss/postcss
164 | [npm-img]: https://img.shields.io/npm/v/postcss-image-set-function.svg
165 | [npm-url]: https://www.npmjs.com/package/postcss-image-set-function
166 |
167 | [CSS Images]: https://drafts.csswg.org/css-images-4/#image-set-notation
168 | [Gulp PostCSS]: https://github.com/postcss/gulp-postcss
169 | [Grunt PostCSS]: https://github.com/nDmitry/grunt-postcss
170 | [PostCSS]: https://github.com/postcss/postcss
171 | [PostCSS Loader]: https://github.com/postcss/postcss-loader
172 | [PostCSS image-set() Function]: https://github.com/jonathantneal/postcss-image-set-function
173 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const { parse } = require('postcss-values-parser');
2 | const processImageSet = require('./lib/process-image-set');
3 |
4 | const imageSetValueMatchRegExp = /(^|[^\w-])(-webkit-)?image-set\(/i;
5 | const imageSetFunctionMatchRegExp = /^(-webkit-)?image-set$/i;
6 |
7 | /**
8 | * @param {{preserve?: boolean, oninvalid?: string}} opts
9 | * @returns {import('postcss').Plugin}
10 | */
11 | module.exports = function creator(opts) {
12 | // prepare options
13 | const preserve = 'preserve' in Object(opts) ? Boolean(opts.preserve) : true;
14 | const oninvalid = 'oninvalid' in Object(opts) ? opts.oninvalid : 'ignore';
15 |
16 | return {
17 | postcssPlugin: 'postcss-image-set-function',
18 | Once(root, helpers) {
19 | // for every declaration
20 | root.walkDecls(decl => {
21 | const {value} = decl;
22 |
23 | // if a declaration likely uses an image-set() function
24 | if (imageSetValueMatchRegExp.test(value)) {
25 | let valueAST
26 |
27 | try {
28 | valueAST = parse(value, { ignoreUnknownWords: true })
29 | } catch (error) {
30 | decl.warn(
31 | helpers.result,
32 | `Failed to parse value '${value}' as an image-set function. Leaving the original value intact.`
33 | )
34 | }
35 |
36 | if (typeof valueAST === 'undefined') {
37 | return
38 | }
39 |
40 | // process every image-set() function
41 | valueAST.walkFuncs(node => {
42 | if (imageSetFunctionMatchRegExp.test(node.name)) {
43 | processImageSet(node.nodes, decl, {
44 | decl,
45 | oninvalid,
46 | preserve,
47 | result: helpers.result,
48 | });
49 | }
50 | });
51 | }
52 | });
53 | },
54 | };
55 | };
56 |
57 | module.exports.postcss = true;
58 |
--------------------------------------------------------------------------------
/lib/get-comma.js:
--------------------------------------------------------------------------------
1 | // return whether a node is a valid comma
2 | module.exports = node => Object(node).type === 'punctuation' && Object(node).value === ',';
3 |
--------------------------------------------------------------------------------
/lib/get-image.js:
--------------------------------------------------------------------------------
1 | const imageSetFunctionMatchRegExp = /^(-webkit-)?image-set$/i;
2 |
3 | const imageFuncRegexp = /^(cross-fade|image|(repeating-)?(conic|linear|radial)-gradient|url)$/i;
4 |
5 | /**
6 | * return a valid image
7 | * @param {import('postcss-values-parser').ChildNode} node
8 | * @returns {string|*|boolean}
9 | */
10 | module.exports = (node) =>
11 | // | | |
12 | // the image-set() function can not be nested inside of itself
13 | Object(node).type === 'func' &&
14 | imageFuncRegexp.test(node.name) &&
15 | !(
16 | node.parent.parent &&
17 | node.parent.parent.type === 'func' &&
18 | imageSetFunctionMatchRegExp.test(node.parent.parent.name)
19 | )
20 | ? (node.raws.before || '') + String(node)
21 | : Object(node).type === 'quoted'
22 | ? node.value
23 | : false;
24 |
--------------------------------------------------------------------------------
/lib/get-media.js:
--------------------------------------------------------------------------------
1 | const postcss = require('postcss');
2 |
3 | const dpiRatios = { dpcm: 2.54, dpi: 1, dppx: 96, x: 96 };
4 |
5 | // return a valid @media rule
6 | module.exports = (node, mediasByDpr) => {
7 | if (Object(node).type === 'numeric' && node.unit in dpiRatios) {
8 | // calculate min-device-pixel-ratio and min-resolution
9 | const dpi = Number(node.value) * dpiRatios[node.unit.toLowerCase()];
10 | const dpr = Math.floor(dpi / dpiRatios.x * 100) / 100;
11 |
12 | if (dpi in mediasByDpr) {
13 | return false;
14 | } else {
15 | const media = mediasByDpr[dpi] = postcss.atRule({
16 | name: 'media',
17 | params: `(-webkit-min-device-pixel-ratio: ${dpr}), (min-resolution: ${dpi}dpi)`
18 | });
19 |
20 | return media;
21 | }
22 | } else {
23 | return false;
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/lib/handle-invalidation.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @param {{ decl: import('postcss').Declaration, oninvalid: string, preserve: boolean, result: import('postcss').Result }} opts
3 | * @param {string} message
4 | * @param {import('postcss-values-parser').ChildNode} word
5 | */
6 | module.exports = (opts, message, word) => {
7 | if (opts.oninvalid === 'warn') {
8 | opts.decl.warn(opts.result, message, { word: String(word) });
9 | } else if (opts.oninvalid === 'throw') {
10 | throw opts.decl.error(message, { word: String(word) });
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/lib/process-image-set.js:
--------------------------------------------------------------------------------
1 | const getComma = require('./get-comma');
2 | const getImage = require('./get-image');
3 | const getMedia = require('./get-media');
4 | const handleInvalidation = require('./handle-invalidation');
5 |
6 | /**
7 | * @param {import('postcss-values-parser').ChildNode[]} imageSetOptionNodes
8 | * @param {import('postcss').Declaration} decl
9 | * @param {{ decl: import('postcss').Declaration, oninvalid: string, preserve: boolean, result: import('postcss').Result }} opts
10 | * @returns {void}
11 | */
12 | module.exports = (imageSetOptionNodes, decl, opts) => {
13 | const parent = decl.parent;
14 | const mediasByDpr = {};
15 |
16 | let length = imageSetOptionNodes.length;
17 | let index = -1;
18 |
19 | while (index < length) {
20 | const [comma, value, media] = [
21 | index < 0 ? true : getComma(imageSetOptionNodes[index]),
22 | getImage(imageSetOptionNodes[index + 1]),
23 | getMedia(imageSetOptionNodes[index + 2], mediasByDpr)
24 | ];
25 |
26 | // handle invalidations
27 | if (!comma) {
28 | handleInvalidation(opts, 'unexpected comma', imageSetOptionNodes[index]);
29 | return;
30 | } else if (!value) {
31 | handleInvalidation(opts, 'unexpected image', imageSetOptionNodes[index + 1]);
32 | return;
33 | } else if (!media) {
34 | handleInvalidation(opts, 'unexpected resolution', imageSetOptionNodes[index + 2]);
35 | return;
36 | }
37 |
38 | // prepare @media { decl: }
39 | const parentClone = parent.clone().removeAll();
40 | const declClone = decl.clone({ value });
41 |
42 | parentClone.append(declClone);
43 | media.append(parentClone);
44 |
45 | index += 3
46 | }
47 |
48 | const medias = Object.keys(mediasByDpr)
49 | .sort((a, b) => a - b)
50 | .map(params => mediasByDpr[params]);
51 |
52 | // conditionally prepend previous siblings
53 | if (medias.length) {
54 | const firstDecl = medias[0].nodes[0].nodes[0];
55 |
56 | if (medias.length === 1) {
57 | decl.value = firstDecl.value
58 | } else {
59 | const siblings = parent.nodes;
60 | const previousSiblings = siblings.slice(0, siblings.indexOf(decl)).concat(firstDecl);
61 |
62 | if (previousSiblings.length) {
63 | const parentClone = parent.cloneBefore().removeAll();
64 |
65 | parentClone.append(previousSiblings);
66 | }
67 |
68 | // prepend any @media { decl: } rules
69 | parent.before(medias.slice(1));
70 |
71 | // conditionally remove the current rule
72 | if (!opts.preserve) {
73 | decl.remove();
74 |
75 | // and then conditionally remove its parent
76 | if (!parent.nodes.length) {
77 | parent.remove();
78 | }
79 | }
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "postcss-image-set-function",
3 | "version": "4.0.2",
4 | "description": "Display resolution-dependent images using the image-set() function in CSS",
5 | "author": "Jonathan Neal ",
6 | "license": "CC0-1.0",
7 | "repository": "csstools/postcss-image-set-function",
8 | "homepage": "https://github.com/csstools/postcss-image-set-function#readme",
9 | "bugs": "https://github.com/csstools/postcss-image-set-function/issues",
10 | "main": "index.js",
11 | "files": [
12 | "lib/"
13 | ],
14 | "scripts": {
15 | "prepublishOnly": "npm test",
16 | "test": "echo 'Running tests...'; npm run test:js && npm run test:tape",
17 | "test:js": "eslint *.js --cache --ignore-path .gitignore --quiet",
18 | "test:tape": "postcss-tape"
19 | },
20 | "engines": {
21 | "node": ">=12"
22 | },
23 | "dependencies": {
24 | "postcss-values-parser": "6.0.1"
25 | },
26 | "peerDependencies": {
27 | "postcss": "^8.3"
28 | },
29 | "devDependencies": {
30 | "eslint": "^8.2.0",
31 | "eslint-config-dev": "^2.0.0",
32 | "postcss": "^8.3.11",
33 | "postcss-tape": "^6.0.1"
34 | },
35 | "eslintConfig": {
36 | "extends": "dev"
37 | },
38 | "keywords": [
39 | "postcss",
40 | "css",
41 | "postcss-plugin",
42 | "image-set",
43 | "background",
44 | "image",
45 | "responsive",
46 | "resolution",
47 | "negotiation",
48 | "optimization"
49 | ]
50 | }
51 |
--------------------------------------------------------------------------------
/test/basic.css:
--------------------------------------------------------------------------------
1 | .test-unchanged-properties {
2 | order: 1;
3 | background-image: url("img/test.png");
4 | order: 2;
5 | background: url(my-img-print.png) top left no-repeat red;
6 | order: 3;
7 | }
8 |
9 | .test-changed-properties {
10 | order: 1;
11 | background-image: image-set(
12 | url(img/test.png) 1x
13 | );
14 | order: 2;
15 | background-image: image-set(
16 | url(img/test.png) 2x
17 | );
18 | order: 3;
19 | background-image: image-set(
20 | url(img/test.png) 1x,
21 | url(img/test-2x.png) 2x
22 | );
23 | order: 4;
24 | background-image: image-set(
25 | url(img/test.png) 1x,
26 | url(img/test-2x.png) 2x,
27 | url(my-img-print.png) 600dpi
28 | );
29 | order: 5;
30 | }
31 |
32 | .test-mixed-units {
33 | order: 1;
34 | background-image: image-set(
35 | url(img/test.png) 1x,
36 | url(img/test-2x.png) 2dppx
37 | );
38 | order: 2;
39 | background-image: image-set(
40 | url(img/test.png) 1x,
41 | url(img/test-2x.png) 20dpcm
42 | );
43 | order: 3;
44 | }
45 |
46 | .test-mixed-order {
47 | order: 1;
48 | background: image-set(
49 | url(../images/bck@3x.png) 3x,
50 | url(../images/bck.png) 1x,
51 | url(../images/bck@2x.png) 2x
52 | );
53 | order: 2;
54 | background: image-set(
55 | url(../images/bck@2x.png) 2x,
56 | url(../images/bck@3x.png) 3x,
57 | url(../images/bck.png) 1x
58 | );
59 | order: 3;
60 | }
61 |
62 | .test-no-url {
63 | order: 1;
64 | background-image: image-set(
65 | "img/test.png" 1x,
66 | "img/test-2x.png" 2x
67 | );
68 | order: 2;
69 | background-image: image-set(
70 | "img/test.png" 1x,
71 | "img/test-2x.png" 2x,
72 | "my-img-print.png" 600dpi
73 | );
74 | order: 3;
75 | }
76 |
77 | .test-webkit-prefix {
78 | order: 1;
79 | background-image: -webkit-image-set(
80 | url(img/test.png) 1x,
81 | url(img/test-2x.png) 2x,
82 | url(my-img-print.png) 600dpi
83 | );
84 | order: 2;
85 | }
86 |
87 | @media (min-width: 1000px) {
88 | .test-within-mq-1 {
89 | background-image: image-set(
90 | url(img/test.png) 1x,
91 | url(img/test-2x.png) 2x,
92 | url(my-img-print.png) 600dpi
93 | );
94 | }
95 | }
96 |
97 | @media (min-width: 768px) and (max-width: 1024px) {
98 | .test-within-mq-2 {
99 | background-image: image-set(
100 | url(img/test.png) 1x,
101 | url(img/test-2x.png) 2x,
102 | url(my-img-print.png) 600dpi
103 | );
104 | }
105 | }
106 |
107 | .test-unparseable-image-set-function {
108 | background-image: image-set(url(data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==) 1x, url(img/test-2x.png) 2x);
109 | background-image: image-set(data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==, url(img/test-2x.png) 2x);
110 | }
111 |
--------------------------------------------------------------------------------
/test/basic.expect.css:
--------------------------------------------------------------------------------
1 | .test-unchanged-properties {
2 | order: 1;
3 | background-image: url("img/test.png");
4 | order: 2;
5 | background: url(my-img-print.png) top left no-repeat red;
6 | order: 3;
7 | }
8 |
9 | .test-changed-properties {
10 | order: 1;
11 | background-image:
12 | url(img/test.png);
13 | order: 2;
14 | background-image:
15 | url(img/test.png);
16 | order: 3;
17 | background-image:
18 | url(img/test.png);
19 | }
20 |
21 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
22 |
23 | .test-changed-properties {
24 | background-image:
25 | url(img/test-2x.png);
26 | }
27 | }
28 |
29 | .test-changed-properties {
30 | background-image: image-set(
31 | url(img/test.png) 1x,
32 | url(img/test-2x.png) 2x
33 | );
34 | order: 4;
35 | background-image:
36 | url(img/test.png);
37 | }
38 |
39 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
40 |
41 | .test-changed-properties {
42 | background-image:
43 | url(img/test-2x.png);
44 | }
45 | }
46 |
47 | @media (-webkit-min-device-pixel-ratio: 6.25), (min-resolution: 600dpi) {
48 |
49 | .test-changed-properties {
50 | background-image:
51 | url(my-img-print.png);
52 | }
53 | }
54 |
55 | .test-changed-properties {
56 | background-image: image-set(
57 | url(img/test.png) 1x,
58 | url(img/test-2x.png) 2x,
59 | url(my-img-print.png) 600dpi
60 | );
61 | order: 5;
62 | }
63 |
64 | .test-mixed-units {
65 | order: 1;
66 | background-image:
67 | url(img/test.png);
68 | }
69 |
70 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
71 |
72 | .test-mixed-units {
73 | background-image:
74 | url(img/test-2x.png);
75 | }
76 | }
77 |
78 | .test-mixed-units {
79 | background-image: image-set(
80 | url(img/test.png) 1x,
81 | url(img/test-2x.png) 2dppx
82 | );
83 | order: 2;
84 | background-image:
85 | url(img/test-2x.png);
86 | }
87 |
88 | @media (-webkit-min-device-pixel-ratio: 1), (min-resolution: 96dpi) {
89 |
90 | .test-mixed-units {
91 | background-image:
92 | url(img/test.png);
93 | }
94 | }
95 |
96 | .test-mixed-units {
97 | background-image: image-set(
98 | url(img/test.png) 1x,
99 | url(img/test-2x.png) 20dpcm
100 | );
101 | order: 3;
102 | }
103 |
104 | .test-mixed-order {
105 | order: 1;
106 | background:
107 | url(../images/bck.png);
108 | }
109 |
110 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
111 |
112 | .test-mixed-order {
113 | background:
114 | url(../images/bck@2x.png);
115 | }
116 | }
117 |
118 | @media (-webkit-min-device-pixel-ratio: 3), (min-resolution: 288dpi) {
119 |
120 | .test-mixed-order {
121 | background:
122 | url(../images/bck@3x.png);
123 | }
124 | }
125 |
126 | .test-mixed-order {
127 | background: image-set(
128 | url(../images/bck@3x.png) 3x,
129 | url(../images/bck.png) 1x,
130 | url(../images/bck@2x.png) 2x
131 | );
132 | order: 2;
133 | background:
134 | url(../images/bck.png);
135 | }
136 |
137 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
138 |
139 | .test-mixed-order {
140 | background:
141 | url(../images/bck@2x.png);
142 | }
143 | }
144 |
145 | @media (-webkit-min-device-pixel-ratio: 3), (min-resolution: 288dpi) {
146 |
147 | .test-mixed-order {
148 | background:
149 | url(../images/bck@3x.png);
150 | }
151 | }
152 |
153 | .test-mixed-order {
154 | background: image-set(
155 | url(../images/bck@2x.png) 2x,
156 | url(../images/bck@3x.png) 3x,
157 | url(../images/bck.png) 1x
158 | );
159 | order: 3;
160 | }
161 |
162 | .test-no-url {
163 | order: 1;
164 | background-image: "img/test.png";
165 | }
166 |
167 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
168 |
169 | .test-no-url {
170 | background-image: "img/test-2x.png";
171 | }
172 | }
173 |
174 | .test-no-url {
175 | background-image: image-set(
176 | "img/test.png" 1x,
177 | "img/test-2x.png" 2x
178 | );
179 | order: 2;
180 | background-image: "img/test.png";
181 | }
182 |
183 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
184 |
185 | .test-no-url {
186 | background-image: "img/test-2x.png";
187 | }
188 | }
189 |
190 | @media (-webkit-min-device-pixel-ratio: 6.25), (min-resolution: 600dpi) {
191 |
192 | .test-no-url {
193 | background-image: "my-img-print.png";
194 | }
195 | }
196 |
197 | .test-no-url {
198 | background-image: image-set(
199 | "img/test.png" 1x,
200 | "img/test-2x.png" 2x,
201 | "my-img-print.png" 600dpi
202 | );
203 | order: 3;
204 | }
205 |
206 | .test-webkit-prefix {
207 | order: 1;
208 | background-image:
209 | url(img/test.png);
210 | }
211 |
212 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
213 |
214 | .test-webkit-prefix {
215 | background-image:
216 | url(img/test-2x.png);
217 | }
218 | }
219 |
220 | @media (-webkit-min-device-pixel-ratio: 6.25), (min-resolution: 600dpi) {
221 |
222 | .test-webkit-prefix {
223 | background-image:
224 | url(my-img-print.png);
225 | }
226 | }
227 |
228 | .test-webkit-prefix {
229 | background-image: -webkit-image-set(
230 | url(img/test.png) 1x,
231 | url(img/test-2x.png) 2x,
232 | url(my-img-print.png) 600dpi
233 | );
234 | order: 2;
235 | }
236 |
237 | @media (min-width: 1000px) {
238 | .test-within-mq-1 {
239 | background-image:
240 | url(img/test.png);
241 | }
242 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
243 | .test-within-mq-1 {
244 | background-image:
245 | url(img/test-2x.png);
246 | }
247 | }
248 | @media (-webkit-min-device-pixel-ratio: 6.25), (min-resolution: 600dpi) {
249 | .test-within-mq-1 {
250 | background-image:
251 | url(my-img-print.png);
252 | }
253 | }
254 | .test-within-mq-1 {
255 | background-image: image-set(
256 | url(img/test.png) 1x,
257 | url(img/test-2x.png) 2x,
258 | url(my-img-print.png) 600dpi
259 | );
260 | }
261 | }
262 |
263 | @media (min-width: 768px) and (max-width: 1024px) {
264 | .test-within-mq-2 {
265 | background-image:
266 | url(img/test.png);
267 | }
268 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
269 | .test-within-mq-2 {
270 | background-image:
271 | url(img/test-2x.png);
272 | }
273 | }
274 | @media (-webkit-min-device-pixel-ratio: 6.25), (min-resolution: 600dpi) {
275 | .test-within-mq-2 {
276 | background-image:
277 | url(my-img-print.png);
278 | }
279 | }
280 | .test-within-mq-2 {
281 | background-image: image-set(
282 | url(img/test.png) 1x,
283 | url(img/test-2x.png) 2x,
284 | url(my-img-print.png) 600dpi
285 | );
286 | }
287 | }
288 |
289 | .test-unparseable-image-set-function {
290 | background-image: image-set(url(data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==) 1x, url(img/test-2x.png) 2x);
291 | background-image: image-set(data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==, url(img/test-2x.png) 2x);
292 | }
293 |
--------------------------------------------------------------------------------
/test/basic.no-preserve.expect.css:
--------------------------------------------------------------------------------
1 | .test-unchanged-properties {
2 | order: 1;
3 | background-image: url("img/test.png");
4 | order: 2;
5 | background: url(my-img-print.png) top left no-repeat red;
6 | order: 3;
7 | }
8 |
9 | .test-changed-properties {
10 | order: 1;
11 | background-image:
12 | url(img/test.png);
13 | order: 2;
14 | background-image:
15 | url(img/test.png);
16 | order: 3;
17 | background-image:
18 | url(img/test.png);
19 | }
20 |
21 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
22 |
23 | .test-changed-properties {
24 | background-image:
25 | url(img/test-2x.png);
26 | }
27 | }
28 |
29 | .test-changed-properties {
30 | order: 4;
31 | background-image:
32 | url(img/test.png);
33 | }
34 |
35 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
36 |
37 | .test-changed-properties {
38 | background-image:
39 | url(img/test-2x.png);
40 | }
41 | }
42 |
43 | @media (-webkit-min-device-pixel-ratio: 6.25), (min-resolution: 600dpi) {
44 |
45 | .test-changed-properties {
46 | background-image:
47 | url(my-img-print.png);
48 | }
49 | }
50 |
51 | .test-changed-properties {
52 | order: 5;
53 | }
54 |
55 | .test-mixed-units {
56 | order: 1;
57 | background-image:
58 | url(img/test.png);
59 | }
60 |
61 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
62 |
63 | .test-mixed-units {
64 | background-image:
65 | url(img/test-2x.png);
66 | }
67 | }
68 |
69 | .test-mixed-units {
70 | order: 2;
71 | background-image:
72 | url(img/test-2x.png);
73 | }
74 |
75 | @media (-webkit-min-device-pixel-ratio: 1), (min-resolution: 96dpi) {
76 |
77 | .test-mixed-units {
78 | background-image:
79 | url(img/test.png);
80 | }
81 | }
82 |
83 | .test-mixed-units {
84 | order: 3;
85 | }
86 |
87 | .test-mixed-order {
88 | order: 1;
89 | background:
90 | url(../images/bck.png);
91 | }
92 |
93 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
94 |
95 | .test-mixed-order {
96 | background:
97 | url(../images/bck@2x.png);
98 | }
99 | }
100 |
101 | @media (-webkit-min-device-pixel-ratio: 3), (min-resolution: 288dpi) {
102 |
103 | .test-mixed-order {
104 | background:
105 | url(../images/bck@3x.png);
106 | }
107 | }
108 |
109 | .test-mixed-order {
110 | order: 2;
111 | background:
112 | url(../images/bck.png);
113 | }
114 |
115 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
116 |
117 | .test-mixed-order {
118 | background:
119 | url(../images/bck@2x.png);
120 | }
121 | }
122 |
123 | @media (-webkit-min-device-pixel-ratio: 3), (min-resolution: 288dpi) {
124 |
125 | .test-mixed-order {
126 | background:
127 | url(../images/bck@3x.png);
128 | }
129 | }
130 |
131 | .test-mixed-order {
132 | order: 3;
133 | }
134 |
135 | .test-no-url {
136 | order: 1;
137 | background-image: "img/test.png";
138 | }
139 |
140 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
141 |
142 | .test-no-url {
143 | background-image: "img/test-2x.png";
144 | }
145 | }
146 |
147 | .test-no-url {
148 | order: 2;
149 | background-image: "img/test.png";
150 | }
151 |
152 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
153 |
154 | .test-no-url {
155 | background-image: "img/test-2x.png";
156 | }
157 | }
158 |
159 | @media (-webkit-min-device-pixel-ratio: 6.25), (min-resolution: 600dpi) {
160 |
161 | .test-no-url {
162 | background-image: "my-img-print.png";
163 | }
164 | }
165 |
166 | .test-no-url {
167 | order: 3;
168 | }
169 |
170 | .test-webkit-prefix {
171 | order: 1;
172 | background-image:
173 | url(img/test.png);
174 | }
175 |
176 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
177 |
178 | .test-webkit-prefix {
179 | background-image:
180 | url(img/test-2x.png);
181 | }
182 | }
183 |
184 | @media (-webkit-min-device-pixel-ratio: 6.25), (min-resolution: 600dpi) {
185 |
186 | .test-webkit-prefix {
187 | background-image:
188 | url(my-img-print.png);
189 | }
190 | }
191 |
192 | .test-webkit-prefix {
193 | order: 2;
194 | }
195 |
196 | @media (min-width: 1000px) {
197 | .test-within-mq-1 {
198 | background-image:
199 | url(img/test.png);
200 | }
201 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
202 | .test-within-mq-1 {
203 | background-image:
204 | url(img/test-2x.png);
205 | }
206 | }
207 | @media (-webkit-min-device-pixel-ratio: 6.25), (min-resolution: 600dpi) {
208 | .test-within-mq-1 {
209 | background-image:
210 | url(my-img-print.png);
211 | }
212 | }
213 | }
214 |
215 | @media (min-width: 768px) and (max-width: 1024px) {
216 | .test-within-mq-2 {
217 | background-image:
218 | url(img/test.png);
219 | }
220 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
221 | .test-within-mq-2 {
222 | background-image:
223 | url(img/test-2x.png);
224 | }
225 | }
226 | @media (-webkit-min-device-pixel-ratio: 6.25), (min-resolution: 600dpi) {
227 | .test-within-mq-2 {
228 | background-image:
229 | url(my-img-print.png);
230 | }
231 | }
232 | }
233 |
234 | .test-unparseable-image-set-function {
235 | background-image: image-set(url(data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==) 1x, url(img/test-2x.png) 2x);
236 | background-image: image-set(data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==, url(img/test-2x.png) 2x);
237 | }
238 |
--------------------------------------------------------------------------------
/test/invalid.css:
--------------------------------------------------------------------------------
1 | .test-invalid-params {
2 | order: 1;
3 | background-image: image-set();
4 | order: 2;
5 | background-image: image-set(
6 | url(img/test.png)
7 | );
8 | order: 3;
9 | background-image: image-set(
10 | 1x
11 | );
12 | order: 4;
13 | }
14 |
15 | .test-invalid-commas {
16 | order: 1;
17 | background-image: image-set(
18 | url(img/test.png) 1x,
19 | );
20 | order: 2;
21 | background-image: image-set(
22 | url(img/test.png) 1x,
23 | url(img/test-2x.png) 2x,
24 | );
25 | order: 3;
26 | }
27 |
28 | .test-invalid-images {
29 | order: 1;
30 | background: image-set(
31 | image-set(
32 | url(img/test.png) 1x
33 | ) 1x
34 | );
35 | order: 2;
36 | background: image-set(
37 | calc(1rem + 1px) 1x
38 | );
39 | order: 3;
40 | }
41 |
42 | .test-invalid-resolutions {
43 | order: 1;
44 | background-image: image-set(
45 | url(img/test.png) 1
46 | );
47 | order: 2;
48 | background-image: image-set(
49 | url(img/test.png) 1omg
50 | );
51 | order: 3;
52 | }
53 |
--------------------------------------------------------------------------------