prejss
v1.4.0
Published
Get the power of PostCSS with plugins in your JSS styles. 🎨 Just put CSS into JS and get it as JSS object.
Downloads
58
Maintainers
Readme
PreJSS 🎨
Fast, component-friendly, fully customizable, universal СSS-to-JSS adapter. Use the best bits of PostCSS, syntax and plugins (1, 2, 3) to get result as JSS objects from Tagged Template literals (a recent addition to JavaScript/ES6).
PostCSS is a tool for transforming styles with JS plugins. These plugins can lint your CSS, support variables and mixins, transpile future CSS syntax, inline images, and more.
PreJSS allows you to get JSS objects "on-the-fly" from plain CSS, PostCSS, SCSS, CSS Modules, Stylus and LESS styles. Just put your CSS and get it as JSS.
Beside of that, PreJSS is the shortest way to get high-optimized Critical CSS for Isomorphic Applications while it still fits good for Single Page Applications.
Are you new to JSS? It will save your time, improve your productivity and reduce cognitive loading by allowing you to use CSS and JSS notation together. It means sometimes you can write CSS, sometimes - JSS. That all according to your choice.
- See PreJSS Example Application with using React.js, isomorphic architecture, Server-Side Rendering (SSR), Hot Module Replacement (HMR), JSS and PreJSS 🎨 with run-time and pre-compilation
Supports:
- React.js for Web
- React Native (WIP)
- Vanilla JS and any kind of Template Engines
- SSR (Server-side Rendering)
- Disabled JavaScript in Web Browser
- Fast Run-time execution by Babel plugin
- Hot Module Replacement with webpack
- Linting
- CLI tool for converting
- Syntax Highlighting (WIP)
Content
Motivation
CSS is good enough solution when you develop web-sites and simple UIs.
But when you develop Web Applications and complex UIs, CSS is something like legacy.
Since 2015 we use React Native where styles are defined by JavaScript objects and we found it extremely useful.
But how to migrate from CSS/SCSS to JSS "smoothly and on-time"?
At first we developed jss-from-css for process SCSS-to-JSS migration in cheapest and fastest way.
Lately we have found that it could be just very nice to define JSS styles in the format which we already used to and even extend it to use some JavaScript injections and so on. We introduced Adapters which provides mechanism to use this package also with any CSS-in-JS library.
So out-of-the-box PreJSS allows you to use PostCSS features and plugins which enable you to use:
- plain CSS
- SCSS
- SASS
- LESS
- Stylus
- SugarSS
It could help your to migrate "smoothly" from any format above to JSS. That's how we solved this issue.
You can use any of PostCSS plugins like Autoprefixer, postcss-next and so on.
Finally, think about it like:
- PostCSS + Template Strings + JSS + ❤️ = PreJSS
Getting Started
To get started using PreJSS in your applications, it would be great to know three things:
PreJSS Styles Declaration is top-level definition which is processing by PreJSS. In other words, CSS Code with Expressions as Tagged Template String which is converting to string and using as input for Parser in Adapters.
Parser is core thing in PreJSS. Usually it's a package with pure function which is using by Adapters. PreJSS Parser can be sync (by default) and async. PreJSS uses prejss-postcss-parser by default (as built-in).
Adapters is core thing and the point for customization your PreJSS. PreJSS Adapter is pure function which handle Tagged Template String, prepare specified CSS, parse it to JSS and finalize JSS when we have to adopt it to other CSS-in-JS platforms (Aphrodite, React Native, etc). There is two kinds of Adapters - sync for basic usage and async – for improving performance of Server-Side Rendering.
The best way to get started right now is to take a look at how these three parts come together to example below.
Installation
npm install prejss --save
Example
import color from 'color'
import preJSS from 'prejss'
const styles = preJSS`
$bg-default: #ccc;
button {
color: ${props => props.isPrimary ? 'palevioletred' : 'green'};
display: block;
margin: 0.5em 0;
font-family: Helvetica, Arial, sans-serif;
&:hover {
text-decoration: underline;
animation: ${rotate360} 2s linear infinite;
}
}
ctaButton {
@include button;
&:hover {
background: ${color('blue').darken(0.3).hex()}
}
}
@media (min-width: 1024px) {
button {
width: 200px;
}
}
@keyframes rotate360 {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@global {
body {
color: $bg-default;
}
button {
color: #888888;
}
}
`
Result
The example above transform styles
to the following object:
// ...
const styles = {
button: {
color: props => props.isPrimary ? 'palevioletred' : 'green',
display: 'block',
margin: '0.5em 0',
fontFamily: 'Helvetica, Arial, sans-serif',
'&:hover' {
textDecoration: 'underline',
animation: 'rotate360 2s linear infinite'
}
},
ctaButton: {
color: () => 'palevioletred',
display: 'block',
margin: '0.5em 0',
fontFamily: 'Helvetica, Arial, sans-serif',
'&:hover' {
textDecoration: 'underline',
animation: 'rotate360 2s linear infinite',
background: color('blue').darken(0.3).hex()
}
},
'@media (min-width: 1024px)': {
button: {
width: 200,
}
},
'@keyframes rotate360': {
from: {
transform: 'rotate(0deg)'
},
to: {
transform: 'rotate(360deg)'
}
},
'@global': {
body: {
color: '#ccc'
},
button: {
color: '#888888'
}
}
}
Render with Vanilla JS
import jss from 'jss'
import preset from 'jss-preset-default'
import styles from './styles'
// One time setup with default plugins and settings.
jss.setup(preset())
const { classes } = jss.createStyleSheet(styles).attach()
document.body.innerHTML = `
<div>
<button class="${classes.button}">Button</button>
<button class="${classes.ctaButton}">CTA Button</button>
</div>
`
Render with React.js
import jss from 'jss'
import preset from 'jss-preset-default'
import injectSheet from 'react-jss'
import styles from './styles'
// One time setup with default plugins and settings.
jss.setup(preset())
const Buttons = ({ button, ctaButton }) => (
<div>
<button className={button}>Button</button>
<button className={ctaButton}>CTA Button</button>
</div>
)
export default injectSheet(styles)(Buttons)
Server-Side Rendering
As you well know, React.js and JSS are both support Server-Side Rendering (SSR).
You can use it with prejss
without any limitations:
import express from 'express'
import jss from 'jss'
import preset from 'jss-preset-default'
import React from 'react'
import { renderToString } from 'react-dom/server'
import { SheetsRegistryProvider, SheetsRegistry } from 'react-jss'
// this module is defined in the previous example
import Buttons from './buttons'
// One time setup with default plugins and settings.
jss.setup(preset())
const app = express()
app.use('/', () => {
const sheets = new SheetsRegistry()
const content = renderToString(
<SheetsRegistryProvider registry={sheets}>
<Buttons />
</SheetsRegistryProvider>
)
const criticalCSS = sheets.toString()
res.send(`
<html>
<head>
<style id="critical-css" type="text/css">
${criticalCSS}
</style>
</head>
<body>
${content}
</body>
</html>
`)
})
app.listen(process.env['PORT'] || 3000)
Performance Matters
PostCSS parser is using by default in PreJSS. Since PostCSS is adopted for high performance - it uses async approach. How to get it as sync PreJSS Constraint? At the moment PreJSS uses deasync for parsing CSS styles on the server. It has some unpleasant costs - deasync
blocks Event Loop so everything could be blocked until CSS parsing operation is processing.
Everything will be OK if you use basic approach which has been described in Example. In this way deasync
affects only total launch time for server application. Generally it's not critical when we compare it with DX (Developer Experience), useful usage.
But if you wrap PreJSS Constraints to functions - it will cause the problem. Let's have a look:
import preJSS from 'prejss'
app.use('/', () => {
// At this point Event Loop will be blocked by deasync
// It means other requests will be "frozen" until CSS is processing
const customStyles = preJSS`
button {
color: green;
display: block;
margin: 0.5em 0;
font-family: Helvetica, Arial, sans-serif;
}
`
res.send(getCustomizedPage(customStyles))
})
perom
Async Adapters as Solution
If you have wrapped PreJSS Constraints please use async Adapter and async-await:
import preJSS, { preJSSAsync } from 'prejss'
app.use('/', async () => {
// At this point Event Loop will not be blocked
// Other requests will be processing parallely while CSS is processing
const customStyles = await preJSSAsync`
button {
color: green;
display: block;
margin: 0.5em 0;
font-family: Helvetica, Arial, sans-serif;
}
`
res.send(getCustomizedPage(customStyles))
})
It will totally solve deasync effect.
Notice: If you don't have async-await (e.g. you have Node.js version lower than 7.6) it will work as well as Promises.
Disabled JavaScript in Web Browser
Server-Side Rendering and Critical CSS both allows your users to see page even without JavaScript in Web Browsers. You could implement GET and POST fallbacks for all possible actions such as CRUD operations like Google did it.
Parsers
There is two kind of parsers - sync and async. For both cases it's a pure function:
(preparedCSS: string): object
(preparedCSS: string): Promise
By default prejss-postcss-parser is using by default. This parser uses postcss-js under hood and supports PostCSS config and plugins.
Feel free to create and distribute your own parser. It should have package name in the following format:
prejss-<PARSER_NAME>-parser
Adapters
What does it mean "Adapters" in PreJSS?
It looks like as "class-to-function" adapter with lifecycle hooks. You already know this concept if you learned React.js or Ember.js.
PreJSS Adapters covers prepare
, parse
and finalize
steps.
Default (built-in) adapters implements only parse
step so you can customize it as you want.
You can create (and distribute!) your own adapters or customize existed one by overriding any of those steps:
prepare(rawStyles: string): string
This hook is using for creating custom pre-processing CSS. It calls before
parse()
and looks like Redux middleware. For example, you can strip JavaScript comments or execute embedded JavaScript code. See example below.parse(CSS: string): object
The main hook which is using for converting CSS to JSS Object. Default Adapter uses PostCSS Processor for this operation.
finalize(result: object): object
This hook is using for post-processing your final object. Here you can convert your JSS Objects to React Native or any other JSS library.
Feel free to play with it:
import preJSS, { createAdapter, defaultAdapter, keyframes } from 'prejss'
const fromMixedCSS = createAdapter({
...defaultAdapter,
prepare: rawStyles => defaultAdapter.prepare(
rawStyles.replace(/^\s*\/\/.*$/gm, '') // remove JS comments
),
})
const getStyles = ({ color, animationSpeed, className }) => fromMixedCSS`
${'button' + (className ? '.' + className : '')}
color: ${() => color || 'palevioletred'};
display: block;
margin: 0.5em 0;
font-family: Helvetica, Arial, sans-serif;
// Let's rotate the board!
&:hover {
text-decoration: underline;
animation: rotate360 ${animationSpeed || '2s'} linear infinite;
}
}
// Special styles for Call-to-Action button
ctaButton {
@include button;
&:hover {
background: ${color('blue').darken(0.3).hex()}
}
}
`
Pre-compilation
It's not great idea to parse CSS in run-time on client-side. It's slow and expensive operations. Additonaly it requires to include PostCSS (or any other parsers) to JavaScript bundle.
The good news is that we don't have to do it! 🎉 Really.
There is great babel-plugin-prejss plugin which transforms PreJSS Constraints CSS styles example above to JSS object in the final scripts.
Step-by-Step manual:
Add
babel-plugin-prejss
to your project:npm install babel-plugin-prejss --save-dev
Configure it by updating
.babelrc
in your project directory:plugins: [ [ 'prejss', { 'namespace': 'preJSS' } ] ]
Build your project! In your JavaScript bundles you will have replaced
preJSS
constraints by JSS objects directly. Babel do it for you. Not a magic - just a next generation JavaScript today. 😉
Hot Module Replacement with webpack
You can get Hot Module Replacement by using webpack and PostJSS loader to get real-time updates while you work with the project.
Step-by-Step manual:
Add
postjss
to your project:npm install postjss --save
Configure your webpack to use it:
{ test: tests.js, use: [ 'postjss/webpack/report-loader', 'babel-loader', 'postjss/webpack/hot-loader', ], },
Linting
Since we use PostCSS as default adapter - you can use stylelint for linting and postcss-reporter for warnings and errors.
So it works with using PostJSS like a charm:
Ecosystem
prejss-cli - get ES6 modules with JSS styles from CSS/SCSS/LESS styles or CSS frameworks
babel-plugin-transform-prejss - Turn PreJSS construction into JSS objects
babel-plugin-prejss - Plugin for Babel for pre-compiliation PreJSS constraints to JSS Objects
prejss-styles-loader - Styles loader for PreJSS and Webpack
postjss - loader for webpack to put Hot Module Replacement feature to your work process
Inspiration
- https://github.com/styled-components/styled-components
Thanks
We would love to say huge thanks for helping us: