Skip to content

Commit e6e202a

Browse files
committed
refactor: Update createWebComponent function and add replaceRootWithHost option
1 parent 177ffee commit e6e202a

File tree

7 files changed

+194
-111
lines changed

7 files changed

+194
-111
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format
44

55
## [Unreleased]
66

7+
## [1.6.4] - 14.10.2024
8+
### Added
9+
- Added support for replaceRootWithHost option
10+
711
## [1.6.3] - 12.07.2024
812
### Added
913
- Fixed issue with style tag injection order SFC

README.md

+121-68
Large diffs are not rendered by default.

docs/index.md

+11-5
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44
`vue-web-component-wrapper` is a powerful Vue 3 plugin designed for transforming full-fledged Vue applications into reusable web components (custom elements). These web components can be integrated into any website, enhancing flexibility and reusability.
55

66
## Why use `vue-web-component-wrapper`?
7-
As of now, Vue 3 does not support the creation of full aplication as web components out of the box. This plugin aims to solve this problem by providing a simple and easy-to-use solution for creating web components from Vue applications. It also provides support for Vue ecosystem plugins such as Vuex, Vue Router, and Vue I18n.
7+
As of now, Vue 3 does not support the creation of full applications as web components out of the box. This plugin aims to solve this problem by providing a simple and easy-to-use solution for creating web components from Vue applications. It also provides support for Vue ecosystem plugins such as Vuex, Vue Router, and Vue I18n.
8+
89
## Demo
910
Check out these demo projects to see `vue-web-component-wrapper` in action:
10-
- **Webpack implentaion**: Check out this [Webpack Demo Project](https://stackblitz.com/edit/vue-web-component-wrapper?file=README.md&startScript=webpack-demo)
11-
- **Vite.js implentaion**: Check out this [Vite Demo Project](https://stackblitz.com/edit/vue-web-component-wrapper?file=README.md&startScript=vite-demo)
11+
- **Webpack implementation**: Check out this [Webpack Demo Project](https://stackblitz.com/edit/vue-web-component-wrapper?file=README.md&startScript=webpack-demo)
12+
- **Vite.js implementation**: Check out this [Vite Demo Project](https://stackblitz.com/edit/vue-web-component-wrapper?file=README.md&startScript=vite-demo)
1213

1314
## Key Features
14-
- **Vue Plugins Compatibility**: Integrates seamlessly with key Vue ecosystem plugins.
15+
- **Vue Plugins Compatibility**: Seamlessly integrates with key Vue ecosystem plugins.
1516
- **CSS Framework Support**: Compatible with major CSS frameworks like Tailwind CSS and Bootstrap.
1617
- **CSS Preprocessor Support**: Supports CSS preprocessors including SCSS and LESS.
1718
- **Scoped CSS**: Enables the use of scoped CSS in your components.
@@ -22,7 +23,12 @@ Check out these demo projects to see `vue-web-component-wrapper` in action:
2223
- **Event Emitting Capability**: Enables the emission and handling of custom events from web components.
2324
- **Disable Style Removal on Unmount**: Option to control the removal of styles upon component unmount, addressing issues with CSS transition.
2425
- **Disable Shadow DOM**: Option to disable the Shadow DOM, rendering content in the light DOM.
26+
- **Replace `:root` with `:host`**: New feature to replace `:root` selectors with `:host` in your CSS, ensuring proper style scoping within the Shadow DOM.
27+
28+
## New Feature Highlight: Replace `:root` with `:host`
29+
Our latest feature allows you to automatically replace `:root` selectors with `:host` in your CSS styles. This is particularly useful when working with CSS frameworks or custom styles that define variables or styles on the `:root` selector. By replacing `:root` with `:host`, these styles are correctly scoped within your web component's Shadow DOM.
2530

31+
[Learn more about the Replace `:root` with `:host` feature](./guide/replace-root-with-host.md)
2632

2733
## Tips
28-
- **Testing Production Build**: the easiest way to test your production build is to run a local server in the `dist` folder. I use [valet](https://laravel.com/docs/10.x/valet) for this, but any local server should work.
34+
- **Testing Production Build**: The easiest way to test your production build is to run a local server in the `dist` folder. You can use [valet](https://laravel.com/docs/10.x/valet) for this, but any local server should work.

docs/usage.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ createWebComponent({
5858
getCurrentInstance,
5959
disableStyleRemoval: false,
6060
disableShadowDOM: false,
61+
replaceRootWithHost: false,
6162
});
6263
```
6364
Each option in the `createWebComponent` function has a specific purpose:
@@ -70,4 +71,5 @@ Each option in the `createWebComponent` function has a specific purpose:
7071
- `createApp`: The `createApp` function from Vue.
7172
- `getCurrentInstance`: The `getCurrentInstance` function from Vue.
7273
- `disableStyleRemoval`: A boolean value that determines whether or not to remove styles on unmount. This is useful for CSS transitions.
73-
- `disableShadowDOM`: A boolean value that determines whether or not to use Shadow DOM for the web component.
74+
- `disableShadowDOM`: A boolean value that determines whether or not to use Shadow DOM for the web component.
75+
- `replaceRootWithHost`: A boolean value that determines whether or not to replace `:root` with `:host` in your CSS framework styles. This is useful for CSS variables that penetrate the Shadow DOM.

package/index.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ export const createWebComponent = ({
1212
createApp,
1313
getCurrentInstance,
1414
disableRemoveStylesOnUnmount = false,
15-
disableShadowDOM = false
15+
disableShadowDOM = false,
16+
replaceRootWithHostInCssFramework = false
1617
}) => {
1718
if (!rootComponent) {
1819
console.warn('No root component provided. Please provide a root component to create a web component.')
@@ -51,7 +52,8 @@ export const createWebComponent = ({
5152
getCurrentInstance,
5253
elementName,
5354
disableRemoveStylesOnUnmount,
54-
disableShadowDOM
55+
disableShadowDOM,
56+
replaceRootWithHostInCssFramework
5557
}, )
5658

5759

package/src/web-component-util.js

+50-35
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@ function convertToOnEventName(eventName) {
1616
return 'on' + eventName.charAt(0).toUpperCase() + eventName.slice(1);
1717
}
1818

19+
// Function to replace ':root' with ':host' in CSS
20+
function replaceRootWithHost(styles) {
21+
if (typeof styles === 'string') {
22+
return styles.replace(/:root/g, ':host');
23+
} else if (Array.isArray(styles)) {
24+
return styles.map(style => style.replace(/:root/g, ':host'));
25+
} else {
26+
return styles;
27+
}
28+
}
29+
1930
export const defineCustomElement = ({
2031
rootComponent,
2132
plugins,
@@ -26,84 +37,87 @@ export const defineCustomElement = ({
2637
getCurrentInstance,
2738
elementName,
2839
disableRemoveStylesOnUnmount,
29-
disableShadowDOM
30-
}) =>
31-
{
32-
const customElementDefiner = disableShadowDOM ? VueDefineCustomElementPatch : VueDefineCustomElement
33-
return customElementDefiner({
40+
disableShadowDOM,
41+
replaceRootWithHostInCssFramework,
42+
}) => {
43+
const customElementDefiner = disableShadowDOM ? VueDefineCustomElementPatch : VueDefineCustomElement;
44+
45+
const modifiedCssFrameworkStyles = replaceRootWithHostInCssFramework
46+
? replaceRootWithHost(cssFrameworkStyles)
47+
: cssFrameworkStyles;
48+
49+
return customElementDefiner({
3450
name: 'vue-custom-element-root-component',
35-
styles: [cssFrameworkStyles],
51+
styles: [modifiedCssFrameworkStyles],
3652
props: {
3753
...rootComponent.props,
3854
modelValue: { type: [String, Number, Boolean, Array, Object] } // v-model support
39-
},
55+
},
4056
emits: rootComponent?.emits,
41-
57+
4258
setup(props, { slots }) {
43-
const emitsList = [...(rootComponent?.emits || []), 'update:modelValue']
44-
const app = createApp()
45-
app.component('app-root', rootComponent)
59+
const emitsList = [...(rootComponent?.emits || []), 'update:modelValue'];
60+
const app = createApp();
61+
app.component('app-root', rootComponent);
4662

4763
if (rootComponent.provide) {
4864
const provide = typeof rootComponent.provide === 'function'
49-
? rootComponent.provide()
65+
? rootComponent.provide()
5066
: rootComponent.provide;
51-
67+
5268
// Setup provide
5369
Object.keys(provide).forEach(key => {
5470
app.provide(key, provide[key]);
5571
});
5672
}
57-
58-
app.mixin({
5973

74+
app.mixin({
6075
mounted() {
61-
6276
if (this.$?.type?.name === 'vue-custom-element-root-component') {
63-
return
77+
return;
6478
}
6579

6680
const insertStyles = (styles) => {
6781
if (styles?.length) {
68-
this.__style = document.createElement('style')
69-
this.__style.innerText = styles.join().replace(/\n/g, '')
70-
nearestElement(this.$el).append(this.__style)
82+
this.__style = document.createElement('style');
83+
this.__style.innerText = styles.join().replace(/\n/g, '');
84+
nearestElement(this.$el).append(this.__style);
7185
}
72-
}
86+
};
7387

74-
insertStyles(this.$?.type.styles)
88+
insertStyles(this.$?.type.styles);
7589
if (this.$options.components) {
7690
for (const comp of Object.values(this.$options.components)) {
77-
insertStyles(comp.styles)
91+
insertStyles(comp.styles);
7892
}
7993
}
8094
},
8195
unmounted() {
82-
if(!disableRemoveStylesOnUnmount) {
83-
this.__style?.remove()
96+
if (!disableRemoveStylesOnUnmount) {
97+
this.__style?.remove();
8498
}
8599
},
86-
})
100+
});
87101

88-
app.use(plugins)
89-
90-
const inst = getCurrentInstance()
91-
Object.assign(inst.appContext, app._context)
92-
Object.assign(inst.provides, app._context.provides)
102+
app.use(plugins);
103+
104+
const inst = getCurrentInstance();
105+
Object.assign(inst.appContext, app._context);
106+
Object.assign(inst.provides, app._context.provides);
93107

94108
// Add support for Vue Devtools
95109
if (process.env.NODE_ENV === 'development' && window.__VUE_DEVTOOLS_GLOBAL_HOOK__) {
96110
const root = document.querySelector(elementName);
97111
app._container = root;
98112
app._instance = inst;
99-
113+
100114
const types = {
101115
Comment: Symbol('v-cmt'),
102116
Fragment: Symbol('v-fgt'),
103117
Static: Symbol('v-stc'),
104118
Text: Symbol('v-txt'),
105119
};
106-
120+
107121
window.__VUE_DEVTOOLS_GLOBAL_HOOK__.emit('app:init', app, app.version, types);
108122
window.__VUE_DEVTOOLS_GLOBAL_HOOK__.Vue = app;
109123
}
@@ -117,7 +131,7 @@ export const defineCustomElement = ({
117131

118132
// Establish named slots
119133
const namedSlots = rootComponent?.namedSlots?.reduce((acc, slotsName) => {
120-
acc[slotsName] = () => h('slot',{ name: slotsName});
134+
acc[slotsName] = () => h('slot', { name: slotsName });
121135
return acc;
122136
}, {});
123137

@@ -134,4 +148,5 @@ export const defineCustomElement = ({
134148
}
135149
);
136150
},
137-
}, disableShadowDOM && { shadowRoot: false })}
151+
}, disableShadowDOM && { shadowRoot: false });
152+
}

package/types.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface CreateWebComponentOptions {
1818
}
1919

2020
export function createWebComponent(options: CreateWebComponentOptions): void
21+
export default createWebComponent
2122

2223

2324
interface defineCustomElementSFCOptions {

0 commit comments

Comments
 (0)