跳至主要内容

[Tailwind] 環境建立

TL;DR

之前參加六角學院的切版直播班,有提供Vite基本設定的模板。 本篇文章只是將內容給拆分出來做個紀錄,實際上應該有更好的專案建立方式。
檔案名稱以及指令會用區塊的方式呈現

設定 .gitignore,git init,npm init

首先在資料夾下將git初始化git init、npm初始化npm init
並且新增.gitignore

.gitignore
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

使用NPM安裝Tailwind

npm install -D tailwindcss postcss autoprefixer
  • autoprefixer是為了加上各個瀏覽器的前綴,例如 -webkit-min-device-pixel-ratio: 2
  • 因為Tailwind本身很大一包,需要使用postcss,可以讓撰寫的CSS精簡化,讓沒有使用到的CSS可以被刪減而不會被編譯出來。
  • tailwindcss就不用說明了… css本人!
-D的意思

其中的-D是npm install中的一個選項,是—-save-dev的縮寫,如果有啟用這個設定的話則會安裝在devDependencies中,代表這個是開發中會使用到的套件而不是正式上線的時候使用的套件。

使用NPM安裝Vite等等其他相依套件

npm install -D ejs vite vite-plugin-ejs
npm install vite-plugin-live-reload glob gh-pages sass sass-loader

以上套件的說明如下

  1. autoprefixer: 用於自動為 CSS 添加瀏覽器前綴,以確保在不同瀏覽器中具有相同的樣式效果。通常在開發期間使用。
  2. ejs: Embedded JavaScript (EJS) 是一個模板引擎,用於生成動態 HTML 內容。它可以幫助你在後端生成 HTML,或在前端透過 JavaScript 嵌入動態內容。
  3. postcss: PostCSS 是一個用於處理 CSS 的工具,它允許你使用插件來轉換、優化和擴展 CSS。常用於前端開發中的 CSS 後處理。
  4. tailwindcss: 一個高度可自訂的 CSS 框架,用於快速開發現代網頁界面。它基於一組預定義的 CSS 類別,可用於快速構建 UI 元素。
  5. vite: Vite 是一個快速的前端開發工具,用於建構現代的 Web 應用程式。它具有快速的開發伺服器、即時重載等功能。
  6. vite-plugin-ejs: 這是一個 Vite 插件,用於支援 EJS 模板。它讓你可以在 Vite 專案中使用 EJS 進行動態內容生成。

在 "dependencies" 中的套件:

  1. @tailwindcss/forms: 這是 Tailwind CSS 的一個官方插件,用於樣式化 HTML 表單元素,使它們與 Tailwind CSS 的風格一致。
  2. gh-pages: 一個用於將靜態網站部署到 GitHub Pages 的工具。通常用於將網站部署到 GitHub。
  3. glob: 一個用於匹配文件路徑的工具,通常用於在 Node.js 中進行文件操作,如檔案搜索。
  4. sasssass-loader: 用於處理 SASS(Syntactically Awesome Style Sheets)或 SCSS(Sassy CSS)的套件。它們允許你在專案中使用 SASS 或 SCSS 語法來編寫 CSS。
  5. vite-plugin-live-reload: 這是一個 Vite 插件,用於實現網頁的即時重載功能,使你在開發期間可以看到即時的變更效果。

設定package.jsonscripts

回到package.json中,補上scripts的內容如下

package.json
  "scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"deploy": "vite build && gh-pages -d dist"
}

所以目前的package.json完整內容應該要如下

package.json
{
"name": "專案名稱",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"deploy": "vite build && gh-pages -d dist"
},
"devDependencies": {
"autoprefixer": "^10.4.15",
"ejs": "^3.1.9",
"postcss": "^8.4.28",
"tailwindcss": "^3.3.3",
"vite": "^4.2.0",
"vite-plugin-ejs": "^1.6.4"
},
"dependencies": {
"@tailwindcss/forms": "^0.5.4",//這個我在上面並沒有特別安裝,如果有需要再去tailwind官方查詢,有介紹到表單的套件
"gh-pages": "^5.0.0",
"glob": "^10.2.2",
"sass": "^1.61.0",
"sass-loader": "^13.2.2",
"vite-plugin-live-reload": "^3.0.2"
}
}
package.json內需要特別注意

因為後續會使用到export default的es module語法,所以這邊一定要在package.json中設定"type": "module"
否則會跳以下的警告:To load an ES module, set "type": "module" in the package.json or use the .mjs extension.

設定vite.config.js

這邊需要在根目錄下新增一隻新的檔案如下

vite.config.js
import { defineConfig } from 'vite';
import { ViteEjsPlugin } from 'vite-plugin-ejs';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
import { glob } from 'glob';

import liveReload from 'vite-plugin-live-reload';

function moveOutputPlugin() {
return {
name: 'move-output',
enforce: 'post',
apply: 'build',
async generateBundle(options, bundle) {
for (const fileName in bundle) {
if (fileName.startsWith('pages/')) {
const newFileName = fileName.slice('pages/'.length);
bundle[fileName].fileName = newFileName;
}
}
},
};
}

export default defineConfig({
// base 的寫法:
// base: '/Repository 的名稱/'
base: '/web-layout-training-vite/',
plugins: [
liveReload(['./layout/**/*.ejs', './pages/**/*.ejs', './pages/**/*.html']),
ViteEjsPlugin(),
moveOutputPlugin(),
],
server: {
// 啟動 server 時預設開啟的頁面
open: 'pages/index.html',
},
build: {
rollupOptions: {
input: Object.fromEntries(
glob
.sync('pages/**/*.html')
.map((file) => [
path.relative('pages', file.slice(0, file.length - path.extname(file).length)),
fileURLToPath(new URL(file, import.meta.url)),
])
),
},
outDir: 'dist',
},
});
vite.config.js注意事項

上方highlight的部分需要修正為repo名稱,否則在部署的時候可能會有問題

設定postcss.config.js

官方文件連結

官方文件有提到,不管有沒有要使用postcss,都需要加上這支postcss.config.js檔案 並且將autoprefixer還有tailwindcss加入postcss.config.js這支檔案 所以我們需要創建一個postcss.config.js檔案,並且寫入以下資料

為何與官方寫法不同

前面有提到,會採用ES6的寫法,所以我們也特別在package.json內加上了"type":"module" 我們這邊不採用官方的module.exports的寫法,而是使用ES6的export default

postcss.config.js
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
}
}

我們在這邊採用ES6的寫法

設定tailwind.config.js

新增一個tailwind.config.js,並且設定如下(這邊同樣不採用官方提供的module.exports的寫法,改用export default)

我們同樣採用ES6的export default寫法

tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
content: ['./pages/**/*.html', './layout/**/*.ejs', './main.js'],
theme: {
extend: {},
},
plugins: [],
}
content 陣列的用途

邊需要注意content內要撰寫tailwind要追蹤的檔案有哪些,是用陣列包字串的方式去撰寫。
例如./pages/\*\*/\*.html代表會追蹤pages資料夾下,不管多少層級(**)下的副檔名是html的檔案
追蹤的用途是為了確認有使用到哪些的tailwind css,會透過postcss去精簡部署時的css檔案。

如果追蹤的檔案沒有使用到的tailwind css,就不會編譯出來,縮減css檔案的大小。
算是補償了tailwind css肥大的問題~
如果發現編譯出來的結果沒有正確顯示,就需要來調整這個區塊的content內容。

設定main.css並加上index.html

官方文件連結

文件中有寫到,需要在main.css中加上以下的內容 又因為我們有使用vite,所以我們需要先創建一個assets資料夾,才在裡面加上一個main.css檔案,寫入官方文件中所指定的資訊

./
├── assets/
│   └── css/
│   └── main.css
├── main.js
├── package-lock.json
├── package.json
├── pages/
│   └── index.html
├── postcss.config.js
├── readme.md
├── tailwind.config.js
└── vite.config.js

測試功能性

可以使用npm run dev,會開啟一個伺服器,並且會隨著每次儲存資料去更新渲染的畫面 例如在index.html中輸入如下 index.html測試tailwind載入範例 index.html測試tailwind測試結果,開發者工具展示

另外,使用npm run build 會建立一個dist資料夾(如下圖),並且在assets內會有一個編譯過後的css檔案 npm run build之後所產生的css檔案

可以觀察到這隻被編譯出來的css檔案中,其實內容並沒有很多。
因為是已經被壓縮過後的所以可讀性不高,可是還是可以看到在最後有一個.text-blue-500的class
是因為我們在index.html中有使用到這個class,所以在編譯的時候經過postcss才會被保留。其他則都是一些base等等的設定。

編譯過後的css
*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.text-blue-500{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity))}