├── .editorconfig
├── .eslintrc
├── .gitignore
├── .npmignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── gulpfile.js
├── index.js
├── lib
└── fastimagesize.js
├── package.json
└── test
├── src
├── css
│ ├── second
│ │ └── second.css
│ └── style.css
├── img
│ ├── logo.png
│ └── wordpress-logo.svg
└── slice
│ ├── finder
│ ├── icon-close.png
│ └── icon-close@2x.png
│ ├── icon-close.png
│ ├── icon-close@2x.png
│ ├── icon-search.png
│ ├── icon-search@2x.png
│ ├── icon-wh.png
│ ├── icon-wh@2x.png
│ ├── keyframe1@2x.png
│ ├── keyframe2@2x.png
│ ├── word.png
│ └── word@2x.png
└── test.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true;
2 |
3 | [*]
4 | indent_style = tab
5 | indent_size = 4
6 | tab_width = 4
7 | end_of_line = lf
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.{json,yml}]
12 | indent_style = space
13 | indent_size = 4
14 |
15 | [README.md]
16 | trim_trailing_whitespace = ignore
17 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "xo",
3 | "rules": {
4 | "indent": [
5 | "error",
6 | "tab"
7 | ],
8 | "one-var": ["error", { uninitialized: "always", initialized: "never" }],
9 | "space-infix-ops": ["error", {"int32Hint": false}]
10 | },
11 | "env": {
12 | "mocha": true,
13 | "node": true
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | test/dist/
4 | *.log
5 | dist/
6 | .idea
7 | .git
8 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .gitignore
2 | .editorconfig
3 | .eslintrc
4 | .idea
5 | node_modules/
6 | npm-debug.log
7 | CHANGELOG.md
8 | test
9 | gulpfile.js
10 | .travis.yml
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - "0.12"
5 | - "4"
6 | - "5"
7 | - "6"
8 | - "stable"
9 | before_script:
10 | - npm install -g mocha
11 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | # Change Log
3 |
4 | Change log for postcss-lazyimagecss.
5 |
6 | ## [0.1.3] - 2016-12-21
7 |
8 | - Add support for detecting even dimensions.
9 |
10 | ## [0.1.2] - 2016-11-07
11 |
12 | - Fix log grammar.
13 |
14 | ## [0.1.1] - 2016-11-05
15 |
16 | - Fix image file or a directory path bugs.
17 |
18 | - Add `chalk` package for a better console log.
19 |
20 | ## [0.1.0] - 2016-11-02
21 |
22 | - Fix bug and release to postcss official github page.
23 |
24 | ## [0.0.11] - 2016-10-15
25 |
26 | - Fix bug with multi path in background-image url.
27 |
28 | ## [0.0.10] - 2016-10-10
29 |
30 | - Add support for multi path in background-image url.
31 |
32 | ## [0.0.8] - 2016-10-09
33 |
34 | - Initial release.
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Jeff Ma
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # postcss-lazyimagecss
2 |
3 | [](https://greenkeeper.io/)
4 |
5 |
6 |
7 | [](https://travis-ci.org/Jeff2Ma/postcss-lazyimagecss)
8 | [](http://badge.fury.io/js/postcss-lazyimagecss)
9 |
10 | A [PostCSS](https://github.com/postcss/postcss) plugin that generates images's CSS `width` & `height` properties from stylesheets automatically.
11 |
12 | Another lazy way to write CSS, feel free to use it :)
13 |
14 | Based on [gulp-lazyimagecss](https://github.com/weixin/gulp-lazyimagecss). Thanks to [hzlzh](https://github.com/hzlzh) and [littledu](https://github.com/littledu).
15 |
16 | ```css
17 | /* Input */
18 | .icon-close {
19 | background-image: url(../slice/icon-close.png); //icon-close.png - 16x16
20 | }
21 |
22 | .icon-new {
23 | background-image: url(../slice/icon-new@2x.png); //icon-new@2x.png - 16x16
24 | }
25 |
26 | /* Output */
27 | .icon-close {
28 | background-image: url(../slice/icon-close.png);
29 | width: 16px;
30 | height: 16px;
31 | }
32 |
33 | .icon-new {
34 | background-image: url(../slice/icon-new@2x.png);
35 | width: 8px;
36 | height: 8px;
37 | background-size: 8px 8px;
38 | }
39 |
40 | ```
41 |
42 | ## Features
43 |
44 | - Support `jpg`/`jpeg`/`png`/`gif`/`bmp`/`svg` file type.
45 |
46 | - Support retina image (file name should like `demo@2x.png`).
47 |
48 | - Both `background-image: url()` and `background: url()` can be detected successfully.
49 |
50 | - CSS property generating will be ignored if any of those `width` / `height` / `background-size` already set.
51 |
52 |
53 | ## Installation
54 |
55 | Install with npm:
56 |
57 | npm install postcss-lazyimagecss --save-dev
58 |
59 |
60 | Or install width [yarn](https://github.com/yarnpkg/yarn):
61 |
62 | yarn add postcss-lazyimagecss --dev
63 |
64 | ## Usage
65 |
66 | ### Work with [Gulp](http://gulpjs.com/)
67 |
68 | Example:
69 |
70 | ```js
71 | var gulp = require('gulp');
72 | var postcss = require('gulp-postcss');
73 | var lazyimagecss = require('postcss-lazyimagecss');
74 |
75 | gulp.task('css', function () {
76 | return gulp.src('./src/css/*.css')
77 | .pipe(another-plugin())
78 | .pipe(postcss([lazyimagecss({
79 | imagePath: ['../img','../slice']
80 | })]))
81 | .pipe(gulp.dest('./dist/css'));
82 | });
83 | ```
84 |
85 | ### Work with Gulp & [gulp-sourcemaps](https://www.npmjs.com/package/gulp-sourcemaps)
86 |
87 | Example:
88 |
89 | ```js
90 | var gulp = require('gulp');
91 | var postcss = require('gulp-postcss');
92 | var lazyimagecss = require('postcss-lazyimagecss');
93 | var sourcemaps = require('gulp-sourcemaps');
94 |
95 | gulp.task('css', function () {
96 | return gulp.src('./src/css/*.css')
97 | .pipe(sourcemaps.init())
98 | .pipe(another-plugin())
99 | .pipe(postcss([lazyimagecss({
100 | imagePath: ['../img','../slice']
101 | })]))
102 | .pipe(sourcemaps.write("."))
103 | .pipe(gulp.dest('./dist'));
104 | });
105 | ```
106 |
107 | ## Options
108 | - **imagePath** Set image path to be worked (e.g. `['../slice','../img']`)
109 |
110 | - **width** Whether output `width` properties in CSS ( default: `true` )
111 |
112 | - **height** Whether output `height` properties in CSS ( default: `true` )
113 |
114 | - **backgroundSize** Whether output `background-size` properties in CSS ( default: `true` )
115 |
116 | ## Contributing
117 |
118 | [Issues](https://github.com/Jeff2Ma/postcss-lazyimagecss/issues/) and [Pull requests](https://github.com/Jeff2Ma/postcss-lazyimagecss/pulls) are welcome.
119 |
120 | ```shell
121 | $ git clone https://github.com/Jeff2Ma/postcss-lazyimagecss
122 | $ cd postcss-lazyimagecss
123 | $ npm i
124 | $ gulp
125 | ```
126 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var postcss = require('gulp-postcss');
3 | var lazyimagecss = require('./index.js');
4 | var mocha = require('gulp-mocha');
5 |
6 | var files = ['index.js'];
7 | var watchFiles = ['index.js', 'test/**/*'];
8 |
9 | gulp.task('lint', function () {
10 | var eslint = require('gulp-eslint');
11 | return gulp.src(files)
12 | .pipe(eslint())
13 | .pipe(eslint.format())
14 | .pipe(eslint.failAfterError());
15 | });
16 |
17 | gulp.task('test', function () {
18 | return gulp.src('test/*.js', { read: false })
19 | .pipe(mocha({ timeout: 1000000 }));
20 | });
21 |
22 | gulp.task('css', function () {
23 | return gulp.src('./test/src/css/**/*.css')
24 | .pipe(postcss([lazyimagecss({
25 | imagePath: ['../img', '../slice']
26 | })]))
27 | .pipe(gulp.dest('./test/dist/css'));
28 | });
29 |
30 | gulp.task('default', ['watch']);
31 |
32 | gulp.task('watch', function () {
33 | gulp.watch(watchFiles, ['css', 'test', 'lint']);
34 | });
35 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var fs = require('fs');
3 | var dirname = require('path').dirname;
4 | var postcss = require('postcss');
5 | var chalk = require('chalk');
6 | var _ = require('lodash');
7 | var fastImageSize = require('./lib/fastimagesize');
8 |
9 | /**
10 | * a helper to find the real image absolute path,
11 | * deal with the issue like `../../img.jpg` and so on.
12 | */
13 | function fixAbsolutePath(dir, relative) {
14 | // find the first time
15 | var absolute = path.resolve(dir, relative);
16 |
17 | // check if is a image file
18 | var reg = /\.(jpg|jpeg|png|gif|svg|bmp)\b/i;
19 | if (!reg.test(absolute)) {
20 | pluginLog('Not a image file: ', absolute);
21 | return;
22 | }
23 |
24 | if (!fs.existsSync(absolute) && (relative.indexOf('../') > -1)) {
25 | relative = relative.replace('../', '');
26 | // find the second time
27 | absolute = path.resolve(dir, relative);
28 | }
29 |
30 | return absolute;
31 | }
32 |
33 | function pluginLog(str, arg) {
34 | return console.log('[' + chalk.blue('postcss-lazyimagecss') + '] ' + chalk.red(str) + arg);
35 | }
36 |
37 | /**
38 | * main function
39 | */
40 | module.exports = postcss.plugin('lazyimagecss', function (options) {
41 | return function (css) {
42 | options = options || {};
43 |
44 | options = _.extend({
45 | width: true,
46 | height: true,
47 | backgroundSize: true,
48 | imagePath: []
49 | }, options);
50 |
51 | var imagePath = options.imagePath;
52 |
53 | if (imagePath.length) {
54 | imagePath = '(' + options.imagePath.join('|') + '/)';
55 | imagePath = imagePath.replace(/\./g, '\\.');
56 | } else {
57 | imagePath = '';
58 | }
59 |
60 | var imageRegex = new RegExp('url\\(["\']?(' + imagePath + '[^)]*?)["\']?\\)');
61 |
62 | css.walkRules(function (rule) {
63 | rule.walkDecls(/^background(-image)?$/, function (decl) {
64 | var rule = decl.parent;
65 | var nodes = rule.nodes;
66 | var value = decl.value;
67 | // var prop = decl.prop;
68 | var CSSWidth = false;
69 | var CSSHeight = false;
70 | var CSSBGSize = false;
71 |
72 | var matchValue = imageRegex.exec(value);
73 |
74 | if (!matchValue || matchValue[1].indexOf('data:') === 0) {
75 | return;
76 | }
77 |
78 | var relativePath = matchValue[1];
79 |
80 | var inputDir = dirname(decl.source.input.file);
81 |
82 | var absolutePath = fixAbsolutePath(inputDir, relativePath);
83 |
84 | if (absolutePath === undefined) {
85 | return;
86 | }
87 |
88 | nodes.forEach(function (node) {
89 | if (node.prop === 'width') {
90 | CSSWidth = true;
91 | }
92 | if (node.prop === 'height') {
93 | CSSHeight = true;
94 | }
95 | if (node.prop === 'background-size' || node.prop === '-webkit-background-size') {
96 | CSSBGSize = true;
97 | }
98 | });
99 |
100 | if (value.indexOf('@2x') > -1) {
101 | options.retina = true;
102 | } else {
103 | options.retina = false;
104 | }
105 |
106 | var info = fastImageSize(absolutePath);
107 |
108 | if (info === undefined) {
109 | pluginLog('File does not exist: ', absolutePath);
110 | return;
111 | }
112 |
113 | if (info.type === 'unknown') {
114 | pluginLog('Unknown type: ', absolutePath);
115 | return;
116 | }
117 |
118 | // check if even dimensions
119 | if (value.indexOf('@2x') > -1 && (info.width % 2 !== 0 || info.height % 2 !== 0)) {
120 | pluginLog('Should have even dimensions: ', absolutePath);
121 | return;
122 | }
123 |
124 | var valueWidth, valueHeight;
125 |
126 | if (options.retina) {
127 | valueWidth = (info.width / 2) + 'px';
128 | valueHeight = (info.height / 2) + 'px';
129 | } else {
130 | valueWidth = info.width + 'px';
131 | valueHeight = info.height + 'px';
132 | }
133 |
134 | if (options.width && !CSSWidth) {
135 | rule.append({prop: 'width', value: valueWidth});
136 | }
137 |
138 | if (options.height && !CSSHeight) {
139 | rule.append({prop: 'height', value: valueHeight});
140 | }
141 |
142 | if (options.backgroundSize && options.retina && !CSSBGSize) {
143 | rule.append({prop: 'background-size', value: valueWidth + ' ' + valueHeight});
144 | }
145 | });
146 | });
147 | };
148 | });
149 |
--------------------------------------------------------------------------------
/lib/fastimagesize.js:
--------------------------------------------------------------------------------
1 | //
2 | // fast-image-size - Simple stand alone module to just extract the image size from image file without using special image libraries.
3 | //
4 | // Please refer to README.md for this module's documentations.
5 | //
6 | // NOTE:
7 | // - Before changing this code please refer to the 'hacking the code section' on README.md.
8 | //
9 | // Copyright (c) 2013 Ziv Barber;
10 | //
11 | // Permission is hereby granted, free of charge, to any person obtaining
12 | // a copy of this software and associated documentation files (the
13 | // 'Software'), to deal in the Software without restriction, including
14 | // without limitation the rights to use, copy, modify, merge, publish,
15 | // distribute, sublicense, and/or sell copies of the Software, and to
16 | // permit persons to whom the Software is furnished to do so, subject to
17 | // the following conditions:
18 | //
19 | // The above copyright notice and this permission notice shall be
20 | // included in all copies or substantial portions of the Software.
21 | //
22 | // THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
23 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
25 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
26 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
27 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
28 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 | //
30 |
31 | var fs = require('fs');
32 | var path = require('path');
33 |
34 | module.exports = exports = function (file_path, callback) {
35 |
36 | function getJpgSize(buffer_data, retInfo) {
37 | // Skip 5 chars, they are for signature
38 | buffer_data = buffer_data.slice(4);
39 |
40 | var i, next;
41 | while (buffer_data.length) {
42 | // read length of the next block
43 | i = buffer_data.readUInt16BE(0);
44 |
45 | // 0xFFC0 is baseline(SOF)
46 | // 0xFFC2 is progressive(SOF2)
47 | next = buffer_data[i + 1];
48 | if (next === 0xC0 || next === 0xC2) {
49 | return {
50 | 'height': buffer_data.readUInt16BE(i + 5),
51 | 'width': buffer_data.readUInt16BE(i + 7)
52 | };
53 | }
54 |
55 | // move to the next block
56 | buffer_data = buffer_data.slice(i + 2);
57 | }
58 | }
59 |
60 | function parseHeaderData(buffer_data, callback_data) {
61 | var retInfo = {};
62 |
63 | // Detect GIF:
64 | if (buffer_data[0] == 0x47 && buffer_data[1] == 0x49 && buffer_data[2] == 0x46) {
65 | retInfo.type = 'gif';
66 | retInfo.width = (buffer_data[7] * 256) + buffer_data[6];
67 | retInfo.height = (buffer_data[9] * 256) + buffer_data[8];
68 |
69 | // Detect JPEG:
70 | } else if (buffer_data[0] == 0xFF && buffer_data[1] == 0xD8 && buffer_data[2] == 0xFF && (buffer_data[3] == 0xE0 || buffer_data[3] == 0xE1)) {
71 | retInfo.type = 'jpeg';
72 | var size = getJpgSize(buffer_data, retInfo);
73 | retInfo.width = size.width;
74 | retInfo.height = size.height;
75 |
76 | // Detect PNG:
77 | } else if (buffer_data[0] == 137 && buffer_data[1] == 80 && buffer_data[2] == 78 && buffer_data[3] == 71 && buffer_data[4] == 13 && buffer_data[5] == 10 && buffer_data[6] == 26 && buffer_data[7] == 10) {
78 | retInfo.type = 'png';
79 |
80 | if (buffer_data[12] == 0x49 && buffer_data[13] == 0x48 && buffer_data[14] == 0x44 && buffer_data[15] == 0x52) {
81 | retInfo.width = (buffer_data[16] * 256 * 256 * 256) + (buffer_data[17] * 256 * 256) + (buffer_data[18] * 256) + buffer_data[19];
82 | retInfo.height = (buffer_data[20] * 256 * 256 * 256) + (buffer_data[21] * 256 * 256) + (buffer_data[22] * 256) + buffer_data[23];
83 | } // Endif.
84 |
85 | // Detect BMP:
86 | } else if (buffer_data[0] == 0x42 && buffer_data[1] == 0x4D) {
87 | retInfo.type = 'bmp';
88 | retInfo.width = (buffer_data[21] * 256 * 256 * 256) + (buffer_data[20] * 256 * 256) + (buffer_data[19] * 256) + buffer_data[18];
89 | retInfo.height = (buffer_data[25] * 256 * 256 * 256) + (buffer_data[24] * 256 * 256) + (buffer_data[23] * 256) + buffer_data[22];
90 | } // Endif.
91 |
92 | retInfo.image = file_path;
93 | if (!retInfo.type) {
94 | retInfo.type = 'unknown';
95 | } // Endif.
96 |
97 | if (callback_data) {
98 | callback_data(retInfo);
99 | } // Endif.
100 |
101 | return retInfo;
102 | };
103 |
104 | function getSVGImageSize(data) {
105 | var retInfo = {};
106 |
107 | retInfo.type = 'svg';
108 | retInfo.width = data.match(/