@develate/quasar-app-extension-quasar-ext-tailwind 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +53 -0
- package/package.json +46 -0
- package/src/index.js +27 -0
- package/src/install.js +52 -0
- package/src/lib/quasar-config-css.js +101 -0
- package/src/templates/tailwind.css +12 -0
- package/src/uninstall.js +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thorsten
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Quasar App Extension "@develate/quasar-ext-tailwind"
|
|
2
|
+
|
|
3
|
+
Adds [Tailwind CSS v4](https://tailwindcss.com) to a Quasar (Vite) app.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
quasar ext add @develate/quasar-ext-tailwind
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This will:
|
|
12
|
+
|
|
13
|
+
- add `tailwindcss`, `@tailwindcss/vite` and `@tailwindcss/postcss` to your app's
|
|
14
|
+
devDependencies,
|
|
15
|
+
- create `/src/css/tailwind.css`,
|
|
16
|
+
- add `'tailwind.css'` to the `css` array of your quasar.config file.
|
|
17
|
+
|
|
18
|
+
On each dev/build, the extension registers the `@tailwindcss/vite` plugin, so
|
|
19
|
+
there is nothing else to configure.
|
|
20
|
+
|
|
21
|
+
## Uninstall
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
quasar ext remove @develate/quasar-ext-tailwind
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This removes `/src/css/tailwind.css` and its `css` array entry. The Tailwind
|
|
28
|
+
packages themselves are left in your package.json — uninstall them yourself if
|
|
29
|
+
nothing else needs them.
|
|
30
|
+
|
|
31
|
+
## Info
|
|
32
|
+
|
|
33
|
+
`/src/css/tailwind.css` is yours to edit — that is where you import Tailwind and
|
|
34
|
+
declare your `@theme`. It ships with the `tw` prefix (`tw:flex`, `tw:p-4`, ...)
|
|
35
|
+
so that Tailwind utilities cannot collide with Quasar's own classes.
|
|
36
|
+
|
|
37
|
+
Tailwind's preflight is included by default and resets browser styles globally,
|
|
38
|
+
which changes how some Quasar components look. To keep Quasar's baseline, import
|
|
39
|
+
the Tailwind layers you want instead of the whole package:
|
|
40
|
+
|
|
41
|
+
```css
|
|
42
|
+
@layer theme, base, components, utilities;
|
|
43
|
+
@import 'tailwindcss/theme.css' layer(theme) prefix(tw);
|
|
44
|
+
@import 'tailwindcss/utilities.css' layer(utilities) prefix(tw);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`@tailwindcss/postcss` is installed for tooling that goes through PostCSS (and
|
|
48
|
+
for editor integrations that look for it); the app itself is built with the Vite
|
|
49
|
+
plugin.
|
|
50
|
+
|
|
51
|
+
## Donate
|
|
52
|
+
|
|
53
|
+
If you appreciate the work that went into this App Extension, please consider [donating to Quasar](https://donate.quasar.dev).
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@develate/quasar-app-extension-quasar-ext-tailwind",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Tailwind CSS v4 for Quasar",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"quasar",
|
|
7
|
+
"quasar-app-extension",
|
|
8
|
+
"quasar-app-vite",
|
|
9
|
+
"quasar-cli",
|
|
10
|
+
"tailwind",
|
|
11
|
+
"tailwindcss",
|
|
12
|
+
"vue",
|
|
13
|
+
"vuejs"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"author": "Thorsten <thorsten@develate.com>",
|
|
17
|
+
"homepage": "https://github.com/develate/quasar-ext-tailwind#readme",
|
|
18
|
+
"bugs": "https://github.com/develate/quasar-ext-tailwind/issues",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/develate/quasar-ext-tailwind.git",
|
|
22
|
+
"directory": "ae"
|
|
23
|
+
},
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "src/index.js",
|
|
26
|
+
"files": [
|
|
27
|
+
"src"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"imports": {
|
|
33
|
+
"#q-app": "@quasar/app-vite"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@quasar/app-vite": "^3.3.0",
|
|
37
|
+
"quasar": "^2.23.3",
|
|
38
|
+
"vue": "^3.5.39"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@quasar/app-vite": "^3.3.0"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">= 26 || ^24 || ^22.12"
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quasar App Extension index/runner script
|
|
3
|
+
* (runs on each dev/build)
|
|
4
|
+
*
|
|
5
|
+
* Docs: https://quasar.dev/app-extensions/development-guide/index-api
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { defineIndexScript } from '#q-app'
|
|
9
|
+
|
|
10
|
+
// can be async
|
|
11
|
+
export default defineIndexScript(api => {
|
|
12
|
+
api.compatibleWith('quasar', '^2.0.0')
|
|
13
|
+
api.compatibleWith('@quasar/app-vite', '^3.3.0')
|
|
14
|
+
|
|
15
|
+
// not fatal: the packages are added by the install script, which runs
|
|
16
|
+
// before they are actually put into node_modules by the packager
|
|
17
|
+
if (api.hasPackage('tailwindcss', '^4.0.0') === false) {
|
|
18
|
+
api.logger.warn(
|
|
19
|
+
'Tailwind CSS v4 is not installed. Run' +
|
|
20
|
+
` "quasar ext invoke ${api.extId}" to set it up.`
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
api.extendQuasarConf(conf => {
|
|
25
|
+
conf.build.vitePlugins.push(['@tailwindcss/vite', {}])
|
|
26
|
+
})
|
|
27
|
+
})
|
package/src/install.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quasar App Extension install script
|
|
3
|
+
* https://quasar.dev/app-extensions/development-guide/install-api
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { basename } from 'node:path'
|
|
7
|
+
import { defineInstallScript } from '#q-app'
|
|
8
|
+
|
|
9
|
+
import { addCssEntry } from './lib/quasar-config-css.js'
|
|
10
|
+
|
|
11
|
+
const tailwindVersion = '^4.3.3'
|
|
12
|
+
const cssFile = 'tailwind.css'
|
|
13
|
+
const cssTarget = `src/css/${cssFile}`
|
|
14
|
+
|
|
15
|
+
// can be async
|
|
16
|
+
export default defineInstallScript(api => {
|
|
17
|
+
api.compatibleWith('quasar', '^2.0.0')
|
|
18
|
+
api.compatibleWith('@quasar/app-vite', '^3.3.0')
|
|
19
|
+
|
|
20
|
+
api.extendPackageJson({
|
|
21
|
+
devDependencies: {
|
|
22
|
+
'@tailwindcss/postcss': tailwindVersion,
|
|
23
|
+
'@tailwindcss/vite': tailwindVersion,
|
|
24
|
+
tailwindcss: tailwindVersion
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
api.renderFile(`./templates/${cssFile}`, cssTarget)
|
|
29
|
+
|
|
30
|
+
const configFile = api.ctx.appPaths.quasarConfigFilename
|
|
31
|
+
const configName = basename(configFile)
|
|
32
|
+
|
|
33
|
+
let status
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
status = addCssEntry(configFile, cssFile)
|
|
37
|
+
} catch (err) {
|
|
38
|
+
api.logger.warn(`Could not read/write ${configName}: ${err.message}`)
|
|
39
|
+
status = 'unsupported'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (status === 'added') {
|
|
43
|
+
api.logger.log(`Added "${cssFile}" to ${configName} > css`)
|
|
44
|
+
} else if (status === 'already-present') {
|
|
45
|
+
api.logger.log(`"${cssFile}" is already in ${configName} > css`)
|
|
46
|
+
} else {
|
|
47
|
+
api.onExitLog(
|
|
48
|
+
`Could not locate the "css" array in ${configName}.` +
|
|
49
|
+
` Please add "${cssFile}" to it yourself.`
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
})
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers to add/remove an entry from the "css" array of the host app's
|
|
3
|
+
* quasar.config file.
|
|
4
|
+
*
|
|
5
|
+
* That file is user-owned source code (js or ts), so it gets edited as text
|
|
6
|
+
* rather than parsed: only the first "css: [ ... ]" array is touched and the
|
|
7
|
+
* surrounding formatting is preserved as much as possible.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'node:fs'
|
|
11
|
+
|
|
12
|
+
const cssArrayRegex = /\bcss\s*:\s*\[([^\]]*)\]/
|
|
13
|
+
|
|
14
|
+
function escapeRegExp(str) {
|
|
15
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function matchCssArray(source) {
|
|
19
|
+
const match = source.match(cssArrayRegex)
|
|
20
|
+
|
|
21
|
+
if (match === null) return null
|
|
22
|
+
|
|
23
|
+
const inner = match[1]
|
|
24
|
+
const start = match.index + match[0].indexOf('[') + 1
|
|
25
|
+
|
|
26
|
+
return { inner, start, end: start + inner.length }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function replaceInner(source, { start, end }, newInner) {
|
|
30
|
+
return source.slice(0, start) + newInner + source.slice(end)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {string} configFile (absolute path to the quasar.config file)
|
|
35
|
+
* @param {string} entry (example: 'tailwind.css')
|
|
36
|
+
* @return {'added' | 'already-present' | 'unsupported'}
|
|
37
|
+
*/
|
|
38
|
+
export function addCssEntry(configFile, entry) {
|
|
39
|
+
const source = fs.readFileSync(configFile, 'utf8')
|
|
40
|
+
const cssArray = matchCssArray(source)
|
|
41
|
+
|
|
42
|
+
if (cssArray === null) return 'unsupported'
|
|
43
|
+
|
|
44
|
+
const { inner } = cssArray
|
|
45
|
+
|
|
46
|
+
if (new RegExp(`(["'])${escapeRegExp(entry)}\\1`).test(inner)) {
|
|
47
|
+
return 'already-present'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// follow whatever quote style the app is already using
|
|
51
|
+
const quote = inner.match(/["']/)?.[0] ?? "'"
|
|
52
|
+
const item = `${quote}${entry}${quote}`
|
|
53
|
+
|
|
54
|
+
let newInner
|
|
55
|
+
|
|
56
|
+
if (inner.trim() === '') {
|
|
57
|
+
newInner = item
|
|
58
|
+
} else if (inner.includes('\n')) {
|
|
59
|
+
const indent = inner.match(/\n([ \t]*)\S/)?.[1] ?? ' '
|
|
60
|
+
const tail = inner.match(/\n[ \t]*$/)?.[0] ?? '\n'
|
|
61
|
+
newInner = `${inner.replace(/,?\s*$/, '')},\n${indent}${item}${tail}`
|
|
62
|
+
} else {
|
|
63
|
+
newInner = `${inner.replace(/,\s*$/, '')}, ${item}`
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fs.writeFileSync(configFile, replaceInner(source, cssArray, newInner), 'utf8')
|
|
67
|
+
return 'added'
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {string} configFile (absolute path to the quasar.config file)
|
|
72
|
+
* @param {string} entry (example: 'tailwind.css')
|
|
73
|
+
* @return {'removed' | 'missing' | 'unsupported'}
|
|
74
|
+
*/
|
|
75
|
+
export function removeCssEntry(configFile, entry) {
|
|
76
|
+
const source = fs.readFileSync(configFile, 'utf8')
|
|
77
|
+
const cssArray = matchCssArray(source)
|
|
78
|
+
|
|
79
|
+
if (cssArray === null) return 'unsupported'
|
|
80
|
+
|
|
81
|
+
const { inner } = cssArray
|
|
82
|
+
const itemRegex = new RegExp(
|
|
83
|
+
`[^\\S\\n]*(["'])${escapeRegExp(entry)}\\1[^\\S\\n]*,?`
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if (itemRegex.test(inner) === false) return 'missing'
|
|
87
|
+
|
|
88
|
+
let newInner = inner
|
|
89
|
+
.replace(itemRegex, '')
|
|
90
|
+
// the removed item might have left an empty line behind
|
|
91
|
+
.replace(/\n[ \t]*(?=\n)/g, '')
|
|
92
|
+
// ...or a dangling comma, if it was the last item
|
|
93
|
+
.replace(/,([^\S\n]*(?:\n[ \t]*)?)$/, '$1')
|
|
94
|
+
// ...or a leading space, if it was the first one
|
|
95
|
+
.replace(/^[^\S\n]+(?=\S)/, '')
|
|
96
|
+
|
|
97
|
+
if (newInner.trim() === '') newInner = ''
|
|
98
|
+
|
|
99
|
+
fs.writeFileSync(configFile, replaceInner(source, cssArray, newInner), 'utf8')
|
|
100
|
+
return 'removed'
|
|
101
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
@import 'tailwindcss' prefix(tw);
|
|
2
|
+
|
|
3
|
+
@theme {
|
|
4
|
+
--font-sans:
|
|
5
|
+
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
|
|
6
|
+
'Noto Sans', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
|
7
|
+
'Segoe UI Symbol', 'Noto Color Emoji';
|
|
8
|
+
--font-serif: ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif;
|
|
9
|
+
--font-mono:
|
|
10
|
+
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
|
|
11
|
+
'Courier New', monospace;
|
|
12
|
+
}
|
package/src/uninstall.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quasar App Extension uninstall script
|
|
3
|
+
* https://quasar.dev/app-extensions/development-guide/uninstall-api
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { basename } from 'node:path'
|
|
7
|
+
import { defineUninstallScript } from '#q-app'
|
|
8
|
+
|
|
9
|
+
import { removeCssEntry } from './lib/quasar-config-css.js'
|
|
10
|
+
|
|
11
|
+
const cssFile = 'tailwind.css'
|
|
12
|
+
const cssTarget = `src/css/${cssFile}`
|
|
13
|
+
|
|
14
|
+
// can be async
|
|
15
|
+
export default defineUninstallScript(api => {
|
|
16
|
+
const configFile = api.ctx.appPaths.quasarConfigFilename
|
|
17
|
+
const configName = basename(configFile)
|
|
18
|
+
|
|
19
|
+
let status
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
status = removeCssEntry(configFile, cssFile)
|
|
23
|
+
} catch (err) {
|
|
24
|
+
api.logger.warn(`Could not read/write ${configName}: ${err.message}`)
|
|
25
|
+
status = 'unsupported'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (status === 'removed') {
|
|
29
|
+
api.logger.log(`Removed "${cssFile}" from ${configName} > css`)
|
|
30
|
+
} else if (status === 'unsupported') {
|
|
31
|
+
api.onExitLog(
|
|
32
|
+
`Could not locate the "css" array in ${configName}.` +
|
|
33
|
+
` Please remove "${cssFile}" from it yourself.`
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
api.removePath(cssTarget)
|
|
38
|
+
|
|
39
|
+
api.onExitLog(
|
|
40
|
+
'The Tailwind packages (tailwindcss, @tailwindcss/vite,' +
|
|
41
|
+
' @tailwindcss/postcss) are still in your package.json.' +
|
|
42
|
+
' Uninstall them if nothing else needs them.'
|
|
43
|
+
)
|
|
44
|
+
})
|