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