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.
- package/README.md +730 -730
- package/bin/strata.js +332 -177
- package/package.json +68 -68
- 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
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
} catch {
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
function
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
+
`)
|