Skip to content

Commit

Permalink
refactor: Update createWebComponent function and add replaceRootWithH…
Browse files Browse the repository at this point in the history
…ost option
  • Loading branch information
EranGrin committed Oct 14, 2024
1 parent 177ffee commit e6e202a
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 111 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format

## [Unreleased]

## [1.6.4] - 14.10.2024
### Added
- Added support for replaceRootWithHost option

## [1.6.3] - 12.07.2024
### Added
- Fixed issue with style tag injection order SFC
Expand Down
189 changes: 121 additions & 68 deletions README.md

Large diffs are not rendered by default.

16 changes: 11 additions & 5 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
`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.

## Why use `vue-web-component-wrapper`?
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.
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.

## Demo
Check out these demo projects to see `vue-web-component-wrapper` in action:
- **Webpack implentaion**: Check out this [Webpack Demo Project](https://stackblitz.com/edit/vue-web-component-wrapper?file=README.md&startScript=webpack-demo)
- **Vite.js implentaion**: Check out this [Vite Demo Project](https://stackblitz.com/edit/vue-web-component-wrapper?file=README.md&startScript=vite-demo)
- **Webpack implementation**: Check out this [Webpack Demo Project](https://stackblitz.com/edit/vue-web-component-wrapper?file=README.md&startScript=webpack-demo)
- **Vite.js implementation**: Check out this [Vite Demo Project](https://stackblitz.com/edit/vue-web-component-wrapper?file=README.md&startScript=vite-demo)

## Key Features
- **Vue Plugins Compatibility**: Integrates seamlessly with key Vue ecosystem plugins.
- **Vue Plugins Compatibility**: Seamlessly integrates with key Vue ecosystem plugins.
- **CSS Framework Support**: Compatible with major CSS frameworks like Tailwind CSS and Bootstrap.
- **CSS Preprocessor Support**: Supports CSS preprocessors including SCSS and LESS.
- **Scoped CSS**: Enables the use of scoped CSS in your components.
Expand All @@ -22,7 +23,12 @@ Check out these demo projects to see `vue-web-component-wrapper` in action:
- **Event Emitting Capability**: Enables the emission and handling of custom events from web components.
- **Disable Style Removal on Unmount**: Option to control the removal of styles upon component unmount, addressing issues with CSS transition.
- **Disable Shadow DOM**: Option to disable the Shadow DOM, rendering content in the light DOM.
- **Replace `:root` with `:host`**: New feature to replace `:root` selectors with `:host` in your CSS, ensuring proper style scoping within the Shadow DOM.

## New Feature Highlight: Replace `:root` with `:host`
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.

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

## Tips
- **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.
- **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.
4 changes: 3 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ createWebComponent({
getCurrentInstance,
disableStyleRemoval: false,
disableShadowDOM: false,
replaceRootWithHost: false,
});
```
Each option in the `createWebComponent` function has a specific purpose:
Expand All @@ -70,4 +71,5 @@ Each option in the `createWebComponent` function has a specific purpose:
- `createApp`: The `createApp` function from Vue.
- `getCurrentInstance`: The `getCurrentInstance` function from Vue.
- `disableStyleRemoval`: A boolean value that determines whether or not to remove styles on unmount. This is useful for CSS transitions.
- `disableShadowDOM`: A boolean value that determines whether or not to use Shadow DOM for the web component.
- `disableShadowDOM`: A boolean value that determines whether or not to use Shadow DOM for the web component.
- `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.
6 changes: 4 additions & 2 deletions package/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ export const createWebComponent = ({
createApp,
getCurrentInstance,
disableRemoveStylesOnUnmount = false,
disableShadowDOM = false
disableShadowDOM = false,
replaceRootWithHostInCssFramework = false
}) => {
if (!rootComponent) {
console.warn('No root component provided. Please provide a root component to create a web component.')
Expand Down Expand Up @@ -51,7 +52,8 @@ export const createWebComponent = ({
getCurrentInstance,
elementName,
disableRemoveStylesOnUnmount,
disableShadowDOM
disableShadowDOM,
replaceRootWithHostInCssFramework
}, )


Expand Down
85 changes: 50 additions & 35 deletions package/src/web-component-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ function convertToOnEventName(eventName) {
return 'on' + eventName.charAt(0).toUpperCase() + eventName.slice(1);
}

// Function to replace ':root' with ':host' in CSS
function replaceRootWithHost(styles) {
if (typeof styles === 'string') {
return styles.replace(/:root/g, ':host');
} else if (Array.isArray(styles)) {
return styles.map(style => style.replace(/:root/g, ':host'));
} else {
return styles;
}
}

export const defineCustomElement = ({
rootComponent,
plugins,
Expand All @@ -26,84 +37,87 @@ export const defineCustomElement = ({
getCurrentInstance,
elementName,
disableRemoveStylesOnUnmount,
disableShadowDOM
}) =>
{
const customElementDefiner = disableShadowDOM ? VueDefineCustomElementPatch : VueDefineCustomElement
return customElementDefiner({
disableShadowDOM,
replaceRootWithHostInCssFramework,
}) => {
const customElementDefiner = disableShadowDOM ? VueDefineCustomElementPatch : VueDefineCustomElement;

const modifiedCssFrameworkStyles = replaceRootWithHostInCssFramework
? replaceRootWithHost(cssFrameworkStyles)
: cssFrameworkStyles;

return customElementDefiner({
name: 'vue-custom-element-root-component',
styles: [cssFrameworkStyles],
styles: [modifiedCssFrameworkStyles],
props: {
...rootComponent.props,
modelValue: { type: [String, Number, Boolean, Array, Object] } // v-model support
},
},
emits: rootComponent?.emits,

setup(props, { slots }) {
const emitsList = [...(rootComponent?.emits || []), 'update:modelValue']
const app = createApp()
app.component('app-root', rootComponent)
const emitsList = [...(rootComponent?.emits || []), 'update:modelValue'];
const app = createApp();
app.component('app-root', rootComponent);

if (rootComponent.provide) {
const provide = typeof rootComponent.provide === 'function'
? rootComponent.provide()
? rootComponent.provide()
: rootComponent.provide;

// Setup provide
Object.keys(provide).forEach(key => {
app.provide(key, provide[key]);
});
}

app.mixin({

app.mixin({
mounted() {

if (this.$?.type?.name === 'vue-custom-element-root-component') {
return
return;
}

const insertStyles = (styles) => {
if (styles?.length) {
this.__style = document.createElement('style')
this.__style.innerText = styles.join().replace(/\n/g, '')
nearestElement(this.$el).append(this.__style)
this.__style = document.createElement('style');
this.__style.innerText = styles.join().replace(/\n/g, '');
nearestElement(this.$el).append(this.__style);
}
}
};

insertStyles(this.$?.type.styles)
insertStyles(this.$?.type.styles);
if (this.$options.components) {
for (const comp of Object.values(this.$options.components)) {
insertStyles(comp.styles)
insertStyles(comp.styles);
}
}
},
unmounted() {
if(!disableRemoveStylesOnUnmount) {
this.__style?.remove()
if (!disableRemoveStylesOnUnmount) {
this.__style?.remove();
}
},
})
});

app.use(plugins)
const inst = getCurrentInstance()
Object.assign(inst.appContext, app._context)
Object.assign(inst.provides, app._context.provides)
app.use(plugins);

const inst = getCurrentInstance();
Object.assign(inst.appContext, app._context);
Object.assign(inst.provides, app._context.provides);

// Add support for Vue Devtools
if (process.env.NODE_ENV === 'development' && window.__VUE_DEVTOOLS_GLOBAL_HOOK__) {
const root = document.querySelector(elementName);
app._container = root;
app._instance = inst;

const types = {
Comment: Symbol('v-cmt'),
Fragment: Symbol('v-fgt'),
Static: Symbol('v-stc'),
Text: Symbol('v-txt'),
};

window.__VUE_DEVTOOLS_GLOBAL_HOOK__.emit('app:init', app, app.version, types);
window.__VUE_DEVTOOLS_GLOBAL_HOOK__.Vue = app;
}
Expand All @@ -117,7 +131,7 @@ export const defineCustomElement = ({

// Establish named slots
const namedSlots = rootComponent?.namedSlots?.reduce((acc, slotsName) => {
acc[slotsName] = () => h('slot',{ name: slotsName});
acc[slotsName] = () => h('slot', { name: slotsName });
return acc;
}, {});

Expand All @@ -134,4 +148,5 @@ export const defineCustomElement = ({
}
);
},
}, disableShadowDOM && { shadowRoot: false })}
}, disableShadowDOM && { shadowRoot: false });
}
1 change: 1 addition & 0 deletions package/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface CreateWebComponentOptions {
}

export function createWebComponent(options: CreateWebComponentOptions): void
export default createWebComponent


interface defineCustomElementSFCOptions {
Expand Down

0 comments on commit e6e202a

Please sign in to comment.