strata-css 1.0.2 → 1.0.3
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 +276 -177
- package/package.json +68 -68
- package/src/index.js +171 -171
package/src/index.js
CHANGED
|
@@ -1,171 +1,171 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Strata CSS — PostCSS Plugin — Optimised v6
|
|
3
|
-
*
|
|
4
|
-
* Warm builds bypass PostCSS entirely — no node creation, no GC pressure
|
|
5
|
-
* Cold builds run full PostCSS pipeline (including autoprefixer etc.)
|
|
6
|
-
* Warm builds return cached string directly — zero object allocation
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
'use strict'
|
|
10
|
-
|
|
11
|
-
const path = require('path')
|
|
12
|
-
const fs = require('fs')
|
|
13
|
-
let postcss
|
|
14
|
-
|
|
15
|
-
// ─── State ────────────────────────────────────────────────────────────
|
|
16
|
-
let dirty = true
|
|
17
|
-
let cachedCSS = null // final output CSS string from last cold build
|
|
18
|
-
|
|
19
|
-
// ─── Config cache ─────────────────────────────────────────────────────
|
|
20
|
-
let cachedConfig = null
|
|
21
|
-
let cachedConfigPath = null
|
|
22
|
-
let cachedConfigMtime = 0
|
|
23
|
-
|
|
24
|
-
function loadConfig(cwd) {
|
|
25
|
-
const configPath = path.resolve(cwd, 'strata.config.js')
|
|
26
|
-
const configPathCjs = path.resolve(cwd, 'strata.config.cjs')
|
|
27
|
-
|
|
28
|
-
if (fs.existsSync(configPathCjs)) {
|
|
29
|
-
try { return require(configPathCjs) } catch {}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (fs.existsSync(configPath)) {
|
|
33
|
-
try { return require(configPath) } catch {}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return {}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// ─── Base CSS ─────────────────────────────────────────────────────────
|
|
40
|
-
const BASE_CSS = require('./layers/base').trim()
|
|
41
|
-
let BASE_AST = null
|
|
42
|
-
|
|
43
|
-
function getBaseAST() {
|
|
44
|
-
if (!postcss) postcss = require('postcss')
|
|
45
|
-
if (!BASE_AST) BASE_AST = postcss.parse(BASE_CSS)
|
|
46
|
-
return BASE_AST
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// ─── Input CSS cache ───────────────────────────────────────────────────
|
|
50
|
-
// strata.css rarely changes — cache it with mtime check
|
|
51
|
-
let cachedInputCSS = null
|
|
52
|
-
let cachedInputPath = null
|
|
53
|
-
let cachedInputMtime = 0
|
|
54
|
-
|
|
55
|
-
function readInputCSS(inputCSSPath) {
|
|
56
|
-
let mtime = 0
|
|
57
|
-
try { mtime = fs.statSync(inputCSSPath).mtimeMs } catch {}
|
|
58
|
-
if (cachedInputCSS && cachedInputPath === inputCSSPath && cachedInputMtime === mtime) {
|
|
59
|
-
return cachedInputCSS
|
|
60
|
-
}
|
|
61
|
-
cachedInputCSS = fs.readFileSync(inputCSSPath, 'utf8')
|
|
62
|
-
cachedInputPath = inputCSSPath
|
|
63
|
-
cachedInputMtime = mtime
|
|
64
|
-
return cachedInputCSS
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// ─── PostCSS Plugin ───────────────────────────────────────────────────
|
|
68
|
-
// Only runs on cold builds — warm builds bypass via strata.build()
|
|
69
|
-
|
|
70
|
-
const plugin = (opts = {}) => ({
|
|
71
|
-
postcssPlugin: 'strata-css',
|
|
72
|
-
|
|
73
|
-
async Once(root) {
|
|
74
|
-
if (!postcss) postcss = require('postcss')
|
|
75
|
-
|
|
76
|
-
const cwd = opts.cwd || process.cwd()
|
|
77
|
-
const config = loadConfig(cwd)
|
|
78
|
-
|
|
79
|
-
const { scanFiles } = require('./scanner/scanner')
|
|
80
|
-
const { generate } = require('./generator/generator')
|
|
81
|
-
|
|
82
|
-
const contentGlobs = config.content || [
|
|
83
|
-
'./src/**/*.{html,jsx,tsx,vue,astro,svelte,js,ts}'
|
|
84
|
-
]
|
|
85
|
-
|
|
86
|
-
const classNames = scanFiles(contentGlobs)
|
|
87
|
-
const { componentCSS, utilityCSS } = generate(classNames, config)
|
|
88
|
-
|
|
89
|
-
// Replace @strata directives
|
|
90
|
-
let baseInserted = false
|
|
91
|
-
root.walkAtRules('strata', rule => {
|
|
92
|
-
const d = rule.params.trim()
|
|
93
|
-
if (d === 'base' && !baseInserted) {
|
|
94
|
-
rule.replaceWith(getBaseAST().clone())
|
|
95
|
-
baseInserted = true
|
|
96
|
-
} else if (d === 'components') {
|
|
97
|
-
// componentCSS now contains multiple @layer sub-layer blocks
|
|
98
|
-
componentCSS ? rule.replaceWith(postcss.parse(componentCSS)) : rule.remove()
|
|
99
|
-
} else if (d === 'utilities') {
|
|
100
|
-
// utilityCSS now contains multiple @layer sub-layer blocks
|
|
101
|
-
utilityCSS ? rule.replaceWith(postcss.parse(utilityCSS)) : rule.remove()
|
|
102
|
-
} else {
|
|
103
|
-
rule.remove()
|
|
104
|
-
}
|
|
105
|
-
})
|
|
106
|
-
}
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
plugin.postcss = true
|
|
110
|
-
module.exports = plugin
|
|
111
|
-
|
|
112
|
-
// ─── Build API — used by CLI ──────────────────────────────────────────
|
|
113
|
-
// Warm builds bypass PostCSS entirely — zero allocation, zero GC pressure
|
|
114
|
-
|
|
115
|
-
module.exports.build = async (inputCSSPath, outputCSSPath, opts = {}) => {
|
|
116
|
-
// ── Warm path: return cached CSS, no work at all ──────────────────
|
|
117
|
-
if (!dirty && cachedCSS) {
|
|
118
|
-
if (outputCSSPath) fs.writeFileSync(outputCSSPath, cachedCSS)
|
|
119
|
-
return cachedCSS
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// ── Cold path: string assembly — no PostCSS parse/stringify ───────
|
|
123
|
-
// PostCSS parse → AST clone → replaceWith → stringify is a costly
|
|
124
|
-
// round-trip that adds no transformation. We replace @strata directives
|
|
125
|
-
// via regex on the raw string, which is O(n) on the input file only.
|
|
126
|
-
const cwd = opts.cwd || process.cwd()
|
|
127
|
-
const config = loadConfig(cwd)
|
|
128
|
-
|
|
129
|
-
const { scanFiles } = require('./scanner/scanner')
|
|
130
|
-
const { generate } = require('./generator/generator')
|
|
131
|
-
|
|
132
|
-
const contentGlobs = config.content || [
|
|
133
|
-
'./src/**/*.{html,jsx,tsx,vue,astro,svelte,js,ts}'
|
|
134
|
-
]
|
|
135
|
-
|
|
136
|
-
const classNames = scanFiles(contentGlobs)
|
|
137
|
-
const { componentCSS, utilityCSS } = generate(classNames, config)
|
|
138
|
-
|
|
139
|
-
// Read input CSS (cached by mtime — strata.css rarely changes)
|
|
140
|
-
const inputCSS = readInputCSS(inputCSSPath)
|
|
141
|
-
|
|
142
|
-
// Replace @strata directives with pre-built CSS strings.
|
|
143
|
-
// Use function replacements (not string literals) so any `$` sequences
|
|
144
|
-
// in the CSS (e.g. inside comments) are never treated as regex back-references.
|
|
145
|
-
const css = inputCSS
|
|
146
|
-
.replace(/^\s*@strata\s+base\s*;/m, () => BASE_CSS)
|
|
147
|
-
.replace(/^\s*@strata\s+components\s*;/m, () => componentCSS || '')
|
|
148
|
-
.replace(/^\s*@strata\s+utilities\s*;/m, () => utilityCSS || '')
|
|
149
|
-
|
|
150
|
-
cachedCSS = css
|
|
151
|
-
dirty = false
|
|
152
|
-
|
|
153
|
-
if (outputCSSPath) {
|
|
154
|
-
fs.mkdirSync(path.dirname(outputCSSPath), { recursive: true })
|
|
155
|
-
fs.writeFileSync(outputCSSPath, css)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return css
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// ─── Cache invalidation ───────────────────────────────────────────────
|
|
162
|
-
module.exports.invalidate = (changedFile) => {
|
|
163
|
-
dirty = true
|
|
164
|
-
cachedCSS = null
|
|
165
|
-
const { clearFileCache } = require('./scanner/scanner')
|
|
166
|
-
clearFileCache(changedFile)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// ─── Direct PostCSS usage (for users using postcss.config.js) ────────
|
|
170
|
-
// Warm path still applies — cached CSS is injected as a raw parse
|
|
171
|
-
module.exports.postcss = true
|
|
1
|
+
/**
|
|
2
|
+
* Strata CSS — PostCSS Plugin — Optimised v6
|
|
3
|
+
*
|
|
4
|
+
* Warm builds bypass PostCSS entirely — no node creation, no GC pressure
|
|
5
|
+
* Cold builds run full PostCSS pipeline (including autoprefixer etc.)
|
|
6
|
+
* Warm builds return cached string directly — zero object allocation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict'
|
|
10
|
+
|
|
11
|
+
const path = require('path')
|
|
12
|
+
const fs = require('fs')
|
|
13
|
+
let postcss
|
|
14
|
+
|
|
15
|
+
// ─── State ────────────────────────────────────────────────────────────
|
|
16
|
+
let dirty = true
|
|
17
|
+
let cachedCSS = null // final output CSS string from last cold build
|
|
18
|
+
|
|
19
|
+
// ─── Config cache ─────────────────────────────────────────────────────
|
|
20
|
+
let cachedConfig = null
|
|
21
|
+
let cachedConfigPath = null
|
|
22
|
+
let cachedConfigMtime = 0
|
|
23
|
+
|
|
24
|
+
function loadConfig(cwd) {
|
|
25
|
+
const configPath = path.resolve(cwd, 'strata.config.js')
|
|
26
|
+
const configPathCjs = path.resolve(cwd, 'strata.config.cjs')
|
|
27
|
+
|
|
28
|
+
if (fs.existsSync(configPathCjs)) {
|
|
29
|
+
try { return require(configPathCjs) } catch {}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (fs.existsSync(configPath)) {
|
|
33
|
+
try { return require(configPath) } catch {}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ─── Base CSS ─────────────────────────────────────────────────────────
|
|
40
|
+
const BASE_CSS = require('./layers/base').trim()
|
|
41
|
+
let BASE_AST = null
|
|
42
|
+
|
|
43
|
+
function getBaseAST() {
|
|
44
|
+
if (!postcss) postcss = require('postcss')
|
|
45
|
+
if (!BASE_AST) BASE_AST = postcss.parse(BASE_CSS)
|
|
46
|
+
return BASE_AST
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ─── Input CSS cache ───────────────────────────────────────────────────
|
|
50
|
+
// strata.css rarely changes — cache it with mtime check
|
|
51
|
+
let cachedInputCSS = null
|
|
52
|
+
let cachedInputPath = null
|
|
53
|
+
let cachedInputMtime = 0
|
|
54
|
+
|
|
55
|
+
function readInputCSS(inputCSSPath) {
|
|
56
|
+
let mtime = 0
|
|
57
|
+
try { mtime = fs.statSync(inputCSSPath).mtimeMs } catch {}
|
|
58
|
+
if (cachedInputCSS && cachedInputPath === inputCSSPath && cachedInputMtime === mtime) {
|
|
59
|
+
return cachedInputCSS
|
|
60
|
+
}
|
|
61
|
+
cachedInputCSS = fs.readFileSync(inputCSSPath, 'utf8')
|
|
62
|
+
cachedInputPath = inputCSSPath
|
|
63
|
+
cachedInputMtime = mtime
|
|
64
|
+
return cachedInputCSS
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ─── PostCSS Plugin ───────────────────────────────────────────────────
|
|
68
|
+
// Only runs on cold builds — warm builds bypass via strata.build()
|
|
69
|
+
|
|
70
|
+
const plugin = (opts = {}) => ({
|
|
71
|
+
postcssPlugin: 'strata-css',
|
|
72
|
+
|
|
73
|
+
async Once(root) {
|
|
74
|
+
if (!postcss) postcss = require('postcss')
|
|
75
|
+
|
|
76
|
+
const cwd = opts.cwd || process.cwd()
|
|
77
|
+
const config = loadConfig(cwd)
|
|
78
|
+
|
|
79
|
+
const { scanFiles } = require('./scanner/scanner')
|
|
80
|
+
const { generate } = require('./generator/generator')
|
|
81
|
+
|
|
82
|
+
const contentGlobs = config.content || [
|
|
83
|
+
'./src/**/*.{html,jsx,tsx,vue,astro,svelte,js,ts}'
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
const classNames = scanFiles(contentGlobs)
|
|
87
|
+
const { componentCSS, utilityCSS } = generate(classNames, config)
|
|
88
|
+
|
|
89
|
+
// Replace @strata directives
|
|
90
|
+
let baseInserted = false
|
|
91
|
+
root.walkAtRules('strata', rule => {
|
|
92
|
+
const d = rule.params.trim()
|
|
93
|
+
if (d === 'base' && !baseInserted) {
|
|
94
|
+
rule.replaceWith(getBaseAST().clone())
|
|
95
|
+
baseInserted = true
|
|
96
|
+
} else if (d === 'components') {
|
|
97
|
+
// componentCSS now contains multiple @layer sub-layer blocks
|
|
98
|
+
componentCSS ? rule.replaceWith(postcss.parse(componentCSS)) : rule.remove()
|
|
99
|
+
} else if (d === 'utilities') {
|
|
100
|
+
// utilityCSS now contains multiple @layer sub-layer blocks
|
|
101
|
+
utilityCSS ? rule.replaceWith(postcss.parse(utilityCSS)) : rule.remove()
|
|
102
|
+
} else {
|
|
103
|
+
rule.remove()
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
plugin.postcss = true
|
|
110
|
+
module.exports = plugin
|
|
111
|
+
|
|
112
|
+
// ─── Build API — used by CLI ──────────────────────────────────────────
|
|
113
|
+
// Warm builds bypass PostCSS entirely — zero allocation, zero GC pressure
|
|
114
|
+
|
|
115
|
+
module.exports.build = async (inputCSSPath, outputCSSPath, opts = {}) => {
|
|
116
|
+
// ── Warm path: return cached CSS, no work at all ──────────────────
|
|
117
|
+
if (!dirty && cachedCSS) {
|
|
118
|
+
if (outputCSSPath) fs.writeFileSync(outputCSSPath, cachedCSS)
|
|
119
|
+
return cachedCSS
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── Cold path: string assembly — no PostCSS parse/stringify ───────
|
|
123
|
+
// PostCSS parse → AST clone → replaceWith → stringify is a costly
|
|
124
|
+
// round-trip that adds no transformation. We replace @strata directives
|
|
125
|
+
// via regex on the raw string, which is O(n) on the input file only.
|
|
126
|
+
const cwd = opts.cwd || process.cwd()
|
|
127
|
+
const config = loadConfig(cwd)
|
|
128
|
+
|
|
129
|
+
const { scanFiles } = require('./scanner/scanner')
|
|
130
|
+
const { generate } = require('./generator/generator')
|
|
131
|
+
|
|
132
|
+
const contentGlobs = config.content || [
|
|
133
|
+
'./src/**/*.{html,jsx,tsx,vue,astro,svelte,js,ts}'
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
const classNames = scanFiles(contentGlobs)
|
|
137
|
+
const { componentCSS, utilityCSS } = generate(classNames, config)
|
|
138
|
+
|
|
139
|
+
// Read input CSS (cached by mtime — strata.css rarely changes)
|
|
140
|
+
const inputCSS = readInputCSS(inputCSSPath)
|
|
141
|
+
|
|
142
|
+
// Replace @strata directives with pre-built CSS strings.
|
|
143
|
+
// Use function replacements (not string literals) so any `$` sequences
|
|
144
|
+
// in the CSS (e.g. inside comments) are never treated as regex back-references.
|
|
145
|
+
const css = inputCSS
|
|
146
|
+
.replace(/^\s*@strata\s+base\s*;/m, () => BASE_CSS)
|
|
147
|
+
.replace(/^\s*@strata\s+components\s*;/m, () => componentCSS || '')
|
|
148
|
+
.replace(/^\s*@strata\s+utilities\s*;/m, () => utilityCSS || '')
|
|
149
|
+
|
|
150
|
+
cachedCSS = css
|
|
151
|
+
dirty = false
|
|
152
|
+
|
|
153
|
+
if (outputCSSPath) {
|
|
154
|
+
fs.mkdirSync(path.dirname(outputCSSPath), { recursive: true })
|
|
155
|
+
fs.writeFileSync(outputCSSPath, css)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return css
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ─── Cache invalidation ───────────────────────────────────────────────
|
|
162
|
+
module.exports.invalidate = (changedFile) => {
|
|
163
|
+
dirty = true
|
|
164
|
+
cachedCSS = null
|
|
165
|
+
const { clearFileCache } = require('./scanner/scanner')
|
|
166
|
+
clearFileCache(changedFile)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ─── Direct PostCSS usage (for users using postcss.config.js) ────────
|
|
170
|
+
// Warm path still applies — cached CSS is injected as a raw parse
|
|
171
|
+
module.exports.postcss = true
|