Linaria 也许是现在 React 最佳的 JSS 方案
Posted 前端时空
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linaria 也许是现在 React 最佳的 JSS 方案相关的知识,希望对你有一定的参考价值。
React 社区一直在探索各种 JSS 方案,比如现在比较出名的 styled-components
,但他们或多或少都有些问题存在,但是社区对 JSS 方案的探索一直没有停下,而现在看上去最像最佳方案的是 Linaria 库。翻了下这个库相关的中文资料几乎没有,于是写了这篇与大家分享介绍下相关的内容
介绍
Linaria 是一个 零运行时 的JSS 框架,其特点有:
-
将 CSS 纳入到 JS 体系中,并且这种支持是零成本的!CSS 相关代码会在编译期被抽出到 CSS 文件中 -
类 Sass 的 CSS 的语法 -
通过使用 CSS 变量,Linaria 支持快速创建动态属性的 React 样式组件 -
使用 CSS sourcemaps 易于定位样式位置 -
支持 stylint -
不再需要预处理器,可以 使用 JavaScript 控制 CSS 的逻辑 -
但是 支持使用预处理器,比如 Sass 或 PostCSS
相对于传统 CSS 方案的优点
1. 样式隔离
const title = css`
font-size: 18px;
`;
类似代码最终会被会被编译成
.k4yi6fg {
font-size: 18px;
}
其 class 命名是通过计算文件路径的哈希值确定的
2. 样式和组件归属到同一文件中
不会再有编写组件时需要在 JS 文件和 CSS 文件跳转的上下文切换的情况。不过如果你想要分离的话,同样也是支持的
3. 可靠安全的重构支持
因为 JSS 的样式其实就是 JS 变量而已,所以你可以很容易通过代码逻辑找到组件相关的样式逻辑,而不用害怕重构会造成预料之外影响
4. 不再需要预处理器
JSS 最迷人的一点就是,当你将 CSS 归属到 JS 下时,你就自动获得来使用 JS 编写 CSS 逻辑的能力,最基本的条件计算,被包装成简单函数调用的的复杂逻辑。这意味着 CSS 的表达能力的上限不再局限于自身,而是由 JS 决定的
例如你可以编写这样的代码
const DEFAULT_COLOR = '#fffff'
const PRIMARY_COLOR = '#de2d68';
const getColor = Math.random() > 0.5 ? PRIMARY_COLOR : DEFAULT_COLOR;
const button = css`
background-color: ___CSS_0___;
&:hover {
background-color: ___CSS_1___;
}
`;
5. Tree shaking
就像我们刚才说的一样, JSS 其实只是 JS 变量,那么自然而然 JS 能做到的 Tree shaking ,Linaria 一样能做到。这点对于 UI 库的开发者其实很有吸引力,不再需要引入额外的 babel 插件,而是自动通过 Tree shaking 来做到样式的按需引入
6. 自动添加浏览器前缀
Linaria 会自动通过添加浏览器前缀,帮你对一些特殊属性做兼容性支持,同时你依然可以使用 PostCSS 做进一步的优化
7. 声明式且动态化的控制 React 组件样式的能力
通过 styled
API ,很容易去声明 React 动态样式组件。原理是通过 CSS 变量来实现组件样式自动更新的能力,常规的 CSS 方案则需要你手动的去维护相关的逻辑
const Box = styled.div`
background-color: orange;
height: ___CSS_0___px;
width: ___CSS_1___px;
`;
<Box size={48}>
相比于 CSS 预处理器的优势
1. 没有新的学习成本
Linaria 的语法可以看作只是支持嵌套的 CSS 语法而已。没有变量,mixins 或 函数什么的,这些都可以用 JS 的逻辑来代替
2. 相对于传统 CSS 方案的优点对于CSS 预处理器也一样
相比于直接写 行内样式 的优势
1. 完全的 CSS 能力支持
行内样式存在局限性,而 Linaria 则支持 CSS 的所有特性:
-
媒体查询 -
CSS3 动画 -
伪类,伪元素
2. 性能优势
通过 class 命名来应用样式要比行内样式快
相比其他的 JSS 方案的优势
1. CSS 的下载和解析是和 JS 分开的
因为 CSS 在编译器被抽出到 CSS 文件中了,因此浏览器可以并行的下载 CSS 和 JS 文件,加速首屏时间
2. 没有额外的解析成本
很多 JSS 框架是通过某个第三方 JS 库来解析 CSS 字符串的,由于需要包含解析器,会使得库的体积增大。并且 CSS 的解析执行被延迟到了 JS 运行时,在一些低端设备上,很容易带来可以感知到的延迟
Linaria 特殊就特殊在它 没有运行时 这一说,它的样式会在编译期解析抽出来,生成 CSS 文件,不需要在运行时额外解析一次。
3. SSR 时没有重复渲染的性能损耗
对于基于组件的 JSS 框架来说,使用不同的 props 渲染同一个组件会使得同一份样式被复制多次,这使得 SSR 时产物的体积会增大。尽管大部分情况下,这种问题带来的性能损耗不值一提,但是对于渲染多个仅有细微差异的大型列表时,很容易使得体积迅速增长
除此之外,在做 SSR 你需要将写在 JS 文件中的 CSS 样式抽取出来,然后传输给浏览器,这同样增加产物体积
Linaria 会生成唯一的样式规则,使用 CSS 变量来应用不同的差异,所以不会有重复样式的问题,也就减少产物的体积
4. 编译期检查语法错误
非法 JS 值和错误的 CSS 语法,都会在编译期检查出来,而不用等到运行时才暴露出来。这意味着你不会在生产模式遇到这些低级错误,同样的, Linaria 支持 stylelint ,你依旧可以获得原来一样的 lint 体验。
5. 熟悉的 CSS 语法
不同于其他的一些 JSS 框架, Linaria 语法只是支持嵌套的 CSS 原生语法而已,没有什么上手成本。完美支持面向 Copy-Paste 编程
6. 支持无 javascript 运行
如果你的网站需要在禁止 JavaScript 的情况运行,或者想要在编译生成静态网页的运行, Linaria 同样可以在这些情况下正常运行,因为它的 没有运行时 特性
安装配置
Linaria 同时支持 Webpack , Rollup 和 Sevlte ,本文只会讲述配合 Webpack 使用的方式,如有其他需求,可以去官网看下配合其他构建工具的文档
首先
如果你在项目里使用里 Babel 做转译或这 polyfill 什么的,那么一定要在根项目下建立一个 Babel 配置文件,把你需要的 presets 和 plugin 写在里面,不然 Linaria 会无法解析你的代码
和 Webpack 配合
和 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
来做一些定制化操作
一个完整的 Webpack 配置例子
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
定制 linaria/loader 的 options 属性
你可以这样来传递 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
,基本上可以覆盖所有场景了,同时为了方便开发也提供了一些语法糖性质的辅助函数
浏览器端 API
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: ___CSS_0___;
color: ___CSS_1___;
width: ___CSS_2___%;
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;
`;
服务端 API (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 也许是现在 React 最佳的 JSS 方案的主要内容,如果未能解决你的问题,请参考以下文章
reactreact-routerredux 也许是最佳小实践2
最好用的json库,也许是JSON for modern C++ 的最佳实践!解决nlohmann json中文无法解析的问题!