├── src ├── types.ts ├── debug.ts ├── utils.ts ├── constants.ts ├── options.ts └── index.ts ├── .github ├── FUNDING.yml └── workflows │ └── release.yml ├── .eslintignore ├── .npmrc ├── .gitignore ├── pnpm-workspace.yaml ├── .eslintrc.json ├── example ├── src │ ├── main.css │ ├── main.ts │ └── App.vue ├── tailwind.config.js ├── index.html ├── vite.config.ts └── package.json ├── .vscode └── settings.json ├── tsconfig.json ├── LICENSE ├── package.json ├── README.md └── pnpm-lock.yaml /src/types.ts: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: antfu 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ignore-workspace-root-check=true 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - example/ 3 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@antfu/eslint-config", 3 | "rules": { 4 | "@typescript-eslint/no-unused-vars": "off" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /example/src/main.css: -------------------------------------------------------------------------------- 1 | .button { 2 | @apply p-4 bg-blue-400 text-white rounded hover:bg-blue-600 cursor-pointer; 3 | margin: 5px; 4 | } 5 | -------------------------------------------------------------------------------- /example/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from '/src/App.vue' 3 | import 'windi.css' 4 | import './main.css' 5 | 6 | createApp(App).mount('#app') 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Windi", 4 | "Windicss" 5 | ], 6 | "typescript.tsdk": "node_modules/typescript/lib", 7 | "volar.tsPlugin": true 8 | } -------------------------------------------------------------------------------- /example/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('windicss/plugin/typography'), 4 | ], 5 | darkMode: 'class', 6 | theme: { 7 | extend: { 8 | colors: { 9 | teal: { 10 | 100: '#096', 11 | }, 12 | }, 13 | }, 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vite App 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - run: npx conventional-github-releaser -p angular 14 | env: 15 | CONVENTIONAL_GITHUB_RELEASER_TOKEN: ${{secrets.GITHUB_TOKEN}} 16 | -------------------------------------------------------------------------------- /example/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { UserConfig } from 'vite' 2 | import Vue from '@vitejs/plugin-vue' 3 | import WindiCSS from 'vite-plugin-windicss' 4 | 5 | const config: UserConfig = { 6 | plugins: [ 7 | Vue({ 8 | include: [/\.vue$/, /\.md$/], 9 | }), 10 | ...WindiCSS({ 11 | safelist: 'shadow shadow-xl', 12 | }), 13 | ], 14 | } 15 | 16 | export default config 17 | -------------------------------------------------------------------------------- /example/src/App.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | -------------------------------------------------------------------------------- /src/debug.ts: -------------------------------------------------------------------------------- 1 | import _debug from 'debug' 2 | 3 | export const debug = { 4 | config: _debug('vite-plugin-windicss:config'), 5 | css: _debug('vite-plugin-windicss:css'), 6 | debug: _debug('vite-plugin-windicss:debug'), 7 | compile: _debug('vite-plugin-windicss:compile'), 8 | glob: _debug('vite-plugin-windicss:glob'), 9 | detect: _debug('vite-plugin-windicss:detect'), 10 | hmr: _debug('vite-plugin-windicss:hmr'), 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "target": "es2017", 5 | "lib": ["ESNext", "DOM"], 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "strictNullChecks": true, 9 | "moduleResolution": "Node", 10 | "resolveJsonModule": true, 11 | "skipLibCheck": true 12 | }, 13 | "exclude": [ 14 | "**/dist", 15 | "**/node_modules", 16 | "**/test" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export function toArray(v: T | T[]): T[] { 2 | if (Array.isArray(v)) 3 | return v 4 | return [v] 5 | } 6 | 7 | export function kebabCase(str: string) { 8 | return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase() 9 | } 10 | 11 | export function include(set: Set, v: T[] | Set) { 12 | for (const i of v) 13 | set.add(i) 14 | } 15 | 16 | export function exclude(set: Set, v: T[] | Set) { 17 | for (const i of v) 18 | set.delete(i) 19 | } 20 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fixture", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "nodemon --watch ../dist/index.js -x \"cross-env DEBUG=vite-plugin-windicss:* vite\"", 7 | "build": "cross-env DEBUG=vite-plugin-windicss:* vite build" 8 | }, 9 | "dependencies": { 10 | "vue": "^3.0.5" 11 | }, 12 | "devDependencies": { 13 | "@vitejs/plugin-vue": "^1.1.4", 14 | "@vue/compiler-sfc": "^3.0.5", 15 | "cross-env": "^7.0.3", 16 | "nodemon": "^2.0.7", 17 | "typescript": "^4.1.5", 18 | "vite": "^2.0.0-beta.69", 19 | "vite-plugin-windicss": "workspace:*" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Anthony Fu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-plugin-windicss", 3 | "version": "0.1.7", 4 | "main": "dist/index.js", 5 | "module": "dist/index.mjs", 6 | "types": "dist/index.d.ts", 7 | "license": "MIT", 8 | "author": "antfu ", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/antfu/vite-plugin-windicss" 12 | }, 13 | "homepage": "https://github.com/antfu/vite-plugin-windicss", 14 | "bugs": "https://github.com/antfu/vite-plugin-windicss/issues", 15 | "files": [ 16 | "dist" 17 | ], 18 | "scripts": { 19 | "dev": "npm run build -- --watch", 20 | "example:dev": "npm -C example run dev", 21 | "example:build": "npm -C example run build", 22 | "build": "tsup src/index.ts --dts --format cjs,esm", 23 | "prepublishOnly": "npm run build", 24 | "release": "npx bumpp --commit --tag --push && npm publish" 25 | }, 26 | "dependencies": { 27 | "fast-glob": "^3.2.5", 28 | "windicss": "^2.1.6" 29 | }, 30 | "peerDependencies": { 31 | "vite": "^2.0.0-beta.69" 32 | }, 33 | "devDependencies": { 34 | "@antfu/eslint-config": "^0.4.3", 35 | "@types/debug": "^4.1.5", 36 | "@types/node": "^14.14.28", 37 | "@typescript-eslint/eslint-plugin": "^4.15.1", 38 | "debug": "^4.3.2", 39 | "eslint": "^7.20.0", 40 | "rollup": "^2.39.0", 41 | "tsup": "^3.12.1", 42 | "typescript": "^4.1.5", 43 | "vite": "^2.0.0-beta.69" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-use-before-define */ 2 | 3 | export const MODULE_ID = 'windi.css' 4 | export const MODULE_ID_VIRTUAL = `/@windicss/${MODULE_ID}` 5 | 6 | export const regexQuotedString = /(["'`])((?:\\\1|(?:(?!\1)).)*?)\1/g 7 | export const regexClassCheck = /^[a-z-]+[a-z0-9:\-/\\]*\.?[a-z0-9]$/ 8 | export const regexHtmlTag = /<([\w-]+)/g 9 | 10 | export const defaultAlias: Record = { 11 | 'router-link': 'a', 12 | } 13 | 14 | export const preflightTags = ['html', 'body', 'div'] 15 | export const htmlTags = [ 16 | 'html', 17 | 'body', 18 | 'div', 19 | 'a', 20 | 'abbr', 21 | 'address', 22 | 'area', 23 | 'article', 24 | 'aside', 25 | 'audio', 26 | 'base', 27 | 'basefont', 28 | 'bdo', 29 | 'blink', 30 | 'blockquote', 31 | 'br', 32 | 'button', 33 | 'canvas', 34 | 'caption', 35 | 'center', 36 | 'col', 37 | 'colgroup', 38 | 'command', 39 | 'comment', 40 | 'datalist', 41 | 'dd', 42 | 'del', 43 | 'details', 44 | 'dir', 45 | 'dl', 46 | 'dt', 47 | 'embed', 48 | 'fieldset', 49 | 'figure', 50 | 'b', 51 | 'big', 52 | 'i', 53 | 'small', 54 | 'tt', 55 | 'font', 56 | 'footer', 57 | 'form', 58 | 'frame', 59 | 'frameset', 60 | 'head', 61 | 'header', 62 | 'hgroup', 63 | 'h1', 64 | 'h2', 65 | 'h3', 66 | 'h4', 67 | 'h5', 68 | 'h6', 69 | 'hr', 70 | 'isindex', 71 | 'iframe', 72 | 'ilayer', 73 | 'img', 74 | 'input', 75 | 'ins', 76 | 'keygen', 77 | 'keygen', 78 | 'label', 79 | 'layer', 80 | 'legend', 81 | 'li', 82 | 'link', 83 | 'map', 84 | 'mark', 85 | 'marquee', 86 | 'menu', 87 | 'meta', 88 | 'meter', 89 | 'multicol', 90 | 'nav', 91 | 'nobr', 92 | 'noembed', 93 | 'noframes', 94 | 'noscript', 95 | 'object', 96 | 'ol', 97 | 'optgroup', 98 | 'option', 99 | 'output', 100 | 'p', 101 | 'param', 102 | 'cite', 103 | 'code', 104 | 'dfn', 105 | 'em', 106 | 'kbd', 107 | 'samp', 108 | 'strong', 109 | 'var', 110 | 'plaintext', 111 | 'pre', 112 | 'progress', 113 | 'q', 114 | 'ruby', 115 | 'script', 116 | 'section', 117 | 'select', 118 | 'spacer', 119 | 'span', 120 | 's', 121 | 'strike', 122 | 'style', 123 | 'sub', 124 | 'sup', 125 | 'svg', 126 | 'table', 127 | 'tbody', 128 | 'td', 129 | 'textarea', 130 | 'tfoot', 131 | 'th', 132 | 'thead', 133 | 'time', 134 | 'title', 135 | 'tr', 136 | 'u', 137 | 'ul', 138 | 'video', 139 | 'wbr', 140 | 'wbr', 141 | 'xmp', 142 | ] as const 143 | 144 | export type TagNames = (typeof htmlTags)[number] 145 | -------------------------------------------------------------------------------- /src/options.ts: -------------------------------------------------------------------------------- 1 | import type { Config as WindiCssOptions } from 'windicss/types/interfaces' 2 | import { defaultAlias, TagNames } from './constants' 3 | import { kebabCase } from './utils' 4 | 5 | export { WindiCssOptions } 6 | 7 | export interface UserOptions { 8 | /** 9 | * Options for windicss/tailwindcss. 10 | * Also accepts string as config file path. 11 | * 12 | * @default 'tailwind.config.js' 13 | */ 14 | windicssOptions?: WindiCssOptions | string 15 | 16 | /** 17 | * Enabled windicss preflight (a.k.a TailwindCSS style reset) 18 | * 19 | * @default true 20 | */ 21 | preflight?: boolean | { 22 | /** 23 | * Safelist to always included 24 | */ 25 | safelist?: string | string[] 26 | 27 | /** 28 | * Alias for resolving preflight 29 | */ 30 | alias?: Record 31 | 32 | /** 33 | * @default true 34 | */ 35 | includeBase?: boolean 36 | 37 | /** 38 | * @default true 39 | */ 40 | includeGlobal?: boolean 41 | 42 | /** 43 | * @default true 44 | */ 45 | includePlugin?: boolean 46 | } 47 | 48 | /** 49 | * Directories to search for classnames 50 | * 51 | * @default 'src' 52 | */ 53 | searchDirs?: string[] 54 | 55 | /** 56 | * File extension to search for classnames 57 | * 58 | * @default 'html', 'vue' 59 | */ 60 | searchExtensions?: string[] 61 | 62 | /** 63 | * Transform CSS for `@apply` directive 64 | * 65 | * @default true 66 | */ 67 | transformCSS?: boolean 68 | 69 | /** 70 | * Sort the genrate utilities 71 | * 72 | * @default true 73 | */ 74 | sortUtilities?: boolean 75 | 76 | /** 77 | * Safe class names to be always included. 78 | */ 79 | safelist?: string | string[] 80 | } 81 | 82 | export function resolveOptions(options: UserOptions) { 83 | const { 84 | windicssOptions = 'tailwind.config.js', 85 | searchExtensions = ['html', 'vue', 'pug', 'jsx', 'tsx', 'svelte'], 86 | searchDirs = ['src'], 87 | preflight = true, 88 | transformCSS = true, 89 | sortUtilities = true, 90 | } = options 91 | 92 | const preflightOptions = Object.assign( 93 | { 94 | includeBase: true, 95 | includeGlobal: true, 96 | includePlugin: true, 97 | alias: {}, 98 | }, 99 | typeof preflight === 'boolean' ? {} : preflight, 100 | ) 101 | 102 | preflightOptions.alias = Object.fromEntries( 103 | Object.entries({ 104 | ...defaultAlias, 105 | ...preflightOptions.alias, 106 | }).filter(([k, v]) => [kebabCase(k), v]), 107 | ) 108 | 109 | return { 110 | windicssOptions, 111 | searchExtensions, 112 | searchDirs, 113 | transformCSS, 114 | preflight: Boolean(preflight), 115 | preflightOptions, 116 | sortUtilities, 117 | } 118 | } 119 | 120 | export type ResolvedOptions = ReturnType 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

vite-plugin-windicss

2 | 3 |

Windicss for Vite
4 | a.k.a On-demand TailwindCSS 5 |

6 | 7 |

8 | 9 | 10 | 11 |

12 | 13 |

14 | 15 | 16 | 17 |

18 | 19 | ## Features 20 | 21 | - ⚡️ **It's FAST** - about 15~20 times faster than Tailwind on Vite 22 | - 🧩 On-demand CSS utilities (Compatible with Tailwind CSS v2) 23 | - 📦 On-demand native elements style reseting 24 | - 🔥 Hot module replacement (HMR) 25 | - 🍃 Load configurations from `tailwind.config.js` 26 | - 🤝 Framework-agnostic - Vue, React, Svelte and vanilla! 27 | - 📄 CSS `@apply` / `@screen` directives transforms (also works for Vue SFC's `