├── test
├── cases
│ ├── jsdom.css
│ ├── jsdom.html
│ ├── empty.css
│ ├── juice-content
│ │ ├── important.css
│ │ ├── font-quotes.css
│ │ ├── media-preserve.css
│ │ ├── width-attr.css
│ │ ├── apply-link-to-style-tag.css
│ │ ├── important.json
│ │ ├── font-quotes.json
│ │ ├── apply-link-to-style-tag-mq.css
│ │ ├── apply-link-to-style-tag.json
│ │ ├── width-attr.json
│ │ ├── media-preserve.json
│ │ ├── apply-link-to-style-tag-mq.json
│ │ ├── apply-link-to-style-tag.html
│ │ ├── apply-link-to-style-tag-mq.html
│ │ ├── important.out
│ │ ├── apply-link-to-style-tag.out
│ │ ├── apply-link-to-style-tag-mq.out
│ │ ├── font-quotes.out
│ │ ├── font-quotes.html
│ │ ├── important.html
│ │ ├── media-preserve.out
│ │ ├── width-attr.out
│ │ ├── width-attr.html
│ │ └── media-preserve.html
│ ├── jsdom.out
│ ├── alpha.css
│ ├── empty.html
│ ├── empty.out
│ ├── id.css
│ ├── class.css
│ ├── preserve-events.css
│ ├── css-quotes.css
│ ├── regression-media.html
│ ├── tag.html
│ ├── css-quotes.html
│ ├── style-preservation.css
│ ├── alpha.html
│ ├── regression-selector-newline.css
│ ├── tag.css
│ ├── id.html
│ ├── media.out
│ ├── alpha.out
│ ├── ignore-pseudos.html
│ ├── media.html
│ ├── cascading.css
│ ├── class+id.html
│ ├── direct-descendents.html
│ ├── id.out
│ ├── css-quotes.out
│ ├── direct-descendents.css
│ ├── media.css
│ ├── class+id.css
│ ├── tag.out
│ ├── regression-selector-newline.html
│ ├── class.html
│ ├── ignore-pseudos.out
│ ├── important.html
│ ├── yui-reset.html
│ ├── normalize.html
│ ├── style-preservation.html
│ ├── important.out
│ ├── regression-media.out
│ ├── style-preservation.out
│ ├── class+id.out
│ ├── regression-media.css
│ ├── preserve-events.html
│ ├── regression-selector-newline.out
│ ├── direct-descendents.out
│ ├── class.out
│ ├── specificity.html
│ ├── preserve-events.out
│ ├── ignore-pseudos.css
│ ├── cascading.html
│ ├── important.css
│ ├── yui-reset.out
│ ├── specificity.out
│ ├── normalize.out
│ ├── cascading.out
│ ├── specificity.css
│ ├── yui-reset.css
│ └── normalize.css
├── html
│ ├── no_css.in.html
│ ├── Test.css
│ ├── spaces in path
│ │ └── Test.css
│ ├── no_css.out.html
│ ├── spaces_in_path.out.html
│ ├── remote_url.in.html
│ ├── doctype.in.html
│ ├── doctype.out.html
│ ├── spaces_in_path.in.html
│ ├── remote_url.out.html
│ ├── two_styles.out.html
│ └── two_styles.in.html
├── test.js
├── juice.test.js
└── run.js
├── .gitignore
├── .npmignore
├── index.js
├── .travis.yml
├── Makefile
├── package.json
├── bin
└── juice
├── lib
├── property.js
├── selector.js
├── utils.js
└── juice.js
├── LICENSE
├── CHANGELOG.md
└── README.md
/test/cases/jsdom.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/cases/jsdom.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/cases/empty.css:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *~
3 |
--------------------------------------------------------------------------------
/test/cases/juice-content/important.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/html/no_css.in.html:
--------------------------------------------------------------------------------
1 |
hi
2 |
--------------------------------------------------------------------------------
/test/cases/juice-content/font-quotes.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/cases/juice-content/media-preserve.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/cases/juice-content/width-attr.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | test/
2 | support/
3 | README.md
4 |
--------------------------------------------------------------------------------
/test/cases/jsdom.out:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = require('./lib/juice');
3 |
--------------------------------------------------------------------------------
/test/cases/alpha.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 0;
3 | }
4 |
--------------------------------------------------------------------------------
/test/cases/empty.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/test/cases/empty.out:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/test/cases/id.css:
--------------------------------------------------------------------------------
1 | #test {
2 | display: none;
3 | }
4 |
--------------------------------------------------------------------------------
/test/html/Test.css:
--------------------------------------------------------------------------------
1 | strong {
2 | color: blue;
3 | }
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.10"
4 |
--------------------------------------------------------------------------------
/test/cases/class.css:
--------------------------------------------------------------------------------
1 | .woot {
2 | overflow: hidden;
3 | }
4 |
--------------------------------------------------------------------------------
/test/cases/preserve-events.css:
--------------------------------------------------------------------------------
1 | p {
2 | color: red;
3 | }
4 |
--------------------------------------------------------------------------------
/test/cases/css-quotes.css:
--------------------------------------------------------------------------------
1 | p {
2 | background: url("/woot");
3 | }
4 |
--------------------------------------------------------------------------------
/test/cases/regression-media.html:
--------------------------------------------------------------------------------
1 | Test
2 | Test
3 |
--------------------------------------------------------------------------------
/test/cases/tag.html:
--------------------------------------------------------------------------------
1 |
2 | Test
3 |
4 |
--------------------------------------------------------------------------------
/test/html/spaces in path/Test.css:
--------------------------------------------------------------------------------
1 | strong {
2 | color: blue;
3 | }
4 |
--------------------------------------------------------------------------------
/test/cases/css-quotes.html:
--------------------------------------------------------------------------------
1 |
2 | woot
3 |
4 |
--------------------------------------------------------------------------------
/test/cases/style-preservation.css:
--------------------------------------------------------------------------------
1 | p { color: blue; background: blue; }
2 |
--------------------------------------------------------------------------------
/test/html/no_css.out.html:
--------------------------------------------------------------------------------
1 | hi
2 |
3 |
4 |
--------------------------------------------------------------------------------
/test/cases/alpha.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/test/cases/juice-content/apply-link-to-style-tag.css:
--------------------------------------------------------------------------------
1 | p {
2 | width: 200px;
3 | }
--------------------------------------------------------------------------------
/test/cases/juice-content/important.json:
--------------------------------------------------------------------------------
1 | {
2 | "preserveImportant": true
3 | }
--------------------------------------------------------------------------------
/test/cases/regression-selector-newline.css:
--------------------------------------------------------------------------------
1 | a,
2 | p {
3 | color: red;
4 | }
5 |
--------------------------------------------------------------------------------
/test/cases/tag.css:
--------------------------------------------------------------------------------
1 | p {
2 | border: 1px solid #f00;
3 | margin: 1px;
4 | }
5 |
--------------------------------------------------------------------------------
/test/cases/id.html:
--------------------------------------------------------------------------------
1 |
2 | woot
3 |
4 |
--------------------------------------------------------------------------------
/test/cases/media.out:
--------------------------------------------------------------------------------
1 |
2 |
3 | hi
4 |
5 |
6 |
--------------------------------------------------------------------------------
/test/cases/alpha.out:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/test/cases/ignore-pseudos.html:
--------------------------------------------------------------------------------
1 |
2 | Test
3 |
4 |
--------------------------------------------------------------------------------
/test/cases/media.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | hi
4 |
5 |
6 |
--------------------------------------------------------------------------------
/test/cases/cascading.css:
--------------------------------------------------------------------------------
1 | ul li {
2 | display: block;
3 | }
4 | li {
5 | color: #f00;
6 | }
7 |
--------------------------------------------------------------------------------
/test/cases/class+id.html:
--------------------------------------------------------------------------------
1 |
2 | Woot
3 |
4 |
--------------------------------------------------------------------------------
/test/cases/juice-content/font-quotes.json:
--------------------------------------------------------------------------------
1 | {
2 | "url": "./",
3 | "removeStyleTags": true
4 | }
--------------------------------------------------------------------------------
/test/cases/direct-descendents.html:
--------------------------------------------------------------------------------
1 |
2 | woot
3 |
4 |
--------------------------------------------------------------------------------
/test/cases/id.out:
--------------------------------------------------------------------------------
1 |
2 | woot
3 |
4 |
--------------------------------------------------------------------------------
/test/cases/css-quotes.out:
--------------------------------------------------------------------------------
1 |
2 | woot
3 |
4 |
--------------------------------------------------------------------------------
/test/cases/direct-descendents.css:
--------------------------------------------------------------------------------
1 | p em {
2 | background: blue;
3 | }
4 | p > em {
5 | color: red;
6 | }
7 |
--------------------------------------------------------------------------------
/test/cases/juice-content/apply-link-to-style-tag-mq.css:
--------------------------------------------------------------------------------
1 | p { width: 200px; }
2 | @media all { p { width: 100px; } }
--------------------------------------------------------------------------------
/test/cases/media.css:
--------------------------------------------------------------------------------
1 | @media only screen and (-webkit-min-device-pixel-ratio: 2) {
2 | color: blue;
3 | }
4 |
--------------------------------------------------------------------------------
/test/cases/class+id.css:
--------------------------------------------------------------------------------
1 | #test {
2 | border: 1px solid #f00;
3 | }
4 | .woot {
5 | display: inline-block;
6 | }
7 |
--------------------------------------------------------------------------------
/test/cases/tag.out:
--------------------------------------------------------------------------------
1 |
2 | Test
3 |
4 |
--------------------------------------------------------------------------------
/test/cases/regression-selector-newline.html:
--------------------------------------------------------------------------------
1 |
2 | Test
3 | Test
4 |
5 |
--------------------------------------------------------------------------------
/test/cases/class.html:
--------------------------------------------------------------------------------
1 |
2 | Test
3 | Test
4 |
5 |
6 |
--------------------------------------------------------------------------------
/test/cases/ignore-pseudos.out:
--------------------------------------------------------------------------------
1 |
2 | Test
3 |
4 |
--------------------------------------------------------------------------------
/test/cases/important.html:
--------------------------------------------------------------------------------
1 |
2 | woot
3 |
4 |
--------------------------------------------------------------------------------
/test/cases/yui-reset.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | hello
6 |
7 |
8 |
--------------------------------------------------------------------------------
/test/cases/juice-content/apply-link-to-style-tag.json:
--------------------------------------------------------------------------------
1 | {
2 | "applyLinksToStyleTags": true,
3 | "removeStyleTags": false
4 | }
--------------------------------------------------------------------------------
/test/cases/normalize.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | normalize.css
6 |
7 |
8 |
--------------------------------------------------------------------------------
/test/cases/style-preservation.html:
--------------------------------------------------------------------------------
1 |
2 | The color should stay intact
3 |
4 |
--------------------------------------------------------------------------------
/test/cases/juice-content/width-attr.json:
--------------------------------------------------------------------------------
1 | {
2 | "url": "./",
3 | "removeStyleTags": true,
4 | "applyWidthAttributes": true
5 | }
--------------------------------------------------------------------------------
/test/cases/important.out:
--------------------------------------------------------------------------------
1 |
2 | woot
3 |
4 |
--------------------------------------------------------------------------------
/test/cases/juice-content/media-preserve.json:
--------------------------------------------------------------------------------
1 | {
2 | "url": "./",
3 | "removeStyleTags": true,
4 | "preserveMediaQueries": true
5 | }
--------------------------------------------------------------------------------
/test/cases/regression-media.out:
--------------------------------------------------------------------------------
1 | Test
2 | Test
3 |
--------------------------------------------------------------------------------
/test/cases/style-preservation.out:
--------------------------------------------------------------------------------
1 |
2 | The color should stay intact
3 |
4 |
--------------------------------------------------------------------------------
/test/cases/class+id.out:
--------------------------------------------------------------------------------
1 |
2 | Woot
3 |
4 |
--------------------------------------------------------------------------------
/test/cases/regression-media.css:
--------------------------------------------------------------------------------
1 | a,
2 | p {
3 | color: green;
4 | }
5 |
6 | @media only screen and (max-width: 600px) {
7 | a { color: red; }
8 | }
--------------------------------------------------------------------------------
/test/cases/preserve-events.html:
--------------------------------------------------------------------------------
1 |
2 | Google
3 | Google
4 |
5 |
--------------------------------------------------------------------------------
/test/cases/regression-selector-newline.out:
--------------------------------------------------------------------------------
1 |
2 | Test
3 | Test
4 |
5 |
--------------------------------------------------------------------------------
/test/cases/direct-descendents.out:
--------------------------------------------------------------------------------
1 |
2 | woot
3 |
4 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 | test:
3 | @node test/run.js
4 | @./node_modules/.bin/expresso \
5 | -t 3000 \
6 | --serial \
7 | test/juice.test.js
8 |
9 | .PHONY: test
10 |
--------------------------------------------------------------------------------
/test/cases/juice-content/apply-link-to-style-tag-mq.json:
--------------------------------------------------------------------------------
1 | {
2 | "applyLinksToStyleTags": true,
3 | "removeStyleTags": true,
4 | "preserveMediaQueries": true
5 | }
--------------------------------------------------------------------------------
/test/cases/class.out:
--------------------------------------------------------------------------------
1 |
2 | Test
3 | Test
4 |
5 |
--------------------------------------------------------------------------------
/test/cases/specificity.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/test/cases/preserve-events.out:
--------------------------------------------------------------------------------
1 |
2 | Google
3 | Google
4 |
5 |
--------------------------------------------------------------------------------
/test/cases/ignore-pseudos.css:
--------------------------------------------------------------------------------
1 | a {
2 | text-decoration: underline;
3 | }
4 |
5 | a:hover {
6 | text-decoration: none;
7 | }
8 |
9 | a:link, a:visited {
10 | font-weight: bold;
11 | }
12 |
--------------------------------------------------------------------------------
/test/cases/juice-content/apply-link-to-style-tag.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | none
7 |
8 |
9 |
--------------------------------------------------------------------------------
/test/cases/juice-content/apply-link-to-style-tag-mq.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | none
7 |
8 |
9 |
--------------------------------------------------------------------------------
/test/cases/juice-content/important.out:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | hi
7 | there
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/html/spaces_in_path.out.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | hello, you are my favorite person
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/html/remote_url.in.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | hello
7 |
8 |
9 |
--------------------------------------------------------------------------------
/test/cases/juice-content/apply-link-to-style-tag.out:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 | none
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/cases/cascading.html:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
--------------------------------------------------------------------------------
/test/html/doctype.in.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | hello
7 |
8 |
--------------------------------------------------------------------------------
/test/html/doctype.out.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | hello
7 |
8 |
--------------------------------------------------------------------------------
/test/html/spaces_in_path.in.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | hello, you are my favorite person
7 |
8 |
9 |
--------------------------------------------------------------------------------
/test/cases/important.css:
--------------------------------------------------------------------------------
1 | a {
2 | color: red !important;
3 | background: blue !important;
4 | border-style: dashed !important;
5 | }
6 |
7 | #a {
8 | color: purple;
9 | background: purple;
10 | border-style: solid;
11 | }
12 |
--------------------------------------------------------------------------------
/test/cases/yui-reset.out:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | hello
6 |
7 |
8 |
--------------------------------------------------------------------------------
/test/cases/juice-content/apply-link-to-style-tag-mq.out:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 | none
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/cases/juice-content/font-quotes.out:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | hi
7 | there
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/html/remote_url.out.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | hello
7 |
8 |
9 |
--------------------------------------------------------------------------------
/test/cases/specificity.out:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/test/cases/normalize.out:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | normalize.css
6 |
7 |
8 |
--------------------------------------------------------------------------------
/test/html/two_styles.out.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | hello, you are my favorite person
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/cases/juice-content/font-quotes.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 | hi
11 | there
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/cases/juice-content/important.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 | hi
14 | there
15 |
16 |
17 |
--------------------------------------------------------------------------------
/test/cases/cascading.out:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
--------------------------------------------------------------------------------
/test/cases/juice-content/media-preserve.out:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
16 | hi
17 |
18 |
19 |
--------------------------------------------------------------------------------
/test/cases/juice-content/width-attr.out:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | none
7 |
8 |
9 |
10 | wide
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/test/cases/juice-content/width-attr.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 | none
11 |
12 |
13 |
14 | wide
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/test/html/two_styles.in.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
16 |
17 |
18 |
19 | hello, you are my favorite person
20 |
21 |
22 |
--------------------------------------------------------------------------------
/test/cases/juice-content/media-preserve.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
20 |
21 |
22 | hi
23 |
24 |
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "juice2",
3 | "version": "1.3.2",
4 | "description": "Inlines css into html source",
5 | "bin": "./bin/juice",
6 | "scripts": {
7 | "test": "mocha --reporter spec"
8 | },
9 | "engines": {
10 | "node": ">=0.10.20"
11 | },
12 | "dependencies": {
13 | "slick": "~1.12.1",
14 | "commander": "~2.3.0",
15 | "cssom": "~0.3.0",
16 | "superagent": "~0.18.2",
17 | "jsdom": "~0.11.1",
18 | "pend": "~1.1.2"
19 | },
20 | "devDependencies": {
21 | "should": "~4.0.4",
22 | "mocha": "~1.20.1",
23 | "expresso": "~0.9.2"
24 | },
25 | "repository": {
26 | "type": "git",
27 | "url": "https://github.com/andrewrk/juice.git"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/bin/juice:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var juice = require('..');
4 | var program = require('commander');
5 | var package = require('../package');
6 | var fs = require('fs');
7 |
8 | program.name = package.name;
9 |
10 | program.version(package.version)
11 | .usage('input.html output.html')
12 | .parse(process.argv);
13 |
14 | if (program.args < 2) program.help();
15 |
16 | var inputFile = program.args[0]
17 | , outputFile = program.args[1];
18 |
19 | juice(inputFile, function(err, html) {
20 | if (err) {
21 | console.error(err.stack)
22 | process.exit(1);
23 | } else {
24 | fs.writeFile(outputFile, html, function(err) {
25 | if (err) {
26 | console.error(err.stack);
27 | process.exit(1);
28 | }
29 | });
30 | }
31 | });
32 |
--------------------------------------------------------------------------------
/test/cases/specificity.css:
--------------------------------------------------------------------------------
1 | #wrap #a { /* [2, 0, 0] - winner */
2 | color: red;
3 | }
4 | #wrap a { /* [1, 0, 1] */
5 | color: #000;
6 | }
7 | #a { /* [1, 0, 0] */
8 | color: #00f;
9 | }
10 | div[a=b] .wrap div { /* [0, 2, 2] */
11 | display: block;
12 | }
13 | #wrap .wrap { /* [1, 1, 0] - winner */
14 | display: inline-block;
15 | }
16 | #wrap div.wrap #a { /* [2, 1, 1] */
17 | background: #000;
18 | }
19 | #wrap[a=b] div.wrap #a { /* [2, 2, 1] - winner */
20 | background: green;
21 | }
22 | #wrap[a=c] div.wrap #a { /* [2, 2, 1] - no match */
23 | background: #00f;
24 | }
25 |
26 | #wrap .wrap #a { /* [2, 1, 0] */
27 | background: #f00;
28 | }
29 | #wrap .wrap #a { /* [2, 1, 0] - winner */
30 | border-color: green;
31 | }
32 | div #wrap-2 #a { /* [2, 0, 1] */
33 | border-color: #008000;
34 | }
35 |
--------------------------------------------------------------------------------
/test/cases/yui-reset.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 | * Code licensed under the BSD License:
4 | * http://developer.yahoo.com/yui/license.html
5 | * version: 3.3.0
6 | * build: 3167
7 | * */
8 | html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:text-top;}sub{vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}
9 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | var juice = require('../');
2 | var path = require('path');
3 | var fs = require('fs');
4 | var Pend = require('pend');
5 | var assert = require('assert');
6 |
7 | var it = global.it;
8 |
9 | var tests = [
10 | "doctype",
11 | "no_css",
12 | "two_styles",
13 | "remote_url",
14 | "spaces_in_path",
15 | ];
16 |
17 |
18 | tests.forEach(function(testName) {
19 | it(testName, createIt(testName));
20 | });
21 |
22 | function createIt(testName) {
23 | return function(cb) {
24 | var pend = new Pend();
25 | var expected, actual;
26 | pend.go(function(cb) {
27 | var filePath = path.join(__dirname, "html", testName + ".in.html");
28 | juice(filePath, function(err, val) {
29 | actual = val;
30 | cb(err);
31 | });
32 | });
33 | pend.go(function(cb) {
34 | fs.readFile(path.join(__dirname, "html", testName + ".out.html"), 'utf8', function(err, val) {
35 | expected = val;
36 | cb(err);
37 | });
38 | });
39 | pend.wait(function(err) {
40 | if (err) return cb(err);
41 | assert.strictEqual(actual.trim(), expected.trim());
42 | cb();
43 | });
44 | };
45 | }
46 |
--------------------------------------------------------------------------------
/lib/property.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * juice
4 | * Copyright(c) 2011 LearnBoost
5 | * MIT Licensed
6 | */
7 |
8 | module.exports = exports = Property;
9 |
10 | /**
11 | * Module dependencies.
12 | */
13 |
14 | var compare = require('./utils').compare
15 |
16 | /**
17 | * CSS property constructor.
18 | *
19 | * @param {String} property
20 | * @param {String} value
21 | * @param {Selector} selector the property originates from
22 | * @api public
23 | */
24 |
25 | function Property (prop, value, selector) {
26 | this.prop = prop;
27 | this.value = value;
28 | this.selector = selector
29 | }
30 |
31 | /**
32 | * Compares with another Property based on Selector#specificity.
33 | *
34 | * @api public
35 | */
36 |
37 | Property.prototype.compare = function (property) {
38 | var a = this.selector.specificity()
39 | , b = property.selector.specificity()
40 | , winner = compare(a, b)
41 |
42 | if (winner == a) return this;
43 | return property;
44 | };
45 |
46 | /**
47 | * Returns CSS property
48 | *
49 | * @api public
50 | */
51 |
52 | Property.prototype.toString = function () {
53 | return this.prop + ': ' + this.value.replace(/['"]+/g, '') + ';';
54 | };
55 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | (The MIT License)
2 |
3 | Copyright (c) 2011 Guillermo Rauch
4 | Copyright (c) 2014 Andrew Kelley
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining
7 | a copy of this software and associated documentation files (the
8 | 'Software'), to deal in the Software without restriction, including
9 | without limitation the rights to use, copy, modify, merge, publish,
10 | distribute, sublicense, and/or sell copies of the Software, and to
11 | permit persons to whom the Software is furnished to do so, subject to
12 | the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be
15 | included in all copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 |
25 |
--------------------------------------------------------------------------------
/lib/selector.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * Module dependencies.
4 | */
5 |
6 | var parser = require('slick').parse
7 |
8 | /**
9 | * Module exports.
10 | */
11 |
12 | module.exports = exports = Selector;
13 |
14 | /**
15 | * CSS selector constructor.
16 | *
17 | * @param {String} selector text
18 | * @param {Array} optionally, precalculated specificity
19 | * @api public
20 | */
21 |
22 | function Selector (text, spec) {
23 | this.text = text;
24 | this.spec = spec;
25 | }
26 |
27 | /**
28 | * Get parsed selector.
29 | *
30 | * @api public
31 | */
32 |
33 | Selector.prototype.parsed = function () {
34 | if (!this.tokens) this.tokens = parse(this.text);
35 | return this.tokens;
36 | };
37 |
38 | /**
39 | * Lazy specificity getter
40 | *
41 | * @api public
42 | */
43 |
44 | Selector.prototype.specificity = function () {
45 | if (!this.spec) this.spec = specificity(this.text, this.parsed());
46 | return this.spec;
47 |
48 | function specificity (text, parsed) {
49 | var expressions = parsed || parse(text)
50 | , spec = [0, 0, 0, 0]
51 | , nots = []
52 |
53 | for (var i = 0; i < expressions.length; i++) {
54 | var expression = expressions[i]
55 | , pseudos = expression.pseudos
56 |
57 | // id awards a point in the second column
58 | if (expression.id) spec[1]++;
59 |
60 | // classes and attributes award a point each in the third column
61 | if (expression.attributes) spec[2] += expression.attributes.length;
62 | if (expression.classList) spec[2] += expression.classList.length;
63 |
64 | // tag awards a point in the fourth column
65 | if (expression.tag && expression.tag !== '*') spec[3]++;
66 |
67 | // pseudos award a point each in the fourth column
68 | if (pseudos) {
69 | spec[3] += pseudos.length;
70 |
71 | for (var p = 0; p < pseudos.length; p++) {
72 | if (pseudos[p].key === 'not'){
73 | nots.push(pseudos[p].value);
74 | spec[3]--;
75 | }
76 | }
77 | }
78 | }
79 |
80 | for (var ii = nots.length; ii--;) {
81 | var not = specificity(nots[ii]);
82 | for (var jj = 4; jj--;) spec[jj] += not[jj];
83 | }
84 |
85 | return spec;
86 | }
87 | }
88 |
89 | /**
90 | * Parses a selector and returns the tokens.
91 | *
92 | * @param {String} selector
93 | * @api private.
94 | */
95 |
96 | function parse (text) {
97 | try {
98 | return parser(text)[0];
99 | } catch (e) {
100 | return [];
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/test/juice.test.js:
--------------------------------------------------------------------------------
1 |
2 | /*!
3 | * Juice unit tests.
4 | */
5 |
6 | /**
7 | * Test dependencies.
8 | */
9 |
10 | var juice = require('../')
11 | , Selector = juice.Selector
12 | , Property = juice.Property
13 | , utils = juice.utils
14 | , should = require('should')
15 |
16 | /**
17 | * Tests.
18 | */
19 |
20 | module.exports = {
21 |
22 | 'test extracting selectors': function () {
23 | var extract = utils.extract;
24 |
25 | extract('#a').should.eql(['#a']);
26 | extract('#a, .b').should.eql(['#a', '.b']);
27 | extract('#a, .b,').should.eql(['#a', '.b']);
28 | extract('.test.a, #a.b').should.eql(['.test.a', '#a.b']);
29 | extract('a[type=text, a=b], .a, .b, #c #d').should
30 | .eql(['a[type=text, a=b]', '.a', '.b', '#c #d']);
31 | extract('a:not(.a,.b,.c)').should.eql(['a:not(.a,.b,.c)']);
32 | extract('a:not(.a,.b,.c), .b').should.eql(['a:not(.a,.b,.c)', '.b']);
33 | extract('a:not(.a,.b,[type=text]), .b').should
34 | .eql(['a:not(.a,.b,[type=text])', '.b']);
35 | extract('a:not(.a,.b,[type=text, a=b]), .b').should
36 | .eql(['a:not(.a,.b,[type=text, a=b])', '.b']);
37 | },
38 |
39 | 'test selector specificity comparison': function () {
40 | var compare = utils.compare;
41 |
42 | compare([0, 1, 2, 3], [0, 2, 0, 0]).should.eql([0, 2, 0, 0]);
43 | compare([0, 2, 0, 0], [0, 1, 2, 3]).should.eql([0, 2, 0, 0]);
44 |
45 | // check that the second reference is returned upon draws
46 | var b = [0, 1, 1, 4];
47 | compare([0, 1, 1, 4], b).should.equal(b);
48 |
49 | compare([0, 0, 0, 4], [0, 0, 0, 10]).should.eql([0, 0, 0, 10]);
50 | compare([0, 0, 0, 10], [0, 0, 0, 4]).should.eql([0, 0, 0, 10]);
51 |
52 | compare([0, 4, 0, 0], [0, 0, 100, 4]).should.eql([0, 4, 0, 0]);
53 | compare([0, 0, 100, 4], [0, 4, 0, 0]).should.eql([0, 4, 0, 0]);
54 |
55 | compare([0, 1, 1, 5], [0, 1, 1, 15]).should.eql([0, 1, 1, 15]);
56 | compare([0, 1, 1, 15], [0, 1, 1, 5]).should.eql([0, 1, 1, 15]);
57 | },
58 |
59 | 'test selector specificity calculator': function () {
60 | function spec (selector) {
61 | return new Selector(selector).specificity();
62 | };
63 |
64 | spec('#test').should.eql([0, 1, 0, 0]);
65 | spec('#a #b #c').should.eql([0, 3, 0, 0]);
66 | spec('.a .b .c').should.eql([0, 0, 3, 0]);
67 | spec('div.a div.b div.c').should.eql([0, 0, 3, 3]);
68 | spec('div a span').should.eql([0, 0, 0, 3]);
69 | spec('#test input[type=text]').should.eql([0, 1, 1, 1]);
70 | spec('[type=text]', [0, 0, 1, 0]);
71 | spec('*').should.eql([0, 0, 0, 0]);
72 | spec('div *').should.eql([0, 0, 0, 1]);
73 | },
74 |
75 | 'test property comparison based on selector specificity': function () {
76 | function prop (k, v, sel) {
77 | return new Property(k, v, new Selector(sel));
78 | }
79 |
80 | var a = prop('color', 'white', '#woot')
81 | , b = prop('color', 'red', '#a #woot')
82 |
83 | a.compare(b).should.equal(b);
84 |
85 | var a = prop('background-color', 'red', '#a')
86 | , b = prop('background-color', 'red', '.a.b.c')
87 |
88 | a.compare(b).should.equal(a);
89 |
90 | var a = prop('background-color', 'red', '#a .b.c')
91 | , b = prop('background-color', 'red', '.a.b.c #c')
92 |
93 | a.compare(b).should.equal(b);
94 | },
95 |
96 | 'test parsing css into a object structure': function () {
97 | var parse = utils.parseCSS;
98 |
99 | parse('a, b { c: e; }').should.eql([
100 | ['a', { '0': 'c', length: 1, _importants: { c: '' }, __starts: 5, c: 'e' } ]
101 | , ['b', { '0': 'c', length: 1, _importants: { c: '' }, __starts: 5, c: 'e' } ]
102 | ]);
103 |
104 | parse([
105 | 'a, b { c: e; }'
106 | , 'b.e #d { d: e; }'
107 | , 'c[a=b] { d: e; }'
108 | ].join('\n')).should.eql([
109 | ['a', { '0': 'c', length: 1, _importants: { c: '' }, __starts: 5, c: 'e' } ]
110 | , ['b', { '0': 'c', length: 1, _importants: { c: '' }, __starts: 5, c: 'e' } ]
111 | , ['b.e #d', { '0': 'd', length: 1, _importants: { d: '' }, __starts: 22, d: 'e' }]
112 | , ['c[a=b]', { '0': 'd', length: 1, _importants: { d: '' }, __starts: 39, d: 'e' }]
113 | ]);
114 | },
115 |
116 | 'test juice': function () {
117 | juice('woot
', 'div { color: red; }')
118 | .should.equal('woot
');
119 | }
120 |
121 | };
122 |
--------------------------------------------------------------------------------
/test/run.js:
--------------------------------------------------------------------------------
1 |
2 | /*!
3 | * Test runner based on Stylus test runner.
4 | */
5 |
6 | /**
7 | * Module dependencies.
8 | */
9 |
10 | var juice = require('../')
11 | , basename = require('path').basename
12 | , fs = require('fs')
13 | , path = require('path');
14 |
15 | /**
16 | * Test count.
17 | */
18 |
19 | var count = 0;
20 |
21 | /**
22 | * Failure count.
23 | */
24 |
25 | var failures = 0;
26 |
27 | /**
28 | * Test the given `test`.
29 | *
30 | * @param {String} test
31 | * @param {Function} fn
32 | */
33 |
34 | function test (testName, fn, options) {
35 | var base = __dirname + '/cases/' + testName
36 | , html = read(base + '.html')
37 | , css = read( base + '.css' )
38 | , config = options ? JSON.parse( read( base + '.json' ) ) : null
39 |
40 | function read (file) {
41 | return fs.readFileSync(file, 'utf8');
42 | }
43 |
44 | var onDone = function ( err, actual )
45 | {
46 | var expected = read( base + '.out' );
47 |
48 | if ( actual.trim() === expected.trim() )
49 | {
50 | fn();
51 | } else
52 | {
53 | fn( actual, expected );
54 | }
55 | };
56 |
57 | if ( config === null )
58 | {
59 | // use the legacy invocation to test backward compatibility
60 | var actual = juice( html, css );
61 | onDone( null, actual );
62 | }
63 | else
64 | {
65 | var slashes = path.sep + path.sep;
66 | config.url = "file:" + slashes + path.dirname(path.resolve(process.cwd(), base + '.html'));
67 | juice.juiceContent( html, config, onDone );
68 | }
69 |
70 | return testName;
71 | }
72 |
73 | /**
74 | * Auto-load and run tests.
75 | */
76 |
77 | fs.readdir( __dirname + '/cases', function ( err, files ) {
78 | if ( err ) throw err;
79 | var tests = [];
80 |
81 | files.forEach( function ( file )
82 | {
83 | if ( /\.html$/.test( file ) ) {
84 | ++count;
85 | tests.push( { basename: basename( file, '.html' ) } );
86 | }
87 | } );
88 |
89 | fs.readdir( __dirname + '/cases/juice-content', function ( err, files )
90 | {
91 | if ( err ) throw err;
92 |
93 | files.forEach( function ( file )
94 | {
95 | if ( /\.html$/.test( file ) ) {
96 | ++count;
97 | tests.push( { basename: 'juice-content/' + basename( file, '.html' ), options: true } );
98 | }
99 | } );
100 |
101 | nextTest( tests );
102 | } );
103 | } );
104 |
105 |
106 | function nextTest( tests )
107 | {
108 | curr = tests.shift();
109 | if ( !curr ) return done();
110 | process.stderr.write( ' \033[90m' + curr.basename + '\033[0m' );
111 | test( curr.basename, function ( actual, expected )
112 | {
113 | if ( actual ) {
114 | ++failures;
115 | console.error( '\r \033[31m✖\033[0m \033[90m' + curr.basename + '\033[0m\n' );
116 | diff( actual, expected );
117 | console.error();
118 | } else {
119 | console.error( '\r \033[36m✔\033[0m \033[90m' + curr.basename + '\033[0m' );
120 | }
121 | nextTest( tests );
122 | }, curr.options );
123 | }
124 |
125 | /**
126 | * Diff `actual` / `expected`.
127 | *
128 | * @param {String} actual
129 | * @param {String} expected
130 | */
131 |
132 | function diff (actual, expected) {
133 | actual = actual.split('\n');
134 | expected = expected.split('\n');
135 | var len = largestLineIn(expected);
136 |
137 | expected.forEach(function(line, i){
138 | if (!line.length && i === expected.length - 1) return;
139 | var other = actual[i]
140 | , pad = len - line.length;
141 | pad = (new Array(++pad)).join(' ');
142 | var same = line === other;
143 | if (same) {
144 | console.error(' %d| %j%s | %j', i+1, line, pad, other);
145 | } else {
146 | console.error(' \033[31m%d| %j%s | %j\033[0m', i+1, line, pad, other);
147 | }
148 | });
149 | }
150 |
151 | /**
152 | * Return the length of the largest line in `lines`.
153 | *
154 | * @param {Array} lines
155 | * @return {Number}
156 | */
157 |
158 | function largestLineIn(lines) {
159 | return lines.reduce(function(n, line){
160 | return Math.max(n, line.length);
161 | }, 0);
162 | }
163 |
164 | /**
165 | * Done!!!
166 | */
167 |
168 | function done () {
169 | console.log();
170 | console.log(
171 | ' \033[90mcompleted\033[0m' +
172 | ' \033[32m%d\033[0m' +
173 | ' \033[90mtests\033[0m', count);
174 |
175 | if (failures) {
176 | console.error(' \033[90mfailed\033[0m' +
177 | ' \033[31m%d\033[0m' +
178 | ' \033[90mtests\033[0m', failures);
179 | process.exit(failures);
180 | }
181 |
182 | console.log();
183 | }
184 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * Module dependencies.
4 | */
5 |
6 | var fs = require('fs')
7 | , cssom = require('cssom')
8 | , jsdom = require('jsdom')
9 | , own = {}.hasOwnProperty
10 | , os = require('os');
11 |
12 | /**
13 | * Returns an array of the selectors.
14 | *
15 | * @license Sizzle CSS Selector Engine - MIT
16 | * @param {String} selectorText from cssom
17 | * @api public
18 | */
19 |
20 | exports.extract = function extract (selectorText) {
21 | var attr = 0
22 | , sels = []
23 | , sel = ''
24 |
25 | for (var i = 0, l = selectorText.length; i < l; i++) {
26 | var c = selectorText.charAt(i);
27 |
28 | if (attr) {
29 | if (']' === c || ')' === c) attr--;
30 | sel += c;
31 | } else {
32 | if (',' === c) {
33 | sels.push(sel);
34 | sel = '';
35 | } else {
36 | if ('[' === c || '(' === c) attr++;
37 | if (sel.length || (c !== ',' && c !== '\n' && c !== ' ')) sel += c;
38 | }
39 | }
40 | }
41 |
42 | if (sel.length) sels.push(sel);
43 |
44 | return sels;
45 | }
46 |
47 | /**
48 | * Returns a parse tree for a CSS source.
49 | * If it encounters multiple selectors separated by a comma, it splits the
50 | * tree.
51 | *
52 | * @param {String} css source
53 | * @api public
54 | */
55 |
56 | exports.parseCSS = function (css) {
57 | var rules = cssom.parse(css).cssRules || []
58 | , ret = []
59 |
60 | for (var i = 0, l = rules.length; i < l; i++) {
61 | if (rules[i].selectorText) { // media queries don't have selectorText
62 | var rule = rules[i]
63 | , selectors = exports.extract(rule.selectorText)
64 |
65 | for (var ii = 0, ll = selectors.length; ii < ll; ii++) {
66 | ret.push([selectors[ii], rule.style]);
67 | }
68 | }
69 | }
70 |
71 | return ret;
72 | }
73 |
74 |
75 | /**
76 | * Returns Media Query text for a CSS source.
77 | *
78 | * @param {String} css source
79 | * @api public
80 | */
81 |
82 | exports.getMediaQueryText = function ( css )
83 | {
84 | var rules = cssom.parse( css ).cssRules || []
85 | var queries = [];
86 |
87 | for ( var i = 0, l = rules.length; i < l; i++ )
88 | {
89 | /* CSS types
90 | STYLE: 1,
91 | IMPORT: 3,
92 | MEDIA: 4,
93 | FONT_FACE: 5,
94 | */
95 |
96 | if ( rules[i].type === cssom.CSSMediaRule.prototype.type )
97 | {
98 | var query = rules[i];
99 | var queryString = [];
100 |
101 | queryString.push( os.EOL + "@media " + query.media[0] + " {" );
102 |
103 | for ( var ii = 0, ll = query.cssRules.length; ii < ll; ii++ )
104 | {
105 | var rule = query.cssRules[ii];
106 |
107 | if ( rule.type === cssom.CSSStyleRule.prototype.type || rule.type === cssom.CSSFontFaceRule.prototype.type )
108 | {
109 | queryString.push( " " + ( rule.type === cssom.CSSStyleRule.prototype.type ? rule.selectorText : "@font-face" ) + " {" );
110 |
111 | for ( var style = 0; style < rule.style.length; style++ )
112 | {
113 | var property = rule.style[style];
114 | var value = rule.style[property];
115 | var important = rule.style._importants[property] ? " !important" : "";
116 | queryString.push( " " + property + ": " + value + important + ";" );
117 | }
118 | queryString.push( " }" );
119 | }
120 | }
121 |
122 | queryString.push( "}" );
123 | var result = queryString.length ? queryString.join( os.EOL ) + os.EOL : "";
124 |
125 | queries.push( result );
126 | }
127 | }
128 |
129 | return queries.join( os.EOL );
130 | }
131 |
132 | /**
133 | * Returns a JSDom jQuery object
134 | *
135 | * api public
136 | */
137 |
138 | exports.jsdom = function (html) {
139 | return jsdom.html(html, null, {
140 | features: {
141 | QuerySelector: ['1.0']
142 | , FetchExternalResources: false
143 | , ProcessExternalResources: false
144 | , MutationEvents: false
145 | }
146 | });
147 | };
148 |
149 | /**
150 | * Converts to array
151 | *
152 | * @api public
153 | */
154 |
155 | exports.toArray = function (arr) {
156 | var ret = [];
157 |
158 | for (var i = 0, l = arr.length; i < l; i++)
159 | ret.push(arr[i]);
160 |
161 | return ret;
162 | };
163 |
164 | /**
165 | * Compares two specificity vectors, returning the winning one.
166 | *
167 | * @param {Array} vector a
168 | * @param {Array} vector b
169 | * @return {Array}
170 | * @api public
171 | */
172 |
173 | exports.compare = function (a, b) {
174 | for (var i = 0; i < 4; i++) {
175 | if (a[i] === b[i]) continue;
176 | if (a[i] > b[i]) return a;
177 | return b;
178 | }
179 |
180 | return b;
181 | }
182 |
183 | exports.extend = function (obj, src) {
184 | for (var key in src) if (own.call(src, key)) obj[key] = src[key];
185 | return obj;
186 | }
187 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | 1.3.2 / 2014-11-24
2 | ==================
3 |
4 | * Add option preserveImportant [jrit]
5 |
6 | 1.3.1 / 2014-11-24
7 | ==================
8 |
9 | * Add option applyLinksToStyleTags [jrit]
10 |
11 | 1.3.0 / 2014-07-22
12 | ==================
13 |
14 | * Update dependencies and code cleanup
15 | * heads up, xhtml might be handled differently
16 | * extract LICENSE from README
17 | * rename History.md to CHANGELOG.md
18 |
19 | 1.2.0 / 2014-05-22
20 | ==================
21 |
22 | * Add option applyWidthAttributes to API [jrit]
23 |
24 | 1.1.0 / 2014-05-22
25 | ==================
26 |
27 | * preserve single quotes for values [jrit]
28 |
29 | 1.0.0 / 2014-05-07
30 | ==================
31 |
32 | * order styles alphabetically to preserve expected style priority [jrit]
33 | * major version bumped in case people expected styles to not be sorted alphabetically
34 |
35 | 0.7.0 / 2014-05-06
36 | ==================
37 |
38 | * add preserveMediaQueries API [jrit]
39 |
40 | 0.6.0 / 2014-04-25
41 | ==================
42 |
43 | * update dependencies [jonkemp]
44 |
45 | 0.5.1 / 2014-03-12
46 | ==================
47 |
48 | * fix not including all stylesheets [andxyz]
49 |
50 | 0.5.0 / 2013-04-15
51 | ==================
52 |
53 | * update dependencies to latest
54 | * fix not preserving DOCTYPE [jgannonjr]
55 | * fix decoding local file paths [alexluke]
56 |
57 | 0.4.0 / 2013-04-15
58 | ==================
59 |
60 | * update jsdom dependency to 0.6.0
61 |
62 | 0.3.3 / 2013-04-08
63 | ==================
64 |
65 | * fix resolving file:// paths on windows (thanks Mirco Zeiss)
66 | * fix crash during cleanup. (thanks Ger Hobbelt)
67 | * update superagent to 0.14.0
68 |
69 | 0.3.2 / 2013-03-26
70 | ==================
71 |
72 | * fix regression: not ignoring pseudos
73 |
74 | 0.3.1 / 2013-03-26
75 | ==================
76 |
77 | * do not crash on ::selectors (covered by normalize.css test case)
78 |
79 | 0.3.0 / 2013-03-26
80 | ==================
81 |
82 | * update jsdom dependency to 0.5.4
83 | * support node v0.10
84 | * switch dependency to slick instead of mootools which was rudely unpublished
85 |
86 | 0.2.0 / 2013-02-13
87 | ==================
88 |
89 | * update jsdom dependency to 0.5.0
90 |
91 | 0.1.3 / 2013-02-12
92 | ==================
93 |
94 | * fix specificity test. all test cases passed now.
95 | * add a command line `juice` program
96 |
97 | 0.1.2 / 2013-02-11
98 | ==================
99 |
100 | * fix incorrectly lowercasing href
101 |
102 | 0.1.1 / 2013-02-11
103 | ==================
104 |
105 | * explicitly document which node versions are supported
106 | with `engines` and travis-ci.
107 | * expose `juice.inlineDocument` and `juice.inlineContent`
108 |
109 | 0.1.0 / 2013-02-07
110 | ==================
111 |
112 | * fix / test case for @media queries
113 | * merge [boost](https://github.com/andrewrk/boost) into juice
114 | * legacy `juice` function still works as is
115 | * add `juice(filePath, [options], callback)`
116 | * add `juice.juiceDocument(document, options, callback)`
117 | * add `juice.juiceContent(html, options, callback)`
118 | * remove `juice.juiceDom`
119 |
120 | 0.0.9 / 2013-02-07
121 | ==================
122 |
123 | * update jsdom dependency to 0.4.0
124 | * update cssom dependency to 0.2.5
125 |
126 | 0.0.8 / 2013-02-06
127 | ==================
128 |
129 | * expose a lower level export so you can operate on a jsdom document [andrewrk]
130 | * fix exports not working [andrewrk]
131 | * fix jshint problems [andrewrk]
132 |
133 | 0.0.7 / 2013-02-06
134 | ==================
135 |
136 | * fixed test case expected outputs to have starting and ending and tags as jsdom appends them in its html() function if they do not exist
137 | * regression test for previous fix for media queries. note i had to wrap my test .out content in tags in order to pass tests, it looks like they are appended at some point for partial html content which just guessing is new behavior from when these tests were written
138 | * make sure the css rule has selectorText to prevent parsing exception. hit this on @media rules which do not have selector text. afaik this means media queries will not be inlined. however everything else is.
139 | * bump jsdom
140 | * Added note regarding node-email-templates to README
141 |
142 | 0.0.6 / 2011-12-20
143 | ==================
144 |
145 | * Corrected juice unit tests for latest cssom.
146 | * Fixed presence of \n in selectors.
147 | * Fixed unneeded removal of inline event handlers in html.
148 | * Bumped jsdom.
149 |
150 | 0.0.5 / 2011-10-10
151 | ==================
152 |
153 | * Added whitelist of pseudos to ignore (fixes `:first-child` etc)
154 | * Fine-tuned jsdom for speed (disabled unneded features).
155 | * Added caching of parsed selectors.
156 |
157 | 0.0.4 / 2011-10-10
158 | ==================
159 |
160 | * Fixed `:hover`.
161 |
162 | 0.0.3 / 2011-10-09
163 | ==================
164 |
165 | * Fixed specificity :not recursion.
166 |
167 | 0.0.2 / 2011-10-09
168 | ==================
169 |
170 | * Fixed specificity calculation for `not()` and pseudos. [arian]
171 |
172 | 0.0.1 / 2011-10-09
173 | ==================
174 |
175 | * Initial release.
176 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/andrewrk/juice)
2 | # Juice 
3 |
4 | Given HTML, juice will inline your CSS properties into the `style`
5 | attribute.
6 |
7 | ## How to use
8 |
9 | ```js
10 | var juice = require('juice2');
11 | juice("/path/to/file.html", function(err, html) {
12 | console.log(html);
13 | });
14 | ```
15 |
16 | `/path/to/file.html`:
17 | ```html
18 |
19 |
20 |
23 |
24 |
25 |
26 | Test
27 |
28 |
29 | ```
30 |
31 | `style.css`
32 | ```css
33 | p {
34 | text-decoration: underline;
35 | }
36 | ```
37 |
38 | Output:
39 | ```html
40 |
41 |
42 |
43 |
44 | Test
45 |
46 |
47 | ```
48 |
49 | ## What is this useful for ?
50 |
51 | - HTML emails. For a comprehensive list of supported selectors see
52 | [here](http://www.campaignmonitor.com/css/)
53 | - Embedding HTML in 3rd-party websites.
54 |
55 | ## Projects using juice
56 |
57 | * [node-email-templates][1] - Node.js module for rendering beautiful emails with [ejs][2] templates and email-friendly inline CSS using [juice][3].
58 | * [swig-email-templates][4] - Uses [swig][5], which gives you [template inheritance][6], and
59 | can generate a [dummy context][7] from a template.
60 |
61 | [1]: https://github.com/niftylettuce/node-email-templates
62 | [2]: https://github.com/visionmedia/ejs
63 | [3]: https://github.com/LearnBoost/juice
64 | [4]: https://github.com/andrewrk/swig-email-templates
65 | [5]: https://github.com/paularmstrong/swig
66 | [6]: https://docs.djangoproject.com/en/dev/topics/templates/#template-inheritance
67 | [7]: https://github.com/andrewrk/swig-dummy-context
68 |
69 | ## Documentation
70 |
71 | ### juice(filePath, [options], callback)
72 |
73 | * `filePath` - html file
74 | * `options` - (optional) object containing these properties:
75 | - `extraCss` - extra css to apply to the file. Defaults to `""`.
76 | - `applyStyleTags` - whether to inline styles in ``
77 | Defaults to `true`.
78 | - `applyLinkTags` [Deprecated, use applyLinksToStyleTags] - whether to resolve ` ` tags
79 | and inline the resulting styles into document `head`. Defaults to `true`.
80 | - `applyLinksToStyleTags` - whether to resolve ` ` tags
81 | and replace them with `style` tags. This works with `preserveMediaQueries`
82 | unlike the deprecated `applyLinkTags`. Defaults to `true`.
83 | - `removeStyleTags` - whether to remove the original ``
84 | tags after (possibly) inlining the css from them. Defaults to `true`.
85 | - `preserveMediaQueries` - preserves all media queries (and contained styles)
86 | within `` tags as a refinement when `removeStyleTags` is `true`.
87 | Other styles are removed. Defaults to `false`.
88 | - `preserveImportant` - preserves `!important` in values. Defaults to `false`.
89 | - `applyWidthAttributes` - whether to use any CSS pixel widths to create
90 | `width` attributes on elements set in `juice.widthElements`
91 | - `removeLinkTags` - whether to remove the original ` `
92 | tags after (possibly) inlining the css from them. Defaults to `true`.
93 | - `url` - how to resolve hrefs. Defaults to using `filePath`. If you want
94 | to override, be sure your `url` has the protocol at the beginning, e.g.
95 | `http://` or `file://`.
96 | * `callback(err, html)`
97 | - `err` - `Error` object or `null`.
98 | - `html` - contains the html from `filePath`, with potentially `