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

narcissus

v1.0.0

Published

## Inline styles, sane

Downloads

1,103

Readme

narcissus

Inline styles, sane

npm

Narcissus is inline style sheets in Javascript, but sane. It borrows and steals many concepts from aphrodite, but extends certain ideas and simplifies the codebase.

Quick feature list:

  • Supports server side rendering
  • Does not generate CSS files
  • Supports media queries
  • Supports pseudo selectors :hover
  • Supports child selectors .parent .child { ... }
  • Small in size

Quick API documentation

Narcissus exports an inject function. This takes a plain javascript object and returns a hashed class name.

import { inject } from 'narcissus';

const className = inject({
  backgroundColor: 'blue',
});

You could then use this className however you like. One popular use case is within react components.

const Component = (props) => {
  return <div
    className={inject({
      backgroundColor: 'blue',
    })}
  >
    Hello there
  </div>;
};

Plain object documentation

Narcissus takes objects like React's inline styles

{
  backgroundColor: 'blue',
  fontSize: 14,
}

Narcissus does that whole browser prefix thing, well, we use inline-style-prefixer/static.

{
  backgroundColor: 'blue',
  userSelect: 'none',
}

// Outputs (added spaces for readability)
// .narcissus_1wbu9hn {
//   background-color:blue;
//   user-select:none;
//   -webkit-user-select:none;
//   -moz-user-select:none;
//   -ms-user-select:none;
// }

Narcissus handles media queries

{
  backgroundColor: 'blue',
  '@media (max-width: 800px)': {
    backgroundColor: 'red',
  },
}

// Outputs (added spaces for readability)
// .narcissus_1fxua3d {
//   background-color:blue;
// }
// @media (max-width: 800px){
//   .narcissus_1fxua3d{
//     background-color:red;
//   }
// }

Narcissus also lets you add :hover, :active, and sub selectors by using a selector extension API. Any key that starts with && (not & since that already means something in CSS) is automatically added to the resulting selector.

So...

{
  backgroundColor: 'blue',
  '&&:hover': {
    backgroundColor: 'red',
  },
  '&& h1': {
    // All h1s underneath this class will have the following styles...
    backgroundColor: 'green',
  },
  '@media (max-width: 800px)': {
    '&&:hover': {
      backgroundColor: 'purple',
    },
  },
}

// Outputs (added spaces for readability)
// .narcissus_1xdcqz1 {
//   background-color:blue;
// }
// .narcissus_1xdcqz1:hover {
//   background-color:red;
// }
// .narcissus_1xdcqz1 h1 {
//   background-color:green;
// }
// @media (max-width: 800px) {
//   .narcissus_1xdcqz1:hover {
//     background-color:purple;
//   }
// }

How does narcissus work, a story

CSS (cascading style sheets) are made up of selectors and style definitions... You probably already know this.

.some_class_name {
  background-color: blue;
}

You can link to css files inside the <head> of an html file with a <link>, but you can also insert them inside the html file itself!

<html>
  <head>
    <style>
      .some_class_name {
        background-color: blue;
      }
    </style>
  </head>
  <body>
  </body>
</html>

If you're using the DOM API, you can insert anything into the HTML, so...

What if... you inserted a CSS string, generated in Javascript, into the head?

You take a plain object...

{
  backgroundColor: 'blue',
}

You transform it into a string...

'{background-color:blue;}'

Give it a class name that is deterministic based on hashing the object... I'll talk about why determinism is important for narcissus below.

'.hashed_class_name{background-color:blue;}'

So what if we wrote a function that injected that string into the head... and returned the generated class name? Then maybe we can do this...

const Component = (props) => {
  return <div
    className={inject({
      backgroundColor: 'blue',
    })}
  />
};

Why not just use inline styles?

Good question, simply because inline styles do not support hovering or media queries the same way that CSS does.

I would love it if we didn't need to hack around CSS at all and if you could style everything inline!

const Component = (props) => {
  return <div
    className={inject({
      backgroundColor: 'blue',
      '&&:hover': {
        backgroundColor: 'red',
      },
      '@media (min-width: 800px)': {
        backgroundColor: 'purple',
      },
    })}
  />
};

Why not just use separate stylesheets?

This is more of a philosophical debate. There are many authors who have articulated the difference between the old HTML/CSS way of writing code and this idea of a component that React has popularized.

Here's my take:

  • Keep the style and the component code closer: not only is the source code literally closer, they are now in the same file, the coupling of a style is closer to the component that is being used. We are not writing selectors that have generic names that may collide with code we write in the future. Narcissus doesn't need to namespace it's selectors because hashing the object effectively does the same thing.

  • Components should be reusable: some components are logical components, and do not care about styles. Those components are reused easily. However, some components, like buttons, should be very specific about how they look no matter where you put them. I believe that such components have a responsibility to define their own styles as tightly as necessary and not allow arbitrary CSS overrides. I believe in a world where you can download a React component from npm, and not have to insert a separate stylesheet somewhere else in the application. You should only need to require() a component once.

Why is determinism important?

Narcissus could have been written with a counter, and each new class name that was generated would just get an incrementally higher value. However, when deploying Javascript in universal environments, as we do at Edusight, we need the react objects the client side generates to exactly match up with the react objects the server generates. Here, using a hash of the style object means the server and client will always agree what class name to generate.

Using narcissus on server side

import { startStatic, stopStatic, inject } from 'narcissus';

// First, startStatic to let narcissus know you are on the server
startStatic();

// Then do all your injection (if you're using react, all your react rendering with the injects in the render function)
inject({ backgroundColor: 'blue' });
inject({ backgroundColor: 'red' });

// Stop and extract the css
// css is the stylesheet string that should be injected into the head
// classNames is an array of generated classNames that you can use to tell narcissus on the client to not rehash
const [ css, classNames ] = stopStatic();

// Some magical serve file
serve(`<html>
  <head>
    <!-- The narcissus tag is used so that narcissus can consistently find itself -->
    <style data-narcissus>
      ${css}
    </style>
  </head>
  <body>
    <script>
      // I'm using ES6 syntax here so it won't actually work, but this is close to what you need
      import { rehydrate } from 'narcissus';
      var CLASSNAMES = ${JSON.stringify(classNames)}
      rehydrate(CLASSNAMES);
    </script>
  </body>
</html>`);