React 社区一直在探索各类 JSS 方案,好比如今比较出名的 styled-components
,但他们或多或少都有些问题存在,可是社区对 JSS 方案的探索一直没有停下,而如今看上去最像最佳方案的是 Linaria 库。翻了下这个库相关的中文资料几乎没有,因而写了这篇与你们分享介绍下相关的内容css
Linaria 是一个 零运行时 的JSS 框架,其特色有:html
const title = css` font-size: 18px; `;
复制代码
相似代码最终会被会被编译成node
.k4yi6fg {
font-size: 18px;
}
复制代码
其 class 命名是经过计算文件路径的哈希值肯定的react
不会再有编写组件时须要在 JS 文件和 CSS 文件跳转的上下文切换的状况。不过若是你想要分离的话,一样也是支持的webpack
由于 JSS 的样式其实就是 JS 变量而已,因此你能够很容易经过代码逻辑找到组件相关的样式逻辑,而不用惧怕重构会形成预料以外影响web
JSS 最迷人的一点就是,当你将 CSS 归属到 JS 下时,你就自动得到来使用 JS 编写 CSS 逻辑的能力,最基本的条件计算,被包装成简单函数调用的的复杂逻辑。这意味着 CSS 的表达能力的上限再也不局限于自身,而是由 JS 决定的npm
例如你能够编写这样的代码编程
const DEFAULT_COLOR = '#fffff'
const PRIMARY_COLOR = '#de2d68';
const getColor = Math.random() > 0.5 ? PRIMARY_COLOR : DEFAULT_COLOR;
const button = css` background-color: ${getColor()}; &:hover { background-color: ${Math.random() > 0.5 ? PRIMARY_COLOR : DEFAULT_COLOR}; } `;
复制代码
就像咱们刚才说的同样, JSS 其实只是 JS 变量,那么天然而然 JS 能作到的 Tree shaking ,Linaria 同样能作到。 这点对于 UI 库的开发者其实颇有吸引力,再也不须要引入额外的 babel 插件,而是自动经过 Tree shaking 来作到样式的按需引入json
Linaria 会自动经过添加浏览器前缀,帮你对一些特殊属性作兼容性支持,同时你依然可使用 PostCSS 作进一步的优化浏览器
经过 styled
API ,很容易去声明 React 动态样式组件。原理是经过 CSS 变量来实现组件样式自动更新的能力,常规的 CSS 方案则须要你手动的去维护相关的逻辑
const Box = styled.div` background-color: orange; height: ${props => props.size}px; width: ${props => props.size}px; `;
<Box size={48}> 复制代码
Linaria 的语法能够看做只是支持嵌套的 CSS 语法而已。没有变量,mixins 或 函数什么的,这些均可以用 JS 的逻辑来代替
行内样式存在局限性,而 Linaria 则支持 CSS 的全部特性:
经过 class 命名来应用样式要比行内样式快
由于 CSS 在编译器被抽出到 CSS 文件中了,所以浏览器能够并行的下载 CSS 和 JS 文件,加速首屏时间
不少 JSS 框架是经过某个第三方 JS 库来解析 CSS 字符串的,因为须要包含解析器,会使得库的体积增大。 而且 CSS 的解析执行被延迟到了 JS 运行时,在一些低端设备上,很容易带来能够感知到的延迟
Linaria 特殊就特殊在它 没有运行时 这一说,它的样式会在编译期解析抽出来,生成 CSS 文件,不须要在运行时额外解析一次。
对于基于组件的 JSS 框架来讲,使用不一样的 props 渲染同一个组件会使得同一份样式被复制屡次,这使得 SSR 时产物的体积会增大。尽管大部分状况下,这种问题带来的性能损耗不值一提,可是对于渲染多个仅有细微差别的大型列表时,很容易使得体积迅速增加
除此以外,在作 SSR 你须要将写在 JS 文件中的 CSS 样式抽取出来,而后传输给浏览器,这一样增长产物体积
Linaria 会生成惟一的样式规则,使用 CSS 变量来应用不一样的差别,因此不会有重复样式的问题,也就减小产物的体积
非法 JS 值和错误的 CSS 语法,都会在编译期检查出来,而不用等到运行时才暴露出来。这意味着你不会在生产模式遇到这些低级错误,一样的, Linaria 支持 stylelint ,你依旧能够得到原来同样的 lint 体验。
不一样于其余的一些 JSS 框架, Linaria 语法只是支持嵌套的 CSS 原生语法而已,没有什么上手成本。完美支持面向 Copy-Paste 编程
若是你的网站须要在禁止 JavaScript 的状况运行,或者想要在编译生成静态网页的运行, Linaria 一样能够在这些状况下正常运行,由于它的 没有运行时 特性
Linaria 同时支持 Webpack , Rollup 和 Sevlte ,本文只会讲述配合 Webpack 使用的方式,若有其余需求,能够去官网看下配合其余构建工具的文档
若是你在项目里使用里 Babel 作转译或这 polyfill 什么的,那么必定要在根项目下创建一个 Babel 配置文件,把你须要的 presets 和 plugin 写在里面,否则 Linaria 会没法解析你的代码
和 Webpack 配合很容易,只须要将 babel-loader 加上 linaria/loader
便可,必定确保 linaria/loader
紧挨着且在 babel-loader 以后
{
test: /\.js$/,
use: [
{ loader: 'babel-loader' },
{
loader: 'linaria/loader',
options: {
sourceMap: process.env.NODE_ENV !== 'production',
},
}
],
}
复制代码
此外,为了将收集到的样式抽取出来,你须要另一个 Webpack 插件 mini-css-extract-plugin
, 执行 npm i -D css-loader mini-css-extract-plugin
来安装
而后导入 mini-css-extract-plugin
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
复制代码
而后设置相应的解析规则和插件
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: process.env.NODE_ENV !== 'production',
},
},
{
loader: 'css-loader',
options: {
sourceMap: process.env.NODE_ENV !== 'production',
},
},
],
},
复制代码
把 mini-css-extract-plugin
加入 Webpack 配置的 plugins
属性中
new MiniCssExtractPlugin({
filename: 'styles.css',
});
复制代码
你能够经过 HTMLWebpackPlugin
插件来将抽离出来的 CSS 文件与构建产生的 html 文件链接起来,对于生产模式,你也许须要将哈希值设置的 CSS 文件名上:
new MiniCssExtractPlugin({
filename: 'styles-[contenthash].css',
});
复制代码
由于 Linaria 会抽离出来的样式文件过一遍 Webpack .css
规则的 loader 流水线,因此你能够很容易去使用 postcss
或 clean-css
来作一些定制化操做
const webpack = require('webpack');
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const dev = process.env.NODE_ENV !== 'production';
module.exports = {
mode: dev ? 'development' : 'production',
devtool: 'source-map',
entry: {
app: './src/index',
},
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: '/dist/',
filename: '[name].bundle.js',
},
optimization: {
noEmitOnErrors: true,
},
plugins: [
new webpack.DefinePlugin({
'process.env': { NODE_ENV: JSON.stringify(process.env.NODE_ENV) },
}),
new MiniCssExtractPlugin({ filename: 'styles.css' }),
],
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{ loader: 'babel-loader' },
{
loader: 'linaria/loader',
options: { sourceMap: dev },
},
],
},
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: process.env.NODE_ENV !== 'production',
},
},
{
loader: 'css-loader',
options: { sourceMap: dev },
},
],
},
{
test: /\.(jpg|png|gif|woff|woff2|eot|ttf|svg)$/,
use: [{ loader: 'file-loader' }],
},
],
},
devServer: {
contentBase: [path.join(__dirname, 'public')],
historyApiFallback: true,
},
};
复制代码
你可使用下面命令来安装全部必须的 npm 库
npm i -D webpack webpack-cli webpack-dev-server mini-css-extract-plugin css-loader file-loader babel-loader
复制代码
你能够这样来传递 options 属性
{
loader: 'linaria/loader',
options: {
sourceMap: false, // 是否产生 CSS source map,默认 false
cacheDirectory: '.linaria-cache', // 缓存所在文件见,默认 .linaria-cache
extension: '.linaria.css', // CSS 文件处于中间态时的命名,默认 .linaria.css
preprocessor: 'stylis', // 定义 css 的预处理器,默认为 stylis
},
}
复制代码
Linaria 使用起来很是简单,只有一个核心方法 `css``` ,基本上能够覆盖全部场景了,同时为了方便开发也提供了一些语法糖性质的辅助函数
css
```css
是一个 标签函数 ,这意味着你能够经过模版字符串 ```` 而非 ()
来调用这个函数,标签函数求值结果会被 Babel 插件转换成一个独一无二的 class 命名
import { css } from 'linaria';
const flower = css` display: inline; color: violet; `;
// flower === flower__9o5awv –> with babel plugin
复制代码
任何在模版字符串写的 CSS 样式都会局限在相应的 class 命名下,包括媒体查询和动画。咱们能够这样声明动画:
import { css } from 'linaria';
const box = css` animation: rotate 1s linear infinite; @keyframes rotate { { from: 0deg; } { to: 360deg; } } `;
复制代码
cx(...classNames: Array<string | false | void | null | 0>) => string
cx()
会对传入的字符串进行拼接,但会忽略掉 Falsy
值,好比 ''
, null
和 undefined
等
import { css, cx } from 'linaria';
const cat = css` font-weight: bold; `;
const yarn = css` color: violet; `;
const fun = css` display: flex; `;
function App({ isPlaying }) {
return <Playground className={cx(cat, yarn, isPlaying && fun)} />;
}
复制代码
cx()
这个函数看着很像一个流行库 classnames
,但仍是有点区别的, cx()
不处理对象
styled
一个用于快速建立 React 组件的辅助对象,它的使用形式很像 styled-components
:
styled
的使用方式和 css
很类似,除此以外,你能够在模版字符串中插入函数来获取组件的 props ,并动态的设置样式
import { styled } from 'linaria/react';
import colors from './colors.json';
const Container = styled.div` background-color: ${colors.background}; color: ${props => props.color}; width: ${100 / 3}%; border: 1px solid red; &:hover { border-color: blue; } `;
复制代码
一样的,全部的样式规则也是局部化的。为了不重复的 CSS 样式代码,咱们能够这样去引用别的样式
const Title = styled.h1` font-size: 36px; `;
const Article = styled.article` font-size: 16px; /* this will evaluate to the selector that refers to `Title` */ ${Title} { margin-bottom: 24px; } `;
复制代码
而且,咱们能够经过 as
属性来指定实际渲染时的 html 标签是什么
// Here `Button` is defined as a `button` tag
const Button = styled.button` background-color: rebeccapurple; `;
// You can switch it to use an `a` tag with the `as` prop
<Button as="a" href="/get-started"> Click me </Button>;
复制代码
styled
也支持相似高阶组件形式的样式嵌套
const Button = styled.button` background-color: rebeccapurple; `;
// The background-color in FancyButton will take precedence
const FancyButton = styled(Button)` background-color: black; `;
复制代码
linaria/server
)collect(html: string, css: string) => string
在作 SSR 时咱们不只须要将相应的 HTML 代码进行返回,也须要将 须要的 样式代码返回,这就那些 关键的 的 CSS 代码,咱们能够经过利用 collect()
函数来抽离出关键的 CSS 代码
import { collect } from 'linaria/server';
const css = fs.readFileSync('./dist/styles.css', 'utf8');
const html = ReactDOMServer.renderToString(<App />);
const { critical, other } = collect(html, css);
// critical – returns critical CSS for given html
// other – returns the rest of styles
复制代码
collect()
会根据元素的 class 属性,将用到的 CSS 代码抽离出来,这样就能够跟随 html 一块儿返回
须要注意的被抽离出来的 css 代码选择器的顺序会变乱掉,这使得若是你的样式依赖选择器顺序的权重,可能就会出现意料的以外的错误,不过因为 Linaria 生成的 class 命名都是惟一的,因此通常不会出现这个问题,但与其余的库协做时须要注意到这点
Linaria 是基于 CSS 变量的,大部分现代浏览器支持这个特性,可是对于 IE 11 以及如下,是不支持的,因此若是你须要支持 IE 11 ,也许 Linaria 不是你最好的选择
linaria/lader
已经更名为 @linaria/webpack4-loader