[{"data":1,"prerenderedAt":504},["ShallowReactive",2],{"blog-lit-web-components-tutorial":3},{"id":4,"title":5,"body":6,"date":488,"description":475,"extension":489,"featuredImage":490,"featuredImageAlt":491,"keywords":492,"meta":493,"navigation":494,"path":495,"seo":496,"stem":497,"subtitle":498,"tags":499,"__hash__":503},"blog\u002Fblog\u002Flit-web-components-tutorial.md","How to Create Reusable Web Components with Lit and Vue",{"type":7,"value":8,"toc":474},"minimark",[9,14,18,22,25,41,47,56,60,63,73,76,122,125,130,133,137,140,184,187,195,206,212,216,223,226,236,239,249,253,256,259,266,269,272,276,283,286,296,299,310,314,318,324,327,330,337,341,348,351,357,362,376,383,394,407,418,422,435,439,449,452,463,467],[10,11,13],"h2",{"id":12},"introduction","Introduction",[15,16,17],"p",{},"Web Components are an incredibly useful tool for developers that are looking to create reusable pieces of frontend code, while supporting many different platforms. In this tutorial, I will be walking through the process of quick-starting a web components project with Lit, and how to implement your new web component in a Vue.js application. The component we will be building will be a simple button, and could also be implemented in other frontend environments like React, Angular, etc.",[10,19,21],{"id":20},"the-scenario","The Scenario",[15,23,24],{},"Imagine this scenario - you are working for a large company with several web applications, all of which do very different things and are maintained by different developers. Your company has a very strong brand presence, and wants to make sure that the user experience and brand presentation are consistent across all of these applications. The teams that worked on starting each of the several web apps worked in isolation, however, so you have one thats built with React, one with Vue, and maybe a couple that are just using JQuery. The obvious answer to \"how do we have a consistent brand UX across sites\" is to use a UI system\u002Flibrary and maintain that separately from the applications that use it, such as Google's Material UI. How in the world could you possibly maintain UI libraries that support all of the frameworks your applications use?",[15,26,27,28,32,33,40],{},"One answer to this question would be ",[29,30,31],"strong",{},"Web Components",". Here is a quick definition for Web Components from ",[34,35,39],"a",{"href":36,"rel":37},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FWeb_Components",[38],"nofollow","MDN",":",[42,43,44],"blockquote",{},[15,45,46],{},"Web Components is a suite of different technologies allowing you to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilize them in your web apps.",[15,48,49,50,55],{},"We can use Web Components to solve the scenario I just described by creating a library of these web components, each representing an element of a UI Library such as a button or text input, and making them flexible enough to be used in different ways. To demonstrate this, I'll create a simple button component using the ",[34,51,54],{"href":52,"rel":53},"https:\u002F\u002Flit.dev\u002F",[38],"Lit framework"," and implement it in a Vue.js app.",[10,57,59],{"id":58},"example","Example",[15,61,62],{},"Before we create our project, I'm going to start by showing a quick example of a component written in TypeScript using Lit. Lit is a new-ish framework from Google that makes it very simple to write Web Components. Here is a \"Hello World\" component from their documentation:",[64,65,68,69,72],"code-block",{"lang":66,"filename":67},"ts","Example.ts","\nimport {html, css, LitElement, property} from 'lit';\n \nexport class SimpleGreeting extends LitElement {\n  static styles = css`p { color: blue }`;\n \n  @property({ type: String })\n  name = 'Somebody';\n \n  render() {\n    return html`",[15,70,71],{},"Hello, ${this.name}!","`;\n  }\n}\n",[15,74,75],{},"Lets break this sample code down step-by-step:",[77,78,79,88,95,98,105,112],"ol",{},[80,81,82,83,87],"li",{},"We import some libraries from the ",[84,85,86],"code",{},"lit"," NPM library",[80,89,90,91,94],{},"The decorator ",[84,92,93],{},"@customElement('simple-greeting')"," declares that we are creating a new custom HTML element, with the tag name \"simple-greeting\"",[80,96,97],{},"We define a class named SimpleGreeting that extends the LitElement class, which will represent our new custom element.",[80,99,100,101,104],{},"A static variable ",[84,102,103],{},"styles"," is defined, which provides CSS styles for our HTML elements.",[80,106,107,108,111],{},"The property ",[84,109,110],{},"name"," of type String is declared, with a default value of 'Somebody'. When the component is rendered, the value used as an element attribute will be used here.",[80,113,114,115,117,118,121],{},"Last, we have a render function that returns an HTML template that populates the DOM when our custom element renders. The property ",[84,116,110],{}," is then interpolated into the HTML template to be rendered in a ",[84,119,120],{},"\u003Cp>"," tag.",[15,123,124],{},"While this component might not seem useful as it is, you can see that we have much more than a static HTML element. It can be reused across many HTML templates, and it is dynamic in that we can provide any value for its properties and it will adapt accordingly. Here is how you might see this element used in an HTML document:",[64,126,129],{"lang":127,"filename":128},"html","Example.html","\n\u003Csimple-greeting name=\"Mr. Jones\" \u002F>\n\u003C!-- output: \u003Cp>Hello, Mr. Jones!\u003C\u002Fp> -->\n",[15,131,132],{},"Now lets create a web component of our own!",[10,134,136],{"id":135},"getting-started","Getting Started",[15,138,139],{},"Lets begin by creating a new web components project on our local machine.",[77,141,142,149,156,181],{},[80,143,144,145,148],{},"Open a terminal window and ",[84,146,147],{},"cd"," to the directory you want to create your project in.",[80,150,151,152,155],{},"Run the command ",[84,153,154],{},"npm init @open-wc"," - this will scaffold a new Web Components project for us quickly.",[80,157,158,159,162,163,166,167,166,170,173,174,162,177,180],{},"Select the options ",[84,160,161],{},"Scaffold a new project"," -> ",[84,164,165],{},"Web Component"," -> Select ",[84,168,169],{},"Linting",[84,171,172],{},"Yes"," to use TypeScript -> ",[84,175,176],{},"ui-library",[84,178,179],{},"yes"," -> Choose yarn or npm, whatever your preference.",[80,182,183],{},"Once the command is finished, open the newly-created directory in your preferred IDE (in my case, VS Code).",[15,185,186],{},"Now you should have a project directory that looks like this:",[188,189],"nuxt-picture",{"format":190,"alt":191,"src":192,"width":193,"loading":194},"webp","Project directory structure","\u002Fimg\u002Fweb-components-project-dir.png","475px","lazy",[15,196,197,198,201,202,205],{},"Now lets create our first Web Component, named ",[84,199,200],{},"base-button"," to represent a regular button in our UI Library. Create a file in the ",[84,203,204],{},"src"," directory named \"BaseButton.ts\", then open it up in your code editor.",[15,207,208,209,211],{},"The first thing we need to do is import some modules from the ",[84,210,86],{}," package that will help us write our component:",[64,213,215],{"lang":66,"filename":214},"\u002Fsrc\u002FBaseButton.ts","\nimport { html, css, LitElement, property } from  'lit-element';\n",[15,217,218,219,222],{},"Next, we'll declare our component similar to what was shown earlier in the ",[84,220,221],{},"simple-greeting"," example:",[64,224,225],{"lang":66,"filename":214},"\nexport class BaseButton extends LitElement {\n    \u002F\u002F ...\n}\n",[15,227,228,229,231,232,235],{},"Now we have a custom element named \"BaseButton\", whose HTML tag will be named ",[84,230,200],{},". Next, we'll create a ",[84,233,234],{},"render()"," function which will return the HTML rendered by our custom element:",[64,237,238],{"lang":66,"filename":214},"\nexport class BaseButton extends LitElement {\n    render() {\n        return html`\n            \u003Cbutton class=\"base-btn\">Base Button\u003C\u002Fbutton>\n        `;\n    }\n}\n",[15,240,241,242,244,245,248],{},"When we use our ",[84,243,200],{}," component in HTML, it will now render a ",[84,246,247],{},"button"," element with the class \"base-btn\" and text content \"Base Button\".",[10,250,252],{"id":251},"properties","Properties",[15,254,255],{},"Now lets define a property variable, which will add some dynamic content to our component:",[64,257,258],{"lang":66,"filename":214},"\nexport class BaseButton extends LitElement {\n    \u002F\u002F New Property\n    @property({ type: String })\n    text = 'Base Button'\n \n    render() {\n        \u002F\u002F Interpolate the property into our template\n        return html`\n            \u003Cbutton class=\"base-btn\">${this.text}\u003C\u002Fbutton>\n        `;\n    }\n}\n",[15,260,261,262,265],{},"Now we've defined a property ",[84,263,264],{},"text"," of type String, with a default value of \"Base Button\". If we were to use our component in HTML like so:",[64,267,268],{"lang":127,"filename":128},"\n\u003Cbase-button text=\"Hello World\" \u002F>\n\u003C!-- output -->\n\u003Cbutton class=\"base-btn\">Hello World\u003C\u002Fbutton>\n",[15,270,271],{},"Congrats, you just created your first dynamic web component!",[10,273,275],{"id":274},"the-demo","The Demo",[15,277,278,279,282],{},"To see our work in action, we first need to register the component in the root file of our project, ",[84,280,281],{},"ui-library.ts",". Place the following code in the file:",[64,284,285],{"lang":66,"filename":281},"\nimport { BaseButton } from '.\u002Fsrc\u002FBaseButton.js';\n \nwindow.customElements.define('base-button', BaseButton);\n",[15,287,288,289,291,292,295],{},"Our custom element is now registered with the tag name ",[84,290,200],{},". Now we will change the contents of the file ",[84,293,294],{},"\u002Fdemo\u002Findex.html",", which is where we'll preview our changes in development:",[64,297,298],{"lang":127,"filename":294},"\n\u003C!doctype html>\n\u003Chtml lang=\"en-GB\">\n\u003C!-- ... -->\n\u003Cbody>\n    \u003Cdiv id=\"demo\">\u003C\u002Fdiv>\n    \u003Cscript type=\"module\">\n        import { html, render } from 'lit-html';\n        import '..\u002Fdist\u002Fui-library.js';\n \n        const title = 'Hello World!';\n \n        render(\n            html`\n                \u003Cbase-button text=\"${title}\">\u003C\u002Fbase-button>\n            `,\n            document.querySelector('#demo')\n        );\n    \u003C\u002Fscript>\n\u003C\u002Fbody>\n\u003C\u002Fhtml>\n",[15,300,301,302,305,306,309],{},"Now open your terminal, and run the command ",[84,303,304],{},"npm start"," (or ",[84,307,308],{},"yarn start",") to start up the development server. After a few seconds, a browser window will open and you should see this:",[188,311],{"format":190,"alt":312,"src":313,"width":193,"loading":194},"Web Component demo browser window","\u002Fimg\u002Fweb-components-demo.png",[10,315,317],{"id":316},"give-it-some-style","Give It Some Style",[15,319,320,321,323],{},"Now lets add some CSS to our button to make it look a bit prettier. To add styles to our Lit Web Component, we define a static variable ",[84,322,103],{}," at the top of our component class:",[64,325,326],{"lang":66,"filename":214},"\nexport class BaseButton extends LitElement {\n    static styles = css`\n        button.base-btn {\n            color: white;\n            background: #2a63bf;\n            border: 0px transparent;\n            border-radius: 0.5rem;\n            padding: 0.5rem 1rem;\n            box-shadow: 0px 2px 4px 2px rgba(0, 0, 0, 0.125);\n            transition: background 250ms ease-in-out;\n        }\n        button.base-btn:hover {\n            cursor: pointer;\n            background: #204b91;\n        }\n    `;\n    \u002F\u002F ...\n}\n",[15,328,329],{},"Now that we've added a pop of color and a cool hover effect, save the file and look back at the browser window to see your updated component:",[15,331,332],{},[333,334],"img",{"alt":335,"src":336},"Styled button web component demo","\u002Fimg\u002Fweb-components-css.gif",[10,338,340],{"id":339},"listening-for-events","Listening for Events",[15,342,343,344,347],{},"Since we're building a button, we're going to want it to perform some action when a user clicks the button. One way we can do this is to define an attribute ",[84,345,346],{},"@click"," in our render function on the button element, and pass it an event handler function:",[64,349,350],{"lang":66,"filename":214},"\nexport class BaseButton extends LitElement {\n    \u002F\u002F ...\n \n    render() {\n        \u002F\u002F Add @click attribute with this.onClick function\n        return html`\n            \u003Cbutton class=\"base-btn\" @click=\"${this.onClick}\">\n                ${this.text}\n            \u003C\u002Fbutton>\n        `;\n    }\n \n    \u002F\u002F Button click event handler\n    onClick() {\n        window.alert(`${this.text} has been clicked!`);\n    }\n}\n",[15,352,353,354,356],{},"Here, we're using the ",[84,355,264],{}," property to send an alert to the browser when the user clicks our button. Now, when you click the button in the demo window you should see this alert popup:",[188,358],{"format":190,"alt":359,"src":360,"width":361,"loading":194},"Web component click handler alert","\u002Fimg\u002Fweb-component-click.png","600px",[15,363,364,365,305,368,371,372,375],{},"Congratulations, now we have a good-looking, dynamic, and reusable Web Component that can be used in many different applications! To finalize our work, open a terminal and run the command ",[84,366,367],{},"npm run build",[84,369,370],{},"yarn build",") to build a production-ready version of our web component, with the output placed in the ",[84,373,374],{},"\u002Fdist"," directory.",[10,377,379,380,382],{"id":378},"using-base-button-in-vue","Using ",[84,381,200],{}," in Vue",[15,384,385,386,389,390,393],{},"Getting our new web component imported into a Vue application is actually pretty simple. In this example, I'm going to be starting with a new Vue project using ",[84,387,388],{},"vue create vue-web-components",". This will create a new project directory with our Vue application. Run ",[84,391,392],{},"cd vue-web-components"," in your terminal to navigate to the new directory.",[15,395,396,397,402,403,406],{},"To make sure our Vue application can render Web Components, we need to install the package ",[34,398,401],{"href":399,"rel":400},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002F@webcomponents\u002Fwebcomponentsjs",[38],"@webcomponents\u002Fwebcomponents",". To do this, run ",[84,404,405],{},"yarn add @webcomponents\u002Fwebcomponentsjs",".",[15,408,409,410,413,414,417],{},"Next, we need to import our production build of the Web Component into our Vue app. The easiest way to do this is by adding the following line to the ",[84,411,412],{},"dependencies"," section of our ",[84,415,416],{},"package.json"," file:",[64,419,421],{"lang":420,"filename":416},"json","\n\"ui-library\": \"..\u002Fui-library\",\n",[15,423,424,425,427,428,430,431,434],{},"This will add our Web Components project as a Node dependency named ",[84,426,176],{},". Now, we need to actually import ",[84,429,176],{}," in our Vue application - this will be done in the file ",[84,432,433],{},"\u002Fsrc\u002Fmain.js",", which is generally the entry-point of all Vue applications.",[64,436,438],{"lang":437,"filename":433},"js","\nimport Vue from 'vue'\nimport App from '.\u002FApp.vue'\n \n\u002F\u002F Import the ui-library package with our base-button Web Component\nimport 'ui-library\u002Fdist\u002Fui-library.js'\n \n\u002F\u002F IMPORTANT: Configure Vue to ignore any elements named base-button.\n\u002F\u002F   If this isn't set, Vue will try to render base-button as a Vue \n\u002F\u002F   component which will cause an error.\nVue.config.ignoredElements = ['base-button']\n \nVue.config.productionTip = false\nnew Vue({\n    render: h => h(App),\n}).$mount('#app')\n",[15,440,441,442,444,445,448],{},"Now our ",[84,443,200],{}," Web Component will be available to any Vue component in our application. To test this out, open the ",[84,446,447],{},"\u002Fsrc\u002FApp.vue"," file and write the following code:",[64,450,451],{"lang":127,"filename":447},"\n\u003Ctemplate>\n    \u003Cdiv id=\"app\">\n        \u003Ch1>Web Components\u003C\u002Fh1>\n        \u003C!-- Our web component! -->\n        \u003Cbase-button text=\"Hello Vue\">\u003C\u002Fbase-button>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n \n\u003Cscript>\nexport default {\n    name: 'App',\n}\n\u003C\u002Fscript>\n \n\u003Cstyle>\n#app {\n    font-family: Avenir, Helvetica, Arial, sans-serif;\n    -webkit-font-smoothing: antialiased;\n    -moz-osx-font-smoothing: grayscale;\n    text-align: center;\n    color: #2c3e50;\n    margin-top: 60px;\n}\n\u003C\u002Fstyle>\n",[15,453,454,455,458,459,462],{},"To start our Vue application development server, run the command ",[84,456,457],{},"yarn serve"," in your terminal and navigate to ",[84,460,461],{},"http:\u002F\u002Flocalhost:8080"," in your browser. If everything went well, you should see your web component rendered properly!",[188,464],{"format":190,"alt":465,"src":466,"width":361,"loading":194},"Vue dev server running with Base Button web component","\u002Fimg\u002Fvue-web-component.png",[15,468,469,470,406],{},"Now you can create more web components in the project we created, and reuse them across your Vue application. You can also use them in other applications, such as React or Angular which would require somewhat different configuration. I hope this tutorial has been helpful! If you have questions, you can contact me through any of the methods listed on my ",[34,471,473],{"href":472},"\u002Fcontact","contact page",{"title":475,"searchDepth":476,"depth":476,"links":477},"",2,[478,479,480,481,482,483,484,485,486],{"id":12,"depth":476,"text":13},{"id":20,"depth":476,"text":21},{"id":58,"depth":476,"text":59},{"id":135,"depth":476,"text":136},{"id":251,"depth":476,"text":252},{"id":274,"depth":476,"text":275},{"id":316,"depth":476,"text":317},{"id":339,"depth":476,"text":340},{"id":378,"depth":476,"text":487},"Using base-button in Vue","2021-05-13","md","lit-vue.png","Lit and Vue logos on a light background","web components,web,components,typescript,lit,lit elements,vue,vuejs,javascript,typescript,shadow dom,tutorial,example,vue 2,vue 3,lit.dev",{},true,"\u002Fblog\u002Flit-web-components-tutorial",{"title":5,"description":475},"blog\u002Flit-web-components-tutorial","How lit is it, really?",[500,501,502],"web-components","vue","tutorials","uuhD7uZApeqtDxniXoNjOG62525ZKL7a5z-s8tZFWQk",1776925020722]