Skip to content

Commit 1700cf3

Browse files
committed
[blog] Introducing Vue 3 & Web Components in Hue Query Editor
1 parent 70d8aac commit 1700cf3

File tree

1 file changed

+280
-0
lines changed

1 file changed

+280
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
---
2+
title: Introducing Vue 3 & Web Components in Hue Query Editor
3+
author: Hue Team
4+
type: post
5+
date: 2021-03-04T00:00:00+00:00
6+
url: /blog/vue3-build-cli-options-composition-api-template-web-components-hue
7+
sf_thumbnail_type:
8+
- none
9+
sf_thumbnail_link_type:
10+
- link_to_post
11+
sf_detail_type:
12+
- none
13+
sf_page_title:
14+
- 1
15+
sf_page_title_style:
16+
- standard
17+
sf_no_breadcrumbs:
18+
- 1
19+
sf_page_title_bg:
20+
- none
21+
sf_page_title_text_style:
22+
- light
23+
sf_background_image_size:
24+
- cover
25+
sf_social_sharing:
26+
- 1
27+
sf_related_articles:
28+
- 1
29+
sf_sidebar_config:
30+
- left-sidebar
31+
sf_left_sidebar:
32+
- Sidebar-2
33+
sf_right_sidebar:
34+
- Sidebar-1
35+
sf_caption_position:
36+
- caption-right
37+
sf_remove_promo_bar:
38+
- 1
39+
ampforwp-amp-on-off:
40+
- default
41+
categories:
42+
- Version 4.10
43+
- Release
44+
45+
---
46+
47+
The Hue project has a longevity of more than 10 years. Over these years some of the technologies we use have become rather old fashioned. So while discussing the improvements to be made in Hue, upgrading UI technology was among the top.
48+
49+
Hue uses a combination of [Mako](https://www.makotemplates.org/) and [Knockout JS](https://knockoutjs.com/) libraries to create all the UI magic. As newer libraries bring more productivity at this point, we decided to start a hunt for the latest best. Following were our goals:
50+
- Introduce a modern UI library that over time will replace Knockout JS
51+
- Package the [component](https://docs.gethue.com/developer/components/) to be shared across various projects
52+
- Move from partial Server-side Rendering to full Client-side Rendering
53+
- Introduce Typescript for stronger code quality
54+
55+
[React](https://reactjs.org/) and [Vue](https://vuejs.org/) were the top candidates. [Angular](https://angularjs.org) and [Svelte](https://svelte.dev) were also on the table. After a few brainstorming sessions we decided to go with Vue.js. Even though all the top frameworks provide a productive way to write dynamic interfaces, Vue was chosen as it was not intrusive, very fast, small and most importantly did fit well with our current [componentization](https://docs.gethue.com/developer/components) effort. We started with Vue 2, but as Vue 3 was released soon after we decided to use to Vue 3 instead. Few questions had to be answered before migrating:
56+
- How to set up the Vue build process?
57+
- What is the best component syntax?
58+
- How to package as web components?
59+
60+
## Build process
61+
62+
Hue had been using webpack to build the UI. As the plan was to gradually upgrade the components, we needed the setup to work with both old and new UI code. Luckily the boilerplate project created by Vue CLI turned out to be using webpack internally. Hence we decided to create a dummy project using CLI and copy the dependencies and configurations.
63+
64+
Creating a dummy project was pretty straightforward. Install CLI using `npm install -g @vue/cli`, and create a project using `vue create hue-dummy`. While creating, instead of going with a project preset, we decided to manually select project features and opted for Vue Version, Babel, Typescript, CSS Preprocessor, Linter & Unit Tests as in the following.
65+
66+
Vue CLI v4.5.11
67+
? Please pick a preset: Manually select features
68+
? Check the features needed for your project:
69+
◉ Choose Vue version
70+
◉ Babel
71+
◉ TypeScript
72+
◯ Progressive Web App (PWA) Support
73+
◯ Router
74+
◯ Vuex
75+
◉ CSS Pre-processors
76+
◉ Linter / Formatter
77+
❯◉ Unit Testing
78+
◯ E2E Testing
79+
80+
In the subsequent page we chose Vue 3, Typescript, SASS, Prettier, Lint, Jest and the CLI started creating the dummy project.
81+
82+
Vue CLI v4.5.11
83+
? Please pick a preset: Manually select features
84+
? Check the features needed for your project: Choose Vue version, Babel, TS, CSS Pre-processors, Linter, Unit
85+
? Choose a version of Vue.js that you want to start the project with 3.x (Preview)
86+
? Use class-style component syntax? No
87+
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes
88+
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with node-sass)
89+
? Pick a linter / formatter config: Prettier
90+
? Pick additional lint features: Lint on save
91+
? Pick a unit testing solution: Jest
92+
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
93+
? Save this as a preset for future projects? (y/N) N
94+
95+
Once the project was created, we took a diff of `package.json` and configuration files of Babel, ESLint etc and the changes were copied and dependencies were updated. In `webpack.config.js`, `vue-loader` and `babel-loader` rules were put in place for .vue & .ts files respectively, and the build started working.
96+
97+
_Note: One configuration from the demo project that caused problems for us was `"jsx": "preserve"` in `tsconfig.json`. It was conflicting with `<>` style type casting. If you want to use JSX instead of Vue template, make sure to use the `as` keyword for typecasting._
98+
99+
## Component syntax, APIs & template
100+
101+
Now that the project started building with Vue & Typescript features, the next challenge was to figure out the writing style, and in turn how a component would look. Vue supports various ways to write a component.
102+
103+
### Class-style syntax
104+
105+
In class-style you define components as ES6 classes. Data, methods and other properties of the components can be annotated with decorators. It also facilitates the use of OOPs features like inheritance, mixins etc. **Vue Class Component** is a library that lets you make your Vue components in class-style syntax. Along with Typescript this looked like the most Object Oriented approach. But as it **adds dependency on an extra library**, we decided to look elsewhere. Moreover it's just a syntactic sugar over the Options API that’s discussed in the next section.
106+
107+
Following is how a component would look in class-style.
108+
109+
import Vue from 'vue'
110+
import Component from 'vue-class-component'
111+
112+
@Component
113+
export default class Counter extends Vue {
114+
// Class definition
115+
}
116+
117+
### Object-style Syntax & Options API
118+
119+
In object-style we define a component as an `options object`. Data, methods and properties of a component are defined as children of the options object. Also we can add functions that would be called in each stage of the component lifecycle. Vue calls them lifecycle hooks. Format of these objects, and signatures of the hooks are defined by the Vue Options API. Along with native Typescript support provided by Vue 3, object style looked good. But Vue 3 had more in store!
120+
121+
Following is how a component is defined in object style.
122+
123+
import { defineComponent } from 'vue';
124+
125+
export default defineComponent({
126+
// Options object definition
127+
});
128+
129+
### Composition API
130+
131+
[Composition API](https://v3.vuejs.org/guide/composition-api-introduction.html) is a new way provided by Vue 3 to create a component. In this the complete component is composed using a `setup function`. Methods are defined as nested closure functions, and reactive data members can be returned as children of the object returned. Special functions like onMounted are provided for defining the lifecycle hooks.
132+
133+
Following is how a component is defined in object style
134+
135+
import { defineComponent, onMounted } from 'vue';
136+
137+
defineComponent(() => {
138+
return {
139+
data: 1
140+
};
141+
});
142+
143+
### Our component template
144+
145+
At the end we decided to go with object-style syntax, and a combination of Options & Composition API where setup hook acts as the setup function. **We decided to mix and match as defining static items like props or components is easy with Options API and dynamic items like reactive provide & inject is easy with Composition API. Moreover, many of the Vue 3 documentation uses this style.** After going through various sources we created the following template for a component. It provides various options (i.e, components, directives, props etc ) that can be used to define a component, the order they could be used, and how each part interacts. This acts as a base for all our components.
146+
147+
<template>
148+
<Comp1 @click="onClick">Click Me!</Comp1>
149+
</template>
150+
151+
<script lang="ts">
152+
import { defineComponent, PropType, inject, provide } from 'vue';
153+
154+
import Comp1 from './Comp1.vue';
155+
import AnotherComp2 from './Comp2.vue';
156+
157+
export default <T, K>() => defineComponent({
158+
components: {
159+
Comp1,
160+
AnotherComp2
161+
},
162+
163+
directives: {
164+
'overflow-on-hover': overflowOnHover
165+
},
166+
167+
provide(): {
168+
hideDrawer: () => void;
169+
} {
170+
return {
171+
hideDrawer: (): void => {
172+
this.$emit('close');
173+
}
174+
};
175+
},
176+
177+
props: {
178+
propA: Boolean,
179+
propB: {
180+
type: String,
181+
default: 'Abc'
182+
},
183+
items: {
184+
type: Object as PropType<SidebarNavigationItem[]>,
185+
required: true
186+
}
187+
},
188+
189+
emits: ['emitted-event-name'],
190+
191+
setup(props): { // Setup function for composition
192+
const injectedReactiveValue: Type = inject('injectedReactiveValue');
193+
194+
return {
195+
dataMember: false,
196+
processedProp: !props.propA,
197+
injectedReactiveValue
198+
}
199+
},
200+
201+
data(): {
202+
return {
203+
genericMember: null as <T | null>
204+
};
205+
},
206+
207+
computed: {
208+
isActive(): Boolean { // Computed getter
209+
// Statements
210+
}
211+
},
212+
213+
mounted(): void {
214+
// Statements
215+
},
216+
unmounted(): void {
217+
// Statements
218+
},
219+
220+
methods: {
221+
onClick(event: Event): void {
222+
console.log(this.processedProp);
223+
}
224+
},
225+
226+
watch: {
227+
items(): void { // Watches items prop
228+
// Statements to be executed
229+
}
230+
},
231+
created() {
232+
this.$watch(
233+
():K => this.foo.bar, // Watch nested property bar of type K
234+
(val:K, prevVal:K): void => {
235+
// Statements to be executed
236+
}
237+
)
238+
}
239+
})
240+
</script>
241+
242+
## Web components
243+
244+
Our next goal was packaging the component to be shared across various projects. As modern Web UIs are built using various technologies we needed a method that's framework agnostic. Enter the web component! Web Components allows you to create reusable custom elements with their functionality encapsulated away from the rest of your code.
245+
246+
But to our surprise the official Vue package for converting a component to a web component did not support Vue 3. And as per [this issue](https://github.com/vuejs/vue-web-component-wrapper/issues/93) it's going to be a while before the support is added. So we had to find an alternative. We created a port of the Vue 2 web component wrapper that works with Vue 3. It's named `vue3-webcomponent-wrapper`. The code is [here](https://github.com/cloudera/hue/tree/master/desktop/core/src/desktop/js/vue/wrapper) and npm package is available [here](https://www.npmjs.com/package/vue3-webcomponent-wrapper). Our port supports reactive attributes, events & slots.
247+
248+
_Note: One main blocker preventing the official wrapper for upgrading was the lack of shadow-root CSS injection in Vue 3 build tooling. As we could live without shadow in Hue this was not an issue and port took hardly a day._
249+
250+
Using our component wrapper is pretty easy. It can be installed using `npm i --save vue3-webcomponent-wrapper`. Once installed following snippets show how to create a custom tag named `my-component`.
251+
252+
Before with Vue 2 and [@vuejs/vue-web-component-wrapper](https://github.com/vuejs/vue-web-component-wrapper).
253+
254+
import Vue from 'vue'
255+
import wrapper from '@vue/web-component-wrapper'
256+
import MyComponent from "./components/MyComponent.vue";
257+
258+
const CustomElement = wrapper(Vue, MyComponent)
259+
window.customElements.define('my-component', CustomElement)
260+
261+
Now with Vue 3 and vue3-webcomponent-wrapper.
262+
263+
import { createApp, h } from "vue";
264+
import wrapper from "vue3-webcomponent-wrapper";
265+
import MyComponent from "./components/MyComponent.vue";
266+
267+
const CustomElement = wrapper(MyComponent, createApp, h);
268+
window.customElements.define("my-component", CustomElement);
269+
270+
Please find more information about the wrapper in this [demo app](https://github.com/sreenaths/vue3-webcomponent-wrapper-demo). And [this](https://github.com/cloudera/hue/blob/master/apps/metastore/src/metastore/templates/metastore.mako#L825) er-diagram tag is a sample use of the wrapper in Hue project.
271+
272+
### Using official Vue 3 web component wrapper
273+
274+
The official wrapper must be very similar to our implementation. That said, the exact function signature of the official wrapper is unknown at this point. Keep a check on [this page](https://www.npmjs.com/package/vue3-webcomponent-wrapper) for future updates.
275+
276+
## And that's it!
277+
278+
Hue moved from using pretty old technologies to the forefront of Web interface development with Vue 3 and the component wrapper. We strongly believe component programming is a very effective development paradigm with its isolation and easy sharing. In the next episode we will demo how it all integrates with the new Hue 5 API.
279+
280+
~ Sreenath from the Hue Team

0 commit comments

Comments
 (0)