标签: tailwindcss

  • 【视频】Nuxt3+Vercel+Serverless 全栈开发(2):配置 TailwindCSS,使用 grid 布局

    【视频】Nuxt3+Vercel+Serverless 全栈开发(2):配置 TailwindCSS,使用 grid 布局

    课程继续。仍然结合近期的开发经验,分享最近我比较喜欢的全栈+高效+免费+云原生技术方案。

    本次课程内容:

    • 配置 TailwindCSS
    • 使用 grid 布局构建页面

    Nuxt3 为支持 SSR,架构要复杂很多,所以大部分组件都要专门适配,比如 pinia,就有 @nuxt/pinia;vue-i18n,也有 @nuxt/i18n。同时,大部分组件的配置也都要归拢到一起,所以使用的时候一般要特意找一下,配一下。

    由于界面布局的因素,使用 flex 需要多一个容器,于是我干脆使用了 display:grid,这样代码会更简单。可能大家平时用 grid 不多,所以不妨从这个小项目入手。

    这次视频剪得比较细,字幕也做了,希望大家能学到东西。

    如果你有任何问题、建议,欢迎留言讨论。请 b 站有号的同学帮忙分享完播一键三连,谢谢大家。

    另外,我也在 YouTube 上上传了一份,大家有空的话,麻烦帮忙关注下我的油管频道,感谢感谢。肉山全栈小课堂 – YouTube

  • 2023 告别 CSS 预处理工具,彻底拥抱 TailwindCSS

    2023 告别 CSS 预处理工具,彻底拥抱 TailwindCSS

    CSS 是声明式语言,很简单,很好学,但是写起来很累,所有东西都要写出来才能生效。复用方面更是无从下手,虽然大家都在不断总结,但始终没能找到足够好用的方案,可以有效改善 CSS 开发。

    于是我们只好把视线转出 CSS 之外,转投 CSS 预处理工具,Less、SASS(SCSS)、Stylus,引入种种 CSS 不具备的功能,帮助我们改进开发体验。比如嵌套、函数、循环、条件,等等。然而如果你细心观察,实际上,这几个工具最近 5、6 年都没怎么更新(我说的是功能性),因为该有的都有了,甚至很稳定;其它来自于 CSS 的改进,几乎跟它们没什么关系,也不用更新。

    最近几年,随着 CSS 发展,一些新特性逐步引入,我觉得这些工具越来越难用,它们能带来的好处已经无法掩盖它们所造成的问题。是时候告别 CSS 预处理工具了,就像我们当年告别 jQuery 一样。

    为什么说预处理工具落后?

    我把理由分成三大类:

    预处理工具的问题

    • 对 CSS 函数兼容性不好,尤其是 rgba()hsl() 这些常用的颜色函数
    • 数值类型转换,有不符合预期的行为,比如 Stylus 实现 content:5

    CSS 的改进

    • CSS 拥有越来越多的函数,可以直接进行计算,比如前面提到的颜色;还有 calc() 来完成基础数学计算
    • CSS 变量非常好用,可以大大改进编程体验,配合各种 JS 框架,我们可以更容易的把数学逻辑和显示效果绑定在一起
    • CSS Houdini 可以实现新功能,即使不深入使用(JS 部分),也有好用的自定义属性
    • CSS 也开始从预处理工具吸收营养,比如近期的嵌套功能已经开始被整合,未来我们可以直接使用

    预处理工具无法跟进的问题

    • 很多缩写、复合属性无法处理,比如 background-imagebox-shadow 等,都支持多属性共同生效,预处理工具擅长的循环、条件、函数无法提供帮助。
    • 预处理,顾名思义,发生在生产之前。实际上,网页在实际浏览时,会有很多因素影响到渲染结果,比如分辨率、dark mode 等。预处理工具对这些需求也没有改进。

    替代方案

    我目前的替代方案基于 TailwindCSS,所以自然包含 PostCSS、AutoPrefixer 等工具。然后用 postcss-import 实现自动导入和模块化;使用 tailwindcss/nesting 实现嵌套。

    为什么选用 TailwindCSS?首先,实际开发中,不管使用什么前端框架,我们都需要大量原子化的胶水样式,比如调整间距、改变字号、给容器添加一些边框、圆角、阴影等。这些样式如果都手写,工作量并不小;学习不同的样式名也是负担;以及最重要的,CSS 优先级问题。使用 TailwindCSS 就都能很好解决。

    TailwindCSS 不仅包含一大堆原子化样式,自身也是个完整且优秀的 CSS 编译器。它包含 reset,提供一组全局通用的 CSS 变量;它可以从各种文件里把我们用到的样式提取出来,构建后生成的 CSS 里只有我们要用到样式,不会有多余的;它会分析我们对样式的使用,合理的调整样式顺序,保证样式能正确生效。使用 TailwindCSS 可以节省很多时间。

    它还自带若干插件,比如解决嵌套的 tailwindcss/nesting,支持内容类元素的的 @tailwindcss/typography 等。使用这些插件也可以帮我们节省很多时间。

    最后,TailwindCSS 的生态不断成长,我们的选择范围越来越宽:HeadlessUI、DaisyUI、付费的 Tailwind UI 等。方便我们从产品生命周期的任意阶段开始集成。

    推荐项目配置

    启动项目的时候,安装依赖。包含 PostCSS + AutoPrefixer、TailwindCSS 和 DaisyUI。前者提供 CSS 处理框架,包含自动导入 css 和嵌套功能;后两者提供可见的 UI。

    pnpm i postcss postcss-import tailwindcss autoprefixer daisyui -D

    自动初始化配置,-p 会自动生成 PostCSS 配置:

    pnpm tailwindcss init -p

    调整 postcss.config.js,启用 postcss-importtailwindcss/nesting。目前我们常用的嵌套规则和 CSS 规范略有区别,不过无所谓,规范也没确定,所以这样就足够了。

    module.exports = {
      plugins: {
        'postcss-import': {},
        'tailwindcss/nesting': {},
        tailwindcss: {},
        autoprefixer: {},
      },
    }

    然后调整 tailwind.config.js

    const DaisyUI = require('daisyui');
    // 这个插件可以帮我们处理文档类内容,我建议常用
    const Typography = require('@tailwindcss/typography');
    
    module.exports = {
      // 从以下文件查找用到的样式
      content: [
        './index.html',
        './src/**/*.{js,ts,jsx,tsx,vue}',
      ],
      theme: {
        extend: {
          // 扩充 TailwindCSS 没有包含的样式
        },
      },
      plugins: [
        DaisyUI,
        Typography,
      ],
      daisyui: {
        themes: [{
          // 只构建一个主题: luxury,并覆盖其中的两个属性
          luxury: {
            ...require('daisyui/src/colors/themes')[ '[data-theme=luxury]' ],
            primary: '#FFA028',
            '--bc': '0 0% 87.5%',
          },
        }],
      },
    }
    

    然后,创建样式入口 main.css。其它样式可以如常写在这个文件里,不过如果要 @import 其它 CSS 文件,就要进行一些调整。具体可以看官方文档。

    @tailwind base;
    @tailwind components;
    @tailwind utilities;

    然后在入口文件引用 main.css 即可:

    import './main.css';

    至此,新项目配置完成,可以照常开发了。

    下期预告

    这次我先分享了整体思路:用新的工具链替代预处理工具,保证已有的功能不缺失。那么下期分享的内容就是使用新的 CSS 特性,更好的完成开发。


    如果你对新 CSS 感兴趣,对预处理工具和新工具链有兴趣和疑问,欢迎留言讨论。如果本文对你有启发,也请帮我点赞分享,谢谢。


    本文参与了 SegmentFault 思否写作挑战赛,欢迎正在阅读的你也加入。

  • 【视频】如何正确使用 TailwindCSS

    【视频】如何正确使用 TailwindCSS

    TailwindCSS 是一个争议很大的样式库。

    他封装了大量原子化的样式,比如 w-4,表示 width: 1remtext-gray-500,表示字体颜色为灰色500。如果我们某个节点同时需要两个样式,就是 class="w-4 text-gray-500"。极端一点的例子是这样子的:

    很明显,大家的争议点在于:

    1. 这么细碎的样式,我为什么不自己写?灵活性还高一些。
    2. 这么写跟 inline style 有什么区别?
    3. 开发一时爽,维护火葬场。

    基本上,发出这些疑问的都是前端。包括我,最初也是这样的想法。但是有一天,我要维护一个老项目,大部分组件都是现成的——引用自某个组件库,或者团队已经写好,只需要调整布局,我发现 TailwindCSS 简直是神器,太方便了。

    于是,当我反复看到大家争论该不该用 TailwindCSS 后,决定做一期视频,表达一下自己的态度:

    1. 我们做技术,要避免对一项技术做非黑即白的判断,更不应该轻易否定一项技术。同时,使用技术 A 并不代表就要拒绝技术 B。
    2. 具体到 TailwindCSS 上,使用它不代表我们从头到尾就要只能用 TailwindCSS;使用其它前端框架也不要求我们绝对不能使用 TailwindCSS。
    3. 所以正确的做法是,我们应该使用 TailwindCSS。
    4. 项目启动时,使用比较完整全面的前端框架,比如 Element UI、AntDesign,或者基于 TailwindCSS 打造的 DaisyUI;然后辅以 TailwindCSS。这样就可以同时照顾开发效率与维护效率,收获最佳效果。

    除了并不会降低开发效率之外,TailwindCSS 还有以下优势:

    1. 它跟内联样式有很大的区别,它的优先级很低,意味着我们也可以很容易覆盖、调整。
    2. TailwindCSS 样子很好看,直接能画出漂亮的界面。
    3. 基于 TailwindCSS 的代码分享很容易,只要复制粘贴 html 即可,在前端工程化日趋复杂的今天,简直是一股清流。
    4. 因为文档组织得更好,后端和其它领域的开发者也很喜欢使用 TailwindCSS 替代手写样式。

    所以无论如何,我都推荐所有团队所有开发者使用 TailwindCSS。当然,用其它原子化样式框架,比如 UnoCSS 也可以。

    如果你有其它意见和建议,欢迎提出讨论。如果你有 B 站账号,恳请三连+关注+转发,感谢。

  • 笔记:使用 Vite+Vue3+TypeScript+Tailwind CSS 开发 Wordle

    笔记:使用 Vite+Vue3+TypeScript+Tailwind CSS 开发 Wordle

    最近有个小游戏很火,叫 Wordle,是个填字游戏,推上随处可见相关分享。各种衍生版也层出不穷。有位朋友让我帮他复刻,反正过年,闲着也是闲着,我就尝试用标题里的新技术栈帮他写了一个。现在已经上线了,对填字游戏感兴趣的同学可以试试:

    My Wordle(特色:1. 支持自定义单词;2. 5~9字母;3. 无限模式,可以一直玩。)

    这篇文章简单记录一些开发过程和经验。

    0. 创建 vite 项目

    npm init vite@latest

    接下来按照提示选择喜欢的配置即可。

    小配置下 vite.config.js,添加 @ 别名:

    // 为了方便后面开发,改为函数式,这样获取状态更容易
    export default defineConfig(({command, mode}) => {
      const isDev = command === 'serve'; // 这里先留个例子,后面骨架屏有用
      return {
        server: {
          host: true, // 允许外部访问,方便手机调试
        },
        plugins: [
          vue(),
        ],
        resolve: {
          alias: {
            '@': resolve(__dirname, './src'),
          },
        },
      };
    });

    注意,上面的配置文件虽然是 ESM,但实际上仍然在 CommonJS 环境里执行,所以有 __dirname

    然后安装依赖:pnpm i

    最后 npm run dev 即可启动开发服务器进行开发。Vite 真是快,空项目 100ms 就启动了。

    1. 安装+配置 Tailwind CSS

    pnpm i -D tailwindcss postcss autoprefixer
    npx tailwindcss init -p

    配置 tailwind.config.js

    module.exports = {
      content: [
        "./index.html",
        // 注意:因为我喜欢用 pug 开发模版,所以一定要在这里添加 `pug`
        "./src/**/*.{vue,js,ts,jsx,tsx,pug}",
      ],
      theme: {
        extend: {},
      },
      plugins: [],
      // 这部分 css 需要通过比较复杂的计算得出,所以要用 `safelist` 保证它们出现在最终 css 里
      safelist: [
        {
          pattern: /w-\d+\/\d+/,
        },
        {
          pattern: /grid-cols-[5-9]/,
        },
      ],
    }

    接下来创建 CSS 入口文件:./src/style/index.css

    @tailwind base;
    @tailwind components;
    @tailwind utilities;

    最后在入口文件里引用它即可:

    import { createApp } from 'vue'
    import App from './App.vue'
    import './index.css'
    
    createApp(App).mount('#app')

    2. 配置 TypeScript

    默认的 TypeScript 就挺好使,这里简单配一下 @ 别名即可:

    {
      "compilerOptions": {
        "paths": {
          "@/*": [
            "./src/*"
          ]
        }
      },
    }

    为开发方便,还需要装一些类型定义:

    pnpm i @types/gtag.js @types/node -D

    3. 配置 eslint

    安装所需依赖:

    pnpm i -D @typescript-eslint/eslint-plugin @typescript-eslint/parser @vue/eslint-config-typescript eslint eslint-plugin-babel eslint-plugin-vue

    配置 .eslintrc.js

    module.exports = {
      "env": {
        "browser": true,
        "es2021": true,
        "node": true
      },
      parser: "vue-eslint-parser",
      parserOptions: {
        parser: "@typescript-eslint/parser",
        sourceType: 'module',
        ecmaVersion: 'latest',
      },
      extends: [
        'eslint:recommended',
        'plugin:vue/vue3-recommended',
        '@vue/typescript/recommended',
      ],
      plugins: [
        'babel',
        'vue',
      ],
      globals: {
        // vue3 <script setup> 工具函数
        withDefaults: true,
        defineProps: true,
        defineEmits: true,
        defineExpose: true,
    
        // 我自己定义的常用类型
        Nullable: true,
        Timeout: true,
    
        $root: true,
      },
      "rules": {
        // vue3 <script setup> 暂时必须关掉这几项
        '@typescript-eslint/no-unused-vars': 0,
        '@typescript-eslint/ban-ts-comment': 0,
        '@typescript-eslint/no-non-null-assertion': 0,
      },
    }

    4. 拆包与懒加载

    Wordle 游戏需要用到词库,5个字母的词库 200+KB,6789的词库都是 300KB,全部放到一起加载太浪费,所以计划按需加载。另外,词库几乎是不变的,但业务逻辑是多变的,如果不拆包的话,每次业务逻辑变更后,用户都要重复下载好几百 KB 的词库,所以要做拆包与懒加载。

    拆包方面,vite build 是基于 rollup 实现的,所以要按照 rollup 的要求,用 rollupOptions 来配置:

    export default defineConfig(({command, mode}) => {
      return {
        ....
        build: {
          rollupOptions: {
            output: {
              manualChunks(id) {
                // fN 是高频词,出题时只用高频词,校验单词时才用全量词库。高频词独立打包。
                if (/[sf]\d\.txt\?raw$/.test(id)) {
                  return 'dict';
                // 分享文案,为方便朋友修改,独立打包
                } else if (/share\d\.txt\?raw$/.test(id)) {
                  return 'share';
                // 其它依赖,打包成 vendor。不知道为什么,必须有这一配置,前面两项才能生效
                } else if (id.includes('node_modules')) {
                  return 'vendor';
                }
              },
            },
          },
        },
      };
    });

    默认只加载高频词,全量词库会在页面准备就绪后,通过 fetch API 异步加载。

    5. 添加骨架屏

    网页里有个静态 footer,因为 CSS 文件加载需要一些时间,所以会先渲染出来,然后再挪到正确的位置,造成抖动。这里有两个选择:1. 隐藏 footer,或挪到 vue 里;2. 添加加载中样式。我选择在(2)上再发展一点,做骨架屏。

    我的思路是:

    1. <div id="app"></div> 里填充只有标题的 header 和填字格
    2. 拆分 tailwind.css,分成
      1. 只包含 reset 和 index.html 所需的 inline css
      2. 包含全部业务样式的外部 css 文件
    3. 编写脚本,build 时往 index.html 里塞入填字格,并把 inline css 写入网页 <head>

    实现方面,首先编辑 vite.config.js

    export default defineConfig(({command, mode}) => {
      // 根据开发、构建环境,使用不同的 tailwind 入口文件
      const tailwind = command === 'serve' ? 'tailwind-sketch.css' : 'tailwind.css';
      return {
        resolve: {
          alias: {
            '@tailwindcss': resolve(__dirname, './src/style/' + tailwind),
          },
        },
      };
    });

    我从 tailwind.css 里移除了 @tailwind base,即 reset 部分。这部分会被 inline 到 index.html 里。然后修改 tailwind.config.js,加入根据环境判断是否需要分析 index.html 的逻辑:

    const isDev = process.env.NODE_ENV ==='development';
    module.exports = {
      content: [
        './src/App.{pug,vue,ts,tsx}',
        './src/**/*.{vue,js,jsx,ts,tsx,pug}',
      ].concat(isDev ? './index.html' : []),
      theme: {
        extend: {},
      },
      plugins: [],
    }
    

    接着修改 build 脚本,增加编译 inline css 和注入功能:

    {
      "name": "aw-woodle-game",
      "version": "0.4.0",
      "scripts": {
        "build": "vue-tsc --noEmit && vite build && npm run build:sketch && npm run build:sketch2 && build/build.js",
        "build:sketch": "tailwind -i src/style/tailwind-sketch.css -o tmp/sketch.css --content index.html --config build/tailwind.config.sketch.js --minify",
        "build:sketch2": "stylus --include-css --compress < src/style/sketch.styl > tmp/sketch2.css",
      },
    }

    6. 总结

    最终我使用这套工具链完成了整个产品的开发。不过仍然有一些问题未解:

    1. Vite 开发环境与构建环境其实是两套工具链,所以能过 dev,未必能过 build,尤其是 TypeScript,会有很多差异。不过只要耐心调试一番,也不难搞。
    2. 不知道是否跟我用 pug 有关,Tailwind 总是慢一拍。即我改了模版,html 变了,但对应的样式并没有出来;过一会儿,就出来了。于是我要么继续改,期待后面它会加上来;要么只有手动刷新。
    3. build 时,rollup 会把 css 放在 <head> 里面,导致浏览器认为这段 CSS 是关键内容,加载完之后才进行第一次渲染(FCP),使得我的骨架屏失效,所以要手动把它挪到 <html> 的最后。(当然在 GA 等代码的前面)

    最终的 Lighthouse 得分比较喜人,贴一下:

    (更多…)
  • Tailwind.css + Postcss 笔记

    Tailwind.css + Postcss 笔记

    0. 缘由

    去年,一篇《Tailwind CSS: From Side-Project Byproduct to Multi-Million Dollar Business》在我的时间线上刷屏,作为 side project 和自由职业的翘楚,他的产品和商业项目十分令人羡慕。

    所以,我一直想找个机会试用一下 Tailwind.css。这次春节,想着放松休闲一下,就开了个小项目,尝试一下新技术栈:

    1. Vue3 全家桶
    2. Tailwind.css + PostCSS
    3. Webpack 工具链

    这篇笔记用来记录心得和体会。


    1. 基础

    官方网站:https://tailwindcss.com/

    2. 安装&配置

    npm install tailwindcss@latest postcss@latest autoprefixer@latest
    // postcss.config.js
    module.exports = {
       plugins: {
         tailwindcss: {},
         autoprefixer: {},
       }
    }

    2.1 创建 tailwindcss 配置

    npx tailwindcss init

    生成的配置文件如下:

    // tailwind.config.js
    module.exports = {
      purge: [],
      darkMode: false, // or 'media' or 'class'
      theme: {
        extend: {},
      },
      variants: {},
      plugins: [],
    }

    2.2 创建 CSS

    @tailwind base;
    @tailwind components;
    @tailwind utilities;

    这个 CSS 无法直接被浏览器使用,需要经过 PostCSS 调用 Tailwind 插件编译后才行。

    2.3 配置 Webpack

    只需要配置 CSS 和 Stylus 规则:

    module.exports = {
      module: {
        rules: [
          {
             test: /.css$/,
             use: [
               isDevServer ? 'style-loader' : MiniCssExtractPlugin.loader,
               'css-loader',
               'postcss-loader'
             ]
           },
           {
             test: /.styl(us)?$/,
             use: [
               isDevServer ? 'style-loader' : MiniCssExtractPlugin.loader,
               'css-loader',
               'postcss-loader',
               'stylus-loader',
             ],
           },
        ]
      }
    }

    2.4 配置 .browserslistrc

    PostCSS 同样需要用 browserslist 处理兼容性问题,所以一定要配置好,比如我近期喜欢用 bootstrap-icons 为图标,需要用到 svg-mask 系列属性,在 Chrome 里就需要补充前缀。那么,如果 browserslist 里没有 Chrome 就不会加前缀(我昨天就踩在这个坑里)。可以使用 npx browserslist 来检查。

    2.5 修改 npm scripts

    PostCSS 和 Tailwind.css 需要用 NODE_ENV 变量决定动作内容,所以必须加到 npm scripts 里。

    {
      "scripts": {
        "serve": "NODE_ENV=development webpack serve --config build/webpack.config.js",
        "build": "NODE_ENV=production webpack --config build/webpack.config.prod.js --mode=production",
        "lint": "eslint --fix --ext=.vue,.js ./"
      }
    }

    2.6 完成

    至此,基础 Tailwind.css + PostCSS + Webpack 配置完成,接下来就可以使用 CSS 实现界面了。