如何在快应用开发中使用 Tailwind CSS 提升开发效率?

快应用 Jul 6, 2023

Tailwind CSS,在 Web 开发领域,已经成为各种框架、开发人员标配;已经有大量实例证明,基于 Tailwind CSS 来编写样式,可以为开发者节省诸多时间;而且,能够优化减小代码体积、提升运行性能,是非常棒的技术套件。 快应用 开发,同样基于 Web 技术栈,理论上也完全可以使用 Tailwind CSS;在实际开发过程中,该如何使用 Tailwind CSS 呢?本文旨在对此做些许分享。


Tailwind CSS 的工作原理是扫描所有 HTML 文件、JavaScript 组件和任何其他模板的类名,生成相应的样式,然后将它们写入静态 CSS 文件。它快速、灵活、可靠,且运行时间为零。官方提供 Tailwind CLI 、PostCSS、Play CDN 等使用方式;下面主要讲下如何基于 Tailwind CLI 方式,在应用开发中使用 Tailwind CSS:

Tailwind CSS 可为快应用开发带来哪些好处?

Tailwind CSS 是一种现代的 CSS 框架,它提供了一组预定义的 CSS 类,用于快速开发现代 Web 应用程序。Tailwind 的核心理念是为开发者提供一种灵活的方法来构建自定义的 UI 组件,而不需要编写大量的 CSS 代码。

Tailwind CSS 的工作原理是扫描所有 HTML 文件、JavaScript 组件和任何其他模板的类名,生成相应的样式,然后将它们写入静态 CSS 文件。它快速、灵活、可靠,且运行时间为零。

在快应用开发中引入,可以为项目带来四方面的好处:更快的编写效率更小的代码总体积更高的运行效率更易于项目维护。更详细说明如下:

  • 预定义的 CSS 类可以减少样式的冗余,从而减小 CSS 文件的体积。
  • 大量配套设施,可以提高开发效率,不必手动编写大量的 CSS 代码。
  • 提供了自动优化工具,可以删除未使用的 CSS 代码。
  • 代码体积减小,可以减少下载、加载、解析等步骤耗时。
  • 使 CSS 样式更规范化,避免冗余的样式和复杂的选择器,优化性能。
  • 拥有活跃的社区和文档,可以帮助开发者快速上手和解决问题。

Tailwind CSS:当今最流行 CSS 框架;相信借助其巧妙设计、结合强大生态,您也会偏爱用它来塑造 UI;如果必须要手动编码:那无需频繁滚动编辑区,无需于 template 与 style 之间横跳,无需绞尽脑汁为类命名,无需再编写重复的 CSS......是多么美妙。

Tailwind CSS

如何在项目中使用 Tailwind CSS?

安装 Tailwind CSS 依赖

通过 npm 安装 tailwindcss 并创建 tailwind.config.js 文件,具体命令如下:

pnpm install -D tailwindcss 
# OR 
# npm install -D tailwindcss 
npx tailwindcss init

配置您的模板路径

在文件中添加所有模板文件的路径 tailwind.config.js,参考性配置如下:

const colors = require('tailwindcss/colors')

const selfCustomColors = {
  brand: {
    DEFAULT: '#1e293b',
  },
  warn: {
    DEFAULT: '#f59e0b',
  },
  link: {
    DEFAULT: '#0ea5e9',
  },
  mark: {
    DEFAULT: '#ff4582',
  },
}

module.exports = {
  mode: 'jit',
  content: [
    './src/**/*.{ux,html,js,svelte,vue,ts}',
    './node_modules/flowbite/**/*.js',
  ],
  purge: {
    enabled: true,
    content: [
      './src/**/*.{ux,html,js,svelte,vue,ts}',
      './node_modules/flowbite/**/*.js',
    ],
  },
  // https://tailwindcss.com/docs/configuration#core-plugins
  corePlugins: {
    preflight: false, // disable base/reset styles
    container: false, // disable container component
    content: false, // disable `content` utility
    accentColor: false, // disable `accent-color` utility
    accessibility: false, // disable `appearance` utility
    appearance: false, // disable `appearance` utility 
    aspectRatio: false, // disable `aspect-ratio` utility
    backgroundOpacity: false, // disable `background-opacity` utility
    backdropBlur: false, // disable `backdrop-blur` utility
    backdropBrightness: false, // disable `backdrop-brightness` utility
    backdropContrast: false, // disable `backdrop-contrast` utility
    backdropGrayscale: false, // disable `backdrop-grayscale` utility
    backdropHueRotate: false, // disable `backdrop-hue-rotate` utility
    backdropInvert: false, // disable `backdrop-invert` utility
    backdropOpacity: false, // disable `backdrop-opacity` utility
    backdropSaturate: false, // disable `backdrop-saturate` utility
    backdropSepia: false, // disable `backdrop-sepia` utility
    blur: false, // disable `blur` utility
    borderCollapse: false, // disable `border-collapse` utility
    borderOpacity: false, // disable `border-opacity` utility
    borderSpacing: false, // disable `border-spacing` utility
    boxShadow: false, // disable `box-shadow` utility
    boxShadowColor: false, // disable `box-shadow-color` utility
    boxDecorationBreak: false, // disable `box-decoration-break` utility
    boxSizing: false, // disable `box-sizing` utility
    breakAfter: false, // disable `break-after` utility
    breakBefore: false, // disable `break-before` utility
    breakInside: false, // disable `break-inside` utility
    brightness: false, // disable `brightness` utility
    captionSide: false, // disable `caption-side` utility
    caretColor: false, // disable `caret-color` utility
    clear: false, // disable `clear` utility
    contrast: false, // disable `contrast` utility
    divideColor: false, // disable `divide-color` utility
    divideOpacity: false, // disable `divide-opacity` utility
    divideStyle: false, // disable `divide-style` utility
    divideWidth: false, // disable `divide-width` utility
    float: false, // disable `float` utility
    fontVariantNumeric: false, // disable `font-variant-numeric` utility
    hyphens: false, // disable `hyphens` utility
    isolation: false, // disable `isolation` utility
    lineClamp: false, // disable `line-clamp` utility
    mixBlendMode: false, // diable `mix-blend-mode` utility
    listStyleImage: false, // disable `list-style-image` utility
    listStylePosition: false, // disable `list-style-position` utility
    listStyleType: false, // disable `list-style-type` utility
    objectPosition: false, // disable `object-position` utility
    opacity: false, // disable `opacity` utility
    outlineColor: false, // disable `outline-color` utility
    outlineOffset: false, // disable `outline-offset` utility
    outlineStyle: false, // disable `outline-style` utility
    outlineWidth: false, // disable `outline-width` utility
    overscrollBehavior: false, // disable `overscroll-behavior` utility
    placeContent: false, // disable `place-content` utility
    placeItems: false, // disable `place-items` utility
    placeSelf: false, // disable `place-self` utility
    placeholderOpacity: false, // disable `placeholder-opacity` utility
    resize: false, // disable `resize` utility
    ringColor: false, // disable `ring-color` utility
    ringOffsetColor: false, // disable `ring-offset-color` utility
    ringOffsetWidth: false, // disable `ring-offset-width` utility
    ringOpacity: false, // disable `ring-opacity` utility
    space: false, // disable `space-between` utility
    textDecorationThickness: false, // disable `text-decoration-thickness` utility
    textOpacity: false, // disable `text-opacity` utility
    textTransform: false, // disable `text-transform` utility
    textUnderlineOffset: false, // disable `text-underline-offset` utility
    touchAction: false, // disable `touch-action` utility
    userSelect: false, // disable `user-select` utility
    verticalAlign: false, // disable `vertical-align` utility
    whitespace: false, // disable `whitespace` utility
    wordBreak: false, // disable `word-break` utility
    willChange: false, // disable `will-change` utility
  },
  darkMode: false,
  theme: {
    colors: { ...colors, ...selfCustomColors },
    extend: {
      width: ({ theme }) => ({
        auto: 'auto',
        ...theme('spacing'),
      }),
      height: ({ theme }) => ({
        auto: 'auto',
        ...theme('spacing'),
      }),
      spacing: {
        '1/2': '2px',
        '1': '4px',
        '3/2': '6px',
        '2': '8px',
        '5/2': '10px',
        '3': '12px',
        '7/2': '14px',
        '4': '16px',
        '5': '20px',
        '6': '24px',
        '7': '28px',
        '8': '32px',
        '9': '36px',
        '10': '40px',
        '11': '44px',
        '12': '48px',
        '14': '56px',
        '16': '64px',
        '20': '80px',
        '24': '96px',
        '28': '112px',
        '32': '128px',
        '36': '144px',
        '40': '160px',
        '44': '176px',
        '48': '192px',
        '52': '208px',
        '56': '224px',
        '60': '240px',
        '72': '288px',
        '80': '320px',
        '96': '384px',
      },
      borderWidth: {
        'DEFAULT': '1px',
        '0': '0px',
        '2': '2px',
        '4': '4px',
        '8': '8px',
      },
      borderRadius: {
        'none': '0',
        '': '1px',
        'sm': '2px',
        'DEFAULT': '4px',
        'md': '6px',
        'lg': '8px',
        'xl': '12px',
        '2xl': '16px',
        '3xl': '24px',
      },
      fontSize: {
        'xm': ['12px', { lineHeight: '16px' }],
        'sm': ['14px', { lineHeight: '20px' }],
        'base': ['16px', { lineHeight: '24px' }],
        'lg': ['18px', { lineHeight: '28px' }],
        'xl': ['20px', { lineHeight: '28px' }],
        '2xl': ['24px', { lineHeight: '32px' }],
        '3xl': ['30px', { lineHeight: '36px' }],
        '4xl': ['36px', { lineHeight: '40px' }],
        '5xl': ['48px', { lineHeight: '60px' }],
        '6xl': ['60px', { lineHeight: '60px' }],
        '7xl': ['72px', { lineHeight: '60px' }],
        '8xl': ['96px', { lineHeight: '60px' }],
        '9xl': ['128px', { lineHeight: '60px' }],
      }
    },
  },
  variants: {
    extend: {},
  },
  plugins: [],
}

将 Tailwind 指令添加到您的 CSS 中

@tailwind 将 Tailwind 每个层的指令添加到您的主 CSS 文件中。

/* input.css */

/* @tailwind base; */
@tailwind components;
@tailwind utilities;

启动 Tailwind CLI 构建过程

运行 CLI 工具来扫描模板文件中的类并构建 CSS,参考性命令如下:

npx tailwindcss -i ./src/input.css -o ./src/output.css --watch

这是看起来路径简短的方式,实际上这个 input.cssoutput.css,无论是名字,或是它存在的路径,皆可以自行指定,您可以按照自己喜欢的方式、或是团队约定俗成的形式;譬如统一存放在:src/assets/styles 目录下。

需要补充说明的是:在团队开发中,输入上述命令,略显繁琐,可以在 package.json 注入命令(如下示例);如此以下,您只需在「终端」运行 pnpm tailwindcssnpm run tailwindcss 命令即可。

"scripts": {
  "tailwindcss": "npx tailwindcss -i ./src/input.css -o ./src/output.css --watch",
}

开始在 UX 中使用 Tailwind CSS

将已编译的 CSS 文件,在 .ux (非 app.ux)文件中,通过 @import 方式引入;

<style>
  @import './../../output.css';
</style>

现在,即可在 .uxtemplate 中,开始使用 Tailwind 的实用程序类来设置内容的样式,如下面这段代码示例:

<template>
    <div class="bg-blue-300 w-full flex flex-col justify-center items-center">
      <text class="text-red-600">TailwindCSS Area</text>
      <text class="text-red-600">Awesome TailwindCSS</text>
    </div>
</template>

当您写完保存之后,打开 output.css 即可如下的 CSS 代码:

/* @tailwind base; */

.flex {
  display: flex
}

.w-full {
  width: 100%
}

.flex-col {
  flex-direction: column
}

.items-center {
  align-items: center
}

.justify-center {
  justify-content: center
}

.bg-blue-300 {
  background-color: #93c5fd
}

.text-red-600 {
  color: #dc2626
}

至此,在 快应用 开发中引入 Tailwind CSS 的准备工作,已经初步完成,您可以与 Tailwind CSS 开启愉快合作之旅,从而促使高效编码、提前完工。假如,您对 Tailwind CSS 尚不熟悉,可通过查阅 Tailwind CSS Doc ,亦可在线体验—— Tailwind Playground

需要补充说明的是,使用 Tailwind CSS 并不破坏您原来的 CSS 书写✍🏻方式;您完全可以结合喜欢的预处理器(如 Sass、Less、Stylus),来共同工作,而无需做更多设置(如下代码示例);但我以为当您熟悉 Tailwind CSS 之后,大有可能也会“移情而别恋”。

<style lang="scss">
  @import './../../assets/styles/style.scss';
  @import './../../assets/styles/output.css';

  .primary-btn {
    width: 90 * $size-factor;
    height: 16 * $size-factor;
    background-color: $brand;
    border-radius: 8 * $size-factor;
    color: $white;
  }
</style>

如何基于 Tailwind CSS 提升开发效率?

安装 Tailwind CSS IntelliSense 扩展

您可以在应用开发工具的「扩展市场」,通过关键字 Tailwind 检索”Tailwind CSS IntelliSense“扩展——适用于 Visual Studio Code 的智能 Tailwind CSS 工具,用户提供自动完成、语法突出显示和 linting 等高级功能来增强 Tailwind 开发体验;略作适配即可支持 *.ux;详细设置,可参见: Tailwind CSS IntelliSense

Tailwind CSS IntelliSense 提升 ux 开发效率

默认情况下,编辑“字符串”内容时(例如在 JSX 属性值中),开发工具中不会触发完成。更新设置 editor.quickSuggestions 可能会改善您的体验:

"editor.quickSuggestions": {
  "strings": "on"
}

参考基于 Tailwind CSS 组件的开源库

Tailwind CSS 生态发展繁荣超乎想象,就连其衍生产品也不可胜数;如 Flowbite ——包含 600 多个 UI 组件、部分和页面的开源库进行开发,该库使用 Tailwind CSS 的实用程序类构建并在 Figma 中设计。它不仅能充当 Tailwind CSS 插件使用,也可以拷贝到项目直接使用,还可以在线查阅效果(支持多种屏幕设备类型),挑选匹配实现方案;在仍是基于组件树构建页面的时代,极大提升页面搭建效率。如: pagination 组件:

Tailwind CSS Flowbite Pagination


常见问题及说明

如果您熟悉 Web 开发,针对上述教程,您可能会有诸多疑惑;实际上,笔者在实践之时,也有发现,只不过因为各种机制限制,目前只能如此。下面就与诸君分享,期可解惑:

不能在 script 标签中,引入输出 CSS 文件么?

按 Web 开发习惯,在 app.ux 文件中可以直接 import 输出 CSS 文件(如下代码所示),那为何未建议如此呢?

<script>
import './output.css'
</script>

因为这样做会报如下错误;具体是缺乏与之匹配的 loader;当然,可以使用 css-loader 和 style-loader 来处理 CSS 文件,详见 ChatGPT | You may need an appropriate loader....

You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders

| /* @tailwind base; */

但在快应用开发过程中,却不能生效;因为 style-loader 的作用是将 CSS 注入 DOM,其中用到了诸多浏览器 API(document),而快应用的开发虽然是基于前端技术栈,但其运行环境止于 v8,实际的渲染是由 Android 底层完成,并未走浏览器这条路,因此无法工作。在构建工具优化、兼容之前,通过 style 标签中引入 CSS,成了主要选择。

为何在 tailwind.config.js 禁用如此多样式?

Tailwind corePlugins 部分允许您完全禁用 Tailwind 通常默认生成的类(如果您的项目不需要它们);这个设计对于快应用实在必要的紧,在上述示例中,对 text-opacity 等样式进行禁用,也是无奈之举。具体原因在于以下两点:

其一:快应用标准虽然基于 Web 技术栈,但只是 Web 标准子集,如蛮多 CSS 属性并不能很好的支持;其二:默认情况下,TailwindCSS 会将样式属性的值存储为 CSS 变量(例如 --tw-text-opacity),以便进行动态计算和响应式设计。

如此一来,在 template 中使用类 text-red-300border-spacing-1,输出的便是如下 CSS;这样的写法,在快应用中并不能得到正确解析,也就无法起到期待效果,于是禁用便成了必须要做之事。当然,这样禁用也好处:利于输出更小提及的 CSS 文件。其他属性亦复如是。

.text-red-300 {
  --tw-text-opacity: 1;
  color: rgb(252 165 165 / var(--tw-text-opacity))
}
.border-spacing-1 {
  --tw-border-spacing-x: 4px;
  --tw-border-spacing-y: 4px;
  border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y)
}

为何要在 input.css 中移除 @tailwind base;

这么做主要未解决问题: How disable default styles for : before and : after

/* input.css */

/* @tailwind base; */
@tailwind components;
@tailwind utilities;

在一般 Web 开发过程中,会将 base、components、utilities 等指令,都添加到 CSS 文件项目中;但,在快应用开发过程中,这无疑是累赘;因为它的存在,会使得 output.css 输出较多对项目没有裨益额外内容(如下代码所示),故而移除;这与通过 corePlugins 禁用基础样式(preflight)、禁用容器组件(container)作用类似。

*, ::before, ::after {
  --tw-border-spacing-x: 0;
  ......
}
::backdrop {
  --tw-border-spacing-x: 0;
  ......
}

为什么要在配置中通过 theme 自定义长度单位?

修改长度单位从 rempx,这也是不得已而为之。 快应用 标准对 长度单位 的支持,在 1070 及以下版本仅支持长度单位 px%。从 1080 版本开始,新增了长度单位 dp。它无法支持 rem,就只能自定义设置为 px;在通过 theme 自定义长度单位,是 Tailwind CSS 官方提供的功能,以此能够实现更精细的样式控制;虽然操作不难,但属性较多、约定繁杂。后续时间如果允许,将探索 rem to px 批量转换插件来实现,以达到快速修改。如果您感兴趣,可以查阅 Tailwind CSS 完整版本配置—— config.full.js

其他暂未解决问题

  • 少部分类名写法,快应用构建不支持

在 Tailwind CSS 机制中,支持使用类名诸如 h-[100px] w-1/3 (对应生成 CSS 如下),然而,在快应用构建工具暂不支持这类名,于是乎这样写便不会生效,当然也不会造成什么问题;后续亟待打包工具优化。

.h-\[100px\] {
  height: 100px
}
.w-1\/3 {
  width: 33.333333%
}

在打包(Toolkit)未解决该问题之前,您可以手动写一个 class,或者使用内联样式(style)来兼容;在这种情况下,更推荐后者;因为引入 Tailwind CSS 的开发模式中,基本不用手写 class,即无需滚动到 style 标签或跳转 less、(s)css、文件,那么直接在 template 中使用内联更为便捷。

<div class="training-list-width" style="width: 326px;"></div>

<style>
.training-list-width {
  width: 326px;
}
</style>
  • 快应用构建,资源存在重复引用问题

前文中提到,在 script 标签中 import CSS 资源,暂时未得到支持;但,在 style 标签中 @import 的 CSS 文件,被 A、B 两个页面(*.ux)引入,那么对应的 css 内容,就会被打入对应页面(即便有的内容没有被使用),从而导致代码体积略有增加(即便参考 如何优化「快应用」rpk 包体积? 一文中的方法,也不可避免)。这是快应用构建本身存在的问题,使用 Tailwind CSS 也不例外。后续需要打包工具优化。

以上,就是在 快应用 开发过程中,引入 Tailwind CSS 所需的操作、以及常见问题说明。肯定还有异常更多情况没有考虑到,后续会逐步补充;如果您在实际运用过程中,欢请留言交流、反馈、分享。

2023 年 07 月 05 日写于〔深圳福田〕

猜您可能感兴趣的文章

Tags

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.