Whidy Writes

Vite中ElementPlus和TailwindCSS最佳实践(一)

发布于:(更新于:

更新说明:2022年06月09日

强烈建议直接看推荐的方案中最新分支的代码,主要是移除了css文件放在index.html的需要。

特别说明

与此同时,这个按需加载的最佳方案,当升级大于 2.2.0Element Plus 后,尝试删除 vite.config.tsbuild 部分配置,也不再存在 el-button 的问题了,当然这个是因为 2.2.0 更新button的样式产生的巧合还是其他,我不再做过多的探索,如果在其他的组件和 Tailwindcss 相关样式发生冲突的时候,你可能仍然需要思考,并加以调整。关于样式冲突的问题,暂时就研究到这里了。

该分支依然保留作为学习和参考,故而不再此分支做依赖升级和配置调整的示例了。


Vite 项目中使用 ElementPlusTailwindCSS 出现按钮组件样式问题,明明开发的时候是好好的,这下没测试,上线原地爆炸,我的蓝色按钮上线裸奔,老板提着刀正在赶往你的工位中。。。快来看看怎么回事!!!

最近一直在尝试使用 Vue3 来开发新的项目,在开发过程中也是遇到了许许多多问题,我目前主要也是用 Vite 搭建项目,这次的问题是升级了TailwindCSS后,导致ElementPlus的按钮样式全部破坏了,研究了一些时间,算是找到了一个并不完美但勉强合理的解决方案。希望能对遇到相同的问题的前端开发者有所帮助。当然这里需要强调的是,Vite.js、Vue3、ElementPlus UI、TailwindCSS这些更新频繁,版本迭代很快,可能一不小心我这篇文章就没有作用了,不过我估计应该在几个月甚至一年内可能都会有效,除非官方优化了Vite构建的能力。

特别说明,本文仅关注vite搭建项目中的UI库和CSS库之间的问题,请不要刻意关注Vite相关配置或TS等其他配置及编码上的无关修改。

项目环境

先说明一下项目依赖的版本(下面有两个版本,低版本是我在生产环境下的情况,高版本是我的Demo中用到的,不过结果相同):

  • vite: 2.6.52.7.2
  • vue: 3.2.223.2.25
  • element-plus: 1.2.0-beta.31.3.0-beta.5
  • tailwindcss: 3.0.63.0.15

实际上样式问题主要还是要注意 TailwindCSS 版本,因为是它覆盖了 ElementPlus 的一些样式。TailwindCSS其实在很早的版本(不记得具体是v2的哪个版本号之后),就出现这个问题了。

实验场景及问题说明

其实总共就两个使用场景,一个是全局引入ElementPlus的情况,还有一个是按需导入ElementPlus的情况。ElementPlus目前仍处于测试阶段,不过全局导入基本上没什么大问题,如果很早的使用这个UI框架的话,会发现采用Vite搭建项目的按需导入方案经常改动。这里我用最新的按需导入方案进行说明。

顺便提一下,该问题在TailwindCSS的Discussions也有人提到,详见:3.x newly increased button, [type='button'], [type='reset'], [type='submit'] style influence,我也在下面简单的回复留言了,有兴趣可以了解下。

这里先把问题通过图片来展示一下:

请注意按钮样式

引发这个问题的原因,其实是这么一段CSS覆盖了第三方UI库的按钮样式:

/* 1. Correct the inability to style clickable types in iOS and Safari. 2. Remove default button styles. */ button, [type='button'], [type='reset'], [type='submit'] { -webkit-appearance: button; /* 1 */ background-color: transparent; /* 2 */ background-image: none; /* 2 */ }

这段代码来自当前最新版(3.0.15)的TailwindCSS的preflight.css文件。搜索该文件 Remove default button styles. 找到罪魁祸首~

场景一:全局引入ElementPlus

“看过图片但不知道代码是咋写的,不会是作者太菜,配置错了引发的BUG吧。”

我们可以先看在线Demo:https://codesandbox.io/s/romantic-frog-9uzlg,如果访问不了,可以看看我在github上存放的项目https://github.com/whidy/elementplus-tailwindcss-best-practice,本地跑一下。

全局引入的情况下,要解决这个问题非常简单,只要让 import 'element-plus/dist/index.css' 引入的顺序在TailwindCSS样式引入之后就行。我就不多解释了。

场景二:按需导入ElementPlus(特别注意)

按照官方的按需导入部分的说明,写好代码, npm run dev 跑一下,咦?看起来都挺好。按需导入的方式看起来没什么问题嘛~

可别高兴的太早,等到打包到测试线上测试或勇士们直接发布上线的时候就要出事了!是的,这里我要说的就是,生产环境和开发环境的样式竟然不一样!千万要注意这个问题。经验不足的开发者可能就懵了。

接下来,我们来仔细研究下,在 开发环境 ,所有的样式都是直接注入到 htmlhead 内,而正好TailwindCSS的样式在最前面,所以未出现样式问题。而在 生产环境 ,打包后的样式文件分为两部分,一部分是项目中引入的TailwindCSS,另一部分则是把所有vue组件中用到的样式(包含按需导入ElementPlus组件的样式)打包成一个文件了。如下图所示:

错误的CSS引入顺序导致的样式问题

很奇怪,为什么Vite开发和生产打包过程中会出现不同的结果,其实不仅Vite存在,我印象中很久以前 Vue-CLI 也遇见过这个问题。当然想要研究这个顺序,就要去关注下构建工具rollup了。

至此,如何解决按需导入ElementPlus的模式下样式顺序就是本文的重点了。我在解决此问题的时候正好也看到有人提到打包顺序问题:Vite injects css assets in wrong order with dynamic import and css modules.

几个解决方案

接下来主要探讨的是针对按需导入情况。这里总结了几个或简单或复杂的方案,大家可以根据实际情况来选择。

方案一:重复引入一个完成的ElementPlus样式文件

这个是最快的办法,修改 main.ts 文件,在引入TailwindCSS的样式文件后,再引入一个全局的 ElementPlus 样式,例如:

import { createApp } from 'vue' import '@/assets/styles/main.css' // 这是tailwindcss的样式 import "element-plus/dist/index.css"; // 重复引入ElementPlus的完整样式 import App from './App.vue' const app = createApp(App) app.mount('#app')

问题解决了,新的“问题”诞生了,构建过程中可能出现的一大串警告提示:

warnings when minifying css: ...

这个对Web程序并没有什么大的影响。然而另一个问题就是,你在调试页面样式的时候会看到重复的样式类名。这是很难受的(对我而言),开发者都应当尽量避免冗余代码,尤其是调试时,如果自己的样式优先级较低,你想关闭ElementPlus组件的样式,你得点两次勾

因此这是懒人解决方案。对应的项目分支solution-1

方案二

这个方案也不难,如果是新项目开发,避坑就很容易了。为什么?请往下看。

TailwindCSS无论是配置文件 tailwind.config.js 的个性化定制还是的样式文件 main.css 个性化定制都是非常灵活的。一开始提到的那段糟糕的按钮样式,其实可以通过几个方式避免:

  1. 移除main.css内的@tailwind base;这行代码(特别注意:不建议移除,请用下面的方式来屏蔽,参考:Base layer must be present,我故意保留此信息以表示重点。);
  2. 修改配置文件tailwind.config.js,禁用掉默认开启的preflight。文档:Disabling Preflight

~~上面的操作二选一即可,当然你非要同时操作也不会爆炸XD。~~完整的官方文档:Preflight

不过没有了基础样式,有个很容易解决的小问题:不同浏览器都有自带的默认样式,那页面的效果就会有所不同,因此,禁用了TailwindCSS的基础样式后,我们需要创建新的基础样式,我个人建议如下:

  • 使用normalize.css中的样式,我印象中TailwindCSS 1.x的时候就用的它;
  • 使用TailwindCSS 3.x文档中提到的基于modern-normalize改进的基础样式表原版;
  • 还有许多基础样式库,比如:minireset.css,或者搜索reset.css寻找适合自己的;
  • 当然你也可以手写一套,其实早期前端很多都是手写的,如果老前端开发的话也许还记得YUIreset.css貌似暴露了自己老了还很菜的事实)。

关于基础样式也要注意 normalize.css 或是 modern-normalizereset.css 有着不同的设计理念,前者会保留部分浏览器自带样式,例如段落p的样式,后者万物清空统一(这是一个CSS相关的大话题,这里不展开讨论)。所以你要自行决定哪种适合自己,至于是手动添加一个css文件还是通过npm来安装并import,你喜欢咯~

所以特别需要注意的是:开发新项目初期就务必做好基础样式配置(曾经因为未正确配置基础样式导致后面大批修改样式的惨剧 ):

对应的项目分支solution-2

方案三(推荐)

聪明的你读到这里,一定会发现方案二其实是有点问题的。请阅读下面这段代码思考片刻:

// Lovely pig has arrived! // _ // _._ _..._ .-', _.._(`)) // '-. ` ' /-._.-' ',/ // ) \ '. // / _ _ | \ // | a a / | // \ .-. ; // '-('' ).-' ,' ; // '-; | .' // \ \ / // | 7 .__ _.-\ \ // | | | ``/ /` / // /,_| | /,_/ / // /,_/ '`-' // // Follow me, speak: "I am a pig, lovely pig XD"

想出来了吗:D。最主要的问题是,就算引入了其他的基础样式,但是没能解决ElementPlus样式可能被覆盖的问题!也就是说按钮的显示正常了,但还是有可能出现新的基础样式中某些样式影响(覆盖)到ElementPlus的样式!因此我们还是要从项目构建编译过程中寻求答案。

我能想到的合理的思路是:【Vite.js中的构建选项】 -> 【找到rollup.js的Big list of options】 -> 【研究rollup.js找到方法并修改项目代码(困难)】 -> 【未能完成上一步,选找新(qi)的(ji)方(yin)案(qiao)】。

是的,我的思路很清晰(奇),我在整个实验过程中认真且完美的执行了上面四个步骤。最后我要详细介绍一下这个新的方案了。

只需要基于场景二,做一点文件修改就好了,分别是 index.htmlmain.tsvite.config.js ,修改如下:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <link rel="icon" href="/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Vite App</title> <!-- 更新日期:2022年06月09日 --> </head> <body> <div id="app"></div> <script type="module" src="/src/main.ts"></script> </body> </html>
// 这是最原始的vite.js创建项目时的main.ts文件。 // 更新日期:2022年06月09日 import { createApp } from 'vue' import App from './App.vue' import '@/assets/styles/main.css' createApp(App).mount('#app')
import { resolve } from "path"; import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import AutoImport from 'unplugin-auto-import/vite' import Components from 'unplugin-vue-components/vite' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' // https://vitejs.dev/config/ export default defineConfig({ plugins: [ vue(), AutoImport({ resolvers: [ElementPlusResolver()], }), Components({ resolvers: [ElementPlusResolver()], }), ], resolve: { alias: { "@": resolve(__dirname, "src"), }, }, build: { rollupOptions: { output: { manualChunks(id) { // 更新日期:2022年06月09日 // 下面这三行css代码打包的配置可以随你移除或者保留~移除后会自动生成一个css,和下面的示例图会有区别。 if (id.includes("assets/styles/main.css")) { return "tailwindcss"; } if (id.includes("element-plus/theme-chalk/")) { // 当然也可以优化下这个判断,不过目前这样写足矣了。 return "element-plus"; } }, }, }, }, server: { port: 3201 } })

完成后,再试试 npm run build 一下,看看结果如何:

正确的CSS引入顺序

图中的名字也很清晰,不过我还是做了序号,稍微解释下:

  1. 这是Vue组件中的样式打包;
  2. tailwindcss的样式文件,名字是根据vite项目配置中设定的;
  3. element-plus的样式文件,名字是根据vite项目配置中设定的。

至此,按需导入ElementPlus场景下使用TailwindCSS造成的样式问题就解决了。

就目前来看,这是相对完美的解决方案,能想到这个方案一方面是知道Vite.js中的 index.html 也能引入JS或CSS模块进行解析构建,另一方面是在解决问题中看到了这个Issues:vendor css injected with wrong order after build,再经过不断各种组合、反复的尝试和研究,摸索出来的。不确定是不是Vite.js在构建方面有待完善,还是我在rollup.js了解不足,我未能在第三步(研究rollup.js找到方法并修改项目代码)解决。

好在最终问题解决了。其实更多需要关注的大概就是 vite.config.jsbuild 那段代码了,有兴趣可以阅读rollup.js - output.manualChunks选项说明。不过我试过这里面的顺序并不产生任何影响。

对应的项目分支solution-3

总结

实际上,我对终极解决方案不是很满意的,精力有限,暂时也就在此止笔了。

项目开发中也有一些其他的感想,比如Vue3发布一年多了,很多配套依然不完善,无论是 ElementPlus 还是 AntD Vue ,目前都是测试阶段,去年年初我在两个内部项目分别尝试了这两个UI,到目前,ElementPlus经过几次Break Change,AntD Vue也从早期适配Vue3的 2.x 升级到 3.x 了。如果开发者是面向商业的、要求较高的项目,使用Vue3框架及尚不稳定的UI框架还是需要更多的人力成本的。

另外TailwindCSS发展迅猛,我很早就在用了,去年又看到个 Windi CSS ,也是很火,或许用这个不会存在按钮样式冲突的问题,大家也可以试试。

最最后,如果有rollup.js大佬看到,或者有更好解决方案的朋友,欢迎留言多多交流~

avatar

Whidy

一名爱折腾的前端开发工程师,喜欢打篮球和分享 ฅʕ•̫͡•ʔฅ