├── test
├── sprite-empty.svg
├── paths.css
├── sprite-module
│ ├── package.json
│ ├── module-sprite-camera.svg
│ └── module-sprite.svg
├── sprite-camera.svg
├── sprite.svg
├── paths.expect.css
├── package.css
├── basic.css
├── package.expect.css
├── basic.svgo.expect.css
├── basic.expect.css
└── basic.base64.expect.css
├── .travis.yml
├── .gitignore
├── .editorconfig
├── .rollup.js
├── .tape.js
├── src
├── lib
│ ├── element-by-id.js
│ ├── element-clone.js
│ ├── element-as-data-uri-svg.js
│ ├── read-closest-svg.md
│ ├── transpile-styles.js
│ ├── transpile-decl.js
│ └── read-closest-svg.js
└── index.js
├── CONTRIBUTING.md
├── package.json
├── CHANGELOG.md
├── README.md
├── INSTALL.md
└── LICENSE.md
/test/sprite-empty.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/paths.css:
--------------------------------------------------------------------------------
1 | test-sprite {
2 | background-image: url("module-sprite-camera.svg");
3 | }
4 |
--------------------------------------------------------------------------------
/test/sprite-module/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sprite-module",
3 | "version": "1.0.0",
4 | "media": "module-sprite.svg",
5 | "private": true
6 | }
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # https://docs.travis-ci.com/user/travis-lint
2 |
3 | language: node_js
4 |
5 | node_js:
6 | - 6
7 |
8 | install:
9 | - npm install --ignore-scripts
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | *.log*
4 | *.result.css
5 | .*
6 | !.editorconfig
7 | !.gitignore
8 | !.rollup.js
9 | !.tape.js
10 | !.travis.yml
11 | /index.*
12 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.rollup.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 |
3 | export default {
4 | input: 'src/index.js',
5 | output: [
6 | { file: 'index.js', format: 'cjs', sourcemap: true },
7 | { file: 'index.mjs', format: 'esm', sourcemap: true }
8 | ],
9 | plugins: [
10 | babel({
11 | presets: [
12 | ['@babel/env', { modules: false, targets: { node: 6 } }]
13 | ]
14 | })
15 | ]
16 | };
17 |
--------------------------------------------------------------------------------
/test/sprite-camera.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/.tape.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'basic': {
3 | message: 'supports basic usage'
4 | },
5 | 'basic:base64': {
6 | message: 'supports { "utf8": false } usage',
7 | options: {
8 | utf8: false
9 | }
10 | },
11 | 'basic:svgo': {
12 | message: 'supports { "svgo": true } usage',
13 | options: {
14 | svgo: true
15 | }
16 | },
17 | 'package': {
18 | message: 'supports package usage'
19 | },
20 | 'paths': {
21 | message: 'supports { "paths" } usage',
22 | options: {
23 | dirs: 'test/sprite-module'
24 | }
25 | }
26 | };
27 |
--------------------------------------------------------------------------------
/test/sprite-module/module-sprite-camera.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/test/sprite.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/test/paths.expect.css:
--------------------------------------------------------------------------------
1 | test-sprite {
2 | background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512' style='fill: black'%3E%3Cpath d='M152 304c0 57.438 46.562 104 104 104s104-46.562 104-104-46.562-104-104-104-104 46.562-104 104zm328-176h-112c-8-32-16-64-48-64h-128c-32 0-40 32-48 64h-112c-17.6 0-32 14.4-32 32v288c0 17.6 14.4 32 32 32h448c17.6 0 32-14.4 32-32v-288c0-17.6-14.4-32-32-32zm-224 318c-78.425 0-142-63.574-142-142 0-78.425 63.575-142 142-142 78.426 0 142 63.575 142 142 0 78.426-63.573 142-142 142zm224-222h-64v-32h64v32z'/%3E%3C/svg%3E");
3 | }
4 |
--------------------------------------------------------------------------------
/src/lib/element-by-id.js:
--------------------------------------------------------------------------------
1 | /* Element by ID
2 | /* ========================================================================== */
3 |
4 | export default function elementById(element, id) {
5 | // conditionally return the matching element
6 | if (element.attr && element.attr.id === id) {
7 | return element;
8 | } else if (element.children) {
9 | // otherwise, return matching child elements
10 | let index = -1;
11 | let child;
12 |
13 | while (child = element.children[++index]) {
14 | child = elementById(child, id);
15 |
16 | if (child) {
17 | return child;
18 | }
19 | }
20 | }
21 |
22 | // return undefined if no matching elements are find
23 | return undefined;
24 | }
25 |
--------------------------------------------------------------------------------
/src/lib/element-clone.js:
--------------------------------------------------------------------------------
1 | /* Clone from element
2 | /* ========================================================================== */
3 |
4 | export default function elementClone(element) {
5 | // element clone
6 | const clone = {};
7 |
8 | // for each key in the element
9 | for (let key in element) {
10 | if (element[key] instanceof Array) {
11 | // conditionally clone the child array
12 | clone[key] = element[key].map(elementClone);
13 | } else if (typeof element[key] === 'object') {
14 | // otherwise, conditionally clone the child object
15 | clone[key] = elementClone(element[key]);
16 | } else {
17 | // otherwise, copy the child
18 | clone[key] = element[key];
19 | }
20 | }
21 |
22 | // return the element clone
23 | return clone;
24 | }
25 |
--------------------------------------------------------------------------------
/test/package.css:
--------------------------------------------------------------------------------
1 | test-sprite-module-with-fragment {
2 | background-image: url(sprite-module#camera);
3 | }
4 |
5 | test-sprite-module-without-fragment {
6 | background-image: url(sprite-module);
7 | }
8 |
9 | test-sprite-module-with-fragment-and-param {
10 | background-image: url("sprite-module#camera" param(--fill blue));
11 | }
12 |
13 | test-sprite-module-file-with-param {
14 | background-image: url("sprite-module/module-sprite-camera.svg" param(--fill blue));
15 | }
16 |
17 | test-sprite-module-file-with-fragment-and-param {
18 | background-image: url("sprite-module/module-sprite.svg#camera" param(--fill blue));
19 | }
20 |
21 | test-sprite-module-with-invalid-fragment-1 {
22 | background-image: url(sprite-module#none);
23 | }
24 |
25 | test-sprite-module-with-invalid-fragment-2 {
26 | background-image: url(sprite-module#);
27 | }
28 |
--------------------------------------------------------------------------------
/test/sprite-module/module-sprite.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/test/basic.css:
--------------------------------------------------------------------------------
1 | test-sprite {
2 | background-image: url("sprite-camera.svg");
3 | }
4 |
5 | test-sprite-with-id {
6 | background-image: url("sprite.svg#camera");
7 | }
8 |
9 | test-sprite-with-id-without-quotes {
10 | background-image: url(sprite.svg#camera);
11 | }
12 |
13 | test-sprite-with-param {
14 | background-image: url("sprite-camera.svg" param(--color blue));
15 | }
16 |
17 | test-sprite-with-hex-param {
18 | background-image: url("sprite-camera.svg" param(--color #00F));
19 | }
20 |
21 | test-sprite-with-rgb-param {
22 | background-image: url("sprite-camera.svg" param(--color rgb(0, 0, 255)));
23 | }
24 |
25 | test-sprite-with-rgba-param {
26 | background-image: url("sprite-camera.svg" param(--color rgba(0, 0, 255, 0.5)));
27 | }
28 |
29 | test-sprite-with-hsl-param {
30 | background-image: url("sprite-camera.svg" param(--color hsl(240, 100%, 50%)));
31 | }
32 |
33 | test-sprite-with-hsla-param {
34 | background-image: url("sprite-camera.svg" param(--color hsla(240, 100%, 50%, 0.5)));
35 | }
36 |
37 | test-sprite-with-id-with-param {
38 | background-image: url("sprite.svg#camera" param(--color blue));
39 | }
40 |
41 | test-empty-sprite {
42 | background-image: url("sprite-empty.svg");
43 | }
44 |
45 | test-empty-sprite-with-id {
46 | background-image: url("sprite-empty.svg#camera");
47 | }
48 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /* Tooling
2 | /* ========================================================================== */
3 |
4 | // external tooling
5 | import postcss from 'postcss';
6 |
7 | // internal tooling
8 | import transpileDecl from './lib/transpile-decl';
9 |
10 | /* Inline SVGs
11 | /* ========================================================================== */
12 |
13 | export default postcss.plugin('postcss-svg-fragments', opts => (css, result) => {
14 | // svg promises array
15 | const promises = [];
16 |
17 | // plugin options
18 | const normalizedOpts = {
19 | // additional directories to search for SVGs
20 | dirs: 'dirs' in Object(opts) ? [].concat(opts.dirs) : [],
21 | // whether to encode as utf-8
22 | utf8: 'utf8' in Object(opts) ? Boolean(opts.utf8) : true,
23 | // whether and how to compress with svgo
24 | svgo: 'svgo' in Object(opts) ? Object(opts.svgo) : false
25 | };
26 |
27 | // cache of file content and json content promises
28 | const cache = {};
29 |
30 | // for each declaration in the stylesheet
31 | css.walkDecls(decl => {
32 | // if the declaration contains a url()
33 | if (containsUrlFunction(decl)) {
34 | // transpile declaration parts
35 | transpileDecl(result, promises, decl, normalizedOpts, cache);
36 | }
37 | });
38 |
39 | // return chained svg promises array
40 | return Promise.all(promises);
41 | });
42 |
43 | /* Inline Tooling
44 | /* ========================================================================== */
45 |
46 | // whether the declaration contains a url()
47 | function containsUrlFunction(decl) {
48 | return /(^|\s)url\(.+\)(\s|$)/.test(decl.value);
49 | }
50 |
--------------------------------------------------------------------------------
/src/lib/element-as-data-uri-svg.js:
--------------------------------------------------------------------------------
1 | /* Tooling
2 | /* ========================================================================== */
3 |
4 | // external tooling
5 | import Svgo from 'svgo';
6 |
7 | /* Element as a data URI SVG
8 | /* ========================================================================== */
9 |
10 | export default function elementAsDataURISvg(element, document, opts) {
11 | // rebuild element as