tailwindcss筆記

tailwindcss筆記

從去年起tailwind在台灣熱度越來越高,另外最近也在HISKIO上買了tailwind的課程學習,所以想說也來蹭一下分享一下所學順便兼做筆記方便日後複習

tailwindcss解決什麼問題?

tailwindcss是一個utility-first的css框架,而且由於tailwind將絕大部分的css樣式通通寫成class使用(ex:mt、flex、grid…),所以可以讓工程師在寫html的時候一併處理樣式而幾乎不用寫額外的css,跟bootstrap之間最大的不同是tailwind沒有像是「Accordion」、「Dropdown」之類的組件,全部都是功能性的class,另外由於使用tailwind等同於直接在html上寫style,所以可以避免樣式幾乎相同卻硬是要命名不同的class,大大減少多餘的css

註:想要玩一下tailwind的話可以先到tailwindplay嚐嚐鮮

tailwindcss配置

tailwind官方有提供三種配置方式

  1. cdn
  2. tailwind cli
  3. npm

CDN

<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">

不過官方不推薦使用這個方式,主要是因為以下原因

  • 無法客製化tailwind的主題
  • 無法使用@apply、@variants等等的指令
  • 無法開啟如group-focus這種額外的變數
  • 無法安裝第三方的plugin
  • 無法進行tree shake移除沒使用的樣式

個人認為最要命的是最後一點,雖然如果沒有要客製化的話直接使用也不是不行,但是tailwind沒做purge的話總共有3MB的大小,丟到vscode看將近有20萬行的css原始碼,這麼大包會出事的阿…

tailwind cli(Node.js版本要在12.13.0以上)

使用以下指定建立tailwind.css (-o為output)

npx tailwindcss -o tailwind.css

要讓自己寫的css跟tailwind一起編譯的話要先將tailwind的基礎樣式用@tailwind引入至自定義的css檔案內

  /* ./src/index.css  */
  @tailwind base; /* 基礎樣式 */
  @tailwind components; /* 模組樣式 */
  @tailwind utilities; /* 共用樣式 */
  @layer base { ... }
  @layer components { ... }

再使用以下指令匯出css( -i為input、-o為output)

  npx tailwindcss -i ./src/index.css -o ./dist/tailwind.css

可透過tailwind.config.js對tailwind本身進行設定,用以下指令建立tailwind.config.js

npx tailwindcss init

tailwind.config.js

  module.exports = {
    purge: [],
    darkMode: false, // 也可以是 'media' 或 'class'
    theme: {
      extend: {},
    },
    variants: {},
    plugins: [],
  }

使用tailwind cli時config檔會自動被讀取,另外如果要以production mode建置的話要在指令前面加上「NODE_ENV=production」這個環境變數

  NODE_ENV=production npx tailwindcss -i ./src/tailwind.css -o ./dist/tailwind.css --minify

註:–minify會把css檔案內的間距、空白移除掉,進一步壓縮css檔案的大小

註:windows作業系統並不自帶NODE_ENV,使用windows的話記得要額外下載

以PostCSS插件的方式來安裝Tailwind

透過Postcss來安裝tailwind,這個方式還可以透過webpack等打包工具整合其他第三方插件來協助開發

透過npm載入tailwindcss、postcss、autoprefixer

npm install -D tailwindcss@latest postcss@latest autoprefixer@latest

postcss是一個加強處理css的js plugin,包括依照Can i use添加瀏覽器前綴、使用新的css功能或是做圖片的處理,我們透過postcss整合tailwind並使用autoprefixer去添加瀏覽器前綴

我本身是使用webpack打包,所以為了要讓webpack能夠讀取解析postcss需要載入postcss loader

  npm install -D postcss-loader

新增postcss.config.js、tailwind.config.js、webpack.config.js

postcss.config.js

  module.exports = {
  plugins: [
    require('tailwindcss'),
    require('autoprefixer')
  ]
}

tailwind.config.js

  module.exports = {
    purge: [],
    darkMode: false, // 也可以是 'media' 或 'class'
    theme: {
      extend: {},
    },
    variants: {},
    plugins: [],
  }

跟tailwind cli一樣把tailwind的樣式引入到自訂義的index.css內

  /* ./src/index.css  */
  @tailwind base; /* 基礎樣式 */
  @tailwind components; /* 模組樣式 */
  @tailwind utilities; /* 共用樣式 */
  @layer base { ... }
  @layer components { ... }

webpack.config.js配置

const path = require('path');
module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'index.js',
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'postcss-loader'],
      },...
    ]
  }
}

註: 記得把index.css給import進index.js

tailwind css intellisense

如果是用vscode開發可以安裝這個插件,在寫class時會出現提示

purge css

tailwind會把沒使用到的樣式移除掉來減少產生的css檔案大小,但是tailwind並不知道有哪些檔案內有使用到tailwind的class,所以如果有檔案使用tailwind的話要在purge內加上content的物件,這樣tailwind就會透過content內的路徑去看這些路徑內的檔案並把檔案內有使用到的class保留下來

module.exports = {
  purge: {
    content: ['./src/**/*.html', './src/**/*.{html,js}']
  }
}

有無purge的差別真的滿大的…

雖然tailwind很貼心的把沒有使用到的css移除掉但是這會衍伸一個問題,如果今天html是透過後台編輯器產生的話就有可能使用到被移除掉的class,為了避免這個問題這時候我們可以在config檔內加上safelist,tailwind在產生css時會把safelist內的class保留下來

module.exports = {
  purge: {
    content: ['./src/**/*.html', './src/**/*.{html,js}'],
    safelist: [
      'bg-blue-500',
      'text-center',
      'hover:opacity-100',
  }
}

補充一下雖然tailwind第三版還沒有正式釋出不過第三版purge被移除掉了,content跟safelist直接寫就可以了

module.exports = {
    content: ['./src/**/*.html', './src/**/*.{html,js}'],
    safelist: [
      'bg-blue-500',
      'text-center',
      'hover:opacity-100',
    ]
}

base

base裡面主要是寫基礎的樣式,如h1的size以及margin之類的

@layer base {
  h1{
    font-size: 20px;
    margin: 1rem 0;
  }
}

@apply

可能會有人覺得使用tailwind會讓class變得又臭又長,這時候我們可以透過tailwind的components、@apply將多個class群組起來

@layer components {
  .card{
    @apply rounded-lg mt-4 px-4 py-3 bg-fb-card;
  }
}

建議只將需要共用的class群組起來就好了

dark Mode

module.exports = {
  darkMode: 'false', //參數可選填media or class
}

dark mode就是所謂的夜間模式,接受「media」、「class」的字串參數,media會以電腦系統的設定來轉換夜間模式而class則是使用手動的方式轉換夜間模式 ,在tailwindplay試一下會發現tailwind就是在html上添加dark的class

@layer base {
  body {
    @apply dark:bg-black;
  }
}

需要在夜間模式顯示的樣式可以在html標籤內或是在自定義的index.css裡的base裡面寫「dark: + utility class」

手動模式要透過js的click事件在html上添加dark的class

  var light_mode_btn = document.getElementById('light')
  var dark_mode_btn = document.getElementById('dark')

  light_mode_btn.addEventListener('click', function () {
    document.documentElement.classList.remove('dark')
  })

  dark_mode_btn.addEventListener('click', function () {
    document.documentElement.classList.add('dark')
  })

variant變體

variant又有人稱為偽類變體,所謂的偽類就是css裡的「:hover」、「:active」、「:focus」等…可以添加互動功能的pseudo-class,在tailwind裡面可以直接在html上寫上variant來增加頁面的互動效果,寫法是「variant: + utility class」

<input class="focus:bg-white hover:bg-black focus:border-white hover:border-black">

在tailwindcss推出JIT之後只要JIT mode有開啟那麼所有class的variant都可以直接使用,但如果使用的是舊版本的tailwind(v1.96以前)又或是新版本的tailwindcss但是沒開啟JIT mode的話就要注意不是所有class的variant預設都是開啟的,可以從Configuring Variants裡面去看tailwind有支援的variant跟預設有開啟的variant

customize自定義

我們要添加自定義的樣式需要在tailwind.config.js裡面預先寫,記得一定要寫在extend裡面不然會把原先tailwind裡相對應的utility class整個洗掉…

module.exports = {
  theme: {
    extend: {
      screens: {},
      spacing:{},
      colors: {},
    },
  }
}

自定義的範圍很廣,可以試RWD斷點(screens)、顏色(colors)、間距(spacing)…,相關的填寫方式直接看官網會比較快,不過每次要自定義樣式就要在tailwind.config.js裡面預先寫其實是有點麻煩…,這時候tailwindcss在2.1版後推出了just in time的功能

JIT(v2.1+)

補充資料

最後補充一下本身在使用webpack + tailwindcss時除了tailwind.config.js以外安裝的套件以及配置

package.json

{
  "name": "tailwindProject",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "cross-env NODE_ENV=development webpack serve --open",
    "build": "cross-env NODE_ENV=production webpack",
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.14.6",// 將js分析成ast,方便各個插件分析語法處理
    "@babel/preset-env": "^7.14.5", // 將尚未被大部分瀏覽器支援的js語法轉換成能被瀏覽器支援的語法,以及讓較舊的瀏覽器也能支援大部分瀏覽器能支援的語法
    "autoprefixer": "^10.2.6", // 給css添加瀏覽器前綴字
    "babel-loader": "^8.2.2", // 使webpack可以讀懂babel
    "clean-webpack-plugin": "^4.0.0-alpha.0", // 打包前先將output資料夾清空
    "compression-webpack-plugin": "^8.0.0", //將檔案進行壓縮產生壓縮檔
    "copy-webpack-plugin": "^9.0.0",// 將src內特定資料夾的檔案複製到dist內的特定資料夾(名稱可自定義也可指定多組)
    "cross-env": "^7.0.3", // windows系統不自帶cross-env環境變數時安裝
    "css-loader": "^5.2.6", // 使webpack可以讀懂css
    "cssnano": "^5.0.8", // 移除css內的空白
    "html-webpack-plugin": "^5.3.1", // 將html輸出至dist資料夾內並在html內引入js檔案
    "mini-css-extract-plugin": "^1.6.0", // 將css抽離成單獨的css檔案並在html內引入css檔案
    "postcss": "^8.3.5", // 加強處理css,包括添加前綴、將新語法轉換成通用的舊語法
    "postcss-loader": "^6.1.0", // 使webpack可以讀懂postcss
    "tailwindcss": "^2.2.4", // tailwindcss
    "webpack": "^5.39.1", //webpack打包工具
    "webpack-cli": "^4.7.2", // webpack cli工具,提供操作webpack相關的cli指令
    "webpack-dev-server": "^3.11.2" // 透過webpack開啟本地server
  }
}

postcss.config.js

module.exports = {
  plugins: [
    require('tailwindcss'),
    require('autoprefixer'),
    require('cssnano')({
      preset: 'default',
    }),
  ]
}

babel

babel.config.json

{
  "presets": ["@babel/preset-env"]
}

.browserslistrc

明確標示要支援的瀏覽器給如autoprefixer、Babel等等…

last 2 versions
not dead
> 0.2%

webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyPlugin = require("copy-webpack-plugin");
const CompressionPlugin = require("compression-webpack-plugin");
const webpack = require('webpack')
const path = require('path');

module.exports = {
  target: 'web',
  entry: {
    index: './src/index.js', //多個entry可以以物件形式輸入
    ...
  },
  mode: process.env.NODE_ENV, // 如果是production mode會使用tree shaking,不會打包沒使用到的東西
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js', //name會去對應到entry裡物件的key值,hash為避免修改網頁時的快取
  },
  devServer: {
    compress: true,
    port: 8080
  },
  // loader
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1, //如果在css檔案內使用@import要將importLoaders設為1,如果是sass則為2
            }
          },
          {
            loader: 'postcss-loader'
          }
        ],
      },
      {
        test: /\.(jpe?g|png|gif)$/,
        // webpack5透過asset module來使用資源而不需載入loader,
        type: 'asset/resource' // 將檔案輸出至output
      },
      {
        test: /\.m?js$/,
        exclude: /node_modules/, //babel處理時忽略node_modules
        use: {
          loader: "babel-loader",
        }
      }
    ],
  },
  // 插件
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css'
    }),
    new CleanWebpackPlugin(),
    new CopyPlugin({
      patterns: [
        { from: "./static", to: "./static" },
      ],
    }),
    new webpack.DefinePlugin({ // 在全域環境注入設定的變數,如果開發跟測試使用的config檔不同可以使用
      // Definitions...
      PRODUCTION: JSON.stringify(false),
      VERSION: JSON.stringify('5fa3b9'),
      BROWSER_SUPPORTS_HTML5: true
    }),
    new CompressionPlugin()
  ],
  devtool: 'source-map' // source-map參數會在console端顯示原始的程式碼而非打包過後的程式碼方便debug
}
//