Skip to content

Commit 1878204

Browse files
committed
feat: v0.4.0 - SSR support, expanded selectors, DRY fixes
- Fixed SSR by using useRuntimeConfig() from #imports instead of event.context - Fixed navigation bug by adding missing selectors (p, li, td, th, span, etc.) - Fixed DRY violation with shared-defaults.ts as single source of truth - Added 12 new element types for better coverage - Added selector documentation - Fixed nitro plugin tests with proper mocking - All 258 tests passing Authored by: Aaron Lippold<lippold@gmail.com>
1 parent ba12ed5 commit 1878204

File tree

14 files changed

+492
-186
lines changed

14 files changed

+492
-186
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,9 @@ session.md
7272
# Project-specific
7373
*.old.ts
7474
*.original.ts
75+
76+
# Archive directory for recovery files
77+
archive/
78+
79+
# Package tarball
80+
*.tgz

docs/selectors.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# Default Selectors
2+
3+
This document describes which HTML elements are processed by nuxt-smartscript by default and which are excluded.
4+
5+
## Elements Processed by Default
6+
7+
### Container Elements
8+
- `main` - Main content areas
9+
- `article` - Article containers
10+
- `section` - Content sections
11+
- `header` - Header areas
12+
- `footer` - Footer areas
13+
- `.content` - Elements with "content" class
14+
- `[role="main"]` - Elements with main ARIA role
15+
- `.prose` - Prose content (common in documentation)
16+
- `.blog-post` - Blog post containers
17+
- `.blog-content` - Blog content areas
18+
19+
### Heading Elements
20+
All heading levels are processed:
21+
- `h1`, `h2`, `h3`, `h4`, `h5`, `h6`
22+
23+
### Text Content Elements
24+
- `p` - Paragraphs
25+
- `li` - List items
26+
- `td` - Table cells
27+
- `th` - Table headers
28+
- `blockquote` - Block quotes
29+
- `caption` - Table/figure captions
30+
- `dt` - Definition terms
31+
- `dd` - Definition descriptions
32+
- `figcaption` - Figure captions
33+
34+
### Inline Elements
35+
- `span` - Generic inline containers
36+
- `a` - Links
37+
- `strong` - Strong emphasis
38+
- `em` - Emphasis
39+
- `b` - Bold text
40+
- `i` - Italic text
41+
- `small` - Small print (often legal text)
42+
- `cite` - Citations
43+
- `abbr` - Abbreviations
44+
45+
### Interactive Elements
46+
- `button` - Button text
47+
- `label` - Form labels
48+
- `legend` - Fieldset legends
49+
- `summary` - Details/summary elements
50+
51+
### Other Semantic Elements
52+
- `address` - Contact information
53+
54+
## Elements Excluded by Default
55+
56+
### Code Elements
57+
- `pre` - Preformatted code blocks
58+
- `code` - Inline code
59+
- `script` - Script tags
60+
- `style` - Style tags
61+
62+
### Custom Exclusions
63+
- `.no-superscript` - Elements with this class
64+
- `[data-no-superscript]` - Elements with this data attribute
65+
66+
### Already Processed
67+
These classes indicate content already transformed:
68+
- `sup.ss-sup` - Already superscripted elements
69+
- `sub.ss-sub` - Already subscripted elements
70+
- `.ss-tm` - Trademark symbols
71+
- `.ss-reg` - Registered symbols
72+
- `.ss-ordinal` - Ordinal numbers
73+
- `.ss-chemical` - Chemical formulas
74+
- `.ss-math` - Mathematical notation
75+
76+
## Customizing Selectors
77+
78+
You can customize which elements are processed in your `nuxt.config.ts`:
79+
80+
```typescript
81+
export default defineNuxtConfig({
82+
smartscript: {
83+
selectors: {
84+
// Add more selectors
85+
include: [
86+
...defaultSelectors,
87+
'.my-custom-class',
88+
'custom-element'
89+
],
90+
// Add more exclusions
91+
exclude: [
92+
...defaultExclusions,
93+
'.skip-this',
94+
'[data-raw-text]'
95+
]
96+
}
97+
}
98+
})
99+
```
100+
101+
## Best Practices
102+
103+
1. **Performance**: More specific selectors (classes, IDs) are faster than broad element selectors
104+
2. **Avoid Over-Processing**: Don't include generic containers like `div` unless necessary
105+
3. **Test Thoroughly**: When adding new selectors, test that they don't interfere with your layout
106+
4. **Use Exclusions**: Use the exclusion classes/attributes to skip specific elements
107+
108+
## Common Use Cases
109+
110+
### Documentation Sites
111+
The default selectors work well for documentation with the `.prose` class and semantic HTML.
112+
113+
### Blogs
114+
Blog-specific classes like `.blog-post` and `.blog-content` are included by default.
115+
116+
### Applications
117+
Interactive elements like buttons and form labels are processed to handle branded UI text.
118+
119+
### Legal Text
120+
The `small` element is included as it often contains copyright notices and legal text with ® and ™ symbols.

src/module.ts

Lines changed: 3 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { fileURLToPath } from 'node:url'
22
import { addPlugin, addServerPlugin, createResolver, defineNuxtModule } from '@nuxt/kit'
3+
import { SHARED_DEFAULTS } from './runtime/shared-defaults'
34

45
// Module options TypeScript interface definition
56
export interface ModuleOptions {
@@ -117,72 +118,8 @@ export default defineNuxtModule<ModuleOptions>({
117118
},
118119
},
119120

120-
// Default configuration options
121-
defaults: {
122-
enabled: true,
123-
positioning: {
124-
trademark: {
125-
body: '-0.5em',
126-
headers: '-0.7em',
127-
fontSize: '0.8em',
128-
},
129-
registered: {
130-
body: '-0.25em',
131-
headers: '-0.45em',
132-
fontSize: '0.8em',
133-
},
134-
ordinals: {
135-
fontSize: '0.75em',
136-
},
137-
chemicals: {
138-
fontSize: '0.75em',
139-
},
140-
},
141-
selectors: {
142-
include: [
143-
'main',
144-
'article',
145-
'.content',
146-
'[role="main"]',
147-
'.prose',
148-
'.blog-post',
149-
'.blog-content',
150-
'section',
151-
'h1',
152-
'h2',
153-
'h3',
154-
'h4',
155-
'h5',
156-
'h6',
157-
'header',
158-
],
159-
exclude: [
160-
'pre',
161-
'code',
162-
'script',
163-
'style',
164-
'.no-superscript',
165-
'[data-no-superscript]',
166-
],
167-
},
168-
performance: {
169-
debounce: 100,
170-
batchSize: 50,
171-
delay: 1500,
172-
},
173-
transformations: {
174-
trademark: true,
175-
registered: true,
176-
copyright: true,
177-
ordinals: true,
178-
chemicals: true,
179-
mathSuper: true,
180-
mathSub: true,
181-
},
182-
ssr: true,
183-
client: true,
184-
debug: false,
185-
},
121+
// Default configuration options - SINGLE SOURCE OF TRUTH
122+
defaults: SHARED_DEFAULTS,
186123

187124
setup(options, nuxt) {
188125
if (!options.enabled) {

src/runtime/nitro/plugin.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import type { NitroAppPlugin } from 'nitropack'
77
import type { SuperscriptConfig } from '../smartscript/types'
8+
import { useRuntimeConfig } from '#imports'
89
import { JSDOM } from 'jsdom'
910
import { mergeConfig } from '../smartscript/config'
1011
import { processElement } from '../smartscript/engine'
@@ -14,9 +15,10 @@ import { createCombinedPattern, createPatterns } from '../smartscript/patterns'
1415
export default <NitroAppPlugin> function (nitro) {
1516
// Hook into the render:html event for both SSR and SSG
1617
// @ts-expect-error - render:html hook exists but not typed in nitropack
17-
nitro.hooks.hook('render:html', (html: Record<string, string[]>, { event }: { event: { context: { $config?: { public?: { smartscript?: SuperscriptConfig } } } } }) => {
18-
// Get configuration from runtime config
19-
const config = event.context.$config?.public?.smartscript
18+
nitro.hooks.hook('render:html', (html: Record<string, string[]>, { event: _event }: { event: any }) => {
19+
// Get runtime config using useRuntimeConfig
20+
const runtimeConfig = useRuntimeConfig()
21+
const config = runtimeConfig.public?.smartscript as SuperscriptConfig
2022

2123
// Skip if no config or SSR processing is disabled
2224
if (!config || config.ssr === false) {

src/runtime/plugin.ts

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
import type { SuperscriptConfig } from './smartscript'
7-
import { defineNuxtPlugin } from '#imports'
7+
import { defineNuxtPlugin, nextTick } from '#imports'
88
import {
99
createCombinedPattern,
1010
createContentObserver,
@@ -153,18 +153,52 @@ export default defineNuxtPlugin((nuxtApp) => {
153153
}
154154
})
155155

156-
// Handle navigation
157-
nuxtApp.hook('page:finish', () => {
156+
// Handle navigation - use multiple hooks to ensure we catch all navigation scenarios
157+
nuxtApp.hook('page:finish', async () => {
158+
logger.debug('page:finish hook - navigation detected')
159+
158160
initializeForNavigation()
159-
setTimeout(process, 100)
161+
162+
// Recreate patterns in case something changed
163+
patterns = createPatterns(config)
164+
combinedPattern = createCombinedPattern(patterns, config)
165+
166+
// Wait for Vue to finish its virtual DOM reconciliation
167+
await nextTick()
168+
169+
// Add a small delay to ensure all Vue updates are complete
170+
setTimeout(() => {
171+
logger.debug('Processing after navigation and Vue reconciliation')
172+
process()
173+
}, 50)
174+
})
175+
176+
// Also hook into page:transition:finish for when transitions are used
177+
nuxtApp.hook('page:transition:finish', () => {
178+
logger.debug('page:transition:finish hook - processing after transition')
179+
// Process immediately since transition is complete
180+
process()
160181
})
161182

162183
// Also hook into router if available
163184
if (nuxtApp.$router && typeof nuxtApp.$router === 'object' && 'afterEach' in nuxtApp.$router) {
164185
const router = nuxtApp.$router as { afterEach: (cb: () => void) => void }
165-
router.afterEach(() => {
186+
router.afterEach(async () => {
187+
logger.debug('Router afterEach - navigation detected')
188+
166189
initializeForNavigation()
167-
setTimeout(process, 150)
190+
191+
// Recreate patterns here too
192+
patterns = createPatterns(config)
193+
combinedPattern = createCombinedPattern(patterns, config)
194+
195+
// Wait for Vue's virtual DOM to finish updating
196+
await nextTick()
197+
198+
setTimeout(() => {
199+
logger.debug('Processing after router navigation and Vue reconciliation')
200+
process()
201+
}, 100)
168202
})
169203
}
170204

0 commit comments

Comments
 (0)