npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

closure-stylesheets-java

v1.12.2

Published

Published JAR of an improved version of Closure Stylesheets to minify CSS, rename classes and expand browser prefixes.

Downloads

3

Readme

Closure Stylesheets

Java CI with Maven

Closure Stylesheets is an extension to CSS that adds variables, functions, conditionals, and mixins to standard CSS. The tool also supports minification, linting, RTL flipping, and CSS class renaming.

Table Of Contents

Fork Diversion

These are the fixes and improvements to the original version:

Checkout 📙 WIKI for some development information.

Get Closure Stylesheets!

Closure Stylesheets is available as a Java jar named closure-stylesheets.jar. You can either download a precompiled jar or build it from source.

Using Closure Stylesheets requires Java. To make sure that Java is installed correctly, try running the following command to print the list of command-line options for Closure Stylesheets:

java -jar closure-stylesheets.jar --help

CSS Extensions

Internally at Google, Closure Stylesheets are frequently referred to as "Google Stylesheets" or "GSS", so you will see references to GSS in the source code. Some developers prefer to be explicit about which files use the Closure Stylesheets extensions to CSS by using a .gss file extension.

Variables

Variables can be defined in Closure Stylesheets using @def followed by a variable name and then a value. Variables can also be defined in terms of other variables. Consider the following file, variable-example.gss:

@def BG_COLOR              rgb(235, 239, 249);

@def DIALOG_BORDER_COLOR   rgb(107, 144, 218);
@def DIALOG_BG_COLOR       BG_COLOR;

body {
  background-color: BG_COLOR;
}

.dialog {
  background-color: DIALOG_BG_COLOR;
  border: 1px solid DIALOG_BORDER_COLOR;
}

Running java -jar closure-stylesheets.jar --pretty-print variable-example.gss will print:

body {
  background-color: #ebeff9;
}
.dialog {
  background-color: #ebeff9;
  border: 1px solid #6b90da;
}

Functions

Closure Stylesheets provides support for several arithmetic functions:

  • add()
  • sub()
  • mult()
  • divide()
  • min()
  • max()

Each of these functions can take a variable number arguments. Arguments may be purely numeric or CSS sizes with units (though mult() and divide() only allow the first argument to have a unit). When units such as px are specified as part of an argument, all arguments to the function must have the same unit. That is, you may do add(3px, 5px) or add(3ex, 5ex), but you cannot do add(3px, 5ex). Here is an example of when it might be helpful to use add():

@def LEFT_HAND_NAV_WIDTH    180px;
@def LEFT_HAND_NAV_PADDING  3px;

.left_hand_nav {
  position: absolute;
  width: LEFT_HAND_NAV_WIDTH;
  padding: LEFT_HAND_NAV_PADDING;
}

.content {
  position: absolute;
  margin-left: add(LEFT_HAND_NAV_PADDING,  /* padding left */
                   LEFT_HAND_NAV_WIDTH,
                   LEFT_HAND_NAV_PADDING); /* padding right */

}

Running java -jar closure-stylesheets.jar --pretty-print functions-example.gss will print:

.left_hand_nav {
  position: absolute;
  width: 180px;
  padding: 3px;
}
.content {
  position: absolute;
  margin-left: 186px;
}

Although these functions are not as full-featured as CSS3 calc() because they do not allow you to mix units as calc() does, they can still help produce more maintainable stylesheets.

There are also built-in functions that deal with colors. For now, you need to see the code for details, but here are the functions and the arguments that they take:

  • blendColorsHsb(startColor, endColor) blends using HSB values
  • blendColorsRgb(startColor, endColor) blends using RGB values
  • makeMutedColor(backgroundColor, foregroundColor [, saturationLoss])
  • addHsbToCssColor(baseColor, hueToAdd, saturationToAdd, brightnessToAdd)
  • makeContrastingColor(color, similarityIndex)
  • adjustBrightness(color, brightness)
  • saturateColor(color, saturationToAdd) increase saturation in HSL color space
  • desaturateColor(color, saturationToRemove) decrease saturation in HSL color space
  • greyscale(color) full desaturation of a color in HSL color space
  • lighten(color, lightnessToAdd) increase the lightness in HSL color space
  • darken(color, lightnessToRemove) decrease the lightness in HSL color space
  • spin(color, hueAngle) increase or decrease hue of the color, like rotating in a color wheel

There is also a selectFrom() function that behaves like the ternary operator:

/* Implies MYDEF = FOO ? BAR : BAZ; */
@def MYDEF selectFrom(FOO, BAR, BAZ);

This could be used with @def FOO true; to have the effect of @def MYDEF = BAR.

It is also possible to define your own functions in Java by implementing GssFunctionMapProvider and passing the fully-qualified class name to Closure Stylesheets via the --gss-function-map-provider flag. If you choose to do this, you will likely want to compose DefaultGssFunctionMapProvider so that your GssFunctionMapProvider provides your custom functions in addition to the built-in arithmetic functions.

Mixins

Mixins make it possible to reuse a list of parameterized declarations. A mixin definition (@defmixin) can be seen as a function with arguments that contains a list of declarations. At the place where a mixin is used (@mixin), the values for the arguments are defined and the declarations are inserted. A mixin can be used in any place where declarations are allowed.

The names of the arguments in the @defmixin declaration must be all uppercase.

Global constants defined with @def can be used in combination with mixins. They can be used both within the definition of a mixin, or as an argument when using a mixin.

For example, consider defining a mixin in mixin-simple-example.gss that could be used to create a shorthand for declaring the dimensions of an element:

@defmixin size(WIDTH, HEIGHT) {
  width: WIDTH;
  height: HEIGHT;
}

.logo {
  @mixin size(150px, 55px);
  background-image: url('http://www.google.com/images/logo_sm.gif');
}

Running java -jar closure-stylesheets.jar --pretty-print mixin-simple-example.gss prints:

.logo {
  width: 150px;
  height: 55px;
  background-image: url('http://www.google.com/images/logo_sm.gif');
}

Mixins are even more compelling when you consider using them to abstract away cross-browser behavior for styles such as gradients:

@defmixin gradient(POS, HSL1, HSL2, HSL3, COLOR, FALLBACK_COLOR) {
  background-color: FALLBACK_COLOR; /* fallback color if gradients are not supported */
  background-image: -webkit-linear-gradient(POS, hsl(HSL1, HSL2, HSL3), COLOR);               /* Chrome 10+,Safari 5.1+ */
  /* @alternate */ background-image: -moz-linear-gradient(POS, hsl(HSL1, HSL2, HSL3), COLOR); /* FF3.6+ */
  /* @alternate */ background-image: -ms-linear-gradient(POS, hsl(HSL1, HSL2, HSL3), COLOR);  /* IE10 */
  /* @alternate */ background-image: -o-linear-gradient(POS, hsl(HSL1, HSL2, HSL3), COLOR);   /* Opera 11.10+ */
}

.header {
  @mixin gradient(top, 0%, 50%, 70%, #cc0000, #f07575);
}

The above is compiled to:

.header {
  background-color: #f07575;
  background-image: -webkit-linear-gradient(top,hsl(0%,50%,70%) ,#cc0000);
  background-image: -moz-linear-gradient(top,hsl(0%,50%,70%) ,#cc0000);
  background-image: -ms-linear-gradient(top,hsl(0%,50%,70%) ,#cc0000);
  background-image: -o-linear-gradient(top,hsl(0%,50%,70%) ,#cc0000);
}

See the section on linting for more details on the @alternate annotation.

Conditionals

Variables can be defined using conditionals with @if, @elseif, and @else. The following is a real-world example adapted from the Closure Library, which defines a cross-browser CSS class to apply the style display: inline-block. The Closure Library example uses browser hacks to define .goog-inline-block, but it can be done explicitly in Closure Stylesheets by using conditionals as shown in conditionals.gss:

@if (BROWSER_IE) {
  @if (BROWSER_IE6) {
    @def GOOG_INLINE_BLOCK_DISPLAY  inline;
  } @elseif (BROWSER_IE7) {
    @def GOOG_INLINE_BLOCK_DISPLAY  inline;
  } @else {
    @def GOOG_INLINE_BLOCK_DISPLAY  inline-block;
  }
} @elseif (BROWSER_FF2) {
  @def GOOG_INLINE_BLOCK_DISPLAY    -moz-inline-box;
} @else {
  @def GOOG_INLINE_BLOCK_DISPLAY    inline-block;
}

.goog-inline-block {
  position: relative;
  display: GOOG_INLINE_BLOCK_DISPLAY;
}

Values for the conditionals can be set via a --define flag. By default, all conditional variables are assumed to be false, so running java -jar closure-stylesheets.jar --pretty-print conditionals.gss will print:

.goog-inline-block {
  position: relative;
  display: inline-block;
}

whereas java -jar closure-stylesheets.jar --define BROWSER_FF2 --pretty-print conditionals.gss will print:

.goog-inline-block {
  position: relative;
  display: -moz-inline-box;
}

It is also possible to specify the --define flag multiple times, so java -jar closure-stylesheets.jar --define BROWSER_IE --define BROWSER_IE6 --pretty-print conditionals.gss will print:

.goog-inline-block {
  position: relative;
  display: inline;
}

Admittedly, to get the benefit of serving the CSS specific to a particular user agent, one must generate a separate stylesheet for each user agent and then serve it appropriately.

Auto Expansion

There is an auto-expansion pass that adds vendor-specific directives, however it was switched off in the Google's version. The reason remains unknown, however it might be because it slow down the compiler.

.row {
  margin: 15px;
  display: flex;
  flex-flow: row wrap;
  height: calc(100vh - 10rem);
  background: linear-gradient(5deg, red, tan);
}
.col {
  flex-flow: row-reverse nowrap;
  display: inline-flex;
  width: calc(10px);
}

This fork allows to switch it on with the --expand-browser-prefix flag:

closure-stylesheets:~$ java -jar closure-stylesheets.jar --expand-browser-prefix \
> --pretty-print example/prefix.css
.row {
  margin: 15px;
  display: flex;
  display: -webkit-box;
  display: -moz-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  flex-flow: row wrap;
  -ms-flex-flow: row wrap;
  -webkit-flex-flow: row wrap;
  height: calc(100vh - 10rem);
  height: -webkit-calc(100vh - 10rem);
  height: -moz-calc(100vh - 10rem);
  background: linear-gradient(5deg,red,tan);
  background: -webkit-linear-gradient(5deg,red,tan);
  background: -moz-linear-gradient(5deg,red,tan);
  background: -ms-linear-gradient(5deg,red,tan);
  background: -o-linear-gradient(5deg,red,tan);
}
.col {
  flex-flow: row-reverse nowrap;
  -ms-flex-flow: row-reverse nowrap;
  -webkit-flex-flow: row-reverse nowrap;
  display: inline-flex;
  display: -webkit-inline-box;
  display: -webkit-inline-flex;
  display: -ms-inline-flexbox;
  width: calc(10px);
  width: -webkit-calc(10px);
  width: -moz-calc(10px);
}

There is no control over which rules to expand by supplied desired browser coverage right now. All known rules will be expanded which can increase the size of the stylesheets by a lot.

Output Prefixes

To generate a separate CSS file with expanded prefixes, the --output-browser-prefix arg must be set to the desired location of the file. In this case, an additional JSON file with the map of properties and values that were expanded will be written to the same location but under the .json name. It then can be used in conjunction with the CSS.supports function to test whether the prefixes file is required.

closure-stylesheets:~$ java -jar closure-stylesheets.jar --expand-browser-prefix \
> --output-browser-prefix example/prefixes.css --pretty-print example/prefix.css
.row {
  margin: 15px;
  display: flex;
  flex-flow: row wrap;
  height: calc(100vh - 10rem);
  background: linear-gradient(5deg,red,tan);
}
.col {
  flex-flow: row-reverse nowrap;
  display: inline-flex;
  width: calc(10px);
}

As you can see, the input does not contain the expanded properties, however 2 new files were generated:

.row {
  display: -webkit-box;
  display: -moz-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  -ms-flex-flow: row wrap;
  -webkit-flex-flow: row wrap;
  height: -webkit-calc(100vh - 10rem);
  height: -moz-calc(100vh - 10rem);
  background: -webkit-linear-gradient(5deg,red,tan);
  background: -moz-linear-gradient(5deg,red,tan);
  background: -ms-linear-gradient(5deg,red,tan);
  background: -o-linear-gradient(5deg,red,tan);
}
.col {
  -ms-flex-flow: row-reverse nowrap;
  -webkit-flex-flow: row-reverse nowrap;
  display: -webkit-inline-box;
  display: -webkit-inline-flex;
  display: -ms-inline-flexbox;
  width: -webkit-calc(10px);
  width: -moz-calc(10px);
}
{
  "background": [
    "linear-gradient(5deg,red,tan)"
  ],
  "display": [
    "flex",
    "inline-flex"
  ],
  "width": [
    "calc(10px)"
  ],
  "flex-flow": [
    "row wrap"
  ]
}

There are 3 general cases for the map:

  1. A property regardless of the value, such as flex-flow that will be expanded into -[]-flex-flow. The shortest value will be added to the map (row wrap), because here we expand the property name.
  2. A value of a property, such as display: flex or display: inline-flex. Each value will be added to the map to be tested against its property name.
  3. A value which is a function, like calc or linear-gradient. The shorted value will be selected (e.g., between calc(10px) and calc(100vh - 10rem), the first one will be selected) and its property name used (such as width in the example).

The map will only be created for unprefixed properties, because only they are expanded, i.e. -ms-flex is not expanded and will not be tested against because it's already in the CSS.

Now the prefixes map can be used on a page to figure out if to download the fallbacks:

/* eslint-env browser */
const entries = Object.entries(prefixes)
let supportsAll = true
if ('CSS' in window && 'supports' in CSS) {
  let nonSupported = []
  for (let i=0; i<entries.length; i++) {
    const [keys, values] = entries[i]
    // eg the key is either "flex", or "flex|-ms-flex"
    // when multiple given, ensure at least one is supported
    const k = keys.split('|')
    let anyKeySupported = false
    for (let j=0; j<k.length; j++) {
      const key = k[j]
      // values is an array against property like [
      //  "flex|-webkit-flex"
      //  "inline-flex|-ms-inline-flexbox",
      // ] or just single value ["auto"]
      const sup = values.map((value) => {
        const v = value.split('|')
        let anyValueSupported = false
        for (let n=0; n<v.length; n++) {
          const val = n[j]
          const s = CSS.supports(key, val)
          if (s) {
            anyValueSupported = true
            break
          } else {
            nonSupported.push(`${key}: ${val}`)
          }
        }
        return anyValueSupported
      })
      const allValuesSupported = sup.filter(Boolean).length
        == values.length
      if (allValuesSupported) {
        anyKeySupported = true
        break
      }
    }
    if (!anyKeySupported) supportsAll = false
  }
  if (nonSupported.length) {
    console.log('Browser does not support CSS properties: %s',
      nonSupported.join('\n '))
  }
} else {
  supportsAll = false
}
if (!supportsAll) {
  const link = document.createElement('link')
  link.rel = 'stylesheet'
  link.href = 'prefixes.css'
  document.head.appendChild(link)
}

And a noscript version should be added to the head of the document:

<html>
  <head>
    <!-- the support detection script -->
    <noscript>
      <link href="prefixes.css" rel="stylesheet">
    </noscript>
  </head>
</html>

Specific Prefixes

When creating a separate output for prefixes, it is possible to have control over which rules are actually kept in the output CSS, and which are put into the external prefixes CSS. This is done with the --prefix flag. It can be of four kinds:

  1. A generic property name, such as hyphens, which will keep all expanded prefixes for the hyphens property name in the output CSS.
  2. An expanded, prefixed property name, e.g., -ms-flex that will keep that rule in the output.
  3. A property name with a generic value that will keep all expansions of that property-value pair, such as position:sticky.
  4. A property name with an expanded value to specify the exact expansions that should be preserved, e.g., display:-ms-flexbox.
  5. A generic function name, e.g., calc.
  6. (todo) An expanded function name, e.g., -webkit-calc.

The map will be generated accordingly with the account of rules already present in the output CSS. Consider the following style:

.row {
  margin: 15px;
  display: flex;
  display: -ms-inline-flexbox;
  display: inline-flex;
  flex: auto;
  hyphens: auto;
  position: sticky;
}
.col {
  height: calc(1rem - 10px);
}
closure-stylesheets:~$ java -jar closure-stylesheets.jar --expand-browser-prefix \
> --output-browser-prefix example/prefix/output.css --prefixes hyphens --prefixes -ms-flex \
> --prefixes display:-ms-flexbox --prefixes position:sticky --prefixes calc --pretty-print \
> example/prefix/style.css
.row {
  margin: 15px;
  display: flex;
  display: -ms-flexbox;
  display: -ms-inline-flexbox;
  display: inline-flex;
  flex: auto;
  -ms-flex: auto;
  hyphens: auto;
  -webkit-hyphens: auto;
  -moz-hyphens: auto;
  -ms-hyphens: auto;
  position: sticky;
  position: -webkit-sticky;
}
.col {
  height: calc(1rem - 10px);
  height: -webkit-calc(1rem - 10px);
  height: -moz-calc(1rem - 10px);
}

This produced the following prefixes file and output map:

.row {
  display: -webkit-box;
  display: -moz-box;
  display: -webkit-flex;
  display: -webkit-inline-box;
  display: -webkit-inline-flex;
  -webkit-box-flex: auto;
  -moz-box-flex: auto;
  -webkit-flex: auto;
}
{
  "flex|-ms-flex": [
    "auto"
  ],
  "display": [
    "flex|-ms-flexbox",
    "inline-flex|-ms-inline-flexbox"
  ]
}
  • The hyphens rule was not added to the map because all of its expansions are present in the source CSS.
  • The flex|-ms-flex were combined into a single key, so that we can check that either of those is supported (property name combination), and each of the expanded display props such as flex and inline-flex were also combined to form flex|-ms-flexbox and -ms-inline-flexbox|inline-flex (property value combination).
  • The position:sticky was not added to the map because it was globally prefixed. Using the script we've given above, it's possible to optimise the prefixes CSS tree loading process.
  • The calc function was not added because it was specified in --prefixes flag.

For example, neither Safari nor Edge support hyphens without the -webkit- prefix, however Firefox has supported it for a number of years already. So we can pass --prefixes -webkit-hyphens:auto -ms-hyphens:auto to include those in the main stylesheet, while placing the rest of the rules in the separate prefixes CSS tree that will be downloaded on demand.

Keyframes

Additionally, keyframe rules can be generated for webkit, by using the @gen-webkit-keyframes comment before the rule. This was part of the compiler however not documented.

/* @gen-webkit-keyframes */
@keyframes fadeIn {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}
.RunFadeIn {
  opacity: 0;
  animation: fadeIn 0.5s;
  animation-fill-mode: forwards;
}
@keyframes fadeIn {
  0% {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
@-webkit-keyframes fadeIn {
  0% {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
.RunFadeIn {
  opacity: 0;
  animation: fadeIn .5s;
  animation-fill-mode: forwards;
}

Minification

You can concatenate and minify a list of stylesheets with the following command:

$ java -jar closure-stylesheets.jar input1.css input2.css input3.css

This will print the minified output to standard out. You can also specify a file to write the output to using the --output-file option:

$ java -jar closure-stylesheets.jar --output-file output.css input1.css input2.css input3.css

Of course, the > operator also works just as well:

$ java -jar closure-stylesheets.jar input1.css input2.css input3.css > output.css

If you would like to create a vendor-specific stylesheet, you can use the --vendor flag. Current recognized vendors are: WEBKIT, MOZILLA, OPERA, MICROSOFT, and KONQUEROR. When this flag is present, all vendor-specific properties for other vendors will be removed.

Linting

Closure Stylesheets performs some static checks on your CSS. For example, its most basic function is to ensure that your CSS parses: if there are any parse errors, Closure Stylesheets will print the errors to standard error and return with an exit code of 1.

--allowed-non-standard-function, --allow-unrecognized-functions

It will also error out when there are unrecognized function names or duplicate style declarations. For example, if you ran Closure Stylesheets on linting.gss:

.logo {
  width: 150px;
  height: 55px;
  background-image: urel('http://www.google.com/images/logo_sm.gif');
  border-color: #DCDCDC;
  border-color: rgba(0, 0, 0, 0.1);
}

Then you would get the following output:

Detected multiple identical, non-alternate declarations in the same ruleset. If this is intentional please use the /* @alternate */ annotation. border-color:[rgba(0,0,0,.1)] in example/linting.gss at line 1 column 1:
.logo {
^

Unknown function "urel" in example/linting.gss at line 4 column 21:
  background-image: urel('http://www.google.com/images/logo_sm.gif');
                    ^

2 error(s), 0 warning(s)

In this particular case, the function urel() should have been url(), though if you are using a function that is not on the whitelist (see CssFunctionNode for the list of recognized functions, which is admittedly incomplete), then you can specify --allowed-non-standard-function to identify additional functions that should be whitelisted:

java -jar closure-stylesheets.jar --allowed-non-standard-function urel example/linting.gss

The --allowed-non-standard-function flag may be specified multiple times.

It is also possible to disable the check for unknown functions altogether using the --allow-unrecognized-functions flag.

Further, in this example, the multiple declarations of border-color are intentional. They are arranged so that user agents that recognize rgba() will use the second declaration whereas those that do not will fall back on the first declaration. In order to suppress this error, use the /* @alternate */ annotation that the error message suggests as follows:

.logo {
  width: 150px;
  height: 55px;
  background-image: url('http://www.google.com/images/logo_sm.gif');
  border-color: #DCDCDC;
  /* @alternate */ border-color: rgba(0, 0, 0, 0.1);
}

This signals that the re-declaration is intentional, which silences the error. It is also common to use this technique with multiple background declarations that use -webkit-linear-gradient, -moz-linear-gradient, etc. In general, using conditionals to select the appropriate declaration based on user agent is preferred; however, that requires the additional overhead of doing user agent detection and serving the appropriate stylesheet, so using the @alternate annotation is a simpler solution.

On the other hand, the check for duplicates can be disabled altogether with --allow-duplicate-declarations flag. This can be useful when compiling some vendor CSS, for example, Bootstrap, which will on purpose write multiple declarations within the same block:

abbr[title],
abbr[data-original-title] {
  text-decoration: underline;
  -webkit-text-decoration: underline dotted;
  text-decoration: underline dotted; /* duplicate */
}
button:focus {
  outline: 1px dotted;
  outline: 5px auto -webkit-focus-ring-color; /* duplicate */
}

--allow-unrecognized-properties, --allowed-unrecognized-property

By default, Closure Stylesheets validates the names of CSS properties used in a stylesheet. We have attempted to capture all legal properties in the hardcoded list of recognized properties that is bundled with Closure Stylesheets. However, you can allow properties that aren't in the list with the --allowed-unrecognized-property flag. Consider the file bleeding-edge.gss:

.amplifier {
  /* A hypothetical CSS property recognized by the latest version of WebKit. */
  -webkit-amp-volume: 11;
}

Then running the compiler would yield the following error:

-webkit-amp-volume is an unrecognized property in example/bleeding-edge.gss at line 3 column 3:
  -webkit-amp-volume: 11;
  ^

1 error(s), 0 warning(s)

You can whitelist -webkit-amp-volume with the --allowed-unrecognized-property flag as follows:

java -jar closure-stylesheets.jar \
    --allowed-unrecognized-property -webkit-amp-volume example/bleeding-edge.gss

Like --allowed-non-standard-function, --allowed-unrecognized-property may be specified multiple times, once for each property to whitelist. We discourage using the blanket --allow-unrecognized-properties because it lets through everything, including simple spelling mistakes.

Note that some recognized properties will emit warnings. These warnings will not be silenced with the --allowed-unrecognized-property flag.

RTL Flipping

Closure Stylesheets has support for generating left-to-right (LTR) as well as right-to-left (RTL) stylesheets. By default, LTR is the assumed directionality for both the input and output, though those settings can be overridden by --input-orientation and --output-orientation, respectively.

For example, consider the following stylesheet, rtl-example.gss, which is designed for an LTR page:

.logo {
  margin-left: 10px;
}

.shortcut_accelerator {
  /* Keyboard shortcuts are untranslated; always left-to-right. */
  /* @noflip */ direction: ltr;
  border-right: 2px solid #ccc;
  padding: 0 2px 0 4px;
}

Generating the equivalent stylesheet to use on an RTL version of the page can be achieved by running java -jar closure-stylesheets.jar --pretty-print --output-orientation RTL rtl.gss, which prints:

.logo {
  margin-right: 10px;
}
.shortcut_accelerator {
  direction: ltr;
  border-left: 2px solid #ccc;
  padding: 0 4px 0 2px;
}

Note how the following properties were changed:

  • margin-left became margin-right
  • border-right became border-left
  • The right and left values of padding were flipped.

However, the direction property was unchanged because of the special @noflip annotation. The annotation may also appear on the line before the property instead of alongside it:

  /* @noflip */
  direction: ltr;

Renaming

Closure Stylesheets makes it possible to rename CSS class names in the generated stylesheet, which helps reduce the size of the CSS that is sent down to your users. Of course, this is not particularly useful unless the class names are renamed consistently in the HTML and JavaScript files that use the CSS. Fortunately, you can use the Closure Compiler to update the class names in your JavaScript and Closure Templates to update the class names in your HTML.

To get the benefits of CSS renaming in Closure, instead of referencing a CSS class name as a string literal, you must use that string literal as an argument to goog.getCssName():

// Do the following instead of goog.dom.getElementByClass('dialog-content'):
var element = goog.dom.getElementByClass(goog.getCssName('dialog-content'));

Similarly, in a Closure Template, you must wrap references to CSS classes with the css command:

{namespace example}

/**
 * @param title
 */
{template .dialog}
<div class=\"{css('dialog-content')}\">
  <div class=\"{css('dialog-title')}\">{$title}</title>
  {call .content data=\"all\" /}
</div>
{/template}

When you generate the JavaScript for the template, be sure to use the --cssHandlingScheme GOOG option with SoyToJsSrcCompiler. This ensures that the generated JavaScript code will also use goog.getCssName(). For example, if the above were named dialog.soy, then the following command would be used to create dialog.soy.js:

java -jar SoyToJsSrcCompiler.jar \\
    --shouldProvideRequireSoyNamespaces \\
    --codeStyle concat \\
    --cssHandlingScheme GOOG \\
    --outputPathFormat '{INPUT_FILE_NAME_NO_EXT}.soy.js' \\
    dialog.soy

The contents of the generated dialog.soy.js file are:

// This file was automatically generated from dialog.soy.
// Please don't edit this file by hand.

goog.provide('example');

goog.require('soy');
goog.require('example');


example.dialog = function(opt_data) {
  return '<div class=\"' + goog.getCssName('dialog-content') + '\"><div class=\"' +
      goog.getCssName('dialog-title') + '\">' + soy.$$escapeHtml(opt_data.title) +
      '</title>' + example.content(opt_data) + '</div>';
};

Note the uses of goog.getCssName() in the generated JavaScript file.

Now that all references to CSS class names are wrapped in goog.getCssName(), it is possible to leverage renaming. By default, goog.getCssName() simply returns the argument that was passed to it, so no renaming is done unless a renaming map has been set.

When running Closure Library code without processing it with the Closure Compiler, it is possible to set a renaming map by defining a global variable named CLOSURE_CSS_NAME_MAPPING in JavaScript code that is loaded before the Closure Library's base.js file. For example, if you defined your CSS in a file named dialog.gss:

.dialog-content {
  padding: 10px;
}

.dialog-title {
  font-weight: bold;
}

Then you would run the following command to generate a stylesheet (dialog.css) with renamed classes, as well as the mapping data as a JavaScript file (renaming_map.js):

java -jar closure-stylesheets.jar \\
    --pretty-print \\
    --output-file dialog.css \\
    --output-renaming-map-format CLOSURE_UNCOMPILED \\
    --rename CLOSURE \\
    --output-renaming-map renaming_map.js \\
    dialog.gss

The generated dialog.css would be as follows:

.a-b {
  padding: 10px;
}
.a-c {
  font-weight: bold;
}

while the generated renaming_map.js would be:

CLOSURE_CSS_NAME_MAPPING = {
  \"dialog\": \"a\",
  \"content\": \"b\",
  \"title\": \"c\"
};

An HTML file that uses the renaming map must be sure to include both the generated stylesheet with renamed class names as well as the renaming map:

<!doctype html>
<html>
<head>
  <link rel=\"stylesheet\" href=\"dialog.css\" type=\"text/css\">
</head>
<body>

  <script src=\"renaming_map.js\"></script>
  <script src=\"path/to/base.js\"></script>
  <script>
    goog.require('example');
  </script>
  <script>
    // Your application logic that uses example.dialog() and other code.
  </script>

</body>
</html>

This ensures that when goog.getCssName('dialog-content') is called, it returns 'a-b'. In this way, the abbreviated name is used in place of the original name throughout the code.

An astute reader will note that so far, we have reduced only the size of the stylesheet, but not the JavaScript. To reduce the size of the JavaScript code, we must use the Closure Compiler in either SIMPLE or ADVANCED mode with the --process_closure_primitives flag enabled (it is enabled by default). When enabled, if it finds a call to goog.setCssNameMapping() in any of its inputs, it will use the argument to goog.setCssNameMapping() as the basis of a renaming map that is applied at compile time. To create the appropriate renaming map with Closure Stylesheets, use CLOSURE_COMPILED as the argument to --output-renaming-map-format:

java -jar closure-stylesheets.jar \\
    --pretty-print \\
    --output-file dialog.css \\
    --output-renaming-map-format CLOSURE_COMPILED \\
    --rename CLOSURE \\
    --output-renaming-map renaming_map.js \\
    dialog.gss

This yields the following content for renaming_map.js:

goog.setCssNameMapping({
  \"dialog\": \"a\",
  \"content\": \"b\",
  \"title\": \"c\"
});

Now renaming_map.js is a suitable input for the Closure Compiler. Recall our original snippet of JavaScript code:

var element = goog.dom.getElementByClass(goog.getCssName('dialog-content'));

If passed to the Closure Compiler in SIMPLE mode along with renaming_map.js, it will be transformed to the following after compilation:

var element = goog.dom.getElementByClass(\"a-b\");

This achieves the goal of reducing both CSS and JS file sizes without changing the behavior of the application.

Admittedly, using CSS renaming is a fairly advanced option that requires a well-organized build system to ensure that the appropriate CSS and JS assets are produced for both development and production. See MoreOnCssRenaming for more details on this topic.

Note: it is also possible to exclude certain class names from being renamed by using the --excluded_classes_from_renaming flag. This may be necessary if some of your HTML is generated by a process that does not take CSS renaming into account. For example, if you are using a Python Django server and are using its template system, then any CSS classes used in those templates will not be renamed (unless you introduce a process to do so). In order to ensure that the JS and CSS that use the HTML reference CSS classes consistently, each CSS class in the Django template should be passed as an argument to Closure Stylesheets with the --excluded_classes_from_renaming flag when generating the CSS.

References to CSS class names that are excluded from renaming should never be wrapped in goog.getCssName(), or else they run the risk of being partially renamed.

Root Selector

When generating CSS that must work only inside of specific element, the --root-selector option may be passed to prepend all selectors with a global selector.

.EXAMPLE {
  display: inline;
}
span {
  text-align: right;
}
.test {
  color: green;
}

In case when the root selector is already part of the selector, it will just be skipped. Only ID and Class root selectors are supported right now.

closure-stylesheets:~$ java -jar closure-stylesheets.jar --root-selector .EXAMPLE --rename \
> CLOSURE --pretty-print example/root-selector.css
.EXAMPLE {
  display: inline;
}
.EXAMPLE span {
  text-align: right;
}
.EXAMPLE .a {
  color: green;
}