strata-css 1.0.2 → 1.0.4

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.
Files changed (4) hide show
  1. package/README.md +730 -730
  2. package/bin/strata.js +332 -177
  3. package/package.json +68 -68
  4. package/src/index.js +171 -171
package/bin/strata.js CHANGED
@@ -1,177 +1,332 @@
1
- #!/usr/bin/env node
2
-
3
- 'use strict'
4
-
5
- const fs = require('fs')
6
- const path = require('path')
7
- const chokidar = require('chokidar')
8
- const strata = require('../src/index')
9
- const { getWatchFiles } = require('../src/scanner/scanner')
10
-
11
- const args = process.argv.slice(2)
12
- const cwd = process.cwd()
13
-
14
- function loadConfig(cwd) {
15
- const configPath = path.resolve(cwd, 'strata.config.js')
16
- const configPathCjs = path.resolve(cwd, 'strata.config.cjs')
17
-
18
- if (fs.existsSync(configPathCjs)) {
19
- try { return require(configPathCjs) } catch {}
20
- }
21
-
22
- if (fs.existsSync(configPath)) {
23
- try { return require(configPath) } catch {}
24
- }
25
-
26
- return {}
27
- }
28
-
29
- // ─── JS minifier ──────────────────────────────────────────────────────
30
- // Strips block comments (preserving /*! banners), line comments,
31
- // and collapses unnecessary whitespace — no external dependency needed.
32
-
33
- function minifyJS(src) {
34
- return src
35
- // preserve /*! banner */ comments, strip all other /* ... */ blocks
36
- .replace(/\/\*(?!!)([\s\S]*?)\*\//g, '')
37
- // strip // line comments (not inside strings — conservative: only at line start or after whitespace)
38
- .replace(/(?:^|\s)\/\/[^\n]*/gm, '')
39
- // collapse runs of whitespace/newlines to a single space
40
- .replace(/[ \t]+/g, ' ')
41
- .replace(/\n\s*\n+/g, '\n')
42
- .trim()
43
- }
44
-
45
- // ─── Build ────────────────────────────────────────────────────────────
46
- // --watch → unminified CSS, unminified JS (fast rebuilds for dev)
47
- // --build → unminified CSS, minified JS (production-ready JS)
48
- // --minify → minified CSS + minified JS (smallest possible output)
49
-
50
- async function build(cssMinify = false, jsMinify = true) {
51
- const config = loadConfig(cwd)
52
- const inputFile = config.input || path.join(cwd, 'strata.css')
53
- const outputFile = config.output || path.join(cwd, 'dist', 'strata.output.css')
54
-
55
- const start = process.hrtime.bigint()
56
- const css = await strata.build(inputFile, outputFile, { cwd, sourceMap: !cssMinify })
57
-
58
- // CSS minification
59
- if (cssMinify) {
60
- const cssnano = require('cssnano')
61
- const postcss = require('postcss')
62
- const result = await postcss([cssnano({ preset: 'default' })]).process(css, { from: outputFile })
63
- fs.writeFileSync(outputFile, result.css)
64
- }
65
-
66
- // Bundle + optionally minify JS components
67
- const componentsDir = path.join(__dirname, '..', 'src', 'components', 'modules')
68
- const jsDest = path.join(path.dirname(outputFile), 'strata.components.js')
69
- if (fs.existsSync(componentsDir)) {
70
- const files = fs.readdirSync(componentsDir).filter(f => f.endsWith('.js')).sort()
71
- const banner = `/*! Strata Components built ${new Date().toISOString().slice(0,10)} */\n`
72
- const raw = files.map(f => fs.readFileSync(path.join(componentsDir, f), 'utf8')).join('\n')
73
- const output = jsMinify ? banner + minifyJS(raw) : banner + raw
74
- fs.mkdirSync(path.dirname(jsDest), { recursive: true })
75
- fs.writeFileSync(jsDest, output)
76
- }
77
-
78
- const ms = (Number(process.hrtime.bigint() - start) / 1_000_000).toFixed(2)
79
- const cssSize = (Buffer.byteLength(fs.readFileSync(outputFile)) / 1024).toFixed(2)
80
- const jsSize = fs.existsSync(jsDest)
81
- ? (Buffer.byteLength(fs.readFileSync(jsDest)) / 1024).toFixed(2)
82
- : '0'
83
- console.log(`[Strata] ✓ Built → ${outputFile} (CSS ${cssSize} KB, JS ${jsSize} KB) in ${ms}ms`)
84
- }
85
-
86
- // ─── Watch ────────────────────────────────────────────────────────────
87
- async function watch() {
88
- console.log('[Strata] Starting in watch mode...')
89
- await build(false, false) // unminified for dev
90
-
91
- const config = loadConfig(cwd)
92
- const contentGlobs = config.content || ['./src/**/*.{html,jsx,tsx,vue,astro,svelte,js,ts}']
93
- const inputFile = config.input || path.join(cwd, 'strata.css')
94
- const watchFiles = [inputFile, ...getWatchFiles(contentGlobs)]
95
-
96
- const watcher = chokidar.watch(watchFiles, { ignoreInitial: true, persistent: true })
97
-
98
- watcher.on('change', async (filePath) => {
99
- console.log(`[Strata] Changed: ${path.relative(cwd, filePath)}`)
100
- strata.invalidate(filePath)
101
- await build(false, false)
102
- })
103
-
104
- watcher.on('add', async (filePath) => {
105
- console.log(`[Strata] Added: ${path.relative(cwd, filePath)}`)
106
- strata.invalidate(filePath)
107
- await build(false, false)
108
- })
109
-
110
- console.log('[Strata] Watching for changes...')
111
- }
112
-
113
- // ─── ESM / framework helpers ──────────────────────────────────────────
114
- function isESMProject(cwd) {
115
- try {
116
- const pkg = JSON.parse(fs.readFileSync(path.resolve(cwd, 'package.json'), 'utf8'))
117
- return pkg.type === 'module'
118
- } catch {
119
- return false
120
- }
121
- }
122
-
123
- function detectOutputPath(cwd) {
124
- try {
125
- const pkg = JSON.parse(fs.readFileSync(path.resolve(cwd, 'package.json'), 'utf8'))
126
- const deps = { ...pkg.dependencies, ...pkg.devDependencies }
127
- if (deps['astro']) return './public/strata.output.css'
128
- if (deps['laravel-vite-plugin']) return './public/strata.output.css'
129
- if (deps['next']) return './public/strata.output.css'
130
- if (deps['react']) return './public/strata.output.css'
131
- if (deps['vue']) return './public/strata.output.css'
132
- if (deps['@sveltejs/kit']) return './public/strata.output.css'
133
- } catch {}
134
- return './dist/strata.output.css'
135
- }
136
-
137
- // ─── Init ─────────────────────────────────────────────────────────────
138
- function init() {
139
- const isESM = isESMProject(cwd)
140
- const output = detectOutputPath(cwd)
141
-
142
- console.log('[Strata] Initializing project...')
143
- console.log(`[Strata] Detected: ${isESM ? 'ESM' : 'CommonJS'} project`)
144
-
145
- const configFile = isESM ? 'strata.config.cjs' : 'strata.config.js'
146
- const postcssFile = isESM ? 'postcss.config.cjs' : 'postcss.config.js'
147
-
148
- const configContent = `module.exports = {\n content: ["./src/**/*.{html,jsx,tsx,vue,astro,svelte,js,ts}"],\n input: "./strata.css",\n output: "${output}"\n}\n`
149
- const postcssContent = `module.exports = {\n plugins: [\n require('strata-css'),\n require('autoprefixer')\n ]\n}\n`
150
- const strataCssContent = `@strata base;\n@strata components;\n@strata utilities;\n`
151
-
152
- fs.writeFileSync(path.resolve(cwd, configFile), configContent)
153
- fs.writeFileSync(path.resolve(cwd, postcssFile), postcssContent)
154
- fs.writeFileSync(path.resolve(cwd, 'strata.css'), strataCssContent)
155
-
156
- console.log(`[Strata] Created: ${configFile}`)
157
- console.log(`[Strata] Created: strata.css`)
158
- console.log(`[Strata] Created: ${postcssFile}`)
159
- console.log(`[Strata] Done! Next steps:`)
160
- console.log(`[Strata] 1. Add <link rel="stylesheet" href="/strata.output.css"> to your HTML`)
161
- console.log(`[Strata] 2. Add data-st-theme="light" to your <html> tag`)
162
- console.log(`[Strata] 3. Run: node node_modules/strata-css/bin/strata.js --build`)
163
- }
164
-
165
- // ─── Run ──────────────────────────────────────────────────────────────
166
- if (args[0] === 'init') init()
167
- else if (args.includes('--watch')) watch()
168
- else if (args.includes('--minify')) build(true, true)
169
- else if (args.includes('--build')) build(false, true)
170
- else console.log(`
171
- Strata CSS
172
-
173
- strata-css init scaffold a new project
174
- strata-css --watch development mode (unminified, fast rebuild)
175
- strata-css --build production build (minified JS, readable CSS)
176
- strata-css --minify production build (minified CSS + JS, smallest output)
177
- `)
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict'
4
+
5
+ const fs = require('fs')
6
+ const path = require('path')
7
+ const readline = require('readline')
8
+ const chokidar = require('chokidar')
9
+ const strata = require('../src/index')
10
+ const { getWatchFiles } = require('../src/scanner/scanner')
11
+
12
+ const args = process.argv.slice(2)
13
+ const cwd = process.cwd()
14
+
15
+ function ask(rl, question) {
16
+ return new Promise(resolve => rl.question(question, resolve))
17
+ }
18
+
19
+ function askYesNo(rl, question, defaultYes = true) {
20
+ const hint = defaultYes ? '[Y/n]' : '[y/N]'
21
+ return new Promise(resolve => {
22
+ rl.question(`${question} ${hint} `, answer => {
23
+ if (!answer.trim()) return resolve(defaultYes)
24
+ resolve(answer.trim().toLowerCase().startsWith('y'))
25
+ })
26
+ })
27
+ }
28
+
29
+ function loadConfig(cwd) {
30
+ const configPath = path.resolve(cwd, 'strata.config.js')
31
+ const configPathCjs = path.resolve(cwd, 'strata.config.cjs')
32
+
33
+ if (fs.existsSync(configPathCjs)) {
34
+ try { return require(configPathCjs) } catch {}
35
+ }
36
+
37
+ if (fs.existsSync(configPath)) {
38
+ try { return require(configPath) } catch {}
39
+ }
40
+
41
+ return {}
42
+ }
43
+
44
+ // ─── JS minifier ──────────────────────────────────────────────────────
45
+ // Strips block comments (preserving /*! banners), line comments,
46
+ // and collapses unnecessary whitespace no external dependency needed.
47
+
48
+ function minifyJS(src) {
49
+ return src
50
+ // preserve /*! banner */ comments, strip all other /* ... */ blocks
51
+ .replace(/\/\*(?!!)([\s\S]*?)\*\//g, '')
52
+ // strip // line comments (not inside strings — conservative: only at line start or after whitespace)
53
+ .replace(/(?:^|\s)\/\/[^\n]*/gm, '')
54
+ // collapse runs of whitespace/newlines to a single space
55
+ .replace(/[ \t]+/g, ' ')
56
+ .replace(/\n\s*\n+/g, '\n')
57
+ .trim()
58
+ }
59
+
60
+ // ─── Build ────────────────────────────────────────────────────────────
61
+ // --watch → unminified CSS, unminified JS (fast rebuilds for dev)
62
+ // --build unminified CSS, minified JS (production-ready JS)
63
+ // --minify → minified CSS + minified JS (smallest possible output)
64
+
65
+ async function build(cssMinify = false, jsMinify = true) {
66
+ const config = loadConfig(cwd)
67
+ const inputFile = config.input || path.join(cwd, 'strata.css')
68
+ const outputFile = config.output || path.join(cwd, 'dist', 'strata.output.css')
69
+
70
+ const start = process.hrtime.bigint()
71
+ const css = await strata.build(inputFile, outputFile, { cwd, sourceMap: !cssMinify })
72
+
73
+ // CSS minification
74
+ if (cssMinify) {
75
+ const cssnano = require('cssnano')
76
+ const postcss = require('postcss')
77
+ const result = await postcss([cssnano({ preset: 'default' })]).process(css, { from: outputFile })
78
+ fs.writeFileSync(outputFile, result.css)
79
+ }
80
+
81
+ // Bundle + optionally minify JS components
82
+ const componentsDir = path.join(__dirname, '..', 'src', 'components', 'modules')
83
+ const jsDest = path.join(path.dirname(outputFile), 'strata.components.js')
84
+ if (fs.existsSync(componentsDir)) {
85
+ const files = fs.readdirSync(componentsDir).filter(f => f.endsWith('.js')).sort()
86
+ const banner = `/*! Strata Components — built ${new Date().toISOString().slice(0,10)} */\n`
87
+ const raw = files.map(f => fs.readFileSync(path.join(componentsDir, f), 'utf8')).join('\n')
88
+ const output = jsMinify ? banner + minifyJS(raw) : banner + raw
89
+ fs.mkdirSync(path.dirname(jsDest), { recursive: true })
90
+ fs.writeFileSync(jsDest, output)
91
+ }
92
+
93
+ const ms = (Number(process.hrtime.bigint() - start) / 1_000_000).toFixed(2)
94
+ const cssSize = (Buffer.byteLength(fs.readFileSync(outputFile)) / 1024).toFixed(2)
95
+ const jsSize = fs.existsSync(jsDest)
96
+ ? (Buffer.byteLength(fs.readFileSync(jsDest)) / 1024).toFixed(2)
97
+ : '0'
98
+ console.log(`[Strata] ✓ Built → ${outputFile} (CSS ${cssSize} KB, JS ${jsSize} KB) in ${ms}ms`)
99
+ }
100
+
101
+ // ─── Watch ────────────────────────────────────────────────────────────
102
+ async function watch() {
103
+ console.log('[Strata] Starting in watch mode...')
104
+ await build(false, false) // unminified for dev
105
+
106
+ const config = loadConfig(cwd)
107
+ const contentGlobs = config.content || ['./src/**/*.{html,jsx,tsx,vue,astro,svelte,js,ts}']
108
+ const inputFile = config.input || path.join(cwd, 'strata.css')
109
+ const watchFiles = [inputFile, ...getWatchFiles(contentGlobs)]
110
+
111
+ const watcher = chokidar.watch(watchFiles, { ignoreInitial: true, persistent: true })
112
+
113
+ watcher.on('change', async (filePath) => {
114
+ console.log(`[Strata] Changed: ${path.relative(cwd, filePath)}`)
115
+ strata.invalidate(filePath)
116
+ await build(false, false)
117
+ })
118
+
119
+ watcher.on('add', async (filePath) => {
120
+ console.log(`[Strata] Added: ${path.relative(cwd, filePath)}`)
121
+ strata.invalidate(filePath)
122
+ await build(false, false)
123
+ })
124
+
125
+ console.log('[Strata] Watching for changes...')
126
+ }
127
+
128
+ // ─── ESM / framework helpers ──────────────────────────────────────────
129
+ function isESMProject(cwd) {
130
+ try {
131
+ const pkg = JSON.parse(fs.readFileSync(path.resolve(cwd, 'package.json'), 'utf8'))
132
+ return pkg.type === 'module'
133
+ } catch {
134
+ return false
135
+ }
136
+ }
137
+
138
+ function detectOutputPath(cwd) {
139
+ try {
140
+ const pkg = JSON.parse(fs.readFileSync(path.resolve(cwd, 'package.json'), 'utf8'))
141
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies }
142
+ if (deps['astro']) return './public/strata.output.css'
143
+ if (deps['laravel-vite-plugin']) return './public/strata.output.css'
144
+ if (deps['next']) return './public/strata.output.css'
145
+ if (deps['react']) return './public/strata.output.css'
146
+ if (deps['vue']) return './public/strata.output.css'
147
+ if (deps['@sveltejs/kit']) return './public/strata.output.css'
148
+ } catch {}
149
+ return './dist/strata.output.css'
150
+ }
151
+
152
+ // ─── Framework detection ──────────────────────────────────────────────
153
+ function detectFramework(cwd) {
154
+ try {
155
+ const pkg = JSON.parse(fs.readFileSync(path.resolve(cwd, 'package.json'), 'utf8'))
156
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies }
157
+
158
+ if (deps['astro']) return 'astro'
159
+ if (deps['laravel-vite-plugin']) return 'laravel'
160
+ if (deps['next']) return 'next'
161
+ if (deps['@sveltejs/kit']) return 'sveltekit'
162
+ if (deps['nuxt']) return 'nuxt'
163
+ if (deps['react'] && deps['vite']) return 'react-vite'
164
+ if (deps['vue'] && deps['vite']) return 'vue-vite'
165
+ } catch {}
166
+ return 'generic'
167
+ }
168
+
169
+ // ─── Init ─────────────────────────────────────────────────────────────
170
+ async function init() {
171
+ const isESM = isESMProject(cwd)
172
+ const framework = detectFramework(cwd)
173
+ const output = detectOutputPath(cwd)
174
+
175
+ const frameworkDev = {
176
+ 'astro': 'astro dev',
177
+ 'laravel': 'vite',
178
+ 'next': 'next dev',
179
+ 'sveltekit': 'vite dev',
180
+ 'nuxt': 'nuxt dev',
181
+ 'react-vite': 'vite',
182
+ 'vue-vite': 'vite',
183
+ 'generic': 'npm start'
184
+ }
185
+
186
+ const frameworkBuild = {
187
+ 'astro': 'astro build',
188
+ 'laravel': 'vite build',
189
+ 'next': 'next build',
190
+ 'sveltekit': 'vite build',
191
+ 'nuxt': 'nuxt build',
192
+ 'react-vite': 'vite build',
193
+ 'vue-vite': 'vite build',
194
+ 'generic': 'npm run build'
195
+ }
196
+
197
+ const rl = readline.createInterface({
198
+ input: process.stdin,
199
+ output: process.stdout
200
+ })
201
+
202
+ console.log('')
203
+ console.log(' strata Setup initiated.')
204
+ console.log('')
205
+ console.log(` ◼ Framework detected : ${framework}`)
206
+ console.log(` ◼ Project type : ${isESM ? 'ESM' : 'CommonJS'}`)
207
+ console.log(` ◼ Output path : ${output}`)
208
+ console.log('')
209
+
210
+ const installConcurrently = await askYesNo(rl,
211
+ ' deps Install concurrently for clean parallel dev?')
212
+
213
+ const updateScripts = await askYesNo(rl,
214
+ ' scripts Auto-update package.json dev and build scripts?')
215
+
216
+ console.log('')
217
+ console.log(' html Where is your main HTML or layout file?')
218
+ console.log(' Examples:')
219
+ console.log(' src/layouts/Layout.astro')
220
+ console.log(' resources/views/layouts/app.blade.php')
221
+ console.log(' src/App.jsx')
222
+ console.log(' index.html')
223
+ const htmlFile = await ask(rl,
224
+ ' Path (leave blank to skip): ')
225
+
226
+ rl.close()
227
+ console.log('')
228
+
229
+ // Create config files
230
+ const configFile = isESM ? 'strata.config.cjs' : 'strata.config.js'
231
+ const postcssFile = isESM ? 'postcss.config.cjs' : 'postcss.config.js'
232
+
233
+ const configContent = `module.exports = {\n content: ["./src/**/*.{html,jsx,tsx,vue,astro,svelte,js,ts}"],\n input: "./strata.css",\n output: "${output}"\n}\n`
234
+ const postcssContent = `module.exports = {\n plugins: [\n require('strata-css'),\n require('autoprefixer')\n ]\n}\n`
235
+ const strataCssContent = `@strata base;\n@strata components;\n@strata utilities;\n`
236
+
237
+ fs.writeFileSync(path.resolve(cwd, configFile), configContent)
238
+ fs.writeFileSync(path.resolve(cwd, postcssFile), postcssContent)
239
+ fs.writeFileSync(path.resolve(cwd, 'strata.css'), strataCssContent)
240
+
241
+ console.log(` ✔ Created: ${configFile}`)
242
+ console.log(` ✔ Created: strata.css`)
243
+ console.log(` ✔ Created: ${postcssFile}`)
244
+
245
+ // Install concurrently
246
+ if (installConcurrently) {
247
+ console.log(' ◼ Installing concurrently...')
248
+ require('child_process').execSync(
249
+ 'npm install --save-dev concurrently',
250
+ { stdio: 'inherit', cwd }
251
+ )
252
+ console.log(' ✔ concurrently installed')
253
+ }
254
+
255
+ // Update package.json scripts
256
+ if (updateScripts) {
257
+ const strataWatch = 'node node_modules/strata-css/bin/strata.js --watch'
258
+ const strataBuild = 'node node_modules/strata-css/bin/strata.js --build'
259
+ const devCmd = frameworkDev[framework] || 'npm start'
260
+ const buildCmd = frameworkBuild[framework] || 'npm run build'
261
+
262
+ const pkgPath = path.resolve(cwd, 'package.json')
263
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
264
+ if (!pkg.scripts) pkg.scripts = {}
265
+
266
+ const hasConcurrently = installConcurrently ||
267
+ pkg.dependencies?.['concurrently'] ||
268
+ pkg.devDependencies?.['concurrently']
269
+
270
+ pkg.scripts.dev = hasConcurrently
271
+ ? `concurrently "${strataWatch}" "${devCmd}"`
272
+ : `${strataWatch} & ${devCmd}`
273
+ pkg.scripts.build = `${strataBuild} && ${buildCmd}`
274
+ pkg.scripts['strata:watch'] = strataWatch
275
+ pkg.scripts['strata:build'] = strataBuild
276
+
277
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2))
278
+ console.log(' ✔ Updated: package.json scripts')
279
+ }
280
+
281
+ // Inject CSS link and theme into HTML file
282
+ if (htmlFile && htmlFile.trim()) {
283
+ const htmlPath = path.resolve(cwd, htmlFile.trim())
284
+ if (fs.existsSync(htmlPath)) {
285
+ let content = fs.readFileSync(htmlPath, 'utf8')
286
+ const cssLink = `\t\t<link rel="stylesheet" href="/strata.output.css">`
287
+ const themeAttr = 'data-st-theme="light"'
288
+
289
+ if (!content.includes('strata.output.css')) {
290
+ content = content.replace('</head>', `${cssLink}\n\t</head>`)
291
+ }
292
+ if (!content.includes('data-st-theme')) {
293
+ content = content.replace('<html', `<html ${themeAttr}`)
294
+ }
295
+
296
+ fs.writeFileSync(htmlPath, content)
297
+ console.log(` ✔ Updated: ${htmlFile.trim()}`)
298
+ } else {
299
+ console.log(` ✗ File not found: ${htmlFile.trim()} — skipped`)
300
+ }
301
+ }
302
+
303
+ // Run initial build
304
+ console.log(' ◼ Running initial build...')
305
+ await strata.build(
306
+ path.resolve(cwd, 'strata.css'),
307
+ path.resolve(cwd, output.replace('./', '')),
308
+ { cwd }
309
+ )
310
+ console.log(' ✔ Initial build complete')
311
+
312
+ console.log('')
313
+ console.log(' strata Setup complete!')
314
+ console.log('')
315
+ console.log(' next Your project is ready.')
316
+ console.log(' Run npm run dev to start.')
317
+ console.log('')
318
+ }
319
+
320
+ // ─── Run ──────────────────────────────────────────────────────────────
321
+ if (args[0] === 'init') init().catch(console.error)
322
+ else if (args.includes('--watch')) watch()
323
+ else if (args.includes('--minify')) build(true, true)
324
+ else if (args.includes('--build')) build(false, true)
325
+ else console.log(`
326
+ Strata CSS
327
+
328
+ strata-css init scaffold a new project
329
+ strata-css --watch development mode (unminified, fast rebuild)
330
+ strata-css --build production build (minified JS, readable CSS)
331
+ strata-css --minify production build (minified CSS + JS, smallest output)
332
+ `)