Skip to content

Commit

Permalink
feature #76: build ui-vue as web component as well
Browse files Browse the repository at this point in the history
  • Loading branch information
sadiqkhoja committed May 21, 2024
1 parent 512f823 commit 836d6fa
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 71 deletions.
5 changes: 3 additions & 2 deletions packages/ui-vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@
"yarn": "1.22.19"
},
"scripts": {
"build": "npm-run-all -nl build:*",
"build": "npm-run-all -nls build:*",
"build:clean": "rimraf dist/",
"build:js": "vite build",
"build:vue": "vite build",
"build:web-component": "vite build --mode web-component",
"dev": "vite",
"test": "npm-run-all -nl test:*",
"test:e2e": "playwright test",
Expand Down
5 changes: 5 additions & 0 deletions packages/ui-vue/src/components/OdkWebForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,8 @@ const print = () => window.print();
</div>
</template>

<style lang="scss">
@import '../assets/css/icomoon.css';
@import '../themes/2024-light/theme.scss';
@import 'primeflex/primeflex.css';
</style>
5 changes: 0 additions & 5 deletions packages/ui-vue/src/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@ import { createApp } from 'vue';
import OdkWebFormDemo from './OdkWebFormDemo.vue';
import { webFormsPlugin } from './WebFormsPlugin';

import './assets/css/icomoon.css';
import './themes/2024-light/theme.scss';
// TODO/sk: Purge it - postcss-purgecss
import 'primeflex/primeflex.css';

import './assets/css/style.scss';

const app = createApp(OdkWebFormDemo as Component);
Expand Down
56 changes: 56 additions & 0 deletions packages/ui-vue/src/index-wc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { createApp, defineCustomElement, getCurrentInstance, h } from 'vue';
import OdkWebForm from './components/OdkWebForm.vue';
import { webFormsPlugin } from './WebFormsPlugin';

interface WebComponent {
styles: string[];
emits: string[];
}

const OdkWebFormWC = OdkWebForm as unknown as WebComponent;

// OdkWebForm is not directly passed to defineCustomElement because we want
// to install the webFormsPlugin for PrimeVue to work in Web Component.
const OdkWebFormElement = defineCustomElement({
styles: OdkWebFormWC.styles,
emits: OdkWebFormWC.emits,
setup(props, { emit }) {
const app = createApp({});
app.use(webFormsPlugin);
const inst = getCurrentInstance();
Object.assign(inst!.appContext, app._context);

// Injecting the style in the host application's head for
// 1 - Custom fonts to work with custom element
// Custom fonts are not yet support in shadow DOM
// Vue doesn't support to turn off shadow DOM see @vuejs#4404
// 2 - PrimeVue adds adhoc elements to the DOM of the host application
// in certain cases like dropdowns
if (!document.getElementById('odk-web-forms-styles')) {
const head = document.head || document.getElementsByTagName('head')[0];
const style = document.createElement('style');
style.id = 'odk-web-forms-styles';
style.innerText = OdkWebFormWC.styles.join('\n');
head.appendChild(style);
}

// To enable emits
// see https://stackoverflow.com/questions/74528406/unable-to-emit-events-when-wrapping-a-vue-3-component-into-a-custom-element
const events = Object.fromEntries(
OdkWebFormWC.emits.map((event: string) => {
return [
`on${event[0].toUpperCase()}${event.slice(1)}`,
(payload: unknown) => emit(event, payload),
];
})
);

return () =>
h(OdkWebFormWC, {
...props,
...events,
});
},
});

customElements.define('odk-web-form', OdkWebFormElement);
6 changes: 0 additions & 6 deletions packages/ui-vue/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import { webFormsPlugin } from './WebFormsPlugin';
import OdkWebForm from './components/OdkWebForm.vue';

import './assets/css/icomoon.css';
import './themes/2024-light/theme.scss';

// TODO/sk: Purge it - using postcss-purgecss
import 'primeflex/primeflex.css';

export { OdkWebForm, webFormsPlugin };
1 change: 1 addition & 0 deletions packages/ui-vue/src/themes/2024-light/theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ $primary50: #d8ecf5;

.odk-form {
width: 100%;
background: var(--gray-200);

.form-wrapper {
max-width: 800px;
Expand Down
98 changes: 63 additions & 35 deletions packages/ui-vue/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import vueJsx from '@vitejs/plugin-vue-jsx';
import { fileURLToPath, URL } from 'node:url';
import { resolve } from 'path';
import type { Root } from 'postcss';
import { defineConfig } from 'vite';
import { defineConfig, type BuildOptions } from 'vite';

// PrimeVue-Sass-Theme defines all rules under @layer primevue
// With that approach host applications rules override everything
// So we are removing @layer at build/serve time here
const removeCssLayer = () => {
return {
postcssPlugin: 'replace-john-with-jane',
postcssPlugin: 'remove-css-layer',
Once(root: Root) {
root.walkAtRules((rule) => {
if (rule.name === 'layer') {
Expand All @@ -23,41 +23,69 @@ const removeCssLayer = () => {
};
removeCssLayer.postcss = true;

export default defineConfig({
plugins: [vue(), vueJsx()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
'primevue/menuitem': 'primevue/menu',
// With following lines, fonts byte array are copied into css file
// Roboto fonts - don't want to copy those in our repository
'./fonts': resolve(
'../../node_modules/primevue-sass-theme/themes/material/material-light/standard/indigo/fonts'
),
// Icomoon fonts
'/fonts': resolve('./src/assets/fonts'),
},
},
build: {
target: 'esnext',
lib: {
formats: ['es'],
entry: resolve(__dirname, 'src/index.ts'),
name: 'OdkWebForms',
fileName: 'index',
},
rollupOptions: {
external: ['vue'],
output: {
globals: {
vue: 'Vue',
export default defineConfig(({ mode }) => {
let build: BuildOptions;

// For building "Web Component", we need to include Vue runtime
if (mode === 'web-component') {
build = {
target: 'esnext',
emptyOutDir: false,
lib: {
formats: ['es'],
entry: resolve(__dirname, 'src/index-wc.ts'),
name: 'OdkWebForms',
fileName: 'odk-web-forms-wc',
},
};
} else {
build = {
target: 'esnext',
emptyOutDir: false,
lib: {
formats: ['es'],
entry: resolve(__dirname, 'src/index.ts'),
name: 'OdkWebForms',
fileName: 'index',
},
rollupOptions: {
external: ['vue'],
output: {
globals: {
vue: 'Vue',
},
},
},
};
}
return {
plugins: [
vue({
// Treat OdkWebForm.vue as Web Component / Custom Element
// This adds the styles in the shadow DOM
customElement: mode === 'web-component' ? /OdkWebForm\.vue/ : /\.ce\.vue$/,
}),
vueJsx(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
'primevue/menuitem': 'primevue/menu',
// With following lines, fonts byte array are copied into css file
// Roboto fonts - don't want to copy those in our repository
'./fonts': resolve(
'../../node_modules/primevue-sass-theme/themes/material/material-light/standard/indigo/fonts'
),
// Icomoon fonts
'/fonts': resolve('./src/assets/fonts'),
vue: 'vue/dist/vue.esm-bundler.js',
},
},
},
css: {
postcss: {
plugins: [removeCssLayer()],
build,
css: {
postcss: {
plugins: [removeCssLayer()],
},
},
},
};
});
48 changes: 25 additions & 23 deletions packages/ui-vue/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,27 +28,29 @@ const BROWSER_ENABLED = BROWSER_NAME != null;

const TEST_ENVIRONMENT = BROWSER_ENABLED ? 'node' : 'jsdom';

export default mergeConfig(
viteConfig,
defineConfig({
test: {
browser: {
enabled: BROWSER_ENABLED,
name: BROWSER_NAME!,
provider: 'playwright',
headless: true,
export default defineConfig((options) => {
return mergeConfig(
viteConfig(options),
defineConfig({
test: {
browser: {
enabled: BROWSER_ENABLED,
name: BROWSER_NAME!,
provider: 'playwright',
headless: true,
},
environment: TEST_ENVIRONMENT,
exclude: [...configDefaults.exclude, 'e2e/*'],
root: fileURLToPath(new URL('./', import.meta.url)),
// Suppress the console error log about parsing CSS stylesheet
// This is an open issue of jsdom
// see primefaces/primevue#4512 and jsdom/jsdom#2177
onConsoleLog(log: string, type: 'stderr' | 'stdout'): false | void {
if (log.startsWith('Error: Could not parse CSS stylesheet') && type === 'stderr') {
return false;
}
},
},
environment: TEST_ENVIRONMENT,
exclude: [...configDefaults.exclude, 'e2e/*'],
root: fileURLToPath(new URL('./', import.meta.url)),
// Suppress the console error log about parsing CSS stylesheet
// This is an open issue of jsdom
// see primefaces/primevue#4512 and jsdom/jsdom#2177
onConsoleLog(log: string, type: 'stderr' | 'stdout'): false | void {
if (log.startsWith('Error: Could not parse CSS stylesheet') && type === 'stderr') {
return false;
}
},
},
})
);
})
);
});

0 comments on commit 836d6fa

Please sign in to comment.