├── .changeset ├── README.md └── config.json ├── .editorconfig ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ └── main.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── eslint.config.mjs ├── index.js ├── lib ├── addProps.js ├── cssProperty.js ├── handleRule.js ├── inline-css.js ├── inlineContent.js ├── pseudoCheck.js ├── removeClassId.js ├── setStyleAttrs.js ├── setTableAttrs.js ├── setWidthAttrs.js └── styleSelector.js ├── package-lock.json ├── package.json ├── packages ├── css-rules │ ├── LICENSE │ ├── README.md │ ├── index.js │ ├── package.json │ └── test │ │ ├── fixtures │ │ ├── file.css │ │ └── malformed.css │ │ └── main.js ├── extract-css │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── index.js │ ├── package.json │ └── test │ │ ├── expected │ │ ├── codeblocks.css │ │ ├── codeblocks.html │ │ ├── file.css │ │ ├── malformed.css │ │ ├── malformed.html │ │ └── out.html │ │ ├── fixtures │ │ ├── codeblocks.css │ │ ├── codeblocks.html │ │ ├── file.css │ │ ├── in.html │ │ ├── malformed.css │ │ └── malformed.html │ │ └── main.js ├── href-content │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── index.js │ ├── package.json │ └── test │ │ ├── fixtures │ │ ├── file.css │ │ ├── in.html │ │ ├── malformed.css │ │ ├── malformed.html │ │ └── multiple │ │ │ ├── one │ │ │ ├── file.css │ │ │ └── in.html │ │ │ └── two │ │ │ ├── file.css │ │ │ └── in.html │ │ └── main.js ├── list-stylesheets │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── index.js │ ├── package.json │ └── test │ │ ├── expected │ │ ├── codeblocks-external.html │ │ ├── codeblocks.html │ │ ├── ejs.html │ │ └── out.html │ │ ├── fixtures │ │ ├── codeblocks-external.html │ │ ├── codeblocks.html │ │ ├── ejs.html │ │ └── in.html │ │ └── main.js ├── mediaquery-text │ ├── LICENSE │ ├── README.md │ ├── index.js │ ├── package.json │ └── test │ │ ├── expected │ │ ├── file.css │ │ └── font-face.css │ │ ├── fixtures │ │ ├── file.css │ │ └── font-face.css │ │ └── main.js ├── remote-content │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── index.js │ ├── package.json │ └── test │ │ ├── fixtures │ │ └── file.css │ │ └── main.js └── style-data │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── index.js │ ├── package.json │ └── test │ ├── expected │ ├── codeblocks-external.html │ ├── codeblocks.html │ ├── data-embed │ │ └── out.html │ ├── ejs.html │ ├── media-queries │ │ └── out.html │ ├── no-style-tag │ │ └── out.html │ └── out.html │ ├── fixtures │ ├── codeblocks-external.html │ ├── codeblocks.html │ ├── data-embed │ │ └── in.html │ ├── ejs.html │ ├── in.html │ ├── media-queries │ │ └── in.html │ └── no-style-tag │ │ └── in.html │ └── main.js ├── test ├── expected │ ├── alpha.html │ ├── cascading.html │ ├── character-entities.html │ ├── class+id.html │ ├── class.html │ ├── codeblocks-external.html │ ├── codeblocks.html │ ├── css-quotes.html │ ├── direct-descendents.html │ ├── doctype.html │ ├── ejs.html │ ├── empty.html │ ├── font-quotes.html │ ├── id.html │ ├── identical-important.html │ ├── ignore-pseudos.html │ ├── important.html │ ├── media-preserve.html │ ├── media.html │ ├── multiple │ │ ├── one │ │ │ └── out.html │ │ └── two │ │ │ └── out.html │ ├── no_css.html │ ├── normalize.html │ ├── out.html │ ├── preserve-events.html │ ├── regression-media.html │ ├── regression-selector-newline.html │ ├── remote_url.html │ ├── remove-html-selectors.html │ ├── spaces_in_path.html │ ├── specificity.html │ ├── style-preservation.html │ ├── table-attr.html │ ├── tag.html │ ├── two_styles.html │ ├── width-attr.html │ ├── xhtml.html │ └── yui-reset.html ├── fixtures │ ├── alpha.css │ ├── alpha.html │ ├── cascading.css │ ├── cascading.html │ ├── character-entities.css │ ├── character-entities.html │ ├── class+id.css │ ├── class+id.html │ ├── class.css │ ├── class.html │ ├── codeblocks-external.html │ ├── codeblocks.css │ ├── codeblocks.html │ ├── css-quotes.css │ ├── css-quotes.html │ ├── direct-descendents.css │ ├── direct-descendents.html │ ├── doctype.html │ ├── ejs.css │ ├── ejs.html │ ├── empty.css │ ├── empty.html │ ├── file.css │ ├── font-quotes.html │ ├── id.css │ ├── id.html │ ├── identical-important.css │ ├── identical-important.html │ ├── ignore-pseudos.css │ ├── ignore-pseudos.html │ ├── important.css │ ├── important.html │ ├── in.html │ ├── malformed.css │ ├── malformed.html │ ├── media-preserve.html │ ├── media.css │ ├── media.html │ ├── multiple │ │ ├── one │ │ │ ├── file.css │ │ │ └── in.html │ │ └── two │ │ │ ├── file.css │ │ │ └── in.html │ ├── no_css.html │ ├── normalize.css │ ├── normalize.html │ ├── preserve-events.css │ ├── preserve-events.html │ ├── regression-media.css │ ├── regression-media.html │ ├── regression-selector-newline.css │ ├── regression-selector-newline.html │ ├── remote_url.html │ ├── remove-html-selectors.css │ ├── remove-html-selectors.html │ ├── spaces in path │ │ └── Test.css │ ├── spaces_in_path.html │ ├── specificity.css │ ├── specificity.html │ ├── style-preservation.css │ ├── style-preservation.html │ ├── table-attr.html │ ├── tag.css │ ├── tag.html │ ├── template.ejs │ ├── two_styles.css │ ├── two_styles.html │ ├── width-attr.html │ ├── xhtml.html │ ├── yui-reset.css │ └── yui-reset.html └── main.js └── turbo.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.5/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "master", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 4 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | # trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | 23 | [{package.json,*.yml}] 24 | indent_style = space 25 | indent_size = 2 26 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "monthly" 12 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | test: 9 | name: Node.js ${{ matrix.node-version }} 10 | runs-on: ubuntu-latest 11 | 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | node-version: 16 | - 20 17 | - 18 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | - uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - run: npm install 25 | - run: npm run lint 26 | - run: npm test 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | .nyc_output 4 | .turbo 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to inline-css 2 | 3 | First, thanks for your initiative to help out! One of the reasons that open 4 | source is so great is because of the eagerness of others to help. 5 | 6 | Please take a moment to review this document in order to make the contribution 7 | process easy and effective for everyone involved. 8 | 9 | Following these guidelines helps to communicate that you respect the time of 10 | the developers managing and developing this open source project. In return, 11 | they should reciprocate that respect in addressing your issue or assessing 12 | patches and features. 13 | 14 | 15 | ## Asking a Question 16 | 17 | Before you ask, do some searching and reading. Check the docs, Google, GitHub, 18 | and StackOverflow. If your question is something that has been answered many 19 | times before, the project maintainers might be tired of repeating themselves. 20 | 21 | Whenever possible, ask your question on a public forum. This allows anyone to 22 | answer and makes the answer available for the next person with the same question. 23 | If all else fails, you might tweet at or email the maintainer(s). 24 | 25 | 26 | ## Using the issue tracker 27 | 28 | The issue tracker is the preferred channel for [bug reports](#bug-reports), 29 | [features requests](#feature-requests) and [submitting pull 30 | requests](#pull-requests), but please respect the following restrictions: 31 | 32 | * Please **do not** use the issue tracker for personal support requests (use 33 | [Stack Overflow](http://stackoverflow.com) or IRC). 34 | 35 | * Please **do not** derail or troll issues. Keep the discussion on topic and 36 | respect the opinions of others. 37 | 38 | 39 | ## Bug reports 40 | 41 | A bug is a _demonstrable problem_ that is caused by the code in the repository. 42 | Good bug reports are extremely helpful - thank you! 43 | 44 | ### Has This Been Asked Before? 45 | 46 | Before you submit a bug report, you should search existing issues. Be sure to 47 | check both currently open issues, as well as issues that are already closed. If 48 | you find an issue that seems to be similar to yours, read through it. 49 | 50 | If this issue is the same as yours, you can comment with additional information 51 | to help the maintainer debug it. Adding a comment will subscribe you to email 52 | notifications, which can be helpful in getting important updates regarding the 53 | issue. If you don't have anything to add but still want to receive email 54 | updates, you can click the "watch" button at the bottom of the comments. 55 | 56 | ### Nope, Hasn't Been Asked Before 57 | 58 | If you can't find anything in the existing issues, don't be shy about filing a 59 | new one. 60 | 61 | You should be sure to include the version the project, as well as versions of 62 | related software. For example, be sure to include the version numbers output by 63 | the commands `node --version` and `npm list`. If you notice that your installed 64 | version is not the latest, use `npm update` and confirm that the issue is still 65 | there. 66 | 67 | Please be as thorough as possible. It helps us address the problem more quickly, 68 | so everyone wins! 69 | 70 | Guidelines for bug reports: 71 | 72 | 1. **Use the GitHub issue search** — check if the issue has already been 73 | reported. 74 | 75 | 2. **Check if the issue has been fixed** — try to reproduce it using the 76 | latest `master` or development branch in the repository. 77 | 78 | 3. **Isolate the problem** — create a [reduced test 79 | case](https://css-tricks.com/reduced-test-cases/) and a live example. 80 | 81 | A good bug report shouldn't leave others needing to chase you up for more 82 | information. Please try to be as detailed as possible in your report. What is 83 | your environment? What steps will reproduce the issue? What browser(s) and OS 84 | experience the problem? What would you expect to be the outcome? All these 85 | details will help people to fix any potential bugs. 86 | 87 | Example: 88 | 89 | > Short and descriptive example bug report title 90 | > 91 | > A summary of the issue and the browser/OS environment in which it occurs. If 92 | > suitable, include the steps required to reproduce the bug. 93 | > 94 | > 1. This is the first step 95 | > 2. This is the second step 96 | > 3. Further steps, etc. 97 | > 98 | > `` - a link to the reduced test case 99 | > 100 | > Any other information you want to share that is relevant to the issue being 101 | > reported. This might include the lines of code that you have identified as 102 | > causing the bug, and potential solutions (and your opinions on their 103 | > merits). 104 | 105 | 106 | ## Feature requests 107 | 108 | Feature requests are welcome. But take a moment to find out whether your idea 109 | fits with the scope and aims of the project. It's up to *you* to make a strong 110 | case to convince the project's developers of the merits of this feature. Please 111 | provide as much detail and context as possible. 112 | 113 | 114 | ## Pull requests 115 | 116 | Good pull requests - patches, improvements, new features - are a fantastic 117 | help. They should remain focused in scope and avoid containing unrelated 118 | commits. 119 | 120 | **Please ask first** before embarking on any significant pull request (e.g. 121 | implementing features, refactoring code, porting to a different language), 122 | otherwise you risk spending a lot of time working on something that the 123 | project's developers might not want to merge into the project. 124 | 125 | Before you set out to improve the code, you should have a focused idea in mind 126 | of what you want to do. 127 | 128 | Each commit should do one thing, and each PR should be one specific improvement. 129 | 130 | Please adhere to the coding conventions used throughout a project (indentation, 131 | accurate comments, etc.) and any other requirements (such as test coverage). 132 | 133 | Follow this process if you'd like your work considered for inclusion in the 134 | project: 135 | 136 | 1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork, 137 | and configure the remotes: 138 | 139 | ```bash 140 | # Clone your fork of the repo into the current directory 141 | $ git clone https://github.com//inline-css 142 | # Navigate to the newly cloned directory 143 | $ cd inline-css 144 | # Assign the original repo to a remote called "upstream" 145 | $ git remote add upstream https://github.com/jonkemp/inline-css 146 | ``` 147 | 148 | 2. If you cloned a while ago, get the latest changes from upstream: 149 | 150 | ```bash 151 | $ git checkout 152 | $ git pull upstream 153 | ``` 154 | 155 | 3. Create a new topic branch (off the main project development branch) to 156 | contain your feature, change, or fix: 157 | 158 | ```bash 159 | $ git checkout -b 160 | ``` 161 | 162 | 4. Add relevant tests to cover the change. 163 | 164 | 5. Make sure test-suite passes: `npm test` 165 | 166 | 6. Commit your changes in logical chunks. Please adhere to these [git commit 167 | message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) 168 | or your code is unlikely be merged into the main project. Use Git's 169 | [interactive rebase](https://help.github.com/articles/interactive-rebase) 170 | feature to tidy up your commits before making them public. 171 | 172 | 7. Locally merge (or rebase) the upstream development branch into your topic branch: 173 | 174 | ```bash 175 | $ git pull [--rebase] upstream 176 | ``` 177 | 178 | 8. Push your topic branch up to your fork: 179 | 180 | ```bash 181 | $ git push origin 182 | ``` 183 | 184 | 9. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) 185 | with a clear title and description. 186 | 187 | ## Conventions of commit messages 188 | 189 | Addding files on repo 190 | 191 | ```bash 192 | $ git commit -m "Add filename" 193 | ``` 194 | 195 | Updating files on repo 196 | 197 | ```bash 198 | $ git commit -m "Update filename, filename2, filename3" 199 | ``` 200 | 201 | Removing files on repo 202 | 203 | ```bash 204 | $ git commit -m "Remove filename" 205 | ``` 206 | 207 | Renaming files on repo 208 | 209 | ```bash 210 | $ git commit -m "Rename filename" 211 | ``` 212 | 213 | Fixing errors and issues on repo 214 | 215 | ```bash 216 | $ git commit -m "Fixed #issuenumber Message about this fix" 217 | ``` 218 | 219 | Adding features on repo 220 | 221 | ```bash 222 | $ git commit -m "Add Feature: nameoffeature Message about this feature" 223 | ``` 224 | 225 | Updating features on repo 226 | 227 | ```bash 228 | $ git commit -m "Update Feature: nameoffeature Message about this update" 229 | ``` 230 | 231 | Removing features on repo 232 | 233 | ```bash 234 | $ git commit -m "Remove Feature: nameoffeature Message about this" 235 | ``` 236 | 237 | Ignoring Travis CI build on repo 238 | 239 | ```bash 240 | $ git commit -m "Commit message here [ci-skip]" 241 | ``` 242 | 243 | **IMPORTANT**: By submitting a patch, you agree to allow the project owner to 244 | license your work under the same license as that used by the project. 245 | 246 | 247 | ## Etiquette 248 | 249 | ### Assume Everyone is Doing Their Best 250 | 251 | Project maintainers are busy, so give them some time. Developers involved in 252 | open source often contribute to many projects. It's not uncommon for a developer 253 | to receive dozens of issues notifications a day, so be patient. Maybe the 254 | maintainer has other important things in their life that they need to address. 255 | Prioritizing those things over something on GitHub doesn't make someone lazy. 256 | The health, happiness, and wellbeing of the real person on the other end of the 257 | internet is much more important than any bug. 258 | 259 | One of the strengths of open source is that you can always fork and fix problems 260 | yourself. 261 | 262 | 263 | ## Conclusion 264 | 265 | Thanks for taking the time to read this! Contributions are welcome. Hopefully, 266 | this guide will help make good contributions easier and ultimately, everyone 267 | benefits. 268 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Jonathan Kemp (jonkemp.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # inline-css [![npm](http://img.shields.io/npm/v/inline-css.svg?style=flat)](https://badge.fury.io/js/inline-css) ![Build Status](https://github.com/jonkemp/inline-css/actions/workflows/main.yml/badge.svg?branch=master) [![Coverage Status](https://coveralls.io/repos/jonkemp/inline-css/badge.svg?branch=master&service=github)](https://coveralls.io/github/jonkemp/inline-css?branch=master) 2 | 3 | [![NPM](https://nodei.co/npm/inline-css.png?downloads=true)](https://nodei.co/npm/inline-css/) 4 | 5 | > Inline your CSS properties into the `style` attribute in an html file. Useful for emails. 6 | 7 | Inspired by the [juice](https://github.com/Automattic/juice) library. 8 | 9 | ## Features 10 | - Uses [cheerio](https://github.com/cheeriojs/cheerio) instead of jsdom 11 | - Works on Windows 12 | - Preserves Doctype 13 | - Modular 14 | - Gets your CSS automatically through style and link tags 15 | - Functions return [A+ compliant](https://promisesaplus.com/) Promises 16 | 17 | ## How It Works 18 | 19 | This module takes html and inlines the CSS properties into the style attribute. 20 | 21 | `/path/to/file.html`: 22 | ```html 23 | 24 | 25 | 28 | 29 | 30 | 31 |

Test

32 | 33 | 34 | ``` 35 | 36 | `style.css` 37 | ```css 38 | p { 39 | text-decoration: underline; 40 | } 41 | ``` 42 | 43 | Output: 44 | ```html 45 | 46 | 47 | 48 | 49 |

Test

50 | 51 | 52 | ``` 53 | 54 | ## What is this useful for ? 55 | 56 | - HTML emails. For a comprehensive list of supported selectors see 57 | [here](http://www.campaignmonitor.com/css/) 58 | - Embedding HTML in 3rd-party websites. 59 | - Performance. Downloading external stylesheets delays the rendering of the page in the browser. Inlining CSS speeds up this process because the browser doesn't have to wait to download an external stylesheet to start rendering the page. 60 | 61 | 62 | ## Install 63 | 64 | Install with [npm](https://npmjs.org/package/inline-css) 65 | 66 | ``` 67 | npm install --save inline-css 68 | ``` 69 | 70 | ## Usage 71 | 72 | ```js 73 | var inlineCss = require('inline-css'); 74 | var html = "
"; 75 | 76 | inlineCss(html, options) 77 | .then(function(html) { console.log(html); }); 78 | ``` 79 | 80 | ## API 81 | 82 | ### inlineCss(html, options) 83 | 84 | 85 | #### options.extraCss 86 | 87 | Type: `String` 88 | Default: `""` 89 | 90 | Extra css to apply to the file. 91 | 92 | 93 | #### options.applyStyleTags 94 | 95 | Type: `Boolean` 96 | Default: `true` 97 | 98 | Whether to inline styles in ``. 99 | 100 | 101 | #### options.applyLinkTags 102 | 103 | Type: `Boolean` 104 | Default: `true` 105 | 106 | Whether to resolve `` tags and inline the resulting styles. 107 | 108 | 109 | #### options.removeStyleTags 110 | 111 | Type: `Boolean` 112 | Default: `true` 113 | 114 | Whether to remove the original `` tags after (possibly) inlining the css from them. 115 | 116 | 117 | #### options.removeLinkTags 118 | 119 | Type: `Boolean` 120 | Default: `true` 121 | 122 | Whether to remove the original `` tags after (possibly) inlining the css from them. 123 | 124 | #### options.url 125 | 126 | Type: `String` 127 | Default: `filePath` 128 | 129 | How to resolve hrefs. **Required**. 130 | 131 | #### options.preserveMediaQueries 132 | 133 | Type: `Boolean` 134 | Default: `false` 135 | 136 | Preserves all media queries (and contained styles) within `` tags as a refinement when `removeStyleTags` is `true`. Other styles are removed. 137 | 138 | #### options.applyWidthAttributes 139 | 140 | Type: `Boolean` 141 | Default: `false` 142 | 143 | Whether to use any CSS pixel widths to create `width` attributes on elements. 144 | 145 | #### options.applyTableAttributes 146 | 147 | Type: `Boolean` 148 | Default: `false` 149 | 150 | Whether to apply the `border`, `cellpadding` and `cellspacing` attributes to `table` elements. 151 | 152 | #### options.removeHtmlSelectors 153 | 154 | Type: `Boolean` 155 | Default: `false` 156 | 157 | Whether to remove the `class` and `id` attributes from the markup. 158 | 159 | #### options.codeBlocks 160 | 161 | Type: `Object` 162 | Default: `{ EJS: { start: '<%', end: '%>' }, HBS: { start: '{{', end: '}}' } }` 163 | 164 | An object where each value has a `start` and `end` to specify fenced code blocks that should be ignored during parsing and inlining. For example, Handlebars (hbs) templates are `HBS: {start: '{{', end: '}}'}`. `codeBlocks` can fix problems where otherwise inline-css might interpret code like `<=` as HTML, when it is meant to be template language code. Note that `codeBlocks` is a dictionary which can contain many different code blocks, so don't do `codeBlocks: {...}` do `codeBlocks.myBlock = {...}`. 165 | 166 | ### Special markup 167 | 168 | #### data-embed 169 | 170 | When a data-embed attribute is present on a tag, inline-css will not inline the styles and will not remove the tags. 171 | 172 | This can be used to embed email client support hacks that rely on css selectors into your email templates. 173 | 174 | ### cheerio options 175 | 176 | Options to passed to [cheerio](https://github.com/cheeriojs/cheerio). 177 | 178 | ## Contributing 179 | 180 | See the [CONTRIBUTING Guidelines](https://github.com/jonkemp/inline-css/blob/master/CONTRIBUTING.md) 181 | 182 | ## License 183 | 184 | MIT © [Jonathan Kemp](http://jonkemp.com) 185 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from 'globals'; 2 | import js from '@eslint/js'; 3 | 4 | /** @type {import('eslint').Linter.Config[]} */ 5 | export default [ 6 | { 7 | ...js.configs.recommended, 8 | }, 9 | { 10 | languageOptions: { 11 | ecmaVersion: 'latest', 12 | sourceType: 'module' 13 | }, 14 | rules: { 15 | "block-scoped-var": 2, 16 | "complexity": [ 17 | 1, 18 | 5 19 | ], 20 | "consistent-return": 1, 21 | "curly": [ 22 | 2, 23 | "all" 24 | ], 25 | "dot-notation": 2, 26 | "eqeqeq": 2, 27 | "no-caller": 2, 28 | "no-else-return": 2, 29 | "no-eq-null": 2, 30 | "no-extend-native": 2, 31 | "no-implicit-coercion": 2, 32 | "no-invalid-this": 2, 33 | "no-loop-func": 2, 34 | "no-multi-spaces": 2, 35 | "no-multi-str": 2, 36 | "no-new-func": 2, 37 | "no-new-wrappers": 2, 38 | "no-new": 2, 39 | "no-param-reassign": 2, 40 | "no-unused-expressions": 2, 41 | "no-useless-call": 2, 42 | "no-useless-concat": 2, 43 | "no-with": 2, 44 | "vars-on-top": 2, 45 | "wrap-iife": [2, "any"], 46 | "yoda": [ 47 | 2, 48 | "never" 49 | ], 50 | 51 | "no-shadow": 2, 52 | "no-use-before-define": 2, 53 | 54 | "callback-return": 2, 55 | "handle-callback-err": 2, 56 | "no-path-concat": 2, 57 | 58 | "array-bracket-spacing": [ 59 | 1, 60 | "always", { 61 | "objectsInArrays": false 62 | } 63 | ], 64 | "block-spacing": 1, 65 | "brace-style": 1, 66 | "camelcase": [ 67 | 1, { 68 | "properties": "never" 69 | } 70 | ], 71 | "comma-spacing": [ 72 | 1, { 73 | "before": false, 74 | "after": true 75 | } 76 | ], 77 | "comma-style": [ 78 | 1, 79 | "last" 80 | ], 81 | "computed-property-spacing": [ 82 | 1, 83 | "never" 84 | ], 85 | "consistent-this": [ 86 | 1, 87 | "self" 88 | ], 89 | "eol-last": 1, 90 | "indent": [ 91 | 2, 92 | 4, { 93 | "SwitchCase": 1 94 | } 95 | ], 96 | "key-spacing": [ 97 | 1, { 98 | "beforeColon": false, 99 | "afterColon": true 100 | } 101 | ], 102 | "keyword-spacing": 1, 103 | "linebreak-style": [ 104 | 1, 105 | "unix" 106 | ], 107 | "lines-around-comment": [ 108 | 1, { 109 | "allowBlockStart": true, 110 | "beforeBlockComment": true, 111 | "beforeLineComment": true 112 | } 113 | ], 114 | "max-depth": [ 115 | 1, 116 | 4 117 | ], 118 | "max-params": [ 119 | 1, 120 | 3 121 | ], 122 | "max-statements": [ 123 | 0, 124 | 10 125 | ], 126 | "new-cap": 2, 127 | "new-parens": 2, 128 | "padding-line-between-statements": [ 129 | "error", 130 | { "blankLine": "always", "prev": ["const", "let", "var"], "next": "*"}, 131 | { "blankLine": "any", "prev": ["const", "let", "var"], "next": ["const", "let", "var"]} 132 | ], 133 | "no-array-constructor": 2, 134 | "no-bitwise": 1, 135 | "no-lonely-if": 1, 136 | "no-multiple-empty-lines": 1, 137 | "no-new-object": 2, 138 | "no-spaced-func": 1, 139 | "no-trailing-spaces": 1, 140 | "no-unneeded-ternary": 1, 141 | "object-curly-spacing": [ 142 | 1, 143 | "always" 144 | ], 145 | "operator-assignment": [ 146 | 1, 147 | "always" 148 | ], 149 | "operator-linebreak": [ 150 | 1, 151 | "after" 152 | ], 153 | "quote-props": [ 154 | 1, 155 | "as-needed" 156 | ], 157 | "quotes": [ 158 | 1, 159 | "single" 160 | ], 161 | "semi": [ 162 | 2, 163 | "always" 164 | ], 165 | "semi-spacing": [ 166 | 1, { 167 | "before": false, 168 | "after": true 169 | } 170 | ], 171 | "space-before-blocks": [ 172 | 1, 173 | "always" 174 | ], 175 | "space-before-function-paren": [1, { "anonymous": "always", "named": "never" }], 176 | "space-in-parens": [1, "never"], 177 | "space-infix-ops": 1, 178 | "space-unary-ops": 0, 179 | "spaced-comment": [1, "always", { "line": { "exceptions": ["-"] } }] 180 | } 181 | }, 182 | { 183 | languageOptions: { 184 | globals: { 185 | ...globals.browser, 186 | ...globals.node, 187 | ...globals.mocha, 188 | } 189 | } 190 | }, 191 | ]; 192 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const inlineContent = require('./lib/inlineContent'); 2 | 3 | module.exports = (html, options) => new Promise((resolve, reject) => { 4 | const opt = Object.assign({}, { 5 | extraCss: '', 6 | applyStyleTags: true, 7 | removeStyleTags: true, 8 | applyLinkTags: true, 9 | removeLinkTags: true, 10 | preserveMediaQueries: false, 11 | removeHtmlSelectors: false, 12 | applyWidthAttributes: false, 13 | applyTableAttributes: false, 14 | codeBlocks: { 15 | EJS: { start: '<%', end: '%>' }, 16 | HBS: { start: '{{', end: '}}' } 17 | }, 18 | xmlMode: false, 19 | decodeEntities: false, 20 | lowerCaseTags: true, 21 | lowerCaseAttributeNames: false, 22 | recognizeCDATA: false, 23 | recognizeSelfClosing: false 24 | }, options); 25 | 26 | inlineContent(String(html), opt) 27 | .then(data => { 28 | resolve(data); 29 | }) 30 | .catch(err => { 31 | reject(err); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /lib/addProps.js: -------------------------------------------------------------------------------- 1 | const cssSelector = require('./styleSelector'); 2 | const importantSelector = cssSelector('', [ 2, 0, 0, 0 ]); 3 | const property = require('./cssProperty'); 4 | 5 | function getProperty(style, name, selector) { 6 | const value = style[name]; 7 | const sel = style._importants[name] ? importantSelector : selector; 8 | 9 | return property(name, value, sel); 10 | } 11 | 12 | // go through the properties 13 | module.exports = ({ styleProps }, style, selector) => { 14 | let i; 15 | const l = style.length; 16 | let name; 17 | let prop; 18 | let existing; 19 | let winner; 20 | 21 | for (i = 0; i < l; i++) { 22 | name = style[i]; 23 | prop = getProperty(style, name, selector); 24 | existing = styleProps[name]; 25 | 26 | if (existing) { 27 | winner = existing.compare(prop); 28 | 29 | if (winner === prop) { 30 | styleProps[name] = prop; 31 | } 32 | } else { 33 | styleProps[name] = prop; 34 | } 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /lib/cssProperty.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Compares two specificity vectors, returning the winning one. 3 | * 4 | * @param {Array} vector a 5 | * @param {Array} vector b 6 | * @return {Array} 7 | * @api public 8 | */ 9 | 10 | function compareSpecificity(a, b) { 11 | let i; 12 | 13 | for (i = 0; i < 4; i++) { 14 | if (a[i] === b[i]) { 15 | continue; 16 | } 17 | if (a[i] > b[i]) { 18 | return a; 19 | } 20 | return b; 21 | } 22 | 23 | return b; 24 | } 25 | 26 | /** 27 | * CSS property constructor. 28 | * 29 | * @param {String} property 30 | * @param {String} value 31 | * @param {Selector} selector the property originates from 32 | * @api public 33 | */ 34 | 35 | module.exports = (prop, value, selector) => { 36 | let o = {}; 37 | 38 | /** 39 | * Compares with another Property based on Selector#specificity. 40 | * 41 | * @api public 42 | */ 43 | 44 | const compare = property => { 45 | const a = selector.specificity(); 46 | const b = property.selector.specificity(); 47 | const winner = compareSpecificity(a, b); 48 | 49 | if (winner === a && a !== b) { 50 | return o; 51 | } 52 | return property; 53 | }; 54 | 55 | o = { 56 | prop, 57 | value, 58 | selector, 59 | compare 60 | }; 61 | 62 | return o; 63 | }; 64 | -------------------------------------------------------------------------------- /lib/handleRule.js: -------------------------------------------------------------------------------- 1 | const cssSelector = require('./styleSelector'); 2 | const parseCSS = require('css-rules'); 3 | const styleSelector = cssSelector('`. 43 | 44 | 45 | #### options.applyLinkTags 46 | 47 | Type: `Boolean` 48 | 49 | Whether to resolve `` tags and inline the resulting styles. 50 | 51 | 52 | #### options.removeStyleTags 53 | 54 | Type: `Boolean` 55 | 56 | Whether to remove the original `` tags after (possibly) inlining the css from them. 57 | 58 | 59 | #### options.removeLinkTags 60 | 61 | Type: `Boolean` 62 | 63 | Whether to remove the original `` tags after (possibly) inlining the css from them. 64 | 65 | 66 | #### options.url 67 | 68 | Type: `String` 69 | 70 | How to resolve hrefs. Required. 71 | 72 | #### options.preserveMediaQueries 73 | 74 | Type: `Boolean` 75 | 76 | Preserves all media queries (and contained styles) within `` tags as a refinement when `removeStyleTags` is `true`. Other styles are removed. 77 | 78 | #### options.codeBlocks 79 | 80 | Type: `Object` 81 | Default: `{ EJS: { start: '<%', end: '%>' }, HBS: { start: '{{', end: '}}' } }` 82 | 83 | An object where each value has a `start` and `end` to specify fenced code blocks that should be ignored during parsing. For example, Handlebars (hbs) templates are `HBS: {start: '{{', end: '}}'}`. Note that `codeBlocks` is a dictionary which can contain many different code blocks, so don't do `codeBlocks: {...}` do `codeBlocks.myBlock = {...}`. 84 | 85 | ### Special markup 86 | 87 | #### data-embed 88 | 89 | When a data-embed attribute is present on a tag, extract-css will not inline the styles and will not remove the tags. 90 | 91 | This can be used to embed email client support hacks that rely on css selectors into your email templates. 92 | 93 | ## Credit 94 | 95 | The code for this module was originally taken from the [Juice](https://github.com/Automattic/juice) library. 96 | 97 | ## License 98 | 99 | MIT © [Jonathan Kemp](http://jonkemp.com) 100 | -------------------------------------------------------------------------------- /packages/extract-css/index.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const async = require('async'); 3 | const getStylesData = require('style-data'); 4 | const getStylesheetList = require('list-stylesheets'); 5 | const getHrefContent = require('href-content'); 6 | 7 | module.exports = (html, options, callback) => { 8 | const tasks = []; 9 | const data = getStylesheetList(html, options); 10 | 11 | tasks.push(cb => { 12 | getStylesData(data.html, options, cb); 13 | }); 14 | if (data.hrefs.length) { 15 | assert.ok(options.url, 'options.url is required'); 16 | } 17 | data.hrefs.forEach(stylesheetHref => { 18 | tasks.push(cb => { 19 | getHrefContent(stylesheetHref, options.url, cb); 20 | }); 21 | }); 22 | 23 | async.parallel(tasks, (err, results) => { 24 | let stylesData; 25 | let css; 26 | 27 | if (err) { 28 | return callback(err); 29 | } 30 | 31 | stylesData = results.shift(); 32 | 33 | results.forEach(content => { 34 | stylesData.css.push(content); 35 | }); 36 | css = stylesData.css.join('\n'); 37 | 38 | return callback(null, stylesData.html, css); 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /packages/extract-css/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "extract-css", 3 | "version": "3.0.2", 4 | "description": "Extract the CSS from an HTML document.", 5 | "main": "index.js", 6 | "files": [ 7 | "index.js" 8 | ], 9 | "repository": "https://github.com/jonkemp/inline-css/tree/master/packages/extract-css", 10 | "keywords": [ 11 | "css" 12 | ], 13 | "author": "Jonathan Kemp (http://jonkemp.com/)", 14 | "license": "MIT", 15 | "homepage": "https://github.com/jonkemp/inline-css/tree/master/packages/extract-css", 16 | "dependencies": { 17 | "async": "^3.2.6", 18 | "href-content": "^2.0.3", 19 | "list-stylesheets": "^2.0.1", 20 | "style-data": "^2.0.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/extract-css/test/expected/codeblocks.css: -------------------------------------------------------------------------------- 1 | body, 2 | p { 3 | color: red; 4 | } 5 | -------------------------------------------------------------------------------- /packages/extract-css/test/expected/codeblocks.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{#if `age <= 40`}}

Young

{{/if}} 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/extract-css/test/expected/file.css: -------------------------------------------------------------------------------- 1 | 2 | h1 { 3 | border: 1px solid #ccc; 4 | } 5 | 6 | body { 7 | font-family: Arial; 8 | } 9 | h1 { 10 | color: blue; 11 | } 12 | .headline { 13 | font-size: 24px; 14 | } 15 | 16 | td { 17 | padding: 5px; 18 | } 19 | -------------------------------------------------------------------------------- /packages/extract-css/test/expected/malformed.css: -------------------------------------------------------------------------------- 1 | body 2 | padding: 0; 3 | } 4 | -------------------------------------------------------------------------------- /packages/extract-css/test/expected/malformed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/extract-css/test/expected/out.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

Hi

9 | 10 | 11 | 12 | 13 | 14 | 15 |
Some Headline
16 | 17 | 18 | -------------------------------------------------------------------------------- /packages/extract-css/test/fixtures/codeblocks.css: -------------------------------------------------------------------------------- 1 | body, 2 | p { 3 | color: red; 4 | } 5 | -------------------------------------------------------------------------------- /packages/extract-css/test/fixtures/codeblocks.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{#if `age <= 40`}}

Young

{{/if}} 4 | -------------------------------------------------------------------------------- /packages/extract-css/test/fixtures/file.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial; 3 | } 4 | h1 { 5 | color: blue; 6 | } 7 | .headline { 8 | font-size: 24px; 9 | } 10 | 11 | td { 12 | padding: 5px; 13 | } 14 | -------------------------------------------------------------------------------- /packages/extract-css/test/fixtures/in.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 |

Hi

13 | 14 | 15 | 16 | 17 |
Some Headline
18 | -------------------------------------------------------------------------------- /packages/extract-css/test/fixtures/malformed.css: -------------------------------------------------------------------------------- 1 | body 2 | padding: 0; 3 | } 4 | -------------------------------------------------------------------------------- /packages/extract-css/test/fixtures/malformed.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/extract-css/test/main.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | const should = require('should'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const Vinyl = require('vinyl'); 7 | const beautify = require('js-beautify').html; 8 | const extractCss = require('../index'); 9 | 10 | function getFile(filePath) { 11 | return new Vinyl({ 12 | path: path.resolve(filePath), 13 | cwd: './test/', 14 | base: path.dirname(filePath), 15 | contents: Buffer.from(String(fs.readFileSync(filePath))) 16 | }); 17 | } 18 | 19 | function compare(fixturePath, expectedHTML, expectedCSS, options, done) { 20 | const file = getFile(fixturePath); 21 | 22 | options.url = `file://${file.path}`; 23 | 24 | extractCss(file.contents.toString('utf8'), options, (err, html, css) => { 25 | beautify(html).should.be.equal(beautify(String(fs.readFileSync(expectedHTML)))); 26 | css.should.be.equal(String(fs.readFileSync(expectedCSS))); 27 | 28 | done(); 29 | }); 30 | } 31 | 32 | describe('extract-css', () => { 33 | it('Should separate css and html', done => { 34 | const options = { 35 | applyStyleTags: true, 36 | removeStyleTags: true, 37 | applyLinkTags: true, 38 | removeLinkTags: true, 39 | preserveMediaQueries: false 40 | }; 41 | compare( 42 | path.join(__dirname, 'fixtures', 'in.html'), 43 | path.join(__dirname, 'expected', 'out.html'), 44 | path.join(__dirname, 'expected', 'file.css'), 45 | options, 46 | done 47 | ); 48 | }); 49 | 50 | it('Should handle malformed CSS', done => { 51 | const options = { 52 | applyStyleTags: true, 53 | removeStyleTags: true, 54 | applyLinkTags: true, 55 | removeLinkTags: true, 56 | preserveMediaQueries: false 57 | }; 58 | compare( 59 | path.join(__dirname, 'fixtures', 'malformed.html'), 60 | path.join(__dirname, 'expected', 'malformed.html'), 61 | path.join(__dirname, 'expected', 'malformed.css'), 62 | options, 63 | done 64 | ); 65 | }); 66 | 67 | it('Should ignore code blocks', done => { 68 | const options = { 69 | applyStyleTags: true, 70 | removeStyleTags: true, 71 | applyLinkTags: true, 72 | removeLinkTags: true, 73 | preserveMediaQueries: false 74 | }; 75 | compare( 76 | path.join(__dirname, 'fixtures', 'codeblocks.html'), 77 | path.join(__dirname, 'expected', 'codeblocks.html'), 78 | path.join(__dirname, 'expected', 'codeblocks.css'), 79 | options, 80 | done 81 | ); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /packages/href-content/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # href-content 2 | 3 | ## 2.0.3 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies 8 | - remote-content@4.0.0 9 | -------------------------------------------------------------------------------- /packages/href-content/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Jonathan Kemp (jonkemp.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /packages/href-content/README.md: -------------------------------------------------------------------------------- 1 | # href-content 2 | 3 | [![NPM](https://nodei.co/npm/href-content.png?downloads=true)](https://nodei.co/npm/href-content/) 4 | 5 | > Get content from link tags in an HTML document. 6 | 7 | ## Install 8 | 9 | Install with [npm](https://npmjs.org/package/href-content) 10 | 11 | ``` 12 | npm install --save href-content 13 | ``` 14 | 15 | ## Usage 16 | 17 | ```js 18 | var getHrefContent = require('href-content'); 19 | 20 | getHrefContent('path/to/css', 'file://path/to/html', function (err, css) { 21 | console.log(css); 22 | }); 23 | ``` 24 | 25 | ## Credit 26 | 27 | The code for this module was originally taken from the [Juice](https://github.com/Automattic/juice) library. 28 | 29 | ## License 30 | 31 | MIT 32 | -------------------------------------------------------------------------------- /packages/href-content/index.js: -------------------------------------------------------------------------------- 1 | const url = require('url'); 2 | const fs = require('fs'); 3 | const getRemoteContent = require('remote-content'); 4 | 5 | module.exports = (destHref, sourceHref, callback) => { 6 | let resolvedUrl; 7 | let parsedUrl; 8 | let toUrl = destHref; 9 | 10 | if (url.parse(sourceHref).protocol === 'file:' && destHref[0] === '/') { 11 | toUrl = destHref.slice(1); 12 | } 13 | resolvedUrl = url.resolve(sourceHref, toUrl); 14 | parsedUrl = url.parse(resolvedUrl); 15 | if (parsedUrl.protocol === 'file:') { 16 | fs.readFile(decodeURIComponent(parsedUrl.pathname), 'utf8', callback); 17 | } else { 18 | getRemoteContent(resolvedUrl, callback); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /packages/href-content/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "href-content", 3 | "version": "2.0.3", 4 | "description": "Get content from link tags in an HTML document.", 5 | "main": "index.js", 6 | "files": [ 7 | "index.js" 8 | ], 9 | "repository": "https://github.com/jonkemp/inline-css/tree/master/packages/href-content", 10 | "keywords": [ 11 | "inline", 12 | "css", 13 | "html", 14 | "email" 15 | ], 16 | "author": "Jonathan Kemp (http://jonkemp.com/)", 17 | "license": "MIT", 18 | "homepage": "https://github.com/jonkemp/inline-css/tree/master/packages/href-content", 19 | "dependencies": { 20 | "remote-content": "^4.0.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/href-content/test/fixtures/file.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial; 3 | } 4 | h1 { 5 | color: blue; 6 | } 7 | .headline { 8 | font-size: 24px; 9 | } 10 | 11 | td { 12 | padding: 5px; 13 | } 14 | -------------------------------------------------------------------------------- /packages/href-content/test/fixtures/in.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 |

Hi

13 | 14 | 15 | 16 | 17 |
Some Headline
18 | 19 | 20 | -------------------------------------------------------------------------------- /packages/href-content/test/fixtures/malformed.css: -------------------------------------------------------------------------------- 1 | body 2 | padding: 0; 3 | } 4 | -------------------------------------------------------------------------------- /packages/href-content/test/fixtures/malformed.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/href-content/test/fixtures/multiple/one/file.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial; 3 | } 4 | h1 { 5 | color: blue; 6 | } 7 | .headline { 8 | font-size: 24px; 9 | } 10 | 11 | td { 12 | padding: 5px; 13 | } 14 | -------------------------------------------------------------------------------- /packages/href-content/test/fixtures/multiple/one/in.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 |

Hi

13 | 14 | 15 | 16 | 17 |
Some Headline
18 | 19 | 20 | -------------------------------------------------------------------------------- /packages/href-content/test/fixtures/multiple/two/file.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial; 3 | } 4 | h1 { 5 | color: red; 6 | } 7 | .headline { 8 | font-size: 24px; 9 | } 10 | 11 | td { 12 | padding: 5px; 13 | } 14 | -------------------------------------------------------------------------------- /packages/href-content/test/fixtures/multiple/two/in.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 |

Hi

13 | 14 | 15 | 16 | 17 |
Some Headline
18 | 19 | 20 | -------------------------------------------------------------------------------- /packages/href-content/test/main.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | const should = require('should'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const Vinyl = require('vinyl'); 7 | const getStylesheetList = require('list-stylesheets'); 8 | const getHrefContent = require('../index'); 9 | 10 | function getFile(filePath) { 11 | return new Vinyl({ 12 | path: path.resolve(filePath), 13 | cwd: './test/', 14 | base: path.dirname(filePath), 15 | contents: Buffer.from(String(fs.readFileSync(filePath))) 16 | }); 17 | } 18 | 19 | function compare(fixturePath, expectedPath, options, done) { 20 | const file = getFile(fixturePath); 21 | const data = getStylesheetList(file.contents.toString('utf8'), options); 22 | 23 | const url = `file://${file.path}`; 24 | 25 | getHrefContent(data.hrefs[0], url, (err, css) => { 26 | css.should.be.equal(String(fs.readFileSync(expectedPath))); 27 | 28 | done(); 29 | }); 30 | } 31 | 32 | describe('href-content', () => { 33 | it('Should get content from link tags in an HTML document', done => { 34 | const options = { 35 | applyLinkTags: true, 36 | removeLinkTags: true 37 | }; 38 | compare(path.join('test', 'fixtures', 'in.html'), path.join('test', 'fixtures', 'file.css'), options, done); 39 | }); 40 | 41 | it('Should get content from link tags in in multiple HTML files', done => { 42 | const options = { 43 | applyLinkTags: true, 44 | removeLinkTags: true 45 | }; 46 | compare(path.join('test', 'fixtures', 'multiple', 'one', 'in.html'), path.join('test', 'fixtures', 'multiple', 'one', 'file.css'), options, () => {}); 47 | compare(path.join('test', 'fixtures', 'multiple', 'two', 'in.html'), path.join('test', 'fixtures', 'multiple', 'two', 'file.css'), options, done); 48 | }); 49 | 50 | it('Should handle malformed CSS', done => { 51 | const options = { 52 | applyLinkTags: true, 53 | removeLinkTags: true 54 | }; 55 | compare(path.join('test', 'fixtures', 'malformed.html'), path.join('test', 'fixtures', 'malformed.css'), options, done); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /packages/list-stylesheets/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # list-stylesheets 2 | 3 | ## 2.0.2 4 | 5 | ### Patch Changes 6 | 7 | - Update dependencies 8 | -------------------------------------------------------------------------------- /packages/list-stylesheets/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Jonathan Kemp (jonkemp.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /packages/list-stylesheets/README.md: -------------------------------------------------------------------------------- 1 | # list-stylesheets 2 | 3 | [![NPM](https://nodei.co/npm/list-stylesheets.png?downloads=true)](https://nodei.co/npm/list-stylesheets/) 4 | 5 | > Get a list of stylesheets from an HTML document. 6 | 7 | ## Install 8 | 9 | Install with [npm](https://npmjs.org/package/list-stylesheets) 10 | 11 | ``` 12 | npm install --save list-stylesheets 13 | ``` 14 | 15 | ## Usage 16 | 17 | ```js 18 | var getStylesheetList = require('list-stylesheets'); 19 | 20 | var data = getStylesheetList(html, options); 21 | 22 | console.log(data.html); 23 | console.log(data.hrefs); 24 | ``` 25 | 26 | ## API 27 | 28 | ### getStylesheetList(html, options) 29 | 30 | #### options.applyLinkTags 31 | 32 | Type: `Boolean` 33 | Default: `true` 34 | 35 | Whether to resolve `` tags and inline the resulting styles. 36 | 37 | #### options.removeLinkTags 38 | 39 | Type: `Boolean` 40 | Default: `true` 41 | 42 | Whether to remove the original `` tags after (possibly) inlining the css from them. 43 | 44 | #### options.codeBlocks 45 | 46 | Type: `Object` 47 | Default: `{ EJS: { start: '<%', end: '%>' }, HBS: { start: '{{', end: '}}' } }` 48 | 49 | An object where each value has a `start` and `end` to specify fenced code blocks that should be ignored during parsing. For example, Handlebars (hbs) templates are `HBS: {start: '{{', end: '}}'}`. Note that `codeBlocks` is a dictionary which can contain many different code blocks, so don't do `codeBlocks: {...}` do `codeBlocks.myBlock = {...}`. 50 | 51 | ### cheerio options 52 | 53 | Options to passed to [cheerio](https://github.com/cheeriojs/cheerio). 54 | 55 | ## Credit 56 | 57 | The code for this module was originally taken from the [Juice](https://github.com/Automattic/juice) library. 58 | 59 | ## License 60 | 61 | MIT © [Jonathan Kemp](http://jonkemp.com) 62 | -------------------------------------------------------------------------------- /packages/list-stylesheets/index.js: -------------------------------------------------------------------------------- 1 | const cheerio = require('cheerio'); 2 | const pick = require('pick-util'); 3 | 4 | function replaceCodeBlock(html, re, block) { 5 | return html.replace(re, () => block); 6 | } 7 | 8 | module.exports = (html, options) => { 9 | const results = {}; 10 | const codeBlocks = { 11 | EJS: { start: '<%', end: '%>' }, 12 | HBS: { start: '{{', end: '}}' } 13 | }; 14 | const codeBlockLookup = []; 15 | const encodeCodeBlocks = _html => { 16 | let __html = _html; 17 | const blocks = Object.assign(codeBlocks, options.codeBlocks); 18 | 19 | Object.keys(blocks).forEach(key => { 20 | const re = new RegExp(blocks[key].start + '([\\S\\s]*?)' + blocks[key].end, 'g'); 21 | 22 | __html = __html.replace(re, match => { 23 | codeBlockLookup.push(match); 24 | return 'EXCS_CODE_BLOCK_' + (codeBlockLookup.length - 1) + '_'; 25 | }); 26 | }); 27 | return __html; 28 | }; 29 | const decodeCodeBlocks = _html => { 30 | let index; 31 | let re; 32 | let __html = _html; 33 | 34 | for (index = 0; index < codeBlockLookup.length; index++) { 35 | re = new RegExp('EXCS_CODE_BLOCK_' + index + '_(="")?', 'gi'); 36 | __html = replaceCodeBlock(__html, re, codeBlockLookup[index]); 37 | } 38 | return __html; 39 | }; 40 | const encodeEntities = _html => encodeCodeBlocks(_html); 41 | const decodeEntities = _html => decodeCodeBlocks(_html); 42 | let $; 43 | 44 | $ = cheerio.load(encodeEntities(html), Object.assign({ 45 | decodeEntities: false 46 | }, pick(options, [ 47 | 'xmlMode', 48 | 'decodeEntities', 49 | 'lowerCaseTags', 50 | 'lowerCaseAttributeNames', 51 | 'recognizeCDATA', 52 | 'recognizeSelfClosing' 53 | ]))); 54 | 55 | results.hrefs = []; 56 | 57 | $('link').each((index, element) => { 58 | const $el = $(element); 59 | 60 | if ($el.attr('rel') && $el.attr('rel').toLowerCase() === 'stylesheet') { 61 | if (options.applyLinkTags) { 62 | results.hrefs.push($el.attr('href')); 63 | } 64 | if (options.removeLinkTags) { 65 | $el.remove(); 66 | } 67 | } 68 | }); 69 | 70 | results.html = decodeEntities($.html()); 71 | 72 | return results; 73 | }; 74 | -------------------------------------------------------------------------------- /packages/list-stylesheets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "list-stylesheets", 3 | "version": "2.0.2", 4 | "description": "Get a list of stylesheets from an HTML document.", 5 | "main": "index.js", 6 | "files": [ 7 | "index.js" 8 | ], 9 | "repository": "https://github.com/jonkemp/inline-css/tree/master/packages/list-stylesheets", 10 | "keywords": [ 11 | "css" 12 | ], 13 | "author": "Jonathan Kemp (http://jonkemp.com/)", 14 | "license": "MIT", 15 | "homepage": "https://github.com/jonkemp/inline-css/tree/master/packages/list-stylesheets", 16 | "dependencies": { 17 | "cheerio": "1.0.0", 18 | "pick-util": "^1.1.5" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/list-stylesheets/test/expected/codeblocks-external.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

<>

4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/list-stylesheets/test/expected/codeblocks.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{#if `age <= 40`}}

Young

{{/if}} 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/list-stylesheets/test/expected/ejs.html: -------------------------------------------------------------------------------- 1 | 2 | <%= config.sitePrefix %>verify?grant=<%= grant._id %>&secret=<%= grant.secret %> 3 |
>
4 | 5 | -------------------------------------------------------------------------------- /packages/list-stylesheets/test/expected/out.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 |

Hi

11 | 12 | 13 | 14 | 15 |
Some Headline
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/list-stylesheets/test/fixtures/codeblocks-external.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

<>

5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/list-stylesheets/test/fixtures/codeblocks.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{#if `age <= 40`}}

Young

{{/if}} 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/list-stylesheets/test/fixtures/ejs.html: -------------------------------------------------------------------------------- 1 | 2 | <%= config.sitePrefix %>verify?grant=<%= grant._id %>&secret=<%= grant.secret %> 3 |
>
4 | 5 | -------------------------------------------------------------------------------- /packages/list-stylesheets/test/fixtures/in.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 |

Hi

13 | 14 | 15 | 16 | 17 |
Some Headline
18 | 19 | 20 | -------------------------------------------------------------------------------- /packages/list-stylesheets/test/main.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | const should = require('should'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const Vinyl = require('vinyl'); 7 | const beautify = require('js-beautify').html; 8 | const getStylesheetList = require('../index'); 9 | 10 | function getFile(filePath) { 11 | return new Vinyl({ 12 | path: path.resolve(filePath), 13 | cwd: './test/', 14 | base: path.dirname(filePath), 15 | contents: Buffer.from(String(fs.readFileSync(filePath))) 16 | }); 17 | } 18 | 19 | describe('list-stylesheets', () => { 20 | it('Should list stylesheets from an html file', done => { 21 | const options = { 22 | applyLinkTags: true, 23 | removeLinkTags: true 24 | }; 25 | 26 | function compare(fixturePath, expectedHTML) { 27 | const file = getFile(fixturePath); 28 | const data = getStylesheetList(file.contents.toString('utf8'), options); 29 | 30 | data.hrefs[0].should.be.equal('file.css'); 31 | beautify(data.html).should.be.equal( 32 | beautify(String(fs.readFileSync(expectedHTML))) 33 | ); 34 | done(); 35 | } 36 | 37 | compare( 38 | path.join(__dirname, 'fixtures', 'in.html'), 39 | path.join(__dirname, 'expected', 'out.html'), 40 | options, 41 | done 42 | ); 43 | }); 44 | 45 | it('Should ignore hbs code blocks', done => { 46 | const options = { 47 | applyLinkTags: true, 48 | removeLinkTags: true 49 | }; 50 | 51 | function compare(fixturePath, expectedHTML) { 52 | const file = getFile(fixturePath); 53 | const data = getStylesheetList(file.contents.toString('utf8'), options); 54 | 55 | data.hrefs[0].should.be.equal('codeblocks.css'); 56 | data.html.replace(/(\r\n|\n|\r)/gm, "") 57 | .should.be.equal( 58 | String(fs.readFileSync(expectedHTML)).replace(/(\r\n|\n|\r)/gm, "")); 59 | done(); 60 | } 61 | 62 | compare( 63 | path.join(__dirname, 'fixtures', 'codeblocks.html'), 64 | path.join(__dirname, 'expected', 'codeblocks.html'), 65 | options, 66 | done 67 | ); 68 | }); 69 | 70 | it('Should ignore ejs code blocks', done => { 71 | const options = { 72 | applyLinkTags: true, 73 | removeLinkTags: true 74 | }; 75 | 76 | function compare(fixturePath, expectedHTML) { 77 | const file = getFile(fixturePath); 78 | const data = getStylesheetList(file.contents.toString('utf8'), options); 79 | 80 | data.hrefs[0].should.be.equal('ejs.css'); 81 | data.html.replace(/(\r\n|\n|\r)/gm, "") 82 | .should.be.equal( 83 | String(fs.readFileSync(expectedHTML)).replace(/(\r\n|\n|\r)/gm, "")); 84 | done(); 85 | } 86 | 87 | compare( 88 | path.join(__dirname, 'fixtures', 'ejs.html'), 89 | path.join(__dirname, 'expected', 'ejs.html'), 90 | options, 91 | done 92 | ); 93 | }); 94 | 95 | it('Should ignore user defined code blocks', done => { 96 | const options = { 97 | codeBlocks: { 98 | craze: { start: '<<', end: '>>' } 99 | } 100 | }; 101 | 102 | function compare(fixturePath, expectedHTML) { 103 | const file = getFile(fixturePath); 104 | const data = getStylesheetList(file.contents.toString('utf8'), options); 105 | 106 | data.html.replace(/(\r\n|\n|\r)/gm, "") 107 | .should.be.equal( 108 | String(fs.readFileSync(expectedHTML)).replace(/(\r\n|\n|\r)/gm, "")); 109 | done(); 110 | } 111 | 112 | compare( 113 | path.join(__dirname, 'fixtures', 'codeblocks-external.html'), 114 | path.join(__dirname, 'expected', 'codeblocks-external.html'), 115 | options, 116 | done 117 | ); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /packages/mediaquery-text/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Jonathan Kemp (jonkemp.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /packages/mediaquery-text/README.md: -------------------------------------------------------------------------------- 1 | # mediaquery-text 2 | 3 | [![NPM](https://nodei.co/npm/mediaquery-text.png?downloads=true)](https://nodei.co/npm/mediaquery-text/) 4 | 5 | > Returns Media Query text for a CSS source. 6 | 7 | ## Install 8 | 9 | Install with [npm](https://npmjs.org/package/mediaquery-text) 10 | 11 | ``` 12 | npm install --save mediaquery-text 13 | ``` 14 | 15 | ## Usage 16 | 17 | ```js 18 | var mediaQueryText = require('mediaquery-text'); 19 | 20 | var mediaQueries = mediaQueryText(css); 21 | 22 | console.log(mediaQueries); // @media only screen and (min-width: 35em) {} 23 | ``` 24 | 25 | ## Credit 26 | 27 | The code for this module was originally taken from the [Juice](https://github.com/Automattic/juice) library. 28 | 29 | ## License 30 | 31 | MIT 32 | -------------------------------------------------------------------------------- /packages/mediaquery-text/index.js: -------------------------------------------------------------------------------- 1 | const cssom = require('cssom'); 2 | const os = require('os'); 3 | const CSSFontFaceRule = require('cssom/lib/CSSFontFaceRule').CSSFontFaceRule; 4 | 5 | /** 6 | * Returns Media Query text for a CSS source. 7 | * 8 | * @param {String} css source 9 | * @api public 10 | */ 11 | 12 | module.exports = css => { 13 | const rules = cssom.parse(css).cssRules || []; 14 | const queries = []; 15 | let queryMedia; 16 | let queryString; 17 | let style; 18 | let property; 19 | let value; 20 | let important; 21 | let result; 22 | 23 | rules.forEach(({ type, media, cssRules }) => { 24 | /* CSS types 25 | STYLE: 1, 26 | IMPORT: 3, 27 | MEDIA: 4, 28 | FONT_FACE: 5, 29 | */ 30 | 31 | if (type === cssom.CSSMediaRule.prototype.type) { 32 | queryMedia = Array.prototype.slice.call(media).join(', '); 33 | queryString = []; 34 | 35 | queryString.push(`${os.EOL}@media ${queryMedia} {`); 36 | 37 | cssRules.forEach(rule => { 38 | if (rule.type === cssom.CSSStyleRule.prototype.type || rule.type === CSSFontFaceRule.prototype.type) { 39 | queryString.push(` ${rule.type === cssom.CSSStyleRule.prototype.type ? rule.selectorText : '@font-face'} {`); 40 | 41 | for (style = 0; style < rule.style.length; style++) { 42 | property = rule.style[style]; 43 | value = rule.style[property]; 44 | important = rule.style._importants[property] ? ' !important' : ''; 45 | queryString.push(` ${property}: ${value}${important};`); 46 | } 47 | queryString.push(' }'); 48 | } 49 | }); 50 | 51 | queryString.push('}'); 52 | result = queryString.length ? queryString.join(os.EOL) + os.EOL : ''; 53 | 54 | queries.push(result); 55 | } 56 | }); 57 | 58 | return queries.join(os.EOL); 59 | }; 60 | -------------------------------------------------------------------------------- /packages/mediaquery-text/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mediaquery-text", 3 | "version": "1.2.0", 4 | "description": "Returns Media Query text for a CSS source.", 5 | "main": "index.js", 6 | "files": [ 7 | "index.js" 8 | ], 9 | "repository": "https://github.com/jonkemp/inline-css/tree/master/packages/mediaquery-text", 10 | "keywords": [ 11 | "mediaqueries", 12 | "css", 13 | "styles", 14 | "inline" 15 | ], 16 | "author": "Jonathan Kemp (http://jonkemp.com/)", 17 | "license": "MIT", 18 | "homepage": "https://github.com/jonkemp/inline-css/tree/master/packages/mediaquery-text", 19 | "dependencies": { 20 | "cssom": "^0.5.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/mediaquery-text/test/expected/file.css: -------------------------------------------------------------------------------- 1 | 2 | @media only screen and (min-width: 35em) { 3 | } 4 | 5 | 6 | @media print, (-o-min-device-pixel-ratio: 5/4), (-webkit-min-device-pixel-ratio: 1.25), (min-resolution: 120dpi) { 7 | } 8 | 9 | 10 | @media print { 11 | *, 12 | *:before, 13 | *:after { 14 | background: transparent !important; 15 | color: #000 !important; 16 | box-shadow: none !important; 17 | text-shadow: none !important; 18 | } 19 | a, 20 | a:visited { 21 | text-decoration: underline; 22 | } 23 | a[href]:after { 24 | content: " (" attr(href) ")"; 25 | } 26 | abbr[title]:after { 27 | content: " (" attr(title) ")"; 28 | } 29 | a[href^="#"]:after, 30 | a[href^="javascript:"]:after { 31 | content: ""; 32 | } 33 | pre, 34 | blockquote { 35 | border: 1px solid #999; 36 | page-break-inside: avoid; 37 | } 38 | thead { 39 | display: table-header-group; 40 | } 41 | tr, 42 | img { 43 | page-break-inside: avoid; 44 | } 45 | img { 46 | max-width: 100% !important; 47 | } 48 | p, 49 | h2, 50 | h3 { 51 | orphans: 3; 52 | widows: 3; 53 | } 54 | h2, 55 | h3 { 56 | page-break-after: avoid; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/mediaquery-text/test/expected/font-face.css: -------------------------------------------------------------------------------- 1 | 2 | @media screen { 3 | a { 4 | color: blue !important; 5 | background: red; 6 | } 7 | @font-face { 8 | font-family: 'Arial2'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/mediaquery-text/test/fixtures/file.css: -------------------------------------------------------------------------------- 1 | /* 2 | * What follows is the result of much research on cross-browser styling. 3 | * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal, 4 | * Kroc Camen, and the H5BP dev community and team. 5 | */ 6 | 7 | /* ========================================================================== 8 | Base styles: opinionated defaults 9 | ========================================================================== */ 10 | 11 | html { 12 | color: #222; 13 | font-size: 1em; 14 | line-height: 1.4; 15 | } 16 | 17 | /* 18 | * Remove text-shadow in selection highlight: 19 | * https://twitter.com/miketaylr/status/12228805301 20 | * 21 | * These selection rule sets have to be separate. 22 | * Customize the background color to match your design. 23 | */ 24 | 25 | ::-moz-selection { 26 | background: #b3d4fc; 27 | text-shadow: none; 28 | } 29 | 30 | ::selection { 31 | background: #b3d4fc; 32 | text-shadow: none; 33 | } 34 | 35 | /* 36 | * A better looking default horizontal rule 37 | */ 38 | 39 | hr { 40 | display: block; 41 | height: 1px; 42 | border: 0; 43 | border-top: 1px solid #ccc; 44 | margin: 1em 0; 45 | padding: 0; 46 | } 47 | 48 | /* 49 | * Remove the gap between audio, canvas, iframes, 50 | * images, videos and the bottom of their containers: 51 | * https://github.com/h5bp/html5-boilerplate/issues/440 52 | */ 53 | 54 | audio, 55 | canvas, 56 | iframe, 57 | img, 58 | svg, 59 | video { 60 | vertical-align: middle; 61 | } 62 | 63 | /* 64 | * Remove default fieldset styles. 65 | */ 66 | 67 | fieldset { 68 | border: 0; 69 | margin: 0; 70 | padding: 0; 71 | } 72 | 73 | /* 74 | * Allow only vertical resizing of textareas. 75 | */ 76 | 77 | textarea { 78 | resize: vertical; 79 | } 80 | 81 | /* ========================================================================== 82 | Browser Upgrade Prompt 83 | ========================================================================== */ 84 | 85 | .browserupgrade { 86 | margin: 0.2em 0; 87 | background: #ccc; 88 | color: #000; 89 | padding: 0.2em 0; 90 | } 91 | 92 | /* ========================================================================== 93 | Author's custom styles 94 | ========================================================================== */ 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | /* ========================================================================== 113 | Helper classes 114 | ========================================================================== */ 115 | 116 | /* 117 | * Hide visually and from screen readers: 118 | * http://juicystudio.com/article/screen-readers-display-none.php 119 | */ 120 | 121 | .hidden { 122 | display: none !important; 123 | visibility: hidden; 124 | } 125 | 126 | /* 127 | * Hide only visually, but have it available for screen readers: 128 | * http://snook.ca/archives/html_and_css/hiding-content-for-accessibility 129 | */ 130 | 131 | .visuallyhidden { 132 | border: 0; 133 | clip: rect(0 0 0 0); 134 | height: 1px; 135 | margin: -1px; 136 | overflow: hidden; 137 | padding: 0; 138 | position: absolute; 139 | width: 1px; 140 | } 141 | 142 | /* 143 | * Extends the .visuallyhidden class to allow the element 144 | * to be focusable when navigated to via the keyboard: 145 | * https://www.drupal.org/node/897638 146 | */ 147 | 148 | .visuallyhidden.focusable:active, 149 | .visuallyhidden.focusable:focus { 150 | clip: auto; 151 | height: auto; 152 | margin: 0; 153 | overflow: visible; 154 | position: static; 155 | width: auto; 156 | } 157 | 158 | /* 159 | * Hide visually and from screen readers, but maintain layout 160 | */ 161 | 162 | .invisible { 163 | visibility: hidden; 164 | } 165 | 166 | /* 167 | * Clearfix: contain floats 168 | * 169 | * For modern browsers 170 | * 1. The space content is one way to avoid an Opera bug when the 171 | * `contenteditable` attribute is included anywhere else in the document. 172 | * Otherwise it causes space to appear at the top and bottom of elements 173 | * that receive the `clearfix` class. 174 | * 2. The use of `table` rather than `block` is only necessary if using 175 | * `:before` to contain the top-margins of child elements. 176 | */ 177 | 178 | .clearfix:before, 179 | .clearfix:after { 180 | content: " "; /* 1 */ 181 | display: table; /* 2 */ 182 | } 183 | 184 | .clearfix:after { 185 | clear: both; 186 | } 187 | 188 | /* ========================================================================== 189 | EXAMPLE Media Queries for Responsive Design. 190 | These examples override the primary ('mobile first') styles. 191 | Modify as content requires. 192 | ========================================================================== */ 193 | 194 | @media only screen and (min-width: 35em) { 195 | /* Style adjustments for viewports that meet the condition */ 196 | } 197 | 198 | @media print, 199 | (-o-min-device-pixel-ratio: 5/4), 200 | (-webkit-min-device-pixel-ratio: 1.25), 201 | (min-resolution: 120dpi) { 202 | /* Style adjustments for high resolution devices */ 203 | } 204 | 205 | /* ========================================================================== 206 | Print styles. 207 | Inlined to avoid the additional HTTP request: 208 | http://www.phpied.com/delay-loading-your-print-css/ 209 | ========================================================================== */ 210 | 211 | @media print { 212 | *, 213 | *:before, 214 | *:after { 215 | background: transparent !important; 216 | color: #000 !important; /* Black prints faster: 217 | http://www.sanbeiji.com/archives/953 */ 218 | box-shadow: none !important; 219 | text-shadow: none !important; 220 | } 221 | 222 | a, 223 | a:visited { 224 | text-decoration: underline; 225 | } 226 | 227 | a[href]:after { 228 | content: " (" attr(href) ")"; 229 | } 230 | 231 | abbr[title]:after { 232 | content: " (" attr(title) ")"; 233 | } 234 | 235 | /* 236 | * Don't show links that are fragment identifiers, 237 | * or use the `javascript:` pseudo protocol 238 | */ 239 | 240 | a[href^="#"]:after, 241 | a[href^="javascript:"]:after { 242 | content: ""; 243 | } 244 | 245 | pre, 246 | blockquote { 247 | border: 1px solid #999; 248 | page-break-inside: avoid; 249 | } 250 | 251 | /* 252 | * Printing Tables: 253 | * http://css-discuss.incutio.com/wiki/Printing_Tables 254 | */ 255 | 256 | thead { 257 | display: table-header-group; 258 | } 259 | 260 | tr, 261 | img { 262 | page-break-inside: avoid; 263 | } 264 | 265 | img { 266 | max-width: 100% !important; 267 | } 268 | 269 | p, 270 | h2, 271 | h3 { 272 | orphans: 3; 273 | widows: 3; 274 | } 275 | 276 | h2, 277 | h3 { 278 | page-break-after: avoid; 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /packages/mediaquery-text/test/fixtures/font-face.css: -------------------------------------------------------------------------------- 1 | p { 2 | color: blue; 3 | } 4 | @media screen { 5 | a { 6 | color: blue !important; 7 | background: red; 8 | } 9 | @font-face { 10 | font-family: 'Arial2'; 11 | } 12 | } 13 | @font-face { 14 | font-family: 'Tahoma2'; 15 | } 16 | -------------------------------------------------------------------------------- /packages/mediaquery-text/test/main.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable */ 2 | 3 | const should = require('should'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const mediaQueryText = require('../index'); 7 | 8 | describe('mediaquery-text', () => { 9 | it('Should return Media Query text for a CSS source', done => { 10 | const mediaQueries = mediaQueryText( 11 | String(fs.readFileSync(path.join(__dirname, 'fixtures', 'file.css'))) 12 | ); 13 | mediaQueries.should.be.equal( 14 | String(fs.readFileSync(path.join(__dirname, 'expected', 'file.css'))) 15 | ); 16 | done(); 17 | }); 18 | 19 | it('Should return Media Query text for a CSS source with @font-face', done => { 20 | const mediaQueries = mediaQueryText( 21 | String(fs.readFileSync(path.join(__dirname, 'fixtures', 'font-face.css'))) 22 | ); 23 | mediaQueries.should.be.equal( 24 | String(fs.readFileSync(path.join(__dirname, 'expected', 'font-face.css'))) 25 | ); 26 | done(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/remote-content/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # remote-content 2 | 3 | ## 4.0.1 4 | 5 | ### Patch Changes 6 | 7 | - Update dependencies 8 | 9 | ## 4.0.0 10 | 11 | ### Major Changes 12 | 13 | - Migrate from superagent to axios 14 | -------------------------------------------------------------------------------- /packages/remote-content/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Jonathan Kemp (jonkemp.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /packages/remote-content/README.md: -------------------------------------------------------------------------------- 1 | # remote-content 2 | 3 | [![NPM](https://nodei.co/npm/remote-content.png?downloads=true)](https://nodei.co/npm/remote-content/) 4 | 5 | > Get remote content. 6 | 7 | ## Install 8 | 9 | Install with [npm](https://npmjs.org/package/remote-content) 10 | 11 | ``` 12 | npm install --save remote-content 13 | ``` 14 | 15 | ## Usage 16 | 17 | ```js 18 | var getRemoteContent = require('remote-content'); 19 | 20 | getRemoteContent(remotePath, function (err, contents) { 21 | console.log(contents); 22 | }); 23 | ``` 24 | 25 | ## Credit 26 | 27 | The code for this module was originally taken from the [Juice](https://github.com/Automattic/juice) library. 28 | 29 | ## License 30 | 31 | MIT 32 | -------------------------------------------------------------------------------- /packages/remote-content/index.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const getProxyForUrl = require('proxy-from-env').getProxyForUrl; 3 | 4 | module.exports = (remoteUrl, callback) => { 5 | const axiosConfig = {}; 6 | const proxyUrl = getProxyForUrl(remoteUrl); 7 | 8 | if (proxyUrl) { 9 | const proxyUrlData = new URL(proxyUrl); 10 | 11 | axiosConfig.proxy = { 12 | protocol: proxyUrlData.protocol.replace(':', ''), 13 | host: proxyUrlData.hostname, 14 | port: proxyUrlData.port, 15 | }; 16 | } 17 | 18 | axios.get(remoteUrl, axiosConfig).then(response => { 19 | if (response.statusText === 'OK') { 20 | return callback(null, response.data); 21 | } 22 | 23 | return callback(new Error(`GET ${remoteUrl} ${response.status}`)); 24 | }).catch(error => { 25 | callback(error); 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /packages/remote-content/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remote-content", 3 | "version": "4.0.1", 4 | "description": "Get remote content.", 5 | "main": "index.js", 6 | "files": [ 7 | "index.js" 8 | ], 9 | "repository": "https://github.com/jonkemp/inline-css/tree/master/packages/remote-content", 10 | "keywords": [ 11 | "axios", 12 | "remote", 13 | "content", 14 | "http", 15 | "ajax", 16 | "request", 17 | "agent" 18 | ], 19 | "author": "Jonathan Kemp (http://jonkemp.com/)", 20 | "license": "MIT", 21 | "homepage": "https://github.com/jonkemp/inline-css/tree/master/packages/remote-content", 22 | "dependencies": { 23 | "axios": "^1.7.9", 24 | "proxy-from-env": "^1.1.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/remote-content/test/fixtures/file.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial; 3 | } 4 | h1 { 5 | color: blue; 6 | } 7 | .headline { 8 | font-size: 24px; 9 | } 10 | 11 | td { 12 | padding: 5px; 13 | } 14 | -------------------------------------------------------------------------------- /packages/remote-content/test/main.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable */ 2 | 3 | const should = require('should'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const getRemoteContent = require('../index'); 7 | 8 | function compare(remotePath, fixturePath, done) { 9 | getRemoteContent(remotePath, (err, css) => { 10 | css.should.be.equal(String(fs.readFileSync(fixturePath))); 11 | 12 | done(); 13 | }); 14 | } 15 | 16 | describe('remote-content', function() { 17 | this.timeout(15000); 18 | 19 | it('Should get remote content from link tags in an HTML document', done => { 20 | compare('https://raw.githubusercontent.com/jonkemp/remote-content/master/test/fixtures/file.css', path.join('test', 'fixtures', 'file.css'), done); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /packages/style-data/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # style-data 2 | 3 | ## 2.0.2 4 | 5 | ### Patch Changes 6 | 7 | - Update dependencies 8 | -------------------------------------------------------------------------------- /packages/style-data/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Jonathan Kemp (jonkemp.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /packages/style-data/README.md: -------------------------------------------------------------------------------- 1 | # style-data 2 | 3 | [![NPM](https://nodei.co/npm/style-data.png?downloads=true)](https://nodei.co/npm/style-data/) 4 | 5 | > Get the content of style tags. 6 | 7 | ## Install 8 | 9 | Install with [npm](https://npmjs.org/package/style-data) 10 | 11 | ``` 12 | npm install --save style-data 13 | ``` 14 | 15 | ## Usage 16 | 17 | ```js 18 | var getStylesData = require('style-data'); 19 | 20 | getStylesData(html, options, function (err, results) { 21 | console.log(results.html); // resulting html 22 | console.log(results.css); // array of css rules 23 | }); 24 | ``` 25 | 26 | ## API 27 | 28 | ### getStylesData(html, options, callback) 29 | 30 | #### options.applyStyleTags 31 | 32 | Type: `Boolean` 33 | Default: `true` 34 | 35 | Whether to inline styles in ``. 36 | 37 | #### options.removeStyleTags 38 | 39 | Type: `Boolean` 40 | Default: `true` 41 | 42 | Whether to remove the original `` tags after (possibly) inlining the css from them. 43 | 44 | #### options.preserveMediaQueries 45 | 46 | Type: `Boolean` 47 | Default: `false` 48 | 49 | Preserves all media queries (and contained styles) within `` tags as a refinement when `removeStyleTags` is `true`. Other styles are removed. 50 | 51 | #### options.codeBlocks 52 | 53 | Type: `Object` 54 | Default: `{ EJS: { start: '<%', end: '%>' }, HBS: { start: '{{', end: '}}' } }` 55 | 56 | An object where each value has a `start` and `end` to specify fenced code blocks that should be ignored during parsing. For example, Handlebars (hbs) templates are `HBS: {start: '{{', end: '}}'}`. Note that `codeBlocks` is a dictionary which can contain many different code blocks, so don't do `codeBlocks: {...}` do `codeBlocks.myBlock = {...}`. 57 | 58 | ### Special markup 59 | 60 | #### data-embed 61 | 62 | When a data-embed attribute is present on a tag, style-data will not inline the styles and will not remove the tags. 63 | 64 | This can be used to embed email client support hacks that rely on css selectors into your email templates. 65 | 66 | ### cheerio options 67 | 68 | Options to passed to [cheerio](https://github.com/cheeriojs/cheerio). 69 | 70 | ## Credit 71 | 72 | The code for this module was originally taken from the [Juice](https://github.com/Automattic/juice) library. 73 | 74 | ## License 75 | 76 | MIT © [Jonathan Kemp](http://jonkemp.com) 77 | -------------------------------------------------------------------------------- /packages/style-data/index.js: -------------------------------------------------------------------------------- 1 | const mediaQueryText = require('mediaquery-text'); 2 | const cheerio = require('cheerio'); 3 | const pick = require('pick-util'); 4 | 5 | function replaceCodeBlock(html, re, block) { 6 | return html.replace(re, () => block); 7 | } 8 | 9 | module.exports = (html, options, callback) => { 10 | const results = {}; 11 | 12 | const codeBlocks = { 13 | EJS: { start: '<%', end: '%>' }, 14 | HBS: { start: '{{', end: '}}' } 15 | }; 16 | 17 | const codeBlockLookup = []; 18 | 19 | const encodeCodeBlocks = _html => { 20 | let __html = _html; 21 | const blocks = Object.assign(codeBlocks, options.codeBlocks); 22 | 23 | Object.keys(blocks).forEach(key => { 24 | const re = new RegExp(`${blocks[key].start}([\\S\\s]*?)${blocks[key].end}`, 'g'); 25 | 26 | __html = __html.replace(re, match => { 27 | codeBlockLookup.push(match); 28 | return `EXCS_CODE_BLOCK_${codeBlockLookup.length - 1}_`; 29 | }); 30 | }); 31 | return __html; 32 | }; 33 | 34 | const decodeCodeBlocks = _html => { 35 | let index; 36 | let re; 37 | let __html = _html; 38 | 39 | for (index = 0; index < codeBlockLookup.length; index++) { 40 | re = new RegExp(`EXCS_CODE_BLOCK_${index}_(="")?`, 'gi'); 41 | __html = replaceCodeBlock(__html, re, codeBlockLookup[index]); 42 | } 43 | return __html; 44 | }; 45 | 46 | const encodeEntities = _html => encodeCodeBlocks(_html); 47 | const decodeEntities = _html => decodeCodeBlocks(_html); 48 | let $; 49 | let styleDataList; 50 | let styleData; 51 | 52 | $ = cheerio.load(encodeEntities(html), Object.assign({ 53 | decodeEntities: false 54 | }, pick(options, [ 55 | 'xmlMode', 56 | 'decodeEntities', 57 | 'lowerCaseTags', 58 | 'lowerCaseAttributeNames', 59 | 'recognizeCDATA', 60 | 'recognizeSelfClosing' 61 | ]))); 62 | 63 | results.css = []; 64 | 65 | $('style').each((index, element) => { 66 | let mediaQueries; 67 | 68 | // if data-embed property exists, skip inlining and removing 69 | if (typeof $(element).data('embed') !== 'undefined') { 70 | return; 71 | } 72 | 73 | styleDataList = element.childNodes; 74 | if (styleDataList.length !== 1) { 75 | callback(new Error('empty style element')); 76 | return; 77 | } 78 | styleData = styleDataList[0].data; 79 | if (options.applyStyleTags) { 80 | results.css.push(styleData); 81 | } 82 | if (options.removeStyleTags) { 83 | if (options.preserveMediaQueries) { 84 | mediaQueries = mediaQueryText(element.childNodes[0].nodeValue); 85 | element.childNodes[0].nodeValue = mediaQueries; 86 | } 87 | if (!mediaQueries) { 88 | $(element).remove(); 89 | } 90 | } 91 | }); 92 | 93 | results.html = decodeEntities($.html()); 94 | 95 | callback(null, results); 96 | }; 97 | -------------------------------------------------------------------------------- /packages/style-data/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "style-data", 3 | "version": "2.0.2", 4 | "description": "Get the content of style tags.", 5 | "main": "index.js", 6 | "files": [ 7 | "index.js" 8 | ], 9 | "repository": "https://github.com/jonkemp/inline-css/tree/master/packages/style-data", 10 | "keywords": [ 11 | "inline", 12 | "css", 13 | "html", 14 | "email" 15 | ], 16 | "author": "Jonathan Kemp (http://jonkemp.com/)", 17 | "license": "MIT", 18 | "homepage": "https://github.com/jonkemp/inline-css/tree/master/packages/style-data", 19 | "dependencies": { 20 | "cheerio": "^1.0.0", 21 | "mediaquery-text": "^1.2.0", 22 | "pick-util": "^1.1.5" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/style-data/test/expected/codeblocks-external.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

<>

4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/style-data/test/expected/codeblocks.html: -------------------------------------------------------------------------------- 1 | 2 | {{#if `age <= 40`}}

Young

{{/if}} 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/style-data/test/expected/data-embed/out.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 15 | 20 | 25 | 30 |

Hi

31 | 32 | 33 | 34 | 35 |
Some Headline
36 | 37 | 38 | -------------------------------------------------------------------------------- /packages/style-data/test/expected/ejs.html: -------------------------------------------------------------------------------- 1 | 2 | <%= config.sitePrefix %>verify?grant=<%= grant._id %>&secret=<%= grant.secret %> 3 |
>
4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/style-data/test/expected/media-queries/out.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 |

Hi

14 | 15 | 16 | 17 | 18 |
Some Headline
19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/style-data/test/expected/no-style-tag/out.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Hi

6 | 7 | 8 | 9 | 10 |
Some Headline
11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/style-data/test/expected/out.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Hi

6 | 7 | 8 | 9 | 10 |
Some Headline
11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/style-data/test/fixtures/codeblocks-external.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

<>

5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/style-data/test/fixtures/codeblocks.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{#if `age <= 40`}}

Young

{{/if}} 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/style-data/test/fixtures/data-embed/in.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 17 | 22 | 27 | 32 | 37 |

Hi

38 | 39 | 40 | 41 | 42 |
Some Headline
43 | 44 | 45 | -------------------------------------------------------------------------------- /packages/style-data/test/fixtures/ejs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%= config.sitePrefix %>verify?grant=<%= grant._id %>&secret=<%= grant.secret %> 4 |
>
5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/style-data/test/fixtures/in.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 |

Hi

13 | 14 | 15 | 16 | 17 |
Some Headline
18 | 19 | 20 | -------------------------------------------------------------------------------- /packages/style-data/test/fixtures/media-queries/in.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 19 |

Hi

20 | 21 | 22 | 23 | 24 |
Some Headline
25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/style-data/test/fixtures/no-style-tag/in.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Hi

8 | 9 | 10 | 11 | 12 |
Some Headline
13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/style-data/test/main.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable */ 2 | 3 | const should = require('should'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const Vinyl = require('vinyl'); 7 | const beautify = require('js-beautify').html; 8 | const getStylesData = require('../index'); 9 | 10 | function getFile(filePath) { 11 | return new Vinyl({ 12 | path: path.resolve(filePath), 13 | cwd: './test/', 14 | base: path.dirname(filePath), 15 | contents: Buffer.from(String(fs.readFileSync(filePath))) 16 | }); 17 | } 18 | 19 | function compare(fixturePath, expectedHTML, css, options, done) { 20 | const file = getFile(fixturePath); 21 | 22 | getStylesData(file.contents.toString('utf8'), options, (err, results) => { 23 | beautify(results.html, { 24 | "preserve-newlines": false 25 | }).should.be.equal( 26 | beautify(String(fs.readFileSync(expectedHTML)), { 27 | "preserve-newlines": false 28 | }) 29 | ); 30 | should.deepEqual(results.css, css); 31 | done(); 32 | }); 33 | } 34 | 35 | describe('style-data', () => { 36 | it('Should separate css and html', done => { 37 | const options = { 38 | applyStyleTags: true, 39 | removeStyleTags: true, 40 | preserveMediaQueries: false 41 | }; 42 | 43 | compare( 44 | path.join(__dirname, 'fixtures', 'in.html'), 45 | path.join(__dirname, 'expected', 'out.html'), 46 | [ '\n h1 {\n border: 1px solid #ccc;\n }\n ' ], 47 | options, 48 | done 49 | ); 50 | }); 51 | 52 | it('Should leave html from no style html', done => { 53 | const options = { 54 | applyStyleTags: true, 55 | removeStyleTags: true, 56 | preserveMediaQueries: false 57 | }; 58 | 59 | compare( 60 | path.join(__dirname, 'fixtures', 'no-style-tag', 'in.html'), 61 | path.join(__dirname, 'expected', 'no-style-tag', 'out.html'), 62 | [], 63 | options, 64 | done 65 | ); 66 | }); 67 | 68 | it('Should leave style blocks if they contain media queries', done => { 69 | const options = { 70 | applyStyleTags: true, 71 | removeStyleTags: true, 72 | preserveMediaQueries: true 73 | }; 74 | 75 | compare( 76 | path.join(__dirname, 'fixtures', 'media-queries', 'in.html'), 77 | path.join(__dirname, 'expected', 'media-queries', 'out.html'), 78 | [ '\n h1 {\n border: 1px solid #ccc;\n }\n ', '\n @media only screen and (min-width: 640px) {\n .headline {\n color: blue;\n }\n }\n ' ], 79 | options, 80 | done 81 | ); 82 | }); 83 | 84 | it('Should ignore hbs code blocks', done => { 85 | const options = { 86 | applyStyleTags: true, 87 | removeStyleTags: true, 88 | preserveMediaQueries: true 89 | }; 90 | 91 | compare( 92 | path.join(__dirname, 'fixtures', 'codeblocks.html'), 93 | path.join(__dirname, 'expected', 'codeblocks.html'), 94 | [], 95 | options, 96 | done 97 | ); 98 | }); 99 | 100 | it('Should ignore ejs code blocks', done => { 101 | const options = { 102 | applyStyleTags: true, 103 | removeStyleTags: true, 104 | preserveMediaQueries: true 105 | }; 106 | 107 | compare( 108 | path.join(__dirname, 'fixtures', 'ejs.html'), 109 | path.join(__dirname, 'expected', 'ejs.html'), 110 | [], 111 | options, 112 | done 113 | ); 114 | }); 115 | 116 | it('Should ignore user defined code blocks', done => { 117 | const options = { 118 | codeBlocks: { 119 | craze: { start: '<<', end: '>>' } 120 | } 121 | }; 122 | 123 | compare( 124 | path.join(__dirname, 'fixtures', 'codeblocks-external.html'), 125 | path.join(__dirname, 'expected', 'codeblocks-external.html'), 126 | [], 127 | options, 128 | done 129 | ); 130 | }); 131 | 132 | it('Should ignore style blocks if data-embed attribute is present on them', done => { 133 | const options = { 134 | applyStyleTags: true, 135 | removeStyleTags: true, 136 | preserveMediaQueries: true 137 | }; 138 | 139 | compare( 140 | path.join(__dirname, 'fixtures', 'data-embed', 'in.html'), 141 | path.join(__dirname, 'expected', 'data-embed', 'out.html'), 142 | [ '\n h1 {\n border: 1px solid #ccc;\n }\n ' ], 143 | options, 144 | done 145 | ); 146 | }); 147 | }); 148 | -------------------------------------------------------------------------------- /test/expected/alpha.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /test/expected/cascading.html: -------------------------------------------------------------------------------- 1 | 2 |
    3 |
  • 4 |
      5 |
    • 6 |
        7 |
      • 8 |
      9 |
    • 10 |
    11 |
  • 12 |
13 | 14 | -------------------------------------------------------------------------------- /test/expected/character-entities.html: -------------------------------------------------------------------------------- 1 | 2 |

Dear <insert name>,

3 | 4 | -------------------------------------------------------------------------------- /test/expected/class+id.html: -------------------------------------------------------------------------------- 1 | 2 |

Woot

3 | 4 | -------------------------------------------------------------------------------- /test/expected/class.html: -------------------------------------------------------------------------------- 1 | 2 |
Test
3 |
Test
4 | 5 | -------------------------------------------------------------------------------- /test/expected/codeblocks-external.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

<>

5 | 6 | 7 | -------------------------------------------------------------------------------- /test/expected/codeblocks.html: -------------------------------------------------------------------------------- 1 | 2 | {{#if `age <= 40`}}

Young

{{/if}} 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/expected/css-quotes.html: -------------------------------------------------------------------------------- 1 | 2 |

woot

3 | 4 | -------------------------------------------------------------------------------- /test/expected/direct-descendents.html: -------------------------------------------------------------------------------- 1 | 2 |

woot

3 | 4 | -------------------------------------------------------------------------------- /test/expected/doctype.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

hello

6 | 7 | 8 | -------------------------------------------------------------------------------- /test/expected/ejs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%= config.sitePrefix %>verify?grant=<%= grant._id %>&secret=<%= grant.secret %> 4 |
>
5 | 6 | -------------------------------------------------------------------------------- /test/expected/empty.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /test/expected/font-quotes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

hi

6 |

there

7 | 8 | 9 | -------------------------------------------------------------------------------- /test/expected/id.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /test/expected/identical-important.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /test/expected/ignore-pseudos.html: -------------------------------------------------------------------------------- 1 | 2 | Test 3 | 4 | -------------------------------------------------------------------------------- /test/expected/important.html: -------------------------------------------------------------------------------- 1 | 2 | woot 3 | 4 | -------------------------------------------------------------------------------- /test/expected/media-preserve.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 |

hi

17 | 18 | 19 | -------------------------------------------------------------------------------- /test/expected/media.html: -------------------------------------------------------------------------------- 1 | 2 |

hi

3 | 4 | 5 | -------------------------------------------------------------------------------- /test/expected/multiple/one/out.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

Hi

9 | 10 | 11 | 12 | 13 |
Some Headline
14 | 15 | 16 | -------------------------------------------------------------------------------- /test/expected/multiple/two/out.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

Hi

9 | 10 | 11 | 12 | 13 |
Some Headline
14 | 15 | 16 | -------------------------------------------------------------------------------- /test/expected/no_css.html: -------------------------------------------------------------------------------- 1 |

hi

2 | 3 | 4 | -------------------------------------------------------------------------------- /test/expected/normalize.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

normalize.css

5 | 6 | 7 | -------------------------------------------------------------------------------- /test/expected/out.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Hi

5 | 6 | 7 | 8 | 9 |
Some Headline
10 | 11 | 12 | -------------------------------------------------------------------------------- /test/expected/preserve-events.html: -------------------------------------------------------------------------------- 1 | 2 | Google 3 |

Google

4 | 5 | -------------------------------------------------------------------------------- /test/expected/regression-media.html: -------------------------------------------------------------------------------- 1 | Test 2 |

Test

3 | 4 | -------------------------------------------------------------------------------- /test/expected/regression-selector-newline.html: -------------------------------------------------------------------------------- 1 | 2 | Test 3 |

Test

4 | 5 | -------------------------------------------------------------------------------- /test/expected/remote_url.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

hello

5 | 6 | 7 | -------------------------------------------------------------------------------- /test/expected/remove-html-selectors.html: -------------------------------------------------------------------------------- 1 | 2 |
Test
3 |
Test
4 | 5 | -------------------------------------------------------------------------------- /test/expected/spaces_in_path.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

hello, you are my favorite person

5 | 6 | 7 | -------------------------------------------------------------------------------- /test/expected/specificity.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |
6 |
7 | 8 | -------------------------------------------------------------------------------- /test/expected/style-preservation.html: -------------------------------------------------------------------------------- 1 | 2 |

The color should stay intact

3 | 4 | -------------------------------------------------------------------------------- /test/expected/table-attr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 |
7 | 8 | 9 | 12 | 13 |
10 | empty 11 |
14 |
17 | 18 | 19 | 22 | 23 |
20 | empty 21 |
24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 34 | 37 | 38 | 39 | 40 | 41 | 44 | 45 | 46 |
28 | th 29 |
35 | td 36 |
42 | td 43 |
47 | 48 | 49 | -------------------------------------------------------------------------------- /test/expected/tag.html: -------------------------------------------------------------------------------- 1 | 2 |

Test

3 | 4 | -------------------------------------------------------------------------------- /test/expected/two_styles.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

hello, you are my favorite person

5 | 6 | 7 | -------------------------------------------------------------------------------- /test/expected/width-attr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

none

6 | 7 | 8 | 11 | 12 |
9 | wide 10 |
13 | wide 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/expected/xhtml.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/expected/yui-reset.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

hello

5 | 6 | 7 | -------------------------------------------------------------------------------- /test/fixtures/alpha.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 0; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/alpha.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/fixtures/cascading.css: -------------------------------------------------------------------------------- 1 | ul li { 2 | display: block; 3 | } 4 | li { 5 | color: #f00; 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/cascading.html: -------------------------------------------------------------------------------- 1 | 2 |
    3 |
  • 4 |
      5 |
    • 6 |
        7 |
      • 8 |
      9 |
    • 10 |
    11 |
  • 12 |
13 | 14 | -------------------------------------------------------------------------------- /test/fixtures/character-entities.css: -------------------------------------------------------------------------------- 1 | p { 2 | margin: 0 0 12px 0; 3 | margin-bottom: 12px; 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/character-entities.html: -------------------------------------------------------------------------------- 1 | 2 |

Dear <insert name>,

3 | 4 | -------------------------------------------------------------------------------- /test/fixtures/class+id.css: -------------------------------------------------------------------------------- 1 | #test { 2 | border: 1px solid #f00; 3 | } 4 | .woot { 5 | display: inline-block; 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/class+id.html: -------------------------------------------------------------------------------- 1 | 2 |

Woot

3 | 4 | -------------------------------------------------------------------------------- /test/fixtures/class.css: -------------------------------------------------------------------------------- 1 | .woot { 2 | overflow: hidden; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/class.html: -------------------------------------------------------------------------------- 1 | 2 |
Test
3 |
Test
4 | 5 | -------------------------------------------------------------------------------- /test/fixtures/codeblocks-external.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

<>

5 | 6 | 7 | -------------------------------------------------------------------------------- /test/fixtures/codeblocks.css: -------------------------------------------------------------------------------- 1 | body, 2 | p { 3 | color: red; 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/codeblocks.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{#if `age <= 40`}}

Young

{{/if}} 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/fixtures/css-quotes.css: -------------------------------------------------------------------------------- 1 | p { 2 | background: url("/woot"); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/css-quotes.html: -------------------------------------------------------------------------------- 1 | 2 |

woot

3 | 4 | -------------------------------------------------------------------------------- /test/fixtures/direct-descendents.css: -------------------------------------------------------------------------------- 1 | p em { 2 | background: blue; 3 | } 4 | p > em { 5 | color: red; 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/direct-descendents.html: -------------------------------------------------------------------------------- 1 | 2 |

woot

3 | 4 | -------------------------------------------------------------------------------- /test/fixtures/doctype.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

hello

7 | 8 | 9 | -------------------------------------------------------------------------------- /test/fixtures/ejs.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: red; 3 | } -------------------------------------------------------------------------------- /test/fixtures/ejs.html: -------------------------------------------------------------------------------- 1 | 2 | <%= config.sitePrefix %>verify?grant=<%= grant._id %>&secret=<%= grant.secret %> 3 |
>
4 | 5 | -------------------------------------------------------------------------------- /test/fixtures/empty.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/fixtures/empty.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /test/fixtures/file.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial; 3 | } 4 | h1 { 5 | color: blue; 6 | } 7 | .headline { 8 | font-size: 24px; 9 | } 10 | 11 | td { 12 | padding: 5px; 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/font-quotes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 |

hi

11 |

there

12 | 13 | 14 | -------------------------------------------------------------------------------- /test/fixtures/id.css: -------------------------------------------------------------------------------- 1 | #test { 2 | display: none; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/id.html: -------------------------------------------------------------------------------- 1 | 2 |
woot
3 | 4 | -------------------------------------------------------------------------------- /test/fixtures/identical-important.css: -------------------------------------------------------------------------------- 1 | div { 2 | color: black !important; 3 | } 4 | 5 | div { 6 | color: blue !important; 7 | } -------------------------------------------------------------------------------- /test/fixtures/identical-important.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /test/fixtures/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/fixtures/ignore-pseudos.html: -------------------------------------------------------------------------------- 1 | 2 | Test 3 | 4 | -------------------------------------------------------------------------------- /test/fixtures/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/fixtures/important.html: -------------------------------------------------------------------------------- 1 | 2 | woot 3 | 4 | -------------------------------------------------------------------------------- /test/fixtures/in.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 |

Hi

13 | 14 | 15 | 16 | 17 |
Some Headline
18 | 19 | 20 | -------------------------------------------------------------------------------- /test/fixtures/malformed.css: -------------------------------------------------------------------------------- 1 | body 2 | padding: 0; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/malformed.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/fixtures/media-preserve.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | 22 |

hi

23 | 24 | 25 | -------------------------------------------------------------------------------- /test/fixtures/media.css: -------------------------------------------------------------------------------- 1 | @media only screen and (-webkit-min-device-pixel-ratio: 2) { 2 | p { 3 | color: blue; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/media.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

hi

4 | 5 | 6 | -------------------------------------------------------------------------------- /test/fixtures/multiple/one/file.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial; 3 | } 4 | h1 { 5 | color: blue; 6 | } 7 | .headline { 8 | font-size: 24px; 9 | } 10 | 11 | td { 12 | padding: 5px; 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/multiple/one/in.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 |

Hi

13 | 14 | 15 | 16 | 17 |
Some Headline
18 | 19 | 20 | -------------------------------------------------------------------------------- /test/fixtures/multiple/two/file.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial; 3 | } 4 | h1 { 5 | color: red; 6 | } 7 | .headline { 8 | font-size: 24px; 9 | } 10 | 11 | td { 12 | padding: 5px; 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/multiple/two/in.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 |

Hi

13 | 14 | 15 | 16 | 17 |
Some Headline
18 | 19 | 20 | -------------------------------------------------------------------------------- /test/fixtures/no_css.html: -------------------------------------------------------------------------------- 1 |

hi

2 | 3 | 4 | -------------------------------------------------------------------------------- /test/fixtures/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v2.1.0 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /** 8 | * Correct `block` display not defined in IE 8/9. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | main, 20 | nav, 21 | section, 22 | summary { 23 | display: block; 24 | } 25 | 26 | /** 27 | * Correct `inline-block` display not defined in IE 8/9. 28 | */ 29 | 30 | audio, 31 | canvas, 32 | video { 33 | display: inline-block; 34 | } 35 | 36 | /** 37 | * Prevent modern browsers from displaying `audio` without controls. 38 | * Remove excess height in iOS 5 devices. 39 | */ 40 | 41 | audio:not([controls]) { 42 | display: none; 43 | height: 0; 44 | } 45 | 46 | /** 47 | * Address styling not present in IE 8/9. 48 | */ 49 | 50 | [hidden] { 51 | display: none; 52 | } 53 | 54 | /* ========================================================================== 55 | Base 56 | ========================================================================== */ 57 | 58 | /** 59 | * 1. Set default font family to sans-serif. 60 | * 2. Prevent iOS text size adjust after orientation change, without disabling 61 | * user zoom. 62 | */ 63 | 64 | html { 65 | font-family: sans-serif; /* 1 */ 66 | -webkit-text-size-adjust: 100%; /* 2 */ 67 | -ms-text-size-adjust: 100%; /* 2 */ 68 | } 69 | 70 | /** 71 | * Remove default margin. 72 | */ 73 | 74 | body { 75 | margin: 0; 76 | } 77 | 78 | /* ========================================================================== 79 | Links 80 | ========================================================================== */ 81 | 82 | /** 83 | * Address `outline` inconsistency between Chrome and other browsers. 84 | */ 85 | 86 | a:focus { 87 | outline: thin dotted; 88 | } 89 | 90 | /** 91 | * Improve readability when focused and also mouse hovered in all browsers. 92 | */ 93 | 94 | a:active, 95 | a:hover { 96 | outline: 0; 97 | } 98 | 99 | /* ========================================================================== 100 | Typography 101 | ========================================================================== */ 102 | 103 | /** 104 | * Address variable `h1` font-size and margin within `section` and `article` 105 | * contexts in Firefox 4+, Safari 5, and Chrome. 106 | */ 107 | 108 | h1 { 109 | font-size: 2em; 110 | margin: 0.67em 0; 111 | } 112 | 113 | /** 114 | * Address styling not present in IE 8/9, Safari 5, and Chrome. 115 | */ 116 | 117 | abbr[title] { 118 | border-bottom: 1px dotted; 119 | } 120 | 121 | /** 122 | * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 123 | */ 124 | 125 | b, 126 | strong { 127 | font-weight: bold; 128 | } 129 | 130 | /** 131 | * Address styling not present in Safari 5 and Chrome. 132 | */ 133 | 134 | dfn { 135 | font-style: italic; 136 | } 137 | 138 | /** 139 | * Address differences between Firefox and other browsers. 140 | */ 141 | 142 | hr { 143 | -moz-box-sizing: content-box; 144 | box-sizing: content-box; 145 | height: 0; 146 | } 147 | 148 | /** 149 | * Address styling not present in IE 8/9. 150 | */ 151 | 152 | mark { 153 | background: #ff0; 154 | color: #000; 155 | } 156 | 157 | /** 158 | * Correct font family set oddly in Safari 5 and Chrome. 159 | */ 160 | 161 | code, 162 | kbd, 163 | pre, 164 | samp { 165 | font-family: monospace, serif; 166 | font-size: 1em; 167 | } 168 | 169 | /** 170 | * Improve readability of pre-formatted text in all browsers. 171 | */ 172 | 173 | pre { 174 | white-space: pre-wrap; 175 | } 176 | 177 | /** 178 | * Set consistent quote types. 179 | */ 180 | 181 | q { 182 | quotes: "\201C" "\201D" "\2018" "\2019"; 183 | } 184 | 185 | /** 186 | * Address inconsistent and variable font size in all browsers. 187 | */ 188 | 189 | small { 190 | font-size: 80%; 191 | } 192 | 193 | /** 194 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 195 | */ 196 | 197 | sub, 198 | sup { 199 | font-size: 75%; 200 | line-height: 0; 201 | position: relative; 202 | vertical-align: baseline; 203 | } 204 | 205 | sup { 206 | top: -0.5em; 207 | } 208 | 209 | sub { 210 | bottom: -0.25em; 211 | } 212 | 213 | /* ========================================================================== 214 | Embedded content 215 | ========================================================================== */ 216 | 217 | /** 218 | * Remove border when inside `a` element in IE 8/9. 219 | */ 220 | 221 | img { 222 | border: 0; 223 | } 224 | 225 | /** 226 | * Correct overflow displayed oddly in IE 9. 227 | */ 228 | 229 | svg:not(:root) { 230 | overflow: hidden; 231 | } 232 | 233 | /* ========================================================================== 234 | Figures 235 | ========================================================================== */ 236 | 237 | /** 238 | * Address margin not present in IE 8/9 and Safari 5. 239 | */ 240 | 241 | figure { 242 | margin: 0; 243 | } 244 | 245 | /* ========================================================================== 246 | Forms 247 | ========================================================================== */ 248 | 249 | /** 250 | * Define consistent border, margin, and padding. 251 | */ 252 | 253 | fieldset { 254 | border: 1px solid #c0c0c0; 255 | margin: 0 2px; 256 | padding: 0.35em 0.625em 0.75em; 257 | } 258 | 259 | /** 260 | * 1. Correct `color` not being inherited in IE 8/9. 261 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 262 | */ 263 | 264 | legend { 265 | border: 0; /* 1 */ 266 | padding: 0; /* 2 */ 267 | } 268 | 269 | /** 270 | * 1. Correct font family not being inherited in all browsers. 271 | * 2. Correct font size not being inherited in all browsers. 272 | * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. 273 | */ 274 | 275 | button, 276 | input, 277 | select, 278 | textarea { 279 | font-family: inherit; /* 1 */ 280 | font-size: 100%; /* 2 */ 281 | margin: 0; /* 3 */ 282 | } 283 | 284 | /** 285 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 286 | * the UA stylesheet. 287 | */ 288 | 289 | button, 290 | input { 291 | line-height: normal; 292 | } 293 | 294 | /** 295 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 296 | * All other form control elements do not inherit `text-transform` values. 297 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. 298 | * Correct `select` style inheritance in Firefox 4+ and Opera. 299 | */ 300 | 301 | button, 302 | select { 303 | text-transform: none; 304 | } 305 | 306 | /** 307 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 308 | * and `video` controls. 309 | * 2. Correct inability to style clickable `input` types in iOS. 310 | * 3. Improve usability and consistency of cursor style between image-type 311 | * `input` and others. 312 | */ 313 | 314 | button, 315 | html input[type="button"], /* 1 */ 316 | input[type="reset"], 317 | input[type="submit"] { 318 | -webkit-appearance: button; /* 2 */ 319 | cursor: pointer; /* 3 */ 320 | } 321 | 322 | /** 323 | * Re-set default cursor for disabled elements. 324 | */ 325 | 326 | button[disabled], 327 | html input[disabled] { 328 | cursor: default; 329 | } 330 | 331 | /** 332 | * 1. Address box sizing set to `content-box` in IE 8/9. 333 | * 2. Remove excess padding in IE 8/9. 334 | */ 335 | 336 | input[type="checkbox"], 337 | input[type="radio"] { 338 | box-sizing: border-box; /* 1 */ 339 | padding: 0; /* 2 */ 340 | } 341 | 342 | /** 343 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 344 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome 345 | * (include `-moz` to future-proof). 346 | */ 347 | 348 | input[type="search"] { 349 | -webkit-appearance: textfield; /* 1 */ 350 | -moz-box-sizing: content-box; 351 | -webkit-box-sizing: content-box; /* 2 */ 352 | box-sizing: content-box; 353 | } 354 | 355 | /** 356 | * Remove inner padding and search cancel button in Safari 5 and Chrome 357 | * on OS X. 358 | */ 359 | 360 | input[type="search"]::-webkit-search-cancel-button, 361 | input[type="search"]::-webkit-search-decoration { 362 | -webkit-appearance: none; 363 | } 364 | 365 | /** 366 | * Remove inner padding and border in Firefox 4+. 367 | */ 368 | 369 | button::-moz-focus-inner, 370 | input::-moz-focus-inner { 371 | border: 0; 372 | padding: 0; 373 | } 374 | 375 | /** 376 | * 1. Remove default vertical scrollbar in IE 8/9. 377 | * 2. Improve readability and alignment in all browsers. 378 | */ 379 | 380 | textarea { 381 | overflow: auto; /* 1 */ 382 | vertical-align: top; /* 2 */ 383 | } 384 | 385 | /* ========================================================================== 386 | Tables 387 | ========================================================================== */ 388 | 389 | /** 390 | * Remove most spacing between table cells. 391 | */ 392 | 393 | table { 394 | border-collapse: collapse; 395 | border-spacing: 0; 396 | } 397 | -------------------------------------------------------------------------------- /test/fixtures/normalize.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

normalize.css

6 | 7 | 8 | -------------------------------------------------------------------------------- /test/fixtures/preserve-events.css: -------------------------------------------------------------------------------- 1 | p { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/preserve-events.html: -------------------------------------------------------------------------------- 1 | 2 | Google 3 |

Google

4 | 5 | -------------------------------------------------------------------------------- /test/fixtures/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/fixtures/regression-media.html: -------------------------------------------------------------------------------- 1 | Test 2 |

Test

3 | -------------------------------------------------------------------------------- /test/fixtures/regression-selector-newline.css: -------------------------------------------------------------------------------- 1 | a, 2 | p { 3 | color: red; 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/regression-selector-newline.html: -------------------------------------------------------------------------------- 1 | 2 | Test 3 |

Test

4 | 5 | -------------------------------------------------------------------------------- /test/fixtures/remote_url.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

hello

7 | 8 | 9 | -------------------------------------------------------------------------------- /test/fixtures/remove-html-selectors.css: -------------------------------------------------------------------------------- 1 | .myClass { 2 | color: red; 3 | font-size: 12px; 4 | } 5 | 6 | #myId { 7 | display: inline-block; 8 | width: 100%; 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/remove-html-selectors.html: -------------------------------------------------------------------------------- 1 | 2 |
Test
3 |
Test
4 | 5 | -------------------------------------------------------------------------------- /test/fixtures/spaces in path/Test.css: -------------------------------------------------------------------------------- 1 | strong { 2 | color: blue; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/spaces_in_path.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

hello, you are my favorite person

7 | 8 | 9 | -------------------------------------------------------------------------------- /test/fixtures/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/fixtures/specificity.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |
6 |
7 | 8 | -------------------------------------------------------------------------------- /test/fixtures/style-preservation.css: -------------------------------------------------------------------------------- 1 | p { color: blue; background: blue; } 2 | -------------------------------------------------------------------------------- /test/fixtures/style-preservation.html: -------------------------------------------------------------------------------- 1 | 2 |

The color should stay intact

3 | 4 | -------------------------------------------------------------------------------- /test/fixtures/table-attr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 28 | 29 | 30 | 31 | 32 | 41 | 42 |
33 | 34 | 35 | 38 | 39 |
36 | empty 37 |
40 |
43 | 44 | 45 | 48 | 49 |
46 | empty 47 |
50 | 51 | 52 | 53 | 56 | 57 | 58 | 59 | 60 | 63 | 64 | 65 | 66 | 67 | 70 | 71 | 72 |
54 | th 55 |
61 | td 62 |
68 | td 69 |
73 | 74 | 75 | -------------------------------------------------------------------------------- /test/fixtures/tag.css: -------------------------------------------------------------------------------- 1 | p { 2 | border: 1px solid #f00; 3 | margin: 1px; 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/tag.html: -------------------------------------------------------------------------------- 1 | 2 |

Test

3 | 4 | -------------------------------------------------------------------------------- /test/fixtures/template.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Hi there {{ name.first }} {{ name.last }}

4 | 5 | 6 | -------------------------------------------------------------------------------- /test/fixtures/two_styles.css: -------------------------------------------------------------------------------- 1 | strong { 2 | color: blue; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/two_styles.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 16 | 17 | 18 | 19 |

hello, you are my favorite person

20 | 21 | 22 | -------------------------------------------------------------------------------- /test/fixtures/width-attr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 |

none

11 | 12 | 13 | 16 | 17 |
14 | wide 15 |
18 | wide 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/fixtures/xhtml.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/fixtures/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/fixtures/yui-reset.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

hello

6 | 7 | 8 | -------------------------------------------------------------------------------- /test/main.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* global describe, it */ 3 | 4 | const should = require('should'); 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const Vinyl = require('vinyl'); 8 | const beautify = require('js-beautify').html; 9 | const inlineCss = require('../index'); 10 | 11 | function getFile(filePath) { 12 | return new Vinyl({ 13 | path: path.resolve(filePath), 14 | cwd: './test/', 15 | base: path.dirname(filePath), 16 | contents: Buffer.from(String(fs.readFileSync(filePath))) 17 | }); 18 | } 19 | 20 | function compare(fixturePath, expectedPath, options, done) { 21 | const file = getFile(fixturePath); 22 | 23 | options.url = `file://${file.path}`; 24 | 25 | inlineCss(file.contents.toString('utf8'), options) 26 | .then(html => { 27 | const expected = beautify(String(fs.readFileSync(expectedPath)), { 28 | "preserve-newlines": false 29 | }); 30 | beautify(html, { 31 | "preserve-newlines": false 32 | }).should.be.equal(expected); 33 | }) 34 | .then(() => { 35 | done() 36 | }) 37 | .catch(err => { 38 | done(err) 39 | }); 40 | } 41 | 42 | describe('inline-css', () => { 43 | it('Should convert linked css to inline css', done => { 44 | const options = {}; 45 | compare(path.join('test', 'fixtures', 'in.html'), path.join('test', 'expected', 'out.html'), options, done); 46 | }); 47 | 48 | it('Should inline css in multiple HTML files', done => { 49 | const options = {}; 50 | compare(path.join('test', 'fixtures', 'multiple', 'one', 'in.html'), path.join('test', 'expected', 'multiple', 'one', 'out.html'), options, () => {}); 51 | compare(path.join('test', 'fixtures', 'multiple', 'two', 'in.html'), path.join('test', 'expected', 'multiple', 'two', 'out.html'), options, done); 52 | }); 53 | 54 | it('Should inline css in edge case (alpha)', done => { 55 | const options = {}; 56 | compare(path.join('test', 'fixtures', 'alpha.html'), path.join('test', 'expected', 'alpha.html'), options, done); 57 | }); 58 | 59 | it('Should inline css in edge case (cascading)', done => { 60 | const options = {}; 61 | compare(path.join('test', 'fixtures', 'cascading.html'), path.join('test', 'expected', 'cascading.html'), options, done); 62 | }); 63 | 64 | it('Should inline css in edge case (class)', done => { 65 | const options = {}; 66 | compare(path.join('test', 'fixtures', 'class.html'), path.join('test', 'expected', 'class.html'), options, done); 67 | }); 68 | 69 | it('Should inline css in edge case (class+id)', done => { 70 | const options = {}; 71 | compare(path.join('test', 'fixtures', 'class+id.html'), path.join('test', 'expected', 'class+id.html'), options, done); 72 | }); 73 | 74 | it('Should inline css in edge case (css-quotes)', done => { 75 | const options = {}; 76 | compare(path.join('test', 'fixtures', 'css-quotes.html'), path.join('test', 'expected', 'css-quotes.html'), options, done); 77 | }); 78 | 79 | it('Should inline css in edge case (direct-descendents)', done => { 80 | const options = {}; 81 | compare(path.join('test', 'fixtures', 'direct-descendents.html'), path.join('test', 'expected', 'direct-descendents.html'), options, done); 82 | }); 83 | 84 | it('Should inline css in edge case (empty)', done => { 85 | const options = {}; 86 | compare(path.join('test', 'fixtures', 'empty.html'), path.join('test', 'expected', 'empty.html'), options, done); 87 | }); 88 | 89 | it('Should inline css in edge case (id)', done => { 90 | const options = {}; 91 | compare(path.join('test', 'fixtures', 'id.html'), path.join('test', 'expected', 'id.html'), options, done); 92 | }); 93 | 94 | it('Should inline last rule if identical rules use important', done => { 95 | const options = {}; 96 | compare(path.join('test', 'fixtures', 'identical-important.html'), path.join('test', 'expected', 'identical-important.html'), options, done); 97 | }); 98 | 99 | it('Should ignore pseudo selectors', done => { 100 | const options = {}; 101 | compare(path.join('test', 'fixtures', 'ignore-pseudos.html'), path.join('test', 'expected', 'ignore-pseudos.html'), options, done); 102 | }); 103 | 104 | it('Should inline css in edge case (important)', done => { 105 | const options = {}; 106 | compare(path.join('test', 'fixtures', 'important.html'), path.join('test', 'expected', 'important.html'), options, done); 107 | }); 108 | 109 | it('Should inline css in edge case (media)', done => { 110 | const options = {}; 111 | compare(path.join('test', 'fixtures', 'media.html'), path.join('test', 'expected', 'media.html'), options, done); 112 | }); 113 | 114 | it('Should inline css in edge case (normalize)', done => { 115 | const options = {}; 116 | compare(path.join('test', 'fixtures', 'normalize.html'), path.join('test', 'expected', 'normalize.html'), options, done); 117 | }); 118 | 119 | it('Should inline css in edge case (preserve-events)', done => { 120 | const options = {}; 121 | compare(path.join('test', 'fixtures', 'preserve-events.html'), path.join('test', 'expected', 'preserve-events.html'), options, done); 122 | }); 123 | 124 | it('Should inline css in edge case (media)', done => { 125 | const options = {}; 126 | compare(path.join('test', 'fixtures', 'regression-media.html'), path.join('test', 'expected', 'regression-media.html'), options, done); 127 | }); 128 | 129 | it('Should inline css in edge case (selector-newline)', done => { 130 | const options = {}; 131 | compare(path.join('test', 'fixtures', 'regression-selector-newline.html'), path.join('test', 'expected', 'regression-selector-newline.html'), options, done); 132 | }); 133 | 134 | it('Should compare properties and inline the most specific', done => { 135 | const options = {}; 136 | compare(path.join('test', 'fixtures', 'specificity.html'), path.join('test', 'expected', 'specificity.html'), options, done); 137 | }); 138 | 139 | it('Should preserve existing inline styles ', done => { 140 | const options = {}; 141 | compare(path.join('test', 'fixtures', 'style-preservation.html'), path.join('test', 'expected', 'style-preservation.html'), options, done); 142 | }); 143 | 144 | it('Should inline css in edge case (tag)', done => { 145 | const options = {}; 146 | compare(path.join('test', 'fixtures', 'tag.html'), path.join('test', 'expected', 'tag.html'), options, done); 147 | }); 148 | 149 | it('Should inline css in edge case (yui-reset)', done => { 150 | const options = {}; 151 | compare(path.join('test', 'fixtures', 'yui-reset.html'), path.join('test', 'expected', 'yui-reset.html'), options, done); 152 | }); 153 | 154 | it('Should inline css with doctype', done => { 155 | const options = {}; 156 | compare(path.join('test', 'fixtures', 'doctype.html'), path.join('test', 'expected', 'doctype.html'), options, done); 157 | }); 158 | 159 | it('Should inline css with no css', done => { 160 | const options = {}; 161 | compare(path.join('test', 'fixtures', 'no_css.html'), path.join('test', 'expected', 'no_css.html'), options, done); 162 | }); 163 | 164 | it('Should inline css with remote url', function(done) { 165 | this.timeout(10000); 166 | const options = {}; 167 | compare(path.join('test', 'fixtures', 'remote_url.html'), path.join('test', 'expected', 'remote_url.html'), options, done); 168 | }); 169 | 170 | it('Should inline css in with spaces in path', done => { 171 | const options = {}; 172 | compare(path.join('test', 'fixtures', 'spaces_in_path.html'), path.join('test', 'expected', 'spaces_in_path.html'), options, done); 173 | }); 174 | 175 | it('Should inline css with two styles', done => { 176 | const options = {}; 177 | compare(path.join('test', 'fixtures', 'two_styles.html'), path.join('test', 'expected', 'two_styles.html'), options, done); 178 | }); 179 | 180 | it('Should inline css with font quotes', done => { 181 | const options = { 182 | url: './', 183 | removeStyleTags: true 184 | }; 185 | compare(path.join('test', 'fixtures', 'font-quotes.html'), path.join('test', 'expected', 'font-quotes.html'), options, done); 186 | }); 187 | 188 | it('Should inline css with two styles', done => { 189 | const options = {}; 190 | compare(path.join('test', 'fixtures', 'two_styles.html'), path.join('test', 'expected', 'two_styles.html'), options, done); 191 | }); 192 | 193 | it('Should inline css and preserve media queries', done => { 194 | const options = { 195 | url: './', 196 | removeStyleTags: true, 197 | preserveMediaQueries: true 198 | }; 199 | compare( 200 | path.join('test', 'fixtures', 'media-preserve.html'), 201 | path.join('test', 'expected', 'media-preserve.html'), 202 | options, 203 | done 204 | ); 205 | }); 206 | 207 | it('Should inline css and create width attributes on elements', done => { 208 | const options = { 209 | url: './', 210 | removeStyleTags: true, 211 | applyWidthAttributes: true 212 | }; 213 | compare(path.join('test', 'fixtures', 'width-attr.html'), path.join('test', 'expected', 'width-attr.html'), options, done); 214 | }); 215 | 216 | it('Should inline css and create table attributes on table elements', done => { 217 | const options = { 218 | url: './', 219 | removeStyleTags: true, 220 | applyTableAttributes: true 221 | }; 222 | compare(path.join('test', 'fixtures', 'table-attr.html'), path.join('test', 'expected', 'table-attr.html'), options, done); 223 | }); 224 | 225 | it('Should inline css in HTML templates', done => { 226 | const options = { 227 | url: './' 228 | }; 229 | compare(path.join('test', 'fixtures', 'template.ejs'), path.join('test', 'fixtures', 'template.ejs'), options, done); 230 | }); 231 | 232 | it('Should inline css in edge case and remove html selectors', done => { 233 | const options = { 234 | removeHtmlSelectors: true 235 | }; 236 | compare(path.join('test', 'fixtures', 'remove-html-selectors.html'), path.join('test', 'expected', 'remove-html-selectors.html'), options, done); 237 | }); 238 | 239 | it('Should error when passed malformed CSS', done => { 240 | const file = getFile(path.join('test', 'fixtures', 'malformed.html')); 241 | const options = { 242 | url: `file://${file.path}` 243 | }; 244 | inlineCss(file.contents.toString('utf8'), options) 245 | .then(html => { 246 | done(new Error('test should error when passed malformed CSS')); 247 | }) 248 | .catch(({message}) => { 249 | message.should.be.equal('Error: Unexpected } (line 3, char 1)'); 250 | done(); 251 | }); 252 | }); 253 | 254 | it('Should handle html character entities correctly', done => { 255 | const options = {}; 256 | compare(path.join('test', 'fixtures', 'character-entities.html'), path.join('test', 'expected', 'character-entities.html'), options, done); 257 | }); 258 | 259 | it('Should error when options.url is not set', done => { 260 | const options = {}; 261 | const file = getFile(path.join('test', 'fixtures', 'template.ejs')); 262 | inlineCss(file.contents.toString('utf8'), options) 263 | .then(html => { 264 | done(new Error('test should error when options.url is not set')); 265 | }) 266 | .catch(err => { 267 | done(); 268 | }); 269 | }); 270 | 271 | it('Should handle xhtml documents correctly', done => { 272 | const options = { 273 | xmlMode: true 274 | }; 275 | compare(path.join('test', 'fixtures', 'xhtml.html'), path.join('test', 'expected', 'xhtml.html'), options, done); 276 | }); 277 | 278 | it('Should ignore hbs code blocks', done => { 279 | const options = { 280 | xmlMode: true 281 | }; 282 | compare(path.join('test', 'fixtures', 'codeblocks.html'), path.join('test', 'expected', 'codeblocks.html'), options, done); 283 | }); 284 | 285 | it('Should ignore ejs code blocks', done => { 286 | const options = { 287 | xmlMode: false 288 | }; 289 | compare(path.join('test', 'fixtures', 'ejs.html'), path.join('test', 'expected', 'ejs.html'), options, done); 290 | }); 291 | 292 | it('Should ignore user defined code blocks', done => { 293 | const options = { 294 | xmlMode: true, 295 | codeBlocks: { 296 | craze: { start: '<<', end: '>>' } 297 | } 298 | }; 299 | compare(path.join('test', 'fixtures', 'codeblocks-external.html'), path.join('test', 'expected', 'codeblocks-external.html'), options, done); 300 | }); 301 | }); 302 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "tasks": { 4 | "version": { 5 | "dependsOn": ["^version"] 6 | }, 7 | "deploy": { 8 | "dependsOn": ["test", "lint"] 9 | }, 10 | "test": { 11 | "dependsOn": ["^test"] 12 | }, 13 | "lint": { 14 | "dependsOn": ["^lint"] 15 | }, 16 | "dev": { 17 | "cache": false, 18 | "persistent": true 19 | } 20 | } 21 | } 22 | --------------------------------------------------------------------------------