Linaria 也許是如今 React 最佳的 JSS 方案

React 社區一直在探索各類 JSS 方案,好比如今比較出名的 styled-components ,但他們或多或少都有些問題存在,可是社區對 JSS 方案的探索一直沒有停下,而如今看上去最像最佳方案的是 Linaria 庫。翻了下這個庫相關的中文資料幾乎沒有,因而寫了這篇與你們分享介紹下相關的內容css

介紹

Linaria 是一個 零運行時 的JSS 框架,其特色有:html

  • 將 CSS 歸入到 JS 體系中,而且這種支持是零成本的! CSS 相關代碼會在編譯期被抽出到 CSS 文件中
  • 類 Sass 的 CSS 的語法
  • 經過使用 CSS 變量,Linaria 支持快速建立動態屬性的 React 樣式組件
  • 使用 CSS sourcemaps 易於定位樣式位置
  • 支持 stylint
  • 再也不須要預處理器,能夠使用 JavaScript 控制 CSS 的邏輯
  • 可是支持使用預處理器,好比 Sass 或 PostCSS

相對於傳統 CSS 方案的優勢

1. 樣式隔離

const title = css` font-size: 18px; `;
複製代碼

相似代碼最終會被會被編譯成node

.k4yi6fg {
  font-size: 18px;
}
複製代碼

其 class 命名是經過計算文件路徑的哈希值肯定的react

2. 樣式和組件歸屬到同一文件中

不會再有編寫組件時須要在 JS 文件和 CSS 文件跳轉的上下文切換的狀況。不過若是你想要分離的話,一樣也是支持的webpack

3. 可靠安全的重構支持

由於 JSS 的樣式其實就是 JS 變量而已,因此你能夠很容易經過代碼邏輯找到組件相關的樣式邏輯,而不用懼怕重構會形成預料以外影響web

4. 再也不須要預處理器

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}; } `;
複製代碼

5. Tree shaking

就像咱們剛纔說的同樣, JSS 其實只是 JS 變量,那麼天然而然 JS 能作到的 Tree shaking ,Linaria 同樣能作到。 這點對於 UI 庫的開發者其實頗有吸引力,再也不須要引入額外的 babel 插件,而是自動經過 Tree shaking 來作到樣式的按需引入json

6. 自動添加瀏覽器前綴

Linaria 會自動經過添加瀏覽器前綴,幫你對一些特殊屬性作兼容性支持,同時你依然可使用 PostCSS 作進一步的優化瀏覽器

7. 聲明式且動態化的控制 React 組件樣式的能力

經過 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}> 複製代碼

相比於 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 流水線,因此你能夠很容易去使用 postcssclean-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 值,好比 ''nullundefined

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; `;
複製代碼

服務端 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/lader 已經更名爲 @linaria/webpack4-loader

相關文章
相關標籤/搜索