Skip to content

Commit 6bc319c

Browse files
authored
refactor(proxy): use ohash diff to determine changes instead (#985)
1 parent 9cc0bd5 commit 6bc319c

File tree

4 files changed

+49
-51
lines changed

4 files changed

+49
-51
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
"defu": "^6.1.4",
7979
"h3": "^1.15.1",
8080
"klona": "^2.0.6",
81+
"ohash": "^2.0.11",
8182
"pathe": "^2.0.3",
8283
"pkg-types": "^2.1.0",
8384
"postcss": "^8.5.3",

pnpm-lock.yaml

+8-10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/internal-context/load.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ const createInternalContext = async (moduleOptions: ModuleOptions, nuxt = useNux
5151
configUpdatedHook[resolvedConfigFile] += 'cfg.content = cfg.purge;'
5252
}
5353

54-
await nuxt.callHook('tailwindcss:loadConfig', new Proxy(config, trackObjChanges(resolvedConfigFile)), resolvedConfigFile, idx, arr as any)
54+
await nuxt.callHook('tailwindcss:loadConfig', config, resolvedConfigFile, idx, arr as any)
55+
trackObjChanges(resolvedConfigFile, resolvedConfig.config, config)
5556
return { ...resolvedConfig, config }
5657
}).catch((e) => {
5758
logger.warn(`Failed to load config \`./${relative(nuxt.options.rootDir, configFile)}\` due to the error below. Skipping..\n`, e)
@@ -160,7 +161,8 @@ const createInternalContext = async (moduleOptions: ModuleOptions, nuxt = useNux
160161
configUpdatedHook[resolvedConfigFile] += 'cfg.content = cfg.purge;'
161162
}
162163

163-
await nuxt.callHook('tailwindcss:loadConfig', new Proxy(config, trackObjChanges(resolvedConfigFile)), resolvedConfigFile, 0, [])
164+
await nuxt.callHook('tailwindcss:loadConfig', config, resolvedConfigFile, 0, [])
165+
trackObjChanges(resolvedConfigFile, resolvedConfig.config, config)
164166
return { ...resolvedConfig, config }
165167
}
166168

@@ -185,12 +187,14 @@ const createInternalContext = async (moduleOptions: ModuleOptions, nuxt = useNux
185187
const moduleConfigs = await getModuleConfigs()
186188
resolvedConfigsCtx.set(moduleConfigs, true)
187189
const tailwindConfig = moduleConfigs.reduce((acc, curr) => configMerger(acc, curr?.config ?? {}), {} as Partial<TWConfig>)
190+
const clonedConfig = configMerger(undefined, tailwindConfig)
188191

189192
// Allow extending tailwindcss config by other modules
190193
configUpdatedHook['main-config'] = ''
191-
await nuxt.callHook('tailwindcss:config', new Proxy(tailwindConfig, trackObjChanges('main-config')))
194+
await nuxt.callHook('tailwindcss:config', clonedConfig)
195+
trackObjChanges('main-config', tailwindConfig, clonedConfig)
192196

193-
const resolvedConfig = resolveTWConfig(tailwindConfig)
197+
const resolvedConfig = resolveTWConfig(clonedConfig)
194198
await nuxt.callHook('tailwindcss:resolvedConfig', resolvedConfig as any, twCtx.tryUse()?.config as any ?? undefined)
195199
twCtx.set({ config: resolvedConfig })
196200

src/internal-context/proxy.ts

+32-37
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { diff } from 'ohash/utils'
12
import logger from '../logger'
23
import type { TWConfig } from '../types'
34
import { twCtx } from './context'
@@ -7,42 +8,36 @@ const JSONStringifyWithUnsupportedVals = (val: any) => JSON.stringify(val, (_, v
78
const JSONStringifyWithRegex = (obj: any) => JSON.stringify(obj, (_, v) => v instanceof RegExp ? `__REGEXP ${v.toString()}` : v)
89

910
export const createObjProxy = (configUpdatedHook: Record<string, string>, meta: ReturnType<typeof twCtx.use>['meta']) => {
10-
const trackObjChanges = (configPath: string, path: (string | symbol)[] = []): ProxyHandler<Partial<TWConfig>> => ({
11-
get: (target, key: string) => {
12-
return (typeof target[key] === 'object' && target[key] !== null)
13-
? new Proxy(target[key], trackObjChanges(configPath, path.concat(key)))
14-
: target[key]
15-
},
16-
17-
set(target, key, value) {
18-
const cfgKey = path.concat(key).map(k => `[${JSON.stringify(k)}]`).join('')
19-
const resultingCode = `cfg${cfgKey} = ${JSONStringifyWithRegex(value)?.replace(/"__REGEXP (.*)"/g, (_, substr) => substr.replace(/\\"/g, '"')) || `cfg${cfgKey}`};`
20-
21-
if (JSONStringifyWithUnsupportedVals(target[key as string]) === JSONStringifyWithUnsupportedVals(value) || configUpdatedHook[configPath].endsWith(resultingCode)) {
22-
return Reflect.set(target, key, value)
11+
return (configPath: string, oldConfig: Partial<TWConfig>, newConfig: Partial<TWConfig>) =>
12+
diff(oldConfig, newConfig).forEach((change) => {
13+
const path = change.key.split('.').map(k => `[${JSON.stringify(k)}]`).join('')
14+
const newValue = change.newValue?.value
15+
16+
switch (change.type) {
17+
case 'removed': configUpdatedHook[configPath] += `delete cfg${path};`
18+
break
19+
case 'added':
20+
case 'changed': {
21+
const resultingCode = `cfg${path} = ${JSONStringifyWithRegex(newValue)?.replace(/"__REGEXP (.*)"/g, (_, substr) => substr.replace(/\\"/g, '"')) || `cfg${path}`};`
22+
23+
if (JSONStringifyWithUnsupportedVals(change.oldValue?.value) === JSONStringifyWithUnsupportedVals(newValue) || configUpdatedHook[configPath].endsWith(resultingCode)) {
24+
return
25+
}
26+
27+
if (JSONStringifyWithUnsupportedVals(newValue).includes(`"${UNSUPPORTED_VAL_STR}"`) && !meta?.disableHMR) {
28+
logger.warn(
29+
`A hook has injected a non-serializable value in \`config${path}\`, so the Tailwind Config cannot be serialized. Falling back to providing the loaded configuration inlined directly to PostCSS loader..`,
30+
'Please consider using a configuration file/template instead (specifying in `configPath` of the module options) to enable additional support for IntelliSense and HMR.',
31+
)
32+
twCtx.set({ meta: { disableHMR: true } })
33+
}
34+
35+
if (JSONStringifyWithRegex(newValue).includes('__REGEXP') && !meta?.disableHMR) {
36+
logger.warn(`A hook is injecting RegExp values in your configuration (check \`config${path}\`) which may be unsafely serialized. Consider moving your safelist to a separate configuration file/template instead (specifying in \`configPath\` of the module options)`)
37+
}
38+
39+
configUpdatedHook[configPath] += resultingCode
40+
}
2341
}
24-
25-
if (JSONStringifyWithUnsupportedVals(value).includes(`"${UNSUPPORTED_VAL_STR}"`) && !meta?.disableHMR) {
26-
logger.warn(
27-
`A hook has injected a non-serializable value in \`config${cfgKey}\`, so the Tailwind Config cannot be serialized. Falling back to providing the loaded configuration inlined directly to PostCSS loader..`,
28-
'Please consider using a configuration file/template instead (specifying in `configPath` of the module options) to enable additional support for IntelliSense and HMR.',
29-
)
30-
twCtx.set({ meta: { disableHMR: true } })
31-
}
32-
33-
if (JSONStringifyWithRegex(value).includes('__REGEXP') && !meta?.disableHMR) {
34-
logger.warn(`A hook is injecting RegExp values in your configuration (check \`config${cfgKey}\`) which may be unsafely serialized. Consider moving your safelist to a separate configuration file/template instead (specifying in \`configPath\` of the module options)`)
35-
}
36-
37-
configUpdatedHook[configPath] += resultingCode
38-
return Reflect.set(target, key, value)
39-
},
40-
41-
deleteProperty(target, key) {
42-
configUpdatedHook[configPath] += `delete cfg${path.concat(key).map(k => `[${JSON.stringify(k)}]`).join('')};`
43-
return Reflect.deleteProperty(target, key)
44-
},
45-
})
46-
47-
return trackObjChanges
42+
})
4843
}

0 commit comments

Comments
 (0)