@live-change/frontend-base 0.9.58 → 0.9.60

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-change/frontend-base",
3
- "version": "0.9.58",
3
+ "version": "0.9.60",
4
4
  "scripts": {
5
5
  "memDev": "node server/start.js memDev --enableSessions --initScript ./init.js --templatePath ../../base-frontend/index.html",
6
6
  "localDevInit": "rm tmp.db; lcli localDev --enableSessions --initScript ./init.js",
@@ -35,22 +35,23 @@
35
35
  "@lezer/rust": "=1.0.1",
36
36
  "@lezer/sass": "=1.0.3",
37
37
  "@lezer/xml": "=1.0.2",
38
- "@live-change/cli": "^0.9.58",
39
- "@live-change/dao": "^0.9.58",
40
- "@live-change/dao-message": "^0.9.58",
41
- "@live-change/dao-sockjs": "^0.9.58",
42
- "@live-change/dao-vue3": "^0.9.58",
43
- "@live-change/dao-websocket": "^0.9.58",
44
- "@live-change/email-service": "^0.9.58",
45
- "@live-change/password-authentication-service": "^0.9.58",
46
- "@live-change/secret-code-service": "^0.9.58",
47
- "@live-change/secret-link-service": "^0.9.58",
48
- "@live-change/security-frontend": "^0.9.58",
49
- "@live-change/session-service": "^0.9.58",
50
- "@live-change/user-service": "^0.9.58",
51
- "@live-change/vue3-components": "^0.9.58",
52
- "@live-change/vue3-ssr": "^0.9.58",
53
- "@primevue/themes": "^4.2.5",
38
+ "@live-change/cli": "^0.9.60",
39
+ "@live-change/dao": "^0.9.60",
40
+ "@live-change/dao-message": "^0.9.60",
41
+ "@live-change/dao-sockjs": "^0.9.60",
42
+ "@live-change/dao-vue3": "^0.9.60",
43
+ "@live-change/dao-websocket": "^0.9.60",
44
+ "@live-change/email-service": "^0.9.60",
45
+ "@live-change/password-authentication-service": "^0.9.60",
46
+ "@live-change/secret-code-service": "^0.9.60",
47
+ "@live-change/secret-link-service": "^0.9.60",
48
+ "@live-change/security-frontend": "^0.9.60",
49
+ "@live-change/session-service": "^0.9.60",
50
+ "@live-change/user-service": "^0.9.60",
51
+ "@live-change/vue3-components": "^0.9.60",
52
+ "@live-change/vue3-ssr": "^0.9.60",
53
+ "@primevue/themes": "^4.3.3",
54
+ "@tailwindcss/node": "4.1.0",
54
55
  "@tailwindcss/vite": "4.1.0",
55
56
  "@unhead/ssr": "^1.6.2",
56
57
  "@vitejs/plugin-vue": "^5.0.5",
@@ -87,6 +88,7 @@
87
88
  "serve-static": "^1.16.2",
88
89
  "tailwindcss": "4.1.0",
89
90
  "tailwindcss-primeui": "^0.4.0",
91
+ "tsx": "4.19.2",
90
92
  "typescript": "5.4.5",
91
93
  "unhead": "^1.9.13",
92
94
  "unplugin-vue-components": "^0.27.0",
@@ -105,7 +107,7 @@
105
107
  "vue3-scroll-border": "0.1.6"
106
108
  },
107
109
  "devDependencies": {
108
- "@live-change/codeceptjs-helper": "^0.9.58",
110
+ "@live-change/codeceptjs-helper": "^0.9.60",
109
111
  "codeceptjs": "^3.6.10",
110
112
  "generate-password": "1.7.1",
111
113
  "playwright": "1.49.1",
@@ -119,5 +121,5 @@
119
121
  "author": "Michał Łaszczewski <michal@laszczewski.pl>",
120
122
  "license": "BSD-3-Clause",
121
123
  "description": "",
122
- "gitHead": "526ab5f7c70f16a84df11518cfb9e54cd960d3fc"
124
+ "gitHead": "8420ad3852216027b648c31f3c908a7b5e6f4292"
123
125
  }
@@ -0,0 +1,349 @@
1
+ import { compile, env, Features, Instrumentation, normalizePath, optimize } from '@tailwindcss/node'
2
+ import { clearRequireCache } from '@tailwindcss/node/require-cache'
3
+ import { Scanner, SourceEntry } from '@tailwindcss/oxide'
4
+ import fs from 'node:fs/promises'
5
+ import path from 'node:path'
6
+ import type { Plugin, ResolvedConfig, ViteDevServer } from 'vite'
7
+
8
+ const DEBUG = env.DEBUG
9
+ const SPECIAL_QUERY_RE = /[?&](?:worker|sharedworker|raw|url)\b/
10
+ const COMMON_JS_PROXY_RE = /\?commonjs-proxy/
11
+ const INLINE_STYLE_ID_RE = /[?&]index\=\d+\.css$/
12
+
13
+ interface TailwindViteConfig {
14
+ sources: SourceEntry[]
15
+ }
16
+
17
+ export default function tailwindcss(pluginConfig: TailwindViteConfig): Plugin[] {
18
+ let servers: ViteDevServer[] = []
19
+ let config: ResolvedConfig | null = null
20
+
21
+ let isSSR = false
22
+ let minify = false
23
+
24
+ let roots: DefaultMap<string, Root> = new DefaultMap((id) => {
25
+ let cssResolver = config!.createResolver({
26
+ ...config!.resolve,
27
+ extensions: ['.css'],
28
+ mainFields: ['style'],
29
+ conditions: ['style', 'development|production'],
30
+ tryIndex: false,
31
+ preferRelative: true,
32
+ })
33
+ function customCssResolver(id: string, base: string) {
34
+ return cssResolver(id, base, true, isSSR)
35
+ }
36
+
37
+ let jsResolver = config!.createResolver(config!.resolve)
38
+ function customJsResolver(id: string, base: string) {
39
+ return jsResolver(id, base, true, isSSR)
40
+ }
41
+ return new Root(id, config!.root, customCssResolver, customJsResolver)
42
+ })
43
+
44
+ return [
45
+ {
46
+ // Step 1: Scan source files for candidates
47
+ name: '@tailwindcss/vite:scan',
48
+ enforce: 'pre',
49
+
50
+ configureServer(server) {
51
+ servers.push(server)
52
+ },
53
+
54
+ async configResolved(_config) {
55
+ config = _config
56
+ minify = config.build.cssMinify !== false
57
+ isSSR = config.build.ssr !== false && config.build.ssr !== undefined
58
+ },
59
+ },
60
+
61
+ {
62
+ // Step 2 (serve mode): Generate CSS
63
+ name: '@tailwindcss/vite:generate:serve',
64
+ apply: 'serve',
65
+ enforce: 'pre',
66
+
67
+ async transform(src, id, options) {
68
+ if (!isPotentialCssRootFile(id)) return
69
+
70
+ using I = new Instrumentation()
71
+ DEBUG && I.start('[@tailwindcss/vite] Generate CSS (serve)')
72
+
73
+ let root = roots.get(id)
74
+
75
+ let generated = await root.generate(src, (file) => this.addWatchFile(file), I, pluginConfig)
76
+ if (!generated) {
77
+ roots.delete(id)
78
+ return src
79
+ }
80
+
81
+ DEBUG && I.end('[@tailwindcss/vite] Generate CSS (serve)')
82
+ return { code: generated }
83
+ },
84
+ },
85
+
86
+ {
87
+ // Step 2 (full build): Generate CSS
88
+ name: '@tailwindcss/vite:generate:build',
89
+ apply: 'build',
90
+ enforce: 'pre',
91
+
92
+ async transform(src, id) {
93
+ if (!isPotentialCssRootFile(id)) return
94
+
95
+ using I = new Instrumentation()
96
+ DEBUG && I.start('[@tailwindcss/vite] Generate CSS (build)')
97
+
98
+ let root = roots.get(id)
99
+
100
+ let generated = await root.generate(src, (file) => this.addWatchFile(file), I, pluginConfig)
101
+ if (!generated) {
102
+ roots.delete(id)
103
+ return src
104
+ }
105
+ DEBUG && I.end('[@tailwindcss/vite] Generate CSS (build)')
106
+
107
+ DEBUG && I.start('[@tailwindcss/vite] Optimize CSS')
108
+ generated = optimize(generated, { minify })
109
+ DEBUG && I.end('[@tailwindcss/vite] Optimize CSS')
110
+
111
+ return { code: generated }
112
+ },
113
+ },
114
+ ] satisfies Plugin[]
115
+ }
116
+
117
+ function getExtension(id: string) {
118
+ let [filename] = id.split('?', 2)
119
+ return path.extname(filename).slice(1)
120
+ }
121
+
122
+ function isPotentialCssRootFile(id: string) {
123
+ if (id.includes('/.vite/')) return
124
+ let extension = getExtension(id)
125
+ let isCssFile =
126
+ (extension === 'css' || id.includes('&lang.css') || id.match(INLINE_STYLE_ID_RE)) &&
127
+ // Don't intercept special static asset resources
128
+ !SPECIAL_QUERY_RE.test(id) &&
129
+ !COMMON_JS_PROXY_RE.test(id)
130
+ return isCssFile
131
+ }
132
+
133
+ function idToPath(id: string) {
134
+ return path.resolve(id.replace(/\?.*$/, ''))
135
+ }
136
+
137
+ /**
138
+ * A Map that can generate default values for keys that don't exist.
139
+ * Generated default values are added to the map to avoid recomputation.
140
+ */
141
+ class DefaultMap<K, V> extends Map<K, V> {
142
+ constructor(private factory: (key: K, self: DefaultMap<K, V>) => V) {
143
+ super()
144
+ }
145
+
146
+ get(key: K): V {
147
+ let value = super.get(key)
148
+
149
+ if (value === undefined) {
150
+ value = this.factory(key, this)
151
+ this.set(key, value)
152
+ }
153
+
154
+ return value
155
+ }
156
+ }
157
+
158
+ class Root {
159
+ // The lazily-initialized Tailwind compiler components. These are persisted
160
+ // throughout rebuilds but will be re-initialized if the rebuild strategy is
161
+ // set to `full`.
162
+ private compiler?: Awaited<ReturnType<typeof compile>>
163
+
164
+ // The lazily-initialized Tailwind scanner.
165
+ private scanner?: Scanner
166
+
167
+ // List of all candidates that were being returned by the root scanner during
168
+ // the lifetime of the root.
169
+ private candidates: Set<string> = new Set<string>()
170
+
171
+ // List of all build dependencies (e.g. imported stylesheets or plugins) and
172
+ // their last modification timestamp. If no mtime can be found, we need to
173
+ // assume the file has always changed.
174
+ private buildDependencies = new Map<string, number | null>()
175
+
176
+ constructor(
177
+ private id: string,
178
+ private base: string,
179
+
180
+ private customCssResolver: (id: string, base: string) => Promise<string | false | undefined>,
181
+ private customJsResolver: (id: string, base: string) => Promise<string | false | undefined>,
182
+ ) {}
183
+
184
+ // Generate the CSS for the root file. This can return false if the file is
185
+ // not considered a Tailwind root. When this happened, the root can be GCed.
186
+ public async generate(
187
+ content: string,
188
+ _addWatchFile: (file: string) => void,
189
+ I: Instrumentation,
190
+ pluginConfig: TailwindViteConfig
191
+ ): Promise<string | false> {
192
+ let inputPath = idToPath(this.id)
193
+
194
+ function addWatchFile(file: string) {
195
+ // Don't watch the input file since it's already a dependency anc causes
196
+ // issues with some setups (e.g. Qwik).
197
+ if (file === inputPath) {
198
+ return
199
+ }
200
+
201
+ // Scanning `.svg` file containing a `#` or `?` in the path will
202
+ // crash Vite. We work around this for now by ignoring updates to them.
203
+ //
204
+ // https://github.com/tailwindlabs/tailwindcss/issues/16877
205
+ if (/[\#\?].*\.svg$/.test(file)) {
206
+ return
207
+ }
208
+ _addWatchFile(file)
209
+ }
210
+
211
+ let requiresBuildPromise = this.requiresBuild()
212
+ let inputBase = path.dirname(path.resolve(inputPath))
213
+
214
+ if (!this.compiler || !this.scanner || (await requiresBuildPromise)) {
215
+ clearRequireCache(Array.from(this.buildDependencies.keys()))
216
+ this.buildDependencies.clear()
217
+
218
+ this.addBuildDependency(idToPath(inputPath))
219
+
220
+ DEBUG && I.start('Setup compiler')
221
+ let addBuildDependenciesPromises: Promise<void>[] = []
222
+ this.compiler = await compile(content, {
223
+ base: inputBase,
224
+ shouldRewriteUrls: true,
225
+ onDependency: (path) => {
226
+ addWatchFile(path)
227
+ addBuildDependenciesPromises.push(this.addBuildDependency(path))
228
+ },
229
+
230
+ customCssResolver: this.customCssResolver,
231
+ customJsResolver: this.customJsResolver,
232
+ })
233
+ await Promise.all(addBuildDependenciesPromises)
234
+ DEBUG && I.end('Setup compiler')
235
+
236
+ DEBUG && I.start('Setup scanner')
237
+
238
+ let sources = (() => {
239
+ // Disable auto source detection
240
+ if (this.compiler.root === 'none') {
241
+ return []
242
+ }
243
+
244
+ // No root specified, auto-detect based on the `**/*` pattern
245
+ if (this.compiler.root === null) {
246
+ return [{ base: this.base, pattern: '**/*', negated: false }]
247
+ }
248
+
249
+ // Use the specified root
250
+ return [{ ...this.compiler.root, negated: false }]
251
+ })().concat(this.compiler.sources).concat(pluginConfig.sources)
252
+
253
+ this.scanner = new Scanner({ sources })
254
+ DEBUG && I.end('Setup scanner')
255
+ } else {
256
+ for (let buildDependency of this.buildDependencies.keys()) {
257
+ addWatchFile(buildDependency)
258
+ }
259
+ }
260
+
261
+ if (
262
+ !(
263
+ this.compiler.features &
264
+ (Features.AtApply | Features.JsPluginCompat | Features.ThemeFunction | Features.Utilities)
265
+ )
266
+ ) {
267
+ return false
268
+ }
269
+
270
+ if (this.compiler.features & Features.Utilities) {
271
+ // This should not be here, but right now the Vite plugin is setup where we
272
+ // setup a new scanner and compiler every time we request the CSS file
273
+ // (regardless whether it actually changed or not).
274
+ DEBUG && I.start('Scan for candidates')
275
+ for (let candidate of this.scanner.scan()) {
276
+ this.candidates.add(candidate)
277
+ }
278
+ DEBUG && I.end('Scan for candidates')
279
+ }
280
+
281
+ if (this.compiler.features & Features.Utilities) {
282
+ // Watch individual files found via custom `@source` paths
283
+ for (let file of this.scanner.files) {
284
+ addWatchFile(file)
285
+ }
286
+
287
+ // Watch globs found via custom `@source` paths
288
+ for (let glob of this.scanner.globs) {
289
+ if (glob.pattern[0] === '!') continue
290
+
291
+ let relative = path.relative(this.base, glob.base)
292
+ if (relative[0] !== '.') {
293
+ relative = './' + relative
294
+ }
295
+ // Ensure relative is a posix style path since we will merge it with the
296
+ // glob.
297
+ relative = normalizePath(relative)
298
+
299
+ addWatchFile(path.posix.join(relative, glob.pattern))
300
+
301
+ let root = this.compiler.root
302
+
303
+ if (root !== 'none' && root !== null) {
304
+ let basePath = normalizePath(path.resolve(root.base, root.pattern))
305
+
306
+ let isDir = await fs.stat(basePath).then(
307
+ (stats) => stats.isDirectory(),
308
+ () => false,
309
+ )
310
+
311
+ if (!isDir) {
312
+ throw new Error(
313
+ `The path given to \`source(…)\` must be a directory but got \`source(${basePath})\` instead.`,
314
+ )
315
+ }
316
+ }
317
+ }
318
+ }
319
+
320
+ DEBUG && I.start('Build CSS')
321
+ let result = this.compiler.build([...this.candidates])
322
+ DEBUG && I.end('Build CSS')
323
+
324
+ return result
325
+ }
326
+
327
+ private async addBuildDependency(path: string) {
328
+ let mtime: number | null = null
329
+ try {
330
+ mtime = (await fs.stat(path)).mtimeMs
331
+ } catch {}
332
+ this.buildDependencies.set(path, mtime)
333
+ }
334
+
335
+ private async requiresBuild(): Promise<boolean> {
336
+ for (let [path, mtime] of this.buildDependencies) {
337
+ if (mtime === null) return true
338
+ try {
339
+ let stat = await fs.stat(path)
340
+ if (stat.mtimeMs > mtime) {
341
+ return true
342
+ }
343
+ } catch {
344
+ return true
345
+ }
346
+ }
347
+ return false
348
+ }
349
+ }
package/vite-config.js CHANGED
@@ -5,7 +5,9 @@ import { findFreePorts } from 'find-free-ports'
5
5
  import path from 'path'
6
6
  import vuePlugin from '@vitejs/plugin-vue'
7
7
 
8
- import tailwindcss from '@tailwindcss/vite'
8
+ //import tailwindcss from '@tailwindcss/vite'
9
+ import { tsImport } from 'tsx/esm/api'
10
+ const tailwindcss = (await tsImport('./tailwindcss-vite.ts', import.meta.url)).default
9
11
 
10
12
  import Markdown from 'unplugin-vue-markdown/vite'
11
13
  import MarkdownItAnchor from 'markdown-it-anchor'
@@ -83,7 +85,33 @@ export default async ({ command, mode, version }, options = {
83
85
  }
84
86
  },
85
87
  }),
86
- tailwindcss(),
88
+ tailwindcss({
89
+ sources: [
90
+ '@live-change/frontend-base/src',
91
+ '@live-change/access-control-frontend/src',
92
+ '@live-change/content-frontend/src',
93
+ '@live-change/blog-frontend/src',
94
+ '@live-change/image-frontend/src',
95
+ '@live-change/security-frontend/src',
96
+ '@live-change/upload-frontend/src',
97
+ '@live-change/url-frontend/src',
98
+ '@live-change/user-frontend/src',
99
+ '@live-change/wysiwyg-frontend/src',
100
+ '@live-change/flow-frontend/src',
101
+ '@live-change/task-frontend/src',
102
+ '@live-change/balance-frontend/src',
103
+ '@live-change/billing-frontend/src',
104
+ '@live-change/survey-frontend/src',
105
+ '@live-change/peer-connection-frontend/src',
106
+ '@live-change/video-call-frontend/src',
107
+ '@live-change/frontend-auto-form/src',
108
+ '@live-change/db-web/src',
109
+ ].map(p => ({
110
+ base: path.dirname(fileURLToPath(import.meta.resolve(p))),
111
+ pattern: '**/*.{vue,css,scss,sass,less,styl,md}',
112
+ negated: false
113
+ }))
114
+ }),
87
115
  Markdown({
88
116
  headEnabled: true,
89
117
  markdownItOptions: {