diff --git a/examples/nuxt3/app.vue b/examples/nuxt3/app.vue index 4380aa37..af399614 100644 --- a/examples/nuxt3/app.vue +++ b/examples/nuxt3/app.vue @@ -2,6 +2,8 @@

Global directive

Expect text with color
Expect only plaintext
+
+

Directive local to a component

Expect text with color
@@ -12,5 +14,7 @@ import { buildVueDompurifyHTMLDirective } from 'vue-dompurify-html'; const rawHtml = ref('Hello!'); +const ssrHtml = useState(() => 'Hello SSR!'); + const vCleanHtml = buildVueDompurifyHTMLDirective(); diff --git a/examples/nuxt3/nuxt.config.ts b/examples/nuxt3/nuxt.config.ts index 231ffd7d..d21d85e8 100644 --- a/examples/nuxt3/nuxt.config.ts +++ b/examples/nuxt3/nuxt.config.ts @@ -1,3 +1,4 @@ export default defineNuxtConfig({ devtools: { enabled: true }, + compatibilityDate: '2025-01-10', }); diff --git a/packages/vue-dompurify-html/dist/vue-dompurify-html.mjs b/packages/vue-dompurify-html/dist/vue-dompurify-html.mjs index 06640703..99b750a8 100644 --- a/packages/vue-dompurify-html/dist/vue-dompurify-html.mjs +++ b/packages/vue-dompurify-html/dist/vue-dompurify-html.mjs @@ -1,50 +1,50 @@ -import l from "dompurify"; -function m(t, e) { - const o = t.hooks ?? {}; - let n; - for (n in o) { - const u = o[n]; - u !== void 0 && e.addHook(n, u); +import c from "isomorphic-dompurify"; +function f(n, r) { + const t = n.hooks ?? {}; + let o; + for (o in t) { + const e = t[o]; + e !== void 0 && r.addHook(o, e); } } -function c() { - return l(); +function d() { + return c(); } -function p(t = {}, e = c) { - const o = e(); - m(t, o); - const n = function(u, i) { - const r = i.value; - if (i.oldValue === r) - return; - const a = `${r}`, s = i.arg, d = t.namedConfigurations, f = t.default ?? {}; - if (d && s !== void 0) { - u.innerHTML = o.sanitize( - a, - d[s] ?? f - ); - return; - } - u.innerHTML = o.sanitize( - a, - f - ); +function a(n, r = {}, t) { + const o = n.value; + if (n.oldValue === o) + return; + const e = `${o}`, i = n.arg, u = r.namedConfigurations, s = r.default ?? {}; + return u && i !== void 0 ? t.sanitize( + e, + u[i] ?? s + ) : t.sanitize(e, s); +} +function l(n = {}, r = d) { + const t = r(); + f(n, t); + const o = function(e, i) { + const u = a(i, n, t); + u !== void 0 && (e.innerHTML = u); }; return { - mounted: n, - updated: n + mounted: o, + updated: o, + getSSRProps: (e) => ({ + innerHTML: a(e, n, c) + }) }; } -const k = { - install(t, e = {}, o = c) { - t.directive( +const p = { + install(n, r = {}, t = d) { + n.directive( "dompurify-html", - p(e, o) + l(r, t) ); } }; export { - p as buildVueDompurifyHTMLDirective, - k as default, - k as vueDompurifyHTMLPlugin + l as buildVueDompurifyHTMLDirective, + p as default, + p as vueDompurifyHTMLPlugin }; diff --git a/packages/vue-dompurify-html/dist/vue-dompurify-html.umd.js b/packages/vue-dompurify-html/dist/vue-dompurify-html.umd.js index d0d21fd5..deb439e2 100644 --- a/packages/vue-dompurify-html/dist/vue-dompurify-html.umd.js +++ b/packages/vue-dompurify-html/dist/vue-dompurify-html.umd.js @@ -1 +1 @@ -(function(e,i){typeof exports=="object"&&typeof module<"u"?i(exports,require("dompurify")):typeof define=="function"&&define.amd?define(["exports","dompurify"],i):(e=typeof globalThis<"u"?globalThis:e||self,i(e.VueDOMPurifyHTML={},e.DOMPurify))})(this,function(e,i){"use strict";function v(t,o){const n=t.hooks??{};let u;for(u in n){const r=n[u];r!==void 0&&o.addHook(u,r)}}function d(){return i()}function s(t={},o=d){const n=o();v(t,n);const u=function(r,f){const c=f.value;if(f.oldValue===c)return;const l=`${c}`,m=f.arg,p=t.namedConfigurations,y=t.default??{};if(p&&m!==void 0){r.innerHTML=n.sanitize(l,p[m]??y);return}r.innerHTML=n.sanitize(l,y)};return{mounted:u,updated:u}}const a={install(t,o={},n=d){t.directive("dompurify-html",s(o,n))}};e.buildVueDompurifyHTMLDirective=s,e.default=a,e.vueDompurifyHTMLPlugin=a,Object.defineProperties(e,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}); +(function(t,r){typeof exports=="object"&&typeof module<"u"?r(exports,require("isomorphic-dompurify")):typeof define=="function"&&define.amd?define(["exports","isomorphic-dompurify"],r):(t=typeof globalThis<"u"?globalThis:t||self,r(t.VueDOMPurifyHTML={},t.dompurify))})(this,function(t,r){"use strict";function m(e,u){const n=e.hooks??{};let o;for(o in n){const i=n[o];i!==void 0&&u.addHook(o,i)}}function d(){return r()}function c(e,u={},n){const o=e.value;if(e.oldValue===o)return;const i=`${o}`,f=e.arg,s=u.namedConfigurations,p=u.default??{};return s&&f!==void 0?n.sanitize(i,s[f]??p):n.sanitize(i,p)}function a(e={},u=d){const n=u();m(e,n);const o=function(i,f){const s=c(f,e,n);s!==void 0&&(i.innerHTML=s)};return{mounted:o,updated:o,getSSRProps:i=>({innerHTML:c(i,e,r)})}}const l={install(e,u={},n=d){e.directive("dompurify-html",a(u,n))}};t.buildVueDompurifyHTMLDirective=a,t.default=l,t.vueDompurifyHTMLPlugin=l,Object.defineProperties(t,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}); diff --git a/packages/vue-dompurify-html/package.json b/packages/vue-dompurify-html/package.json index 7bf02297..37eedc30 100644 --- a/packages/vue-dompurify-html/package.json +++ b/packages/vue-dompurify-html/package.json @@ -45,7 +45,7 @@ "test-mutation": "stryker run" }, "dependencies": { - "dompurify": "^3.2.1" + "isomorphic-dompurify": "^2.20.0" }, "peerDependencies": { "vue": "^3.0.0" diff --git a/packages/vue-dompurify-html/src/dompurify-html.ts b/packages/vue-dompurify-html/src/dompurify-html.ts index 4d878d6c..3e1dd858 100644 --- a/packages/vue-dompurify-html/src/dompurify-html.ts +++ b/packages/vue-dompurify-html/src/dompurify-html.ts @@ -1,11 +1,11 @@ import type { DirectiveHook, ObjectDirective, DirectiveBinding } from 'vue'; -import dompurify from 'dompurify'; +import dompurify from 'isomorphic-dompurify'; import type { DOMPurify, UponSanitizeElementHookEvent, UponSanitizeAttributeHookEvent, HookName, -} from 'dompurify'; +} from 'isomorphic-dompurify'; type MinimalDOMPurifyInstance = Pick; export type DOMPurifyInstanceBuilder = () => MinimalDOMPurifyInstance; @@ -112,9 +112,37 @@ function setUpHooks( } export function defaultDOMPurifyInstanceBuilder(): MinimalDOMPurifyInstance { + // @ts-expect-error This expression is not callable. Type 'typeof _default' has no call signatures. return dompurify(); } +function _getInnerHTML( + binding: DirectiveBinding, + config: DirectiveConfig = {}, + dompurifyInstance: MinimalDOMPurifyInstance, +): string | undefined { + const current_value = binding.value; + if (binding.oldValue === current_value) { + return; + } + + const current_value_string = `${current_value}`; + const arg = binding.arg; + const namedConfigurations = config.namedConfigurations; + const defaultConfig = config.default ?? {}; + + // Returned named config + if (namedConfigurations && arg !== undefined) { + return dompurifyInstance.sanitize( + current_value_string, + namedConfigurations[arg] ?? defaultConfig, + ); + } + + // Returned default config + return dompurifyInstance.sanitize(current_value_string, defaultConfig); +} + export function buildDirective( config: DirectiveConfig = {}, buildDOMPurifyInstance: DOMPurifyInstanceBuilder = defaultDOMPurifyInstanceBuilder, @@ -127,29 +155,22 @@ export function buildDirective( el: HTMLElement, binding: DirectiveBinding, ): void { - const current_value = binding.value; - if (binding.oldValue === current_value) { - return; - } - const current_value_string = `${current_value}`; - const arg = binding.arg; - const namedConfigurations = config.namedConfigurations; - const defaultConfig = config.default ?? {}; - if (namedConfigurations && arg !== undefined) { - el.innerHTML = dompurifyInstance.sanitize( - current_value_string, - namedConfigurations[arg] ?? defaultConfig, - ); - return; + const innerHTML = _getInnerHTML(binding, config, dompurifyInstance); + + if (innerHTML !== undefined) { + el.innerHTML = innerHTML; } - el.innerHTML = dompurifyInstance.sanitize( - current_value_string, - defaultConfig, - ); }; return { mounted: updateComponent, updated: updateComponent, + getSSRProps: (binding) => { + const innerHTML = _getInnerHTML(binding, config, dompurify); + + return { + innerHTML, + }; + }, }; } diff --git a/packages/vue-dompurify-html/test/index.spec.ts b/packages/vue-dompurify-html/test/index.spec.ts index 08b9bcd9..72ea6382 100644 --- a/packages/vue-dompurify-html/test/index.spec.ts +++ b/packages/vue-dompurify-html/test/index.spec.ts @@ -3,7 +3,7 @@ import { createApp } from 'vue'; import type { DOMPurifyInstanceBuilder } from '../src'; import VueDOMPurifyHTML from '../src'; import { buildVueDompurifyHTMLDirective } from '../src'; -import type { DOMPurify } from 'dompurify'; +import type { DOMPurify } from 'isomorphic-dompurify'; describe('VueDOMPurifyHTML Plugin Install', (): void => { it('can be installed', (): void => { diff --git a/packages/vue-dompurify-html/test/vue-dompurify-html.spec.ts b/packages/vue-dompurify-html/test/vue-dompurify-html.spec.ts index 46f0031e..dc395595 100644 --- a/packages/vue-dompurify-html/test/vue-dompurify-html.spec.ts +++ b/packages/vue-dompurify-html/test/vue-dompurify-html.spec.ts @@ -3,11 +3,13 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { mount } from '@vue/test-utils'; import type { DOMPurifyInstanceBuilder } from '../src/dompurify-html'; import { buildDirective } from '../src/dompurify-html'; -import type { DOMPurify } from 'dompurify'; -import { sanitize, addHook } from 'dompurify'; +import type { DOMPurify } from 'isomorphic-dompurify'; +import { sanitize, addHook } from 'isomorphic-dompurify'; -vi.mock('dompurify', async () => { - const actual: { default: DOMPurify } = await vi.importActual('dompurify'); +vi.mock('isomorphic-dompurify', async () => { + const actual: { default: DOMPurify } = await vi.importActual( + 'isomorphic-dompurify', + ); const spy = { ...actual, sanitize: vi.fn(actual.default.sanitize), diff --git a/packages/vue-dompurify-html/types/dompurify-html.d.ts b/packages/vue-dompurify-html/types/dompurify-html.d.ts index 06817724..2cbe1f82 100755 --- a/packages/vue-dompurify-html/types/dompurify-html.d.ts +++ b/packages/vue-dompurify-html/types/dompurify-html.d.ts @@ -1,5 +1,5 @@ import type { ObjectDirective } from 'vue'; -import type { DOMPurify, UponSanitizeElementHookEvent, UponSanitizeAttributeHookEvent } from 'dompurify'; +import type { DOMPurify, UponSanitizeElementHookEvent, UponSanitizeAttributeHookEvent } from 'isomorphic-dompurify'; type MinimalDOMPurifyInstance = Pick; export type DOMPurifyInstanceBuilder = () => MinimalDOMPurifyInstance; export interface MinimalDOMPurifyConfig { diff --git a/packages/vue-dompurify-html/vite.config.mts b/packages/vue-dompurify-html/vite.config.mts index 8acfaba3..e4a0d99d 100644 --- a/packages/vue-dompurify-html/vite.config.mts +++ b/packages/vue-dompurify-html/vite.config.mts @@ -7,7 +7,7 @@ export default defineConfig({ name: 'VueDOMPurifyHTML', }, rollupOptions: { - external: ['dompurify'], + external: ['isomorphic-dompurify'], output: { globals: { dompurify: 'DOMPurify', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9281eafd..0909ad11 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -75,9 +75,9 @@ importers: packages/vue-dompurify-html: dependencies: - dompurify: - specifier: ^3.2.1 - version: 3.2.1 + isomorphic-dompurify: + specifier: ^2.20.0 + version: 2.20.0 devDependencies: '@stryker-mutator/core': specifier: 8.7.1 @@ -1980,8 +1980,8 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} - dompurify@3.2.1: - resolution: {integrity: sha512-NBHEsc0/kzRYQd+AY6HR6B/IgsqzBABrqJbpCDQII/OK6h7B7LXzweZTDsqSW2LkTRpoxf18YUP+YjGySk6B3w==} + dompurify@3.2.3: + resolution: {integrity: sha512-U1U5Hzc2MO0oW3DF+G9qYN0aT7atAou4AgI0XjWz061nyBPbdxkfdhfy5uMgGn6+oLFCfn44ZGbdDqCzVmlOWA==} domutils@3.1.0: resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} @@ -2607,6 +2607,10 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isomorphic-dompurify@2.20.0: + resolution: {integrity: sha512-zOq12fJPtNE+4dPd2S0xpWXl8NZj0C6k2xikT1yl/Lv/5p3QLafZqlVFy4xTGU9qHSkyEENcIbp2c0oahCNRYg==} + engines: {node: '>=18'} + istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -4426,7 +4430,7 @@ snapshots: '@babel/traverse': 7.25.9 '@babel/types': 7.26.0 convert-source-map: 2.0.0 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.3.7 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -4721,7 +4725,7 @@ snapshots: '@babel/parser': 7.26.2 '@babel/template': 7.25.9 '@babel/types': 7.26.3 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.3.7 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -5842,7 +5846,7 @@ snapshots: dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.3.7 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 @@ -6094,6 +6098,12 @@ snapshots: transitivePeerDependencies: - supports-color + agent-base@7.1.0: + dependencies: + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + agent-base@7.1.0(supports-color@9.4.0): dependencies: debug: 4.3.7(supports-color@9.4.0) @@ -6525,6 +6535,10 @@ snapshots: dependencies: ms: 2.0.0 + debug@4.3.7: + dependencies: + ms: 2.1.3 + debug@4.3.7(supports-color@9.4.0): dependencies: ms: 2.1.3 @@ -6597,7 +6611,7 @@ snapshots: dependencies: domelementtype: 2.3.0 - dompurify@3.2.1: + dompurify@3.2.3: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -7118,8 +7132,8 @@ snapshots: http-proxy-agent@7.0.2: dependencies: - agent-base: 7.1.0(supports-color@9.4.0) - debug: 4.3.7(supports-color@9.4.0) + agent-base: 7.1.0 + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -7142,7 +7156,7 @@ snapshots: https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.3 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -7289,6 +7303,16 @@ snapshots: isexe@2.0.0: {} + isomorphic-dompurify@2.20.0: + dependencies: + dompurify: 3.2.3 + jsdom: 26.0.0 + transitivePeerDependencies: + - bufferutil + - canvas + - supports-color + - utf-8-validate + istanbul-lib-coverage@3.2.2: {} istanbul-lib-report@3.0.1: @@ -7300,7 +7324,7 @@ snapshots: istanbul-lib-source-maps@5.0.6: dependencies: '@jridgewell/trace-mapping': 0.3.25 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.3.7 istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color @@ -8871,7 +8895,7 @@ snapshots: vite-node@2.1.8(@types/node@22.7.4)(terser@5.26.0): dependencies: cac: 6.7.14 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.3.7 es-module-lexer: 1.5.4 pathe: 1.1.2 vite: 5.4.11(@types/node@22.7.4)(terser@5.26.0) @@ -8974,7 +8998,7 @@ snapshots: '@vitest/spy': 2.1.8 '@vitest/utils': 2.1.8 chai: 5.1.2 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.3.7 expect-type: 1.1.0 magic-string: 0.30.13 pathe: 1.1.2