Giter Site home page Giter Site logo

stranger6667 / css-inline Goto Github PK

View Code? Open in Web Editor NEW
228.0 228.0 29.0 3.65 MB

High-performance library for inlining CSS into HTML 'style' attributes

Home Page: https://css-inline.org/

License: MIT License

Rust 70.70% Python 3.47% CSS 0.02% TypeScript 3.03% HTML 3.07% Ruby 3.11% C 1.88% JavaScript 14.73%
css css-inline hacktoberfest html javascript python ruby rust wasm

css-inline's Introduction

Hi, I am Dmitry ๐Ÿ‘‹

Software Engineer with more than 12 years of experience specializing in Rust and Python with a focus on writing parsers and fuzzing.

  • ๐ŸŒ Based in Prague, Czech Republic ๐Ÿ‡จ๐Ÿ‡ฟ
  • ๐Ÿ’ก Interested in software testing & building reliable systems
  • ๐ŸŽ“ Studied information security
  • ๐Ÿšฒ Love traveling
  • ๐Ÿ‘‹ Reach me on LinkedIn, Twitter or Telegram

css-inline's People

Contributors

a2aaron avatar adriangb avatar caseyjhol avatar cheapsteak avatar chiefmilesedgeworth avatar dependabot[bot] avatar laplus-sadness avatar mogost avatar sbeckeriv avatar stranger6667 avatar synapticarbors avatar yhaiovyi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

css-inline's Issues

Sdist is broken for Python

โžœ pip install css-inline --no-binary :all:
Collecting css-inline
  Downloading css_inline-0.6.1.tar.gz (5.5 kB)
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
    Preparing wheel metadata ... done
Building wheels for collected packages: css-inline
  Building wheel for css-inline (PEP 517) ... error
  ERROR: Command errored out with exit status 1:
...
  error: failed to get `css-inline` as a dependency of package `css-inline-python v0.6.1 (/tmp/pip-install-71d_ro91/css-inline_d9ee84badfa04fe3b7c66254f7a0c899)`
  
  Caused by:
    failed to load source for dependency `css-inline`
  
  Caused by:
    Unable to update /tmp/pip-install-71d_ro91
  
  Caused by:
    failed to read `/tmp/pip-install-71d_ro91/Cargo.toml`
  
  Caused by:
    No such file or directory (os error 2)
...
  subprocess.CalledProcessError: Command '['cargo', 'metadata', '--manifest-path', 'Cargo.toml', '--format-version', '1']' returned non-zero exit status 101.
  ----------------------------------------
  ERROR: Failed building wheel for css-inline```

Optionally ignore style tags in HTML, accept additional argument for css to inline?

๐Ÿ‘‹ Hi there :)

Have you considered allowing css to inline be passed as an additional parameter?

(This might be related to #10 , but not quite the same)

The use case I'm thinking of is to possibly replace juice in mjml (I'm not affiliated, just a lib user), where css specified in <mj-style inline="inline"> is collected as a separate string that's currently passed into juice as extraCss, and inlining of other css is disabled (which makes sense because sometimes you do want to tell clients that respect style sheets to do something different from outlook)

https://github.com/mjmlio/mjml/blob/246df840f4d0fcd812e51ca55bd6bef6592cb0e6/packages/mjml-core/src/index.js#L317-L319

Minimize inlined css

Now we have some extra spaces that are not needed. ; in the end, is also optional

WASM support

Currently we can't due to dependency on openssl, but switching to rustls will solve this issue

Optimize serialization to a string

As far as I see String::from_utf8_unchecked can be used instead of String:from_utf8_lossy because the output is always valid utf8.

  • The input document is &str which is unicode;
  • Everything added to the tree is String which is also a valid utf8

Unless there are some raw bytes manipulations inside serialize that lead to a non-utf8 output, then it should be safe. As far as I see the serializer - all bytes are from ascii symbols or &str::as_bytes() which is utf-8. Need to check better

Problem with selectors' priorities

While working on integrating your tool into our project, we, with a colleague, noticed that the "css-inline" behaves unexpectedly in some situations in part of rules priority ("Specificity" in terms of css).

I'm very grateful to @midezz for the provided examples and help with investigating the issue.

There are a couple of demos.

"Correct" case:

Source HTML

<html>
<head>
<style>
a {
        color: #17bebb;
}
.test-class {
        color: #ffffff;
}
</style>
</head>
<body>
<a class="test-class" href="https://example.com">Test</a>
</body>
</html>

Inlined HTML

<html><head>
<style>
a {
        color: #17bebb;
}
.test-class {
        color: #ffffff;
}
</style>
</head>
<body>
<a class="test-class" href="https://example.com" style="color: #ffffff;">Test</a>
</body></html>

"Unexpected" case:

<html>
<head>
<style>
.test-class {
        color: #ffffff;
}
a {
        color: #17bebb;
}
</style>
</head>
<body>
<a class="test-class" href="https://example.com">Test</a>
</body>
</html>
<html><head>
<style>
.test-class {
        color: #ffffff;
}
a {
        color: #17bebb;
}
</style>
</head>
<body>
<a class="test-class" href="https://example.com" style="color: #17bebb;">Test</a>
</body></html>

Description

As we see, the order of css-rules declaration affects the rules application priority. That seems wrong, as we expect "class rules" to be of higher priority than "tag rules".

Ref: https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity

If I'm not mistaken, it seems to be a problem with the merge_styles function, if I'm not mistaken (https://github.com/Stranger6667/css-inline/blob/master/css-inline/src/lib.rs#L397).

Note

I didn't have a chance to check the behaviour with an "!important" rule, but it should be quite an important case here too :)

Benchmarks

Bench candidates:

  • inline
  • merge_styles

Test data:

  • Some big realistic HTML & complex styles
  • Small realistic HTML & simple styles
  • No-op HTML & styles

Problem with option 'remove_style_tags=True'

Hello!

There is a problem when we use css_inline with the option remove_style_tags=True. This problem appears when there are a few <style> tags in HTML.

HTML

<html>
<head>
<style>
body {
    margin: 0 auto !important;
    padding: 0 !important;
    height: 100% !important;
    width: 100% !important;
    background: #f1f1f1;
}
a {
    text-decoration: none;
}
</style>
<style>
.test-class {
        color: #ffffff;
}
a {
        color: #17bebb;
}
</style>
</head>
<body>
<a class="test-class" href="https://example.com">Test</a>
</body>
</html>

Use css_inline with remove_style_tags=True

inliner = css_inline.CSSInliner(remove_style_tags=True)
html_without_style = inliner(html)

Result

<html>
<head>
<style>
.test-class {
        color: #ffffff;
}
a {
        color: #17bebb;
}
</style>
</head>
<body style="height: 100% !important;margin: 0 auto !important;padding: 0 !important;width: 100% !important;background: #f1f1f1;">
<a class="test-class" href="https://example.com" style="text-decoration: none;">Test</a>
</body>
</html>

The result is not correct. As you can see, not all styles apply to tag <a>, and not all tags <style></style> are removed.

Use css_inline without any options.

html_with_style = css_inline.inline(html)

Result

<html>
<head>
<style>
body {
    margin: 0 auto !important;
    padding: 0 !important;
    height: 100% !important;
    width: 100% !important;
    background: #f1f1f1;
}
a {
    text-decoration: none;
}
</style>
<style>
.test-class {
        color: #ffffff;
}
a {
        color: #17bebb;
}
</style>
</head>
<body style="margin: 0 auto !important;width: 100% !important;height: 100% !important;background: #f1f1f1;padding: 0 !important;">
<a class="test-class" href="https://example.com" style="color: #ffffff;text-decoration: none;">Test</a>
</body>
</html>

The result is correct.

bug: Conditional html comments shouldn't be removed

Conditional comments like <!--[if mso]>Hello outlook<![endif]--> and <!--[if !mso]><!--><h1>Hello not outlook</h1><!--<![endif]--> are used to either hide things from outlook or only show things to outlook, it looks like those are currently being stripped out

I have a failing test written on this branch)

Not sure where the problem is; I see code in kuchiki that references comment nodes; this repo seems to be using the newest version, is it possibly a bug on kuchiki?

Implement builder pattern for `CSSInliner`

Instead of this:

let options = css_inline::InlineOptions {
        load_remote_stylesheets: false,
        ..Default::default()
    };
let inliner = css_inline::CSSInliner::new(options);

We can do this:

let inliner = css_inline::CSSInliner::options()
    .load_remote_stylesheets(false)
    .build();

Consider loading external stylesheets in parallel

Inlining itself could be also optimized if we could just join all loaded CSS into a single string. Worth researching, however, I am not sure how common it is to have emails with multiple style / link tags

Optimize WASM package size

The current size: 1492887 bytes

How much we can cut off:

  • wasm-opt = ['-Os'] - 6629 bytes
  • wasm-opt = ['-Oz'] - 6763 bytes
  • opt-level = "s" - 338471 bytes
  • opt-level = "z" - 442795 bytes

So, the best I could get with the options above combined is a reduction by 447844 bytes in cost of some performance

Options I didn't try on my machine yet:

  • Making HTTP loading conditional (e.g. via a feature flag + compile a separate package) should be around 640 Kb
  • Smaller allocator, ~10kb

Python interface

PyO3 + python tests + python benchmarks against premailer & pynliner

Stylesheet incorrectly overrides `style` attribute values

When an element has both a class attribute and a style attribute, styles from the stylesheet are merged into the destination style attribute. However, existing values in the element's style are overwritten by the values from the stylesheet whereas existing style attribute values should override any values in the stylesheet. This issue results in incorrect styles being applied on elements.

Sample Jest test

import { inline } from "css-inline";

describe("css-inline", () => {
  it.only("correctly allows style tag to override stylesheet", () => {
    const document =
`<html>
  <head><style>p.MsoNormal {margin-left:0in;font-size:14px;font-family:Arial;}</style></head>
  <body><p class=MsoNormal style='margin-left:.25in;font-family:Calibri;'>Some text goes here</p></body>
</html>`;

    const expected =
`<html><head></head>
  <body><p class="MsoNormal" style="margin-left:.25in;font-family:Calibri;font-size:14px;">Some text goes here</p></body>
</html>`;

    expect(inline(document, { inline_style_tags: true, remove_style_tags: true })).toEqual(expected);
  });
});

Expected result (margin-left should stay as .25in and font-family should stay as Calibri):

<html><head></head>
<body><p class="MsoNormal" style="margin-left:.25in;font-family:Calibri;font-size:14px;">Some text goes here</p></body>
</html>

Actual result (stylesheet overrides all existing values in style attribute):

<html><head></head>
<body><p class="MsoNormal" style="margin-left:0in;font-family:Arial;font-size:14px;">Some text goes here</p>
</body>
</html>

[suggestion] Move to postfix .inline

Dearest Maintainer,

Neat project. I hope to use it soon. I was playing with the command line tool and was confused by the error messages.

 time ./target/release/css-inline  --remove-style-tags --load-remote-stylesheets ~/trash/1MB.html
/home/becker/trash/1MB.html: FAILURE (No such file or directory (os error 2))

My file is there. what is not there is inlined./home/becker/trash/1MB.html. If you move to postfix it will work for absolute and relative paths.

Dictated but not reviewed.
Becker

Make `inline` more generic

Currently it always creates a string, but it might write to a file instead. One of the ways - accept output: W where W: Write. In this case it will be more efficient for writing to a file - there will be no need to allocate a vec and a string.

Also, there could be a built-in shortcut that will work as the current version, but will be using the new, generic one internally

Setup CI

  • Pre-commit
  • Cargo check
  • Cargo clippy
  • tests stable
  • tests nightly + coverage
  • rust fmt

Optionally remove all `class` attributes

Problem

Since everything is inlined, classes are useless and likely won't bring any benefits but occupy some space in the output. Hence I believe we can safely remove them.

Implementation

  • Add the keep_classes configuration option (everywhere, including CLI and all bindings). The default should be false.
  • Pass this config option down to the serializer and check it before writing classes to the output buffer here.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.