longhand v1.0.0
longhand is a highly specialized CSS parser, focused on taking a shorthand style declaration -- such as margin: 10px -- and returning the implicit declarations that make up the shorthand (the longhands, if you will) -- such as margin-top: 10px; margin-right: 10px; margin-bottom: 10px; margin-left: 10px.
Features
- Supports all CSS shorthand properties*
- Can parse shorthand properties into their longhand equivalents
- Can check if a property is a valid longhand property for a given shorthand property
- Supports both camelCase 🐪 and kebab-case 🍢
- TypeScript types included
- JSDoc-style API documentation
- No external dependencies
- Unit tested
*on the browser. See Limitations for more details regarding server-side limitations.
Installation
pnpm
pnpm add longhandnpm
npm install longhandyarn
yarn add longhandUsage
Getting Started
The longhand package exports a single class, Longhand, which can be used to parse shorthand properties into their longhand equivalents. Both instance parse and static parse methods are available.
import { Longhand } from "longhand";
const { parse } = new Longhand();
{
const parsed = parse("margin", "10px"); // Instance method
} /* or */ {
const parsed = Longhand.parse("margin", "10px"); // Static method
}
// `{...}` just to create a new block scope to keep the example validThe parse method returns an instance of the LonghandStyle class, which contains the parsed longhand styles, without the original shorthand property. Those can, however, be accessed separately via originalProperty and originalValue.
import { Longhand } from "longhand";
const longhand = new Longhand();
const parsed = longhand.parse("margin", "10px");
console.log(parsed.originalProperty); // "margin"
console.log(parsed.originalValue); // "10px"
console.log(parsed.kebabCaseStyles); // { "margin-top": "10px", "margin-right": "10px", "margin-bottom": "10px", "margin-left": "10px" }
console.log(parsed.camelCaseStyles); // { "marginTop": "10px", "marginRight": "10px", "marginBottom": "10px", "marginLeft": "10px" }
console.log(parsed.styles); // { "margin-top": "10px", "margin-right": "10px", "margin-bottom": "10px", "margin-left": "10px", "marginTop": "10px", "marginRight": "10px", "marginBottom": "10px", "marginLeft": "10px" }
console.log(parsed.kebabCaseProperties); // [ "margin-top", "margin-right", "margin-bottom", "margin-left" ]
console.log(parsed.camelCaseProperties); // [ "marginTop", "marginRight", "marginBottom", "marginLeft" ]
console.log(parsed.properties); // [ "margin-top", "margin-right", "margin-bottom", "margin-left", "marginTop", "marginRight", "marginBottom", "marginLeft" ]
console.log(parsed.length); // 4
console.log(parsed.isValidLonghandProperty("margin-top")); // true
console.log(parsed.isValidLonghandProperty("margin-middle")); // falseExamples
For examples, see examples:
Documentation
For the full API, see DOCUMENTATION.md.
Running Server Side
The way it's written, longhand leverages browser methods to parse shorthands. This is to avoid reinventing the wheel. Unfortunately, this means that to work in Node (or Bun, or Deno, or the next big runtime), someone has to reinvent the wheel, or make it accessible on the server. Technically, only the CSSOM needs to be emulated, but that's accessed in the browser through a the window (element.style) so we'll need full DOM emulation through something like jsdom or happy-dom, or a full-on headless browser like puppeteer. If the former option is pursued, like in the server example, then the following code should enable functionality:
import { JSDOM } from "jsdom";
import { Longhand } from "longhand";
const dom = new JSDOM("<!DOCTYPE html>");
// @ts-expect-error
global.window = dom.window;
global.document = dom.window.document;Running longhand in a headless browser should not need any additional configuration.
Limitations
Zero-dependency just means that no external packages are needed, but the runtime environment is effectively a dependency, and therefore, YMMV.
There are two major limitations to look out for with this package:
jsdomis imperfect, and they have made some really questionable decisions particularly regarding the CSSOM -- which makes sense; who in their right mind emulates CSS 🥲? -- so you might get strange results. I have done my best to support as much as possible (see this) but as you can see by the test input shorthand-properties.json, styles marked asunsupported.propertymean thatjsdomdoesn't support the property at all (mostly really just the--webkit-*exclusives), and those marked asunsupported.valuemean thatjsdomunderstands the property but doesn't know thelonghands exist. Perhaps, this will someday improve. But for now, I recommend not using this package server-side, unless you have a contrived use-case that you can test works. (FYI,happy-domworks even worse according to the tests.)1.1. Another silly limitation of
jsdomis that, inlonghand,background: invalidthrows forinvalidnot being a validbackgroundvalue, butborderRadius: invalidis accepted as valid.As anyone who's worked on the web knows, browsers have a mind of their own, especially when it comes to CSS support. Therefore, the CSSOM also differs slightly browser to browser. This is less of an issue, and is kind of expected, but you should keep in mind that not even the people behind whatever browser you're reading this in can agree on a universal model, so this package can't do that either.
Why?
Fair question.
The short(hand) answer is that I thought I needed it for work, so I thought I'd turn it into a full-on package. Turns out, I didn't need it for work.
The long(hand) answer -- and the arguably more useful one for a README -- is that CSS shorthands are super convoluted to work with in a structured manner, so if one were to ever write a CSS parser, they would have to figure out all the different ways in which the browser unwraps a shorthand property into its individual components. All pre-existing solutions were incomplete, and they didn't need to be, so I wrote this.
License
MIT License (whatever that means)
© 2024-Present
1 year ago