tailwind-to-style 3.2.1 → 3.2.2

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 CHANGED
@@ -1,6 +1,8 @@
1
1
  # tailwind-to-style
2
2
 
3
3
  [📦 View on npm](https://www.npmjs.com/package/tailwind-to-style)
4
+ | [🌐 Landing Page](https://bigetion.github.io/tailwind-to-style/landing.html)
5
+ | [🛝 Playground](https://bigetion.github.io/tailwind-to-style/sandbox.html)
4
6
 
5
7
  [![npm version](https://img.shields.io/npm/v/tailwind-to-style.svg)](https://www.npmjs.com/package/tailwind-to-style)
6
8
  [![Build Status](https://github.com/Bigetion/tailwind-to-style/workflows/CI%2FCD/badge.svg)](https://github.com/Bigetion/tailwind-to-style/actions)
@@ -179,6 +181,8 @@ tws('p-0.5 m-1.5 gap-2.5')
179
181
 
180
182
  Generates real CSS from Tailwind classes with full selector support, SCSS-like nesting, and auto-injects a `<style>` tag into the DOM.
181
183
 
184
+ > **HMR-safe** — each `twsx()` call owns a stable slot in the injected style tag keyed by its top-level selectors. When you edit styles during development, the old slot is **replaced** (not appended), so changes are reflected immediately without a hard refresh.
185
+
182
186
  ```javascript
183
187
  import { twsx } from 'tailwind-to-style'
184
188
 
@@ -621,10 +625,41 @@ function App() {
621
625
 
622
626
  ### Vue
623
627
 
628
+ ```vue
629
+ <script setup>
630
+ import 'tailwind-to-style/preflight.css'
631
+ import { tws, twsx } from 'tailwind-to-style'
632
+
633
+ // twsx() at the top level of <script setup> is HMR-safe.
634
+ // When you edit the classes, Vite's HMR re-runs this block and
635
+ // the old CSS slot is replaced automatically — no hard refresh needed.
636
+ twsx({
637
+ 'html': 'bg-gray-100 min-h-screen flex items-center justify-center',
638
+ '.card': [
639
+ 'bg-white p-5 border border-gray-300 rounded-xl shadow-md',
640
+ {
641
+ '&:hover': 'shadow-xl',
642
+ '> .title': 'text-xl font-bold text-gray-900 mb-3',
643
+ '> .body': 'text-sm text-gray-700',
644
+ },
645
+ ],
646
+ })
647
+ </script>
648
+
649
+ <template>
650
+ <div class="card">
651
+ <div class="title">Card Title</div>
652
+ <p class="body">Styled with twsx — hot reload works out of the box.</p>
653
+ </div>
654
+ </template>
655
+ ```
656
+
657
+ For simple inline styles, use `tws()` with the reactive system:
658
+
624
659
  ```vue
625
660
  <script setup>
626
661
  import { tws } from 'tailwind-to-style'
627
- const btnStyle = tws('bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600', true)
662
+ const btnStyle = tws('bg-blue-500 text-white px-4 py-2 rounded-lg', true)
628
663
  </script>
629
664
 
630
665
  <template>
@@ -670,7 +705,7 @@ v3.2.0 includes major performance optimizations:
670
705
  - **Pre-compiled regex** — compiled once at module load, reused for every call
671
706
  - **Multi-level LRU caching** — class resolution, CSS generation, config lookups
672
707
  - **Bounded caches** — Maps capped at 5,000 entries, Sets at 10,000 to prevent memory leaks
673
- - **`sheet.insertRule()` injection** — avoids full stylesheet reparsing on each call
708
+ - **Slot-based CSS injection** — each `twsx()` call owns a named slot; updates rebuild the tag instead of appending, preventing HMR accumulation
674
709
  - **FNV-1a hashing** — 100x faster than `JSON.stringify` for cache keys
675
710
 
676
711
  ```
package/dist/core/tws.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * tailwind-to-style v3.2.1
2
+ * tailwind-to-style v3.2.2
3
3
  * Runtime Tailwind CSS to inline styles converter
4
4
  *
5
5
  * @author Bigetion
@@ -1,5 +1,5 @@
1
1
  /**
2
- * tailwind-to-style v3.2.1
2
+ * tailwind-to-style v3.2.2
3
3
  * Runtime Tailwind CSS to inline styles converter
4
4
  *
5
5
  * @author Bigetion
@@ -1,5 +1,5 @@
1
1
  /**
2
- * tailwind-to-style v3.2.1
2
+ * tailwind-to-style v3.2.2
3
3
  * Runtime Tailwind CSS to inline styles converter
4
4
  *
5
5
  * @author Bigetion
@@ -8998,27 +8998,40 @@ function fastObjectHash(obj) {
8998
8998
  */
8999
8999
  function twsx(obj) {
9000
9000
  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
9001
- // Create fast hash key from input (100x faster than JSON.stringify)
9001
+ // Create fast hash key from input content (100x faster than JSON.stringify)
9002
9002
  const cacheKey = fastObjectHash(obj, options);
9003
+ const {
9004
+ inject = true
9005
+ } = options;
9006
+
9007
+ // Derive a STABLE registry key from the object's top-level selector KEYS only.
9008
+ // This key is independent of the CSS class values, so when styles are edited
9009
+ // during HMR (same selectors, different Tailwind classes) the old slot is
9010
+ // replaced rather than a new slot being created — preventing CSS accumulation.
9011
+ const registryKey = obj && typeof obj === "object" && !Array.isArray(obj) ? Object.keys(obj).sort().join("|") : cacheKey; // fallback for edge cases
9003
9012
 
9004
9013
  // Check cache first
9005
9014
  if (_twsxInputCache.has(cacheKey)) {
9006
9015
  const cached = _twsxInputCache.get(cacheKey);
9007
9016
 
9008
- // Handle injection for cached result
9009
- const {
9010
- inject = true
9011
- } = options;
9017
+ // Re-inject with registryKey so the slot stays registered (no-op when unchanged).
9012
9018
  if (inject && (IS_BROWSER || _ssrCollecting)) {
9013
- autoInjectCss(cached);
9019
+ autoInjectCss(cached, registryKey);
9014
9020
  }
9015
9021
  return cached;
9016
9022
  }
9017
9023
 
9018
- // Cache miss: call original twsxNoCache and cache result
9019
- const result = twsxNoCache(obj, options);
9024
+ // Cache miss: generate CSS without internal auto-injection so that twsx owns
9025
+ // the injection and can pass the selector-based registryKey for slot replacement.
9026
+ const result = twsxNoCache(obj, {
9027
+ ...options,
9028
+ inject: false
9029
+ });
9020
9030
  _twsxInputCache.set(cacheKey, result);
9021
9031
  evictMap(_twsxInputCache);
9032
+ if (inject && (IS_BROWSER || _ssrCollecting)) {
9033
+ autoInjectCss(result, registryKey);
9034
+ }
9022
9035
  return result;
9023
9036
  }
9024
9037
 
@@ -9038,12 +9051,54 @@ function getCssHash(str) {
9038
9051
 
9039
9052
  // Enhanced auto-inject CSS with performance monitoring & SSR support
9040
9053
  const injectedCssHashSet = new Set();
9054
+
9055
+ // Registry of sourceKey → cssBlock for smart slot-based replacement.
9056
+ // Prevents stale CSS chunks from accumulating across HMR cycles.
9057
+ const _cssBlockRegistry = new Map();
9058
+
9059
+ /**
9060
+ * Rebuild the single twsx style tag from the full CSS block registry.
9061
+ * Called whenever a block is added or updated.
9062
+ */
9063
+ function rebuildStyleTag() {
9064
+ let styleTag = document.getElementById("twsx-auto-style");
9065
+ if (!styleTag) {
9066
+ styleTag = document.createElement("style");
9067
+ styleTag.id = "twsx-auto-style";
9068
+ styleTag.setAttribute("data-twsx", "");
9069
+ document.head.appendChild(styleTag);
9070
+ }
9071
+ styleTag.textContent = [..._cssBlockRegistry.values()].join("\n");
9072
+ }
9041
9073
  function autoInjectCss(cssString) {
9074
+ let sourceKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
9042
9075
  const marker = performanceMonitor.start("css:inject");
9043
9076
  try {
9044
9077
  // SSR mode: collect CSS strings instead of DOM injection
9045
9078
  if (_ssrCollecting) ;
9046
9079
  if (IS_BROWSER) {
9080
+ if (sourceKey) {
9081
+ // Slot-based update: each unique twsx(obj) call owns its own CSS block.
9082
+ // When styles change (new content, same logical call site via cacheKey)
9083
+ // the old slot is replaced and the style tag is fully rebuilt, so no
9084
+ // stale rules from previous HMR cycles can pile up.
9085
+ const existing = _cssBlockRegistry.get(sourceKey);
9086
+ if (existing === cssString) {
9087
+ // Identical content – nothing to do.
9088
+ performanceMonitor.end(marker);
9089
+ return;
9090
+ }
9091
+ _cssBlockRegistry.set(sourceKey, cssString);
9092
+ rebuildStyleTag();
9093
+ if (_cssBlockRegistry.size % 10 === 0) {
9094
+ logger.debug(`CSS registry stats: ${_cssBlockRegistry.size} blocks registered`);
9095
+ }
9096
+ performanceMonitor.end(marker);
9097
+ return;
9098
+ }
9099
+
9100
+ // Fallback path (e.g. direct autoInjectCss calls without a sourceKey):
9101
+ // keep the original hash-based dedup + append behaviour.
9047
9102
  const cssHash = getCssHash(cssString);
9048
9103
  if (injectedCssHashSet.has(cssHash)) {
9049
9104
  performanceMonitor.end(marker);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * tailwind-to-style v3.2.1
2
+ * tailwind-to-style v3.2.2
3
3
  * Runtime Tailwind CSS to inline styles converter
4
4
  *
5
5
  * @author Bigetion
@@ -8996,27 +8996,40 @@ function fastObjectHash(obj) {
8996
8996
  */
8997
8997
  function twsx(obj) {
8998
8998
  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
8999
- // Create fast hash key from input (100x faster than JSON.stringify)
8999
+ // Create fast hash key from input content (100x faster than JSON.stringify)
9000
9000
  const cacheKey = fastObjectHash(obj, options);
9001
+ const {
9002
+ inject = true
9003
+ } = options;
9004
+
9005
+ // Derive a STABLE registry key from the object's top-level selector KEYS only.
9006
+ // This key is independent of the CSS class values, so when styles are edited
9007
+ // during HMR (same selectors, different Tailwind classes) the old slot is
9008
+ // replaced rather than a new slot being created — preventing CSS accumulation.
9009
+ const registryKey = obj && typeof obj === "object" && !Array.isArray(obj) ? Object.keys(obj).sort().join("|") : cacheKey; // fallback for edge cases
9001
9010
 
9002
9011
  // Check cache first
9003
9012
  if (_twsxInputCache.has(cacheKey)) {
9004
9013
  const cached = _twsxInputCache.get(cacheKey);
9005
9014
 
9006
- // Handle injection for cached result
9007
- const {
9008
- inject = true
9009
- } = options;
9015
+ // Re-inject with registryKey so the slot stays registered (no-op when unchanged).
9010
9016
  if (inject && (IS_BROWSER || _ssrCollecting)) {
9011
- autoInjectCss(cached);
9017
+ autoInjectCss(cached, registryKey);
9012
9018
  }
9013
9019
  return cached;
9014
9020
  }
9015
9021
 
9016
- // Cache miss: call original twsxNoCache and cache result
9017
- const result = twsxNoCache(obj, options);
9022
+ // Cache miss: generate CSS without internal auto-injection so that twsx owns
9023
+ // the injection and can pass the selector-based registryKey for slot replacement.
9024
+ const result = twsxNoCache(obj, {
9025
+ ...options,
9026
+ inject: false
9027
+ });
9018
9028
  _twsxInputCache.set(cacheKey, result);
9019
9029
  evictMap(_twsxInputCache);
9030
+ if (inject && (IS_BROWSER || _ssrCollecting)) {
9031
+ autoInjectCss(result, registryKey);
9032
+ }
9020
9033
  return result;
9021
9034
  }
9022
9035
 
@@ -9036,12 +9049,54 @@ function getCssHash(str) {
9036
9049
 
9037
9050
  // Enhanced auto-inject CSS with performance monitoring & SSR support
9038
9051
  const injectedCssHashSet = new Set();
9052
+
9053
+ // Registry of sourceKey → cssBlock for smart slot-based replacement.
9054
+ // Prevents stale CSS chunks from accumulating across HMR cycles.
9055
+ const _cssBlockRegistry = new Map();
9056
+
9057
+ /**
9058
+ * Rebuild the single twsx style tag from the full CSS block registry.
9059
+ * Called whenever a block is added or updated.
9060
+ */
9061
+ function rebuildStyleTag() {
9062
+ let styleTag = document.getElementById("twsx-auto-style");
9063
+ if (!styleTag) {
9064
+ styleTag = document.createElement("style");
9065
+ styleTag.id = "twsx-auto-style";
9066
+ styleTag.setAttribute("data-twsx", "");
9067
+ document.head.appendChild(styleTag);
9068
+ }
9069
+ styleTag.textContent = [..._cssBlockRegistry.values()].join("\n");
9070
+ }
9039
9071
  function autoInjectCss(cssString) {
9072
+ let sourceKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
9040
9073
  const marker = performanceMonitor.start("css:inject");
9041
9074
  try {
9042
9075
  // SSR mode: collect CSS strings instead of DOM injection
9043
9076
  if (_ssrCollecting) ;
9044
9077
  if (IS_BROWSER) {
9078
+ if (sourceKey) {
9079
+ // Slot-based update: each unique twsx(obj) call owns its own CSS block.
9080
+ // When styles change (new content, same logical call site via cacheKey)
9081
+ // the old slot is replaced and the style tag is fully rebuilt, so no
9082
+ // stale rules from previous HMR cycles can pile up.
9083
+ const existing = _cssBlockRegistry.get(sourceKey);
9084
+ if (existing === cssString) {
9085
+ // Identical content – nothing to do.
9086
+ performanceMonitor.end(marker);
9087
+ return;
9088
+ }
9089
+ _cssBlockRegistry.set(sourceKey, cssString);
9090
+ rebuildStyleTag();
9091
+ if (_cssBlockRegistry.size % 10 === 0) {
9092
+ logger.debug(`CSS registry stats: ${_cssBlockRegistry.size} blocks registered`);
9093
+ }
9094
+ performanceMonitor.end(marker);
9095
+ return;
9096
+ }
9097
+
9098
+ // Fallback path (e.g. direct autoInjectCss calls without a sourceKey):
9099
+ // keep the original hash-based dedup + append behaviour.
9045
9100
  const cssHash = getCssHash(cssString);
9046
9101
  if (injectedCssHashSet.has(cssHash)) {
9047
9102
  performanceMonitor.end(marker);