├── package.json ├── LICENSE ├── index.js └── README.md /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@samuells/vue-tailwindcss-responsive-directive", 3 | "description": "Vue directive for tailwind responsive variants.", 4 | "version": "0.9.2", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/samuells/vue-tailwindcss-responsive-directive.git" 9 | }, 10 | "main": "index.js" 11 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Samuel Snopko 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 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | function mapBreakpointsOnClasses(dirValue, origClasses = "") { 2 | const screens = Object.keys(dirValue); 3 | const regex = /\s+/g; 4 | screens.forEach(screen => { 5 | const screenClasses = " " + dirValue[screen]; 6 | origClasses += screenClasses.replace(regex, ` ${screen}:`); 7 | }); 8 | return origClasses; 9 | } 10 | 11 | // Server side directive 12 | // In NuxtJS it is needed for Universal mode 13 | export const server = (node, dir) => { 14 | node.data.class += mapBreakpointsOnClasses(dir.value); 15 | }; 16 | 17 | // Client side directive (SPA mode) 18 | // In NuxtJS it is needed for SPA & Universal mode 19 | export const client = { 20 | bind(el, dir) { 21 | el.className += mapBreakpointsOnClasses(dir.value); 22 | }, 23 | update(el, dir, vnode) { 24 | const classes = vnode.data.staticClass + " " + vnode.data.class; 25 | el.className = mapBreakpointsOnClasses(dir.value, classes); 26 | } 27 | }; 28 | 29 | // This is extractor for PurgeCSS 30 | export const extractor = { 31 | extractor: class { 32 | static extract(content) { 33 | // clean content from unnecesary spaces 34 | const cleanedContent = content.replace(/\s+/g, ` `); 35 | // get only v-screen directive content 36 | const screenDirectives = cleanedContent.match( 37 | /v-screen=("|'){((?!{|}).)+}("|')/g 38 | ); 39 | let whitelabledClasses = ""; 40 | if (screenDirectives !== null) { 41 | screenDirectives.forEach(string => { 42 | let valueString = string.match(/{.+}/g)[0]; 43 | valueString = valueString 44 | .replace(/\s:/g, ":") 45 | .replace(/:\s/g, '":') 46 | .replace(/,\s/g, ",") 47 | .replace(/{\s/g, "{") 48 | .replace(/'/g, '"') 49 | // .replace(/:/g, '":') 50 | .replace(/{/g, '{"') 51 | .replace(/,/g, ',"'); 52 | 53 | const valueObject = JSON.parse(valueString); 54 | const screens = Object.keys(valueObject); 55 | screens.forEach(screen => { 56 | const screenClasses = " " + valueObject[screen]; 57 | whitelabledClasses += screenClasses.replace(/\s+/g, ` ${screen}:`); 58 | }); 59 | }); 60 | } 61 | // original - get all strings 62 | const allStrings = content.match(/[A-z0-9-:\\/]+/g); 63 | // prepare computed screen classes 64 | const whitelabledArray = whitelabledClasses.split(" "); 65 | 66 | allStrings.push(...whitelabledArray); 67 | 68 | return allStrings; 69 | } 70 | }, 71 | extensions: ["vue"] 72 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TailwindCSS Directive for Responsive classes (SPA/SSR) 2 | [![npm (scoped with tag)](https://img.shields.io/npm/v/@samuells/vue-tailwindcss-responsive-directive/latest.svg?style=flat-square)](https://www.npmjs.com/package/@samuells/vue-tailwindcss-responsive-directive) 3 | [![npm](https://img.shields.io/npm/dt/@samuells/vue-tailwindcss-responsive-directive.svg?style=flat-square)](https://www.npmjs.com/package/@samuells/vue-tailwindcss-responsive-directive) 4 | [![Dependencies](https://david-dm.org/samuells/vue-tailwindcss-responsive-directive/status.svg?style=flat-square)](https://david-dm.org/samuells/vue-tailwindcss-responsive-directive) 5 | 6 | > VueJS directive which gives the order to TailwindCSS classes. 7 | 8 | 9 | 10 | **Table of Contents** 11 | 12 | - [WHY ⁉](#why-%E2%81%89) 13 | - [Instead of this code (bad sample)](#instead-of-this-code-bad-sample) 14 | - [Write and read this (good sample)](#write-and-read-this-good-sample) 15 | - [Setup](#setup) 16 | - [VueJS (Client)](#vuejs-client) 17 | - [NuxtJS](#nuxtjs) 18 | - [Client Side (SPA & Universal mode)](#client-side-spa--universal-mode) 19 | - [Server Side (Univeral mode)](#server-side-univeral-mode) 20 | - [PurgeCSS && @nuxtjs/tailwindcss](#purgecss--nuxtjstailwindcss) 21 | - [Known Problems](#known-problems) 22 | - [MIT License](#mit-license) 23 | - [Author & Contributors](#author--contributors) 24 | 25 | 26 | 27 | ## WHY ⁉ 28 | 29 | Do you like to write the your CSS using the [TailwindCSS framework](https://tailwindcss.com/)? 30 | 31 | **But you hate those never ending strings of classes?** 32 | 33 | ### Instead of this code (bad sample) 34 | ``` html 35 |
38 | ``` 39 | 40 | ### Write and read this (good sample) 41 | ``` html 42 |
50 | ``` 51 | 52 | - Radically improve the readability of your code. 53 | - Provides **client(SPA)** and **server(SSR)** directive 54 | - Provides **extractor** and setup for PurgeCSS. 55 | 56 | ## Setup 57 | 58 | ```bash 59 | $ npm i @samuells/vue-tailwindcss-responsive-directive 60 | or 61 | $ yarn add @samuells/vue-tailwindcss-responsive-directive 62 | ``` 63 | 64 | ### VueJS (Client) 65 | 66 | Create custom directive and name it "`screen`" 67 | 68 | ```js 69 | import Vue from 'vue' 70 | import { client } from '@samuells/vue-tailwindcss-responsive-directive' 71 | 72 | Vue.directive('screen', client) 73 | ``` 74 | 75 | ### NuxtJS 76 | 77 | #### Client Side (SPA & Universal mode) 78 | 79 | Create custom client side directive named "`screen`" as plugin file 80 | 81 | ```js 82 | // ./plugins/tailwind-screen.js 83 | import Vue from 'vue' 84 | import { client } from '@samuells/vue-tailwindcss-responsive-directive' 85 | 86 | Vue.directive('screen', client) 87 | ``` 88 | 89 | #### Server Side (Univeral mode) 90 | 91 | ```js 92 | // ./nuxt.config.js 93 | import { server } from '@samuells/vue-tailwindcss-responsive-directive' 94 | 95 | // add to setup 96 | export default { 97 | ... 98 | render: { 99 | bundleRenderer: { 100 | directives: { 101 | server 102 | } 103 | } 104 | }, 105 | ... 106 | } 107 | ``` 108 | 109 | **Not needed for SPA mode** 110 | 111 | #### PurgeCSS && @nuxtjs/tailwindcss 112 | 113 | If you are using [@nuxtjs/tailwindcss](https://github.com/nuxt-community/nuxt-tailwindcss) you have by default enabled PurgeCSS and you need to update the extractors of PurgeCSS. 114 | 115 | ```js 116 | // ./nuxt.config.js 117 | import { extractor } from '@samuells/vue-tailwindcss-responsive-directive' 118 | 119 | // add to setup 120 | export default { 121 | ... 122 | purgeCSS: { 123 | extractors: () => [ 124 | // this is original extractor ignoring vue files 125 | { 126 | extractor: class { 127 | static extract(content) { 128 | return content.match(/[A-z0-9-:\\/]+/g) 129 | } 130 | }, 131 | extensions: ['html', 'js'] 132 | }, 133 | // this is special extractor for vue files 134 | extractor 135 | ] 136 | }, 137 | ... 138 | } 139 | ``` 140 | 141 | ## Known Problems 142 | 143 | - **Don't use pseudo elements and classes in directive.** 144 | 145 | ``` html 146 |
152 | ``` 153 | 154 | This is an edge case which is currently in testing and we are looking for better solution. 155 | 156 | ---- 157 | 158 | ## MIT License 159 | 160 | ## Author & Contributors 161 | © [Samuel Snopko](https://samuelsnopko.com) 162 | 163 | You can ping me on [twitter](https://twitter.com/samuelsnopko). 164 | 165 | _Thx for help & brainstorming to:_ 166 | - [@peter9ke](https://github.com/peter9ke) 167 | - [@manniL](https://github.com/manniL) 168 | 169 | {...💚} 170 | --------------------------------------------------------------------------------