Skip to content

Commit 536bf53

Browse files
authored
Merge pull request #55 from kernoeb/main
feat: handle nonce/csp
2 parents 042258b + 468a51d commit 536bf53

File tree

5 files changed

+23
-5
lines changed

5 files changed

+23
-5
lines changed

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ See the [Documentation](https://erangrin.github.io/vue-web-component-wrapper) fo
4949
- **Async Initialization**: Option to delay the initialization until its Promise resolves.
5050
- **Loader Support**: Support for loader spinner elements until the application is fully initialized.
5151
- **Hide slot content until the component is fully mounted**: Option to hide the content of named slots until the web-component is fully mounted.
52+
5253
## CSS Frameworks Examples
5354

5455
- **Tailwind CSS**: [Demo](https://stackblitz.com/edit/vue-web-component-wrapper?file=README.md&startScript=tailwind-demo)
@@ -167,6 +168,7 @@ createWebComponent({
167168
- **asyncInitialization**: Accepts a function that returns a Promise.
168169
- **loaderAttribute**: Defines the attribute used to mark loader spinner (default is `data-web-component-loader`).
169170
- **hideSlotContentUntilMounted**: Hide the content of named slots until the component is fully mounted.
171+
- **nonce**: Content Security Policy (CSP) nonce for your web component.
170172

171173
### asyncInitialization
172174

@@ -175,7 +177,7 @@ The `asyncInitialization` option accepts a function that returns a Promise. The
175177
#### Example Usage
176178

177179
```javascript
178-
const asyncPromise = () => {
180+
const asyncPromise = () => {
179181
return new Promise((resolve) => {
180182
setTimeout(() => {
181183
resolve()
@@ -192,7 +194,7 @@ createWebComponent({
192194
h,
193195
createApp,
194196
getCurrentInstance,
195-
asyncInitialization: asyncPromise, // default is Promise.resolve()
197+
asyncInitialization: asyncPromise, // default is Promise.resolve()
196198
loaderAttribute: 'data-web-component-loader',
197199
hideSlotContentUntilMounted: true, // default is false
198200
});
@@ -270,6 +272,10 @@ createWebComponent({
270272

271273
The `cssFrameworkStyles` option imports the CSS of your CSS framework or any other global CSS styles your application needs. By setting `replaceRootWithHostInCssFramework` to `true`, any `:root` selectors in your styles will be replaced with `:host`, ensuring correct scoping within the web component.
272274

275+
### nonce
276+
277+
The `nonce` option is used to set a Content Security Policy (CSP) nonce for your web component. This is useful when your application uses inline scripts or styles, as it allows you to specify a unique nonce value that can be used to whitelist the inline content.
278+
273279
### 4. Build Your Application
274280

275281
Tested bundlers to build the web-component application.

package/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const createWebComponent = ({
1717
asyncInitialization = () => Promise.resolve(),
1818
loaderAttribute = 'data-web-component-loader',
1919
hideSlotContentUntilMounted = false,
20+
nonce
2021
}) => {
2122
if (!rootComponent) {
2223
console.warn('No root component provided. Please provide a root component to create a web component.')
@@ -59,7 +60,8 @@ export const createWebComponent = ({
5960
replaceRootWithHostInCssFramework,
6061
asyncInitialization,
6162
loaderAttribute,
62-
hideSlotContentUntilMounted
63+
hideSlotContentUntilMounted,
64+
nonce
6365
}, ).then((customElementConfig) => {
6466
customElements.define(
6567
elementName,

package/src/api-custom-element.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ export interface DefineCustomElementConfig {
4646
* @default true
4747
*/
4848
shadowRoot?: boolean
49+
/**
50+
* Nonce to use for CSP
51+
*/
52+
nonce?: string
4953
}
5054

5155
// defineCustomElement provides the same type inference as defineComponent
@@ -186,6 +190,7 @@ export const defineSSRCustomElement = ((
186190
config?: DefineCustomElementConfig
187191
) => {
188192

193+
189194
// @ts-expect-error
190195
return defineCustomElement(options, hydrate)
191196
}) as typeof defineCustomElement
@@ -525,6 +530,7 @@ export class VueElement extends BaseClass {
525530
styles.forEach(css => {
526531
const s = document.createElement('style')
527532
s.textContent = css
533+
if (this._config.nonce) s.setAttribute('nonce', this._config.nonce);
528534
this._root!.prepend(s)
529535
if (__DEV__) {
530536
;(this._styles || (this._styles = [])).push(s)

package/src/web-component-util.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ export const defineCustomElement = ({
4040
replaceRootWithHostInCssFramework,
4141
asyncInitialization,
4242
loaderAttribute,
43-
hideSlotContentUntilMounted
43+
hideSlotContentUntilMounted,
44+
nonce
4445
}) =>
4546
{
4647
const customElementDefiner = disableShadowDOM ? VueDefineCustomElementPatch : VueDefineCustomElement
@@ -50,6 +51,7 @@ export const defineCustomElement = ({
5051
: cssFrameworkStyles;
5152
const customElementConfig = customElementDefiner({
5253
styles: [modifiedCssFrameworkStyles],
54+
nonce,
5355
props: {
5456
...rootComponent.props,
5557
modelValue: { type: [String, Number, Boolean, Array, Object] } // v-model support
@@ -82,6 +84,7 @@ export const defineCustomElement = ({
8284
if (styles?.length) {
8385
this.__style = document.createElement('style')
8486
this.__style.innerText = styles.join().replace(/\n/g, '')
87+
if (nonce) this.__style.setAttribute('nonce', nonce);
8588
nearestElement(this.$el).append(this.__style)
8689
}
8790
}
@@ -168,7 +171,7 @@ export const defineCustomElement = ({
168171
}
169172
);
170173
},
171-
}, disableShadowDOM && { shadowRoot: false })
174+
}, { shadowRoot: !disableShadowDOM, nonce })
172175

173176
return asyncInitialization().then(() => {
174177
return customElementConfig;

package/types.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export interface CreateWebComponentOptions {
1919
asyncInitialization?: () => Promise<any>
2020
loaderAttribute?: string
2121
hideSlotContentUntilMounted?: boolean
22+
nonce?: string
2223
}
2324

2425
export function createWebComponent(options: CreateWebComponentOptions): void

0 commit comments

Comments
 (0)