[{"data":1,"prerenderedAt":926},["ShallowReactive",2],{"blog-tag-tutorials":3},[4,452],{"id":5,"title":6,"body":7,"date":438,"description":428,"extension":439,"featuredImage":440,"featuredImageAlt":428,"keywords":441,"meta":442,"navigation":443,"path":444,"seo":445,"stem":446,"subtitle":447,"tags":448,"__hash__":451},"blog\u002Fblog\u002Femulating-component-behavior-with-javascript-closures.md","Emulating Component Behavior with JavaScript Closures",{"type":8,"value":9,"toc":427},"minimark",[10,15,27,42,45,52,56,63,101,104,111,115,126,131,169,191,195,198,202,219,236,244,251,255,273,277,280,337,344,348,352,355,362,365,369,372,397,400,405,408,416,424],[11,12,14],"h2",{"id":13},"introduction","Introduction",[16,17,18,19,26],"p",{},"Recently, in an effort to level-up my advanced JavaScript knowledge, I've been diving into the \"wonderful\" world of Closures. If you've done any research on how Closures work in JavaScript for yourself, I'm sure you understand that it can be very confusing at first. For reference, here is the definition of a Closure from ",[20,21,25],"a",{"href":22,"rel":23},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FJavaScript\u002FClosures",[24],"nofollow","the MDN Docs",":",[28,29,30],"blockquote",{},[16,31,32,33,37,38,41],{},"A ",[34,35,36],"strong",{},"closure"," is the combination of a function bundled together (enclosed) with references to its surrounding state (the ",[34,39,40],{},"lexical environment","). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.",[16,43,44],{},"Got it? Me neither.",[16,46,47,48,51],{},"I think an easier way of understanding Closures is to think of them as a way of manipulating the way variable scoping works in JavaScript to create the effect of ",[34,49,50],{},"private variables or functions",". Take this code snippet for example:",[11,53,55],{"id":54},"example","Example",[57,58,62],"code-block",{"lang":59,"filename":60,"highlight-lines":61},"js","Closure.js","7","\nfunction makeCalculator(x) {\n  return function add(y) {\n    return x + y\n  }\n}\n \nconst calculateFive = makeCalculator(5);\n \nconsole.log(calculateFive(4))\n\u002F\u002F Prints \"9\"\nconsole.log(calculateFive.add(4))\n\u002F\u002F TypeError - calculateFive.add is not a function\n",[16,64,65,66,70,71,74,75,78,79,81,82,85,86,89,90,92,93,96,97,100],{},"So we now have a function called ",[67,68,69],"code",{},"makeCalculator"," that takes a base value ",[67,72,73],{},"x",", and returns a function that takes another parameter ",[67,76,77],{},"y"," and adds it to ",[67,80,73],{},". Note that, while the function ",[67,83,84],{},"add(y)"," exists in the function block of ",[67,87,88],{},"makeCalculator(x)",", it is not accessible by any functions created with ",[67,91,69],{},". Basically, we have a Calculator component generator that ",[34,94,95],{},"obfuscates its addition logic"," from everything else, but is still ",[34,98,99],{},"clear in what it does"," from an outside perspective.",[16,102,103],{},"So, this is cool and all, but how can we actually use Closures in a practical application?",[16,105,106,107,110],{},"After thinking about this unique behavior for a while (talking to my cat about it), I figured out that we can ",[34,108,109],{},"emulate the behavior of Components",", similar to frontend JavaScript frameworks like React, using this function pattern! Allow me to walk you through my thought process.",[11,112,114],{"id":113},"initializing-a-component","Initializing a Component",[16,116,117,118,120,121,125],{},"The first thing we need to do is write a function that, similar to ",[67,119,69],{},", contains some data or functions related to our component, and returns a subset of those. And the first thing that we do with any variable, Class object, or component is ",[122,123,124],"em",{},"initialize it."," So lets write a function that does just that:",[57,127,130],{"lang":59,"filename":128,"highlight-lines":129},"Component.js","16","\nconst createComponent = function(props) {\n  const propsMap = new Map();\n \n  const render = (props) => {\n    \u002F\u002F Some rendering logic\n  }\n \n  return {\n    init() {\n      \u002F\u002F Some initialization stuff\n    }\n  }\n}\n \n\u002F\u002F NewComponent is assigned the value returned by createComponent\nconst NewComponent = createComponent({ name: 'Dan' })\n \nNewComponent.init()\nNewComponent.render() \u002F\u002F This will result in a TypeError!\n",[16,132,133,134,137,138,141,142,144,145,148,149,151,152,154,155,157,158,160,161,164,165,168],{},"Now I've created the function ",[67,135,136],{},"createComponent",", which returns a set of functions (for now, just ",[67,139,140],{},"init()","), and also includes some constants that are accessible only to the functions executed inside the scope of ",[67,143,136],{},". Then, I define a new variable ",[67,146,147],{},"NewComponent"," that is assigned the return value of ",[67,150,136],{},", which will be an object containing ",[67,153,140],{},". After ",[67,156,147],{}," is initialized, I can call the ",[67,159,140],{}," function from it, but I won't be able to directly access the values of ",[67,162,163],{},"propsMap"," and ",[67,166,167],{},"render",".",[16,170,171,172,175,176,179,180,182,183,186,187,190],{},"So we got something going here, but it doesn't really ",[122,173,174],{},"do anything yet",". What I ",[122,177,178],{},"want it to do"," is, on calling ",[67,181,140],{},", to ",[34,184,185],{},"render a new HTML element"," as a child of some other element, which in this case will be a ",[67,188,189],{},"\u003Cdiv>"," with an ID of \"app\". I also want those props I'm passing to be used in some way in that render.",[11,192,194],{"id":193},"creating-a-new-dom-element","Creating a New DOM Element",[16,196,197],{},"Here's how I accomplished this task:",[57,199,201],{"lang":59,"filename":128,"highlight-lines":200},"6-15","\nconst createComponent = function(props) {\n  \u002F\u002F Assign props value to propsMap so we can properly update them later\n  var propsMap = new Map(Object.entries(props));\n \n  const render = () => {\n    var name = propsMap.get('name') || ''\n    var app = document.getElementById(\"app\");\n    var el = document.createElement(\"div\");\n \n    \u002F\u002F Assign an ID so I can reference it somewhere else\n    el.id = \"new-component\";\n    el.innerHTML = name;\n \n    \u002F\u002F Attach the new element to the root \"app\" node\n    app.appendChild(el);\n  };\n \n  return {\n    init() {\n      render(props); \u002F\u002F Called on `init`, but still not available to NewComponent\n    }\n  };\n};\n \n\u002F\u002F Create two independent components\nconst ComponentOne = createComponent({ name: 'Dan' })\nComponentOne.init()\n \nconst ComponentTwo = createComponent({ name: 'John' })\nComponentOne.init()\n",[16,203,204,205,207,208,211,212,215,216,218],{},"Now I've fleshed out the ",[67,206,167],{}," function to actually create a new element in the DOM, and gave it an ID value so that we can reference it later through the ",[67,209,210],{},"document"," object. I also created an additional ",[67,213,214],{},"Map"," object named ",[67,217,163],{}," so that we can mutate props freely later on.",[16,220,221,222,224,225,227,228,231,232,235],{},"The meat of the ",[67,223,167],{}," function is that it's getting the app element as a mounting point, creating a new ",[67,226,189],{}," element with a unique ID, and set its ",[67,229,230],{},"innerHTML"," to the name string we pass in as props. Now, the result of running ",[67,233,234],{},"NewComponent.init()"," is shown below:",[237,238],"nuxt-picture",{"format":239,"alt":240,"src":241,"width":242,"loading":243},"webp","Screenshot of the result of the current createComponent function","\u002Fimg\u002Fclosure-component-1.png","400px","lazy",[16,245,246,247,250],{},"The result is that we have two independent components that render in the DOM, and are unaffected by each other. ",[122,248,249],{},"Absolutely bonkers!!!"," Nothing really crazy yet, but it's a start.",[11,252,254],{"id":253},"make-em-dynamic","Make 'em Dynamic",[16,256,257,258,261,262,264,265,268,269,272],{},"The next step is to make our components dynamic, so that when we change the value of ",[67,259,260],{},"props"," the DOM element created by ",[67,263,167],{}," will be updated accordingly. So we'll need another function available publically through ",[67,266,267],{},"ComponentOne"," or ",[67,270,271],{},"ComponentTwo"," so that we can update those props and trigger a re-render. We're also going to check the DOM before rendering to see if the component node already exists, to avoid duplicate nodes.",[57,274,276],{"lang":59,"filename":128,"highlight-lines":275},"6-9,17,19,50-52","\nconst createComponent = function (props) {\n  var propsMap = new Map();\n \n  \u002F\u002F Allows us to mutate props freely\n  const setPropValues = (p) => {\n    const propEntries = Object.entries(p);\n    propEntries.forEach(([key, value]) => {\n      propsMap.set(key, value);\n    });\n  };\n \n  \u002F\u002F Returns the current DOM node, or creates a new one and returns it.\n  const getRootNode = () => {\n    var id = propsMap.get(\"id\"); \u002F\u002F new prop for element IDs\n \n    \u002F\u002F Check if the element exists in the DOM\n    const existing = document.getElementById(id);\n \n    if (!existing) {\n      \u002F\u002F Append the root node to our app element\n      var app = document.getElementById(\"app\");\n      var el = document.createElement(\"div\");\n      el.id = id;\n \n      app.appendChild(el);\n \n      return el; \u002F\u002F Return the new element...\n    }\n \n    return existing; \u002F\u002F Otherwise, return the existing one.\n  };\n \n  const render = () => {\n    var name = propsMap.get(\"name\") || \"\";\n    var el = getRootNode();\n \n    el.innerHTML = name;\n  };\n \n  \u002F\u002F Update props to values from p and render in DOM\n  const renderWithProps = (p) => {\n    setPropValues(p);\n    render();\n  };\n \n  return {\n    init() {\n      renderWithProps(props); \u002F\u002F props from creatComponent()\n    },\n    update(newProps) {\n      renderWithProps(newProps); \u002F\u002F props from update()\n    }\n  };\n};\n",[16,278,279],{},"So, there's a few things going on here now.",[281,282,283,294,304,311,322],"ul",{},[284,285,286,287,290,291,293],"li",{},"There's a new function ",[67,288,289],{},"setPropValues()"," that updates the ",[67,292,163],{}," Map object with whatever values we provide it.",[284,295,296,297,300,301,303],{},"Another new function named ",[67,298,299],{},"renderWithProps()",", that assigns prop values using ",[67,302,289],{}," and renders the component to the DOM.",[284,305,306,307,310],{},"Yet another new function ",[67,308,309],{},"getRootNode()",", which checks the DOM to see if the component has already been rendered, and otherwise creates and attaches a new node to the DOM. Then it returns either the new or existing node.",[284,312,313,314,317,318,321],{},"I updated the ",[67,315,316],{},"render()"," function to call ",[67,319,320],{},"getRootNode"," to avoid duplicate nodes being attached to the DOM when the component updates.",[284,323,324,325,328,329,332,333,336],{},"Last, I added the function ",[67,326,327],{},"update(newProps)"," to the return value of ",[67,330,331],{},"createComponent()",", which takes a parameter ",[67,334,335],{},"newProps"," and re-renders, making our component dynamic!",[16,338,339,340,343],{},"To test the encapsulation of props and behavior of rendering vs. updating these components, I added a couple of buttons to the DOM that call ",[67,341,342],{},"update({ ...newProps })"," on click:",[57,345,347],{"lang":59,"filename":128,"highlight-lines":346},"11-15","\nconst ComponentOne = createComponent({\n  id: \"component-one\", \u002F\u002F new prop\n  name: \"Dan\",\n});\nComponentOne.init();\n \n\u002F\u002F (ComponentTwo init code here)\n \nconst btnOne = document.createElement(\"button\");\nbtnOne.innerHTML = \"Update One\";\nbtnOne.addEventListener(\"click\", function () {\n  ComponentOne.update({\n    name: \"Peter\"\n  });\n});\n \n\u002F\u002F btnTwo code here that updates ComponentTwo\n",[11,349,351],{"id":350},"the-moment-of-truth","The Moment of Truth",[16,353,354],{},"Now, let's see if it actually works...",[16,356,357],{},[358,359],"img",{"alt":360,"src":361},"Screen recording of final result","\u002Fimg\u002Fclosure-component-result.gif",[16,363,364],{},"And it does! 🥳",[11,366,368],{"id":367},"how-closure-makes-this-work","How Closure Makes This Work",[16,370,371],{},"So, you might be thinking to yourself, \"Sure, you've got some functions and some variables and functions within those functions, but I don't understand how closures fit into this.\" Allow me to explain.",[16,373,374,375,377,378,164,380,382,383,386,387,390,391,164,393,396],{},"The Closure here exists in ",[67,376,136],{}," and the functions contained and returned by it. We have variables and functions, like ",[67,379,163],{},[67,381,299],{},", which are used in the internal logic of our Component, but are not accessible by any variables that are assigned the resulting value of the expression ",[67,384,385],{},"createComponent(props)",". We ",[122,388,389],{},"specifically"," allow only certain functions, such as ",[67,392,140],{},[67,394,395],{},"update()"," to be called outside the function. This behavior keeps the internal workings of the Component hidden from its callers, but still provides all the necessary interfaces to create a dynamic component.",[16,398,399],{},"The code snippet below, similar to the first example, demonstrates this distinction between our \"public\" and \"private\" values:",[57,401,404],{"lang":59,"filename":402,"highlight-lines":403},"Result.js","7,10-12,15","\nconst component = createComponent({\n  id: 'example',\n  name: 'Dan'\n})\n \n\u002F\u002F Renders a new node in the DOM with the text \"Dan\"\ncomponent.init()\n \n\u002F\u002F Updates the props of our component and updates the existing DOM node\ncomponent.update({\n  name: 'Peter'\n})\n \n\u002F\u002F Results in a TypeError - component.render is not a function!\ncomponent.render()\n",[16,406,407],{},"Our end result is essentially a super-watered down Virtual DOM API, but I think it serves as a good example for how a Closure can be implemented in JavaScript in a way that is both useful and clear in its implementation. I hope this tutorial has been helpful for you, whether you had no idea what a Closure was, or if you already knew about Closures but have never seen how it's used.",[16,409,410,411,168],{},"Here's a link to my code ",[20,412,415],{"href":413,"rel":414},"https:\u002F\u002Fgithub.com\u002Fdvalinotti\u002Fcomponents-with-closure",[24],"repo on GitHub",[16,417,418,419,168],{},"If you'd like to mess around with this yourself, here is a link to the ",[20,420,423],{"href":421,"rel":422},"https:\u002F\u002Fcodesandbox.io\u002Fs\u002Ffunny-cannon-o0p75?file=\u002Fsrc\u002Findex.js",[24],"project on CodeSandbox",[16,425,426],{},"Thanks for reading! 😊",{"title":428,"searchDepth":429,"depth":429,"links":430},"",2,[431,432,433,434,435,436,437],{"id":13,"depth":429,"text":14},{"id":54,"depth":429,"text":55},{"id":113,"depth":429,"text":114},{"id":193,"depth":429,"text":194},{"id":253,"depth":429,"text":254},{"id":350,"depth":429,"text":351},{"id":367,"depth":429,"text":368},"2021-06-03","md","closure-post-thumbnail.jpg","javascript,js,vanilla,vanilla javascript,components,function,var,const,let,closure,closures,scope,variable scope,tutorial",{},true,"\u002Fblog\u002Femulating-component-behavior-with-javascript-closures",{"title":6,"description":428},"blog\u002Femulating-component-behavior-with-javascript-closures","Read along as I create a poor man's Virtual DOM API.",[449,450],"javascript","tutorials","HLmsIjPXOiSLHRGuHnWmConT1PE6fX9u6OkUod3fJcI",{"id":453,"title":454,"body":455,"date":913,"description":428,"extension":439,"featuredImage":914,"featuredImageAlt":915,"keywords":916,"meta":917,"navigation":443,"path":918,"seo":919,"stem":920,"subtitle":921,"tags":922,"__hash__":925},"blog\u002Fblog\u002Flit-web-components-tutorial.md","How to Create Reusable Web Components with Lit and Vue",{"type":8,"value":456,"toc":901},[457,459,462,466,469,481,486,495,497,500,509,512,556,559,564,567,571,574,618,621,626,637,643,647,654,657,666,669,679,683,686,689,696,699,702,706,713,716,726,729,740,744,748,754,757,760,766,770,777,780,786,791,805,812,823,835,846,850,863,866,876,879,890,894],[11,458,14],{"id":13},[16,460,461],{},"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.",[11,463,465],{"id":464},"the-scenario","The Scenario",[16,467,468],{},"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?",[16,470,471,472,475,476,26],{},"One answer to this question would be ",[34,473,474],{},"Web Components",". Here is a quick definition for Web Components from ",[20,477,480],{"href":478,"rel":479},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FWeb_Components",[24],"MDN",[28,482,483],{},[16,484,485],{},"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.",[16,487,488,489,494],{},"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 ",[20,490,493],{"href":491,"rel":492},"https:\u002F\u002Flit.dev\u002F",[24],"Lit framework"," and implement it in a Vue.js app.",[11,496,55],{"id":54},[16,498,499],{},"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:",[57,501,504,505,508],{"lang":502,"filename":503},"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`",[16,506,507],{},"Hello, ${this.name}!","`;\n  }\n}\n",[16,510,511],{},"Lets break this sample code down step-by-step:",[513,514,515,522,529,532,539,546],"ol",{},[284,516,517,518,521],{},"We import some libraries from the ",[67,519,520],{},"lit"," NPM library",[284,523,524,525,528],{},"The decorator ",[67,526,527],{},"@customElement('simple-greeting')"," declares that we are creating a new custom HTML element, with the tag name \"simple-greeting\"",[284,530,531],{},"We define a class named SimpleGreeting that extends the LitElement class, which will represent our new custom element.",[284,533,534,535,538],{},"A static variable ",[67,536,537],{},"styles"," is defined, which provides CSS styles for our HTML elements.",[284,540,541,542,545],{},"The property ",[67,543,544],{},"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.",[284,547,548,549,551,552,555],{},"Last, we have a render function that returns an HTML template that populates the DOM when our custom element renders. The property ",[67,550,544],{}," is then interpolated into the HTML template to be rendered in a ",[67,553,554],{},"\u003Cp>"," tag.",[16,557,558],{},"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:",[57,560,563],{"lang":561,"filename":562},"html","Example.html","\n\u003Csimple-greeting name=\"Mr. Jones\" \u002F>\n\u003C!-- output: \u003Cp>Hello, Mr. Jones!\u003C\u002Fp> -->\n",[16,565,566],{},"Now lets create a web component of our own!",[11,568,570],{"id":569},"getting-started","Getting Started",[16,572,573],{},"Lets begin by creating a new web components project on our local machine.",[513,575,576,583,590,615],{},[284,577,578,579,582],{},"Open a terminal window and ",[67,580,581],{},"cd"," to the directory you want to create your project in.",[284,584,585,586,589],{},"Run the command ",[67,587,588],{},"npm init @open-wc"," - this will scaffold a new Web Components project for us quickly.",[284,591,592,593,596,597,600,601,600,604,607,608,596,611,614],{},"Select the options ",[67,594,595],{},"Scaffold a new project"," -> ",[67,598,599],{},"Web Component"," -> Select ",[67,602,603],{},"Linting",[67,605,606],{},"Yes"," to use TypeScript -> ",[67,609,610],{},"ui-library",[67,612,613],{},"yes"," -> Choose yarn or npm, whatever your preference.",[284,616,617],{},"Once the command is finished, open the newly-created directory in your preferred IDE (in my case, VS Code).",[16,619,620],{},"Now you should have a project directory that looks like this:",[237,622],{"format":239,"alt":623,"src":624,"width":625,"loading":243},"Project directory structure","\u002Fimg\u002Fweb-components-project-dir.png","475px",[16,627,628,629,632,633,636],{},"Now lets create our first Web Component, named ",[67,630,631],{},"base-button"," to represent a regular button in our UI Library. Create a file in the ",[67,634,635],{},"src"," directory named \"BaseButton.ts\", then open it up in your code editor.",[16,638,639,640,642],{},"The first thing we need to do is import some modules from the ",[67,641,520],{}," package that will help us write our component:",[57,644,646],{"lang":502,"filename":645},"\u002Fsrc\u002FBaseButton.ts","\nimport { html, css, LitElement, property } from  'lit-element';\n",[16,648,649,650,653],{},"Next, we'll declare our component similar to what was shown earlier in the ",[67,651,652],{},"simple-greeting"," example:",[57,655,656],{"lang":502,"filename":645},"\nexport class BaseButton extends LitElement {\n    \u002F\u002F ...\n}\n",[16,658,659,660,662,663,665],{},"Now we have a custom element named \"BaseButton\", whose HTML tag will be named ",[67,661,631],{},". Next, we'll create a ",[67,664,316],{}," function which will return the HTML rendered by our custom element:",[57,667,668],{"lang":502,"filename":645},"\nexport class BaseButton extends LitElement {\n    render() {\n        return html`\n            \u003Cbutton class=\"base-btn\">Base Button\u003C\u002Fbutton>\n        `;\n    }\n}\n",[16,670,671,672,674,675,678],{},"When we use our ",[67,673,631],{}," component in HTML, it will now render a ",[67,676,677],{},"button"," element with the class \"base-btn\" and text content \"Base Button\".",[11,680,682],{"id":681},"properties","Properties",[16,684,685],{},"Now lets define a property variable, which will add some dynamic content to our component:",[57,687,688],{"lang":502,"filename":645},"\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",[16,690,691,692,695],{},"Now we've defined a property ",[67,693,694],{},"text"," of type String, with a default value of \"Base Button\". If we were to use our component in HTML like so:",[57,697,698],{"lang":561,"filename":562},"\n\u003Cbase-button text=\"Hello World\" \u002F>\n\u003C!-- output -->\n\u003Cbutton class=\"base-btn\">Hello World\u003C\u002Fbutton>\n",[16,700,701],{},"Congrats, you just created your first dynamic web component!",[11,703,705],{"id":704},"the-demo","The Demo",[16,707,708,709,712],{},"To see our work in action, we first need to register the component in the root file of our project, ",[67,710,711],{},"ui-library.ts",". Place the following code in the file:",[57,714,715],{"lang":502,"filename":711},"\nimport { BaseButton } from '.\u002Fsrc\u002FBaseButton.js';\n \nwindow.customElements.define('base-button', BaseButton);\n",[16,717,718,719,721,722,725],{},"Our custom element is now registered with the tag name ",[67,720,631],{},". Now we will change the contents of the file ",[67,723,724],{},"\u002Fdemo\u002Findex.html",", which is where we'll preview our changes in development:",[57,727,728],{"lang":561,"filename":724},"\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",[16,730,731,732,735,736,739],{},"Now open your terminal, and run the command ",[67,733,734],{},"npm start"," (or ",[67,737,738],{},"yarn start",") to start up the development server. After a few seconds, a browser window will open and you should see this:",[237,741],{"format":239,"alt":742,"src":743,"width":625,"loading":243},"Web Component demo browser window","\u002Fimg\u002Fweb-components-demo.png",[11,745,747],{"id":746},"give-it-some-style","Give It Some Style",[16,749,750,751,753],{},"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 ",[67,752,537],{}," at the top of our component class:",[57,755,756],{"lang":502,"filename":645},"\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",[16,758,759],{},"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:",[16,761,762],{},[358,763],{"alt":764,"src":765},"Styled button web component demo","\u002Fimg\u002Fweb-components-css.gif",[11,767,769],{"id":768},"listening-for-events","Listening for Events",[16,771,772,773,776],{},"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 ",[67,774,775],{},"@click"," in our render function on the button element, and pass it an event handler function:",[57,778,779],{"lang":502,"filename":645},"\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",[16,781,782,783,785],{},"Here, we're using the ",[67,784,694],{}," 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:",[237,787],{"format":239,"alt":788,"src":789,"width":790,"loading":243},"Web component click handler alert","\u002Fimg\u002Fweb-component-click.png","600px",[16,792,793,794,735,797,800,801,804],{},"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 ",[67,795,796],{},"npm run build",[67,798,799],{},"yarn build",") to build a production-ready version of our web component, with the output placed in the ",[67,802,803],{},"\u002Fdist"," directory.",[11,806,808,809,811],{"id":807},"using-base-button-in-vue","Using ",[67,810,631],{}," in Vue",[16,813,814,815,818,819,822],{},"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 ",[67,816,817],{},"vue create vue-web-components",". This will create a new project directory with our Vue application. Run ",[67,820,821],{},"cd vue-web-components"," in your terminal to navigate to the new directory.",[16,824,825,826,831,832,168],{},"To make sure our Vue application can render Web Components, we need to install the package ",[20,827,830],{"href":828,"rel":829},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002F@webcomponents\u002Fwebcomponentsjs",[24],"@webcomponents\u002Fwebcomponents",". To do this, run ",[67,833,834],{},"yarn add @webcomponents\u002Fwebcomponentsjs",[16,836,837,838,841,842,845],{},"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 ",[67,839,840],{},"dependencies"," section of our ",[67,843,844],{},"package.json"," file:",[57,847,849],{"lang":848,"filename":844},"json","\n\"ui-library\": \"..\u002Fui-library\",\n",[16,851,852,853,855,856,858,859,862],{},"This will add our Web Components project as a Node dependency named ",[67,854,610],{},". Now, we need to actually import ",[67,857,610],{}," in our Vue application - this will be done in the file ",[67,860,861],{},"\u002Fsrc\u002Fmain.js",", which is generally the entry-point of all Vue applications.",[57,864,865],{"lang":59,"filename":861},"\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",[16,867,868,869,871,872,875],{},"Now our ",[67,870,631],{}," Web Component will be available to any Vue component in our application. To test this out, open the ",[67,873,874],{},"\u002Fsrc\u002FApp.vue"," file and write the following code:",[57,877,878],{"lang":561,"filename":874},"\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",[16,880,881,882,885,886,889],{},"To start our Vue application development server, run the command ",[67,883,884],{},"yarn serve"," in your terminal and navigate to ",[67,887,888],{},"http:\u002F\u002Flocalhost:8080"," in your browser. If everything went well, you should see your web component rendered properly!",[237,891],{"format":239,"alt":892,"src":893,"width":790,"loading":243},"Vue dev server running with Base Button web component","\u002Fimg\u002Fvue-web-component.png",[16,895,896,897,168],{},"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 ",[20,898,900],{"href":899},"\u002Fcontact","contact page",{"title":428,"searchDepth":429,"depth":429,"links":902},[903,904,905,906,907,908,909,910,911],{"id":13,"depth":429,"text":14},{"id":464,"depth":429,"text":465},{"id":54,"depth":429,"text":55},{"id":569,"depth":429,"text":570},{"id":681,"depth":429,"text":682},{"id":704,"depth":429,"text":705},{"id":746,"depth":429,"text":747},{"id":768,"depth":429,"text":769},{"id":807,"depth":429,"text":912},"Using base-button in Vue","2021-05-13","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",{},"\u002Fblog\u002Flit-web-components-tutorial",{"title":454,"description":428},"blog\u002Flit-web-components-tutorial","How lit is it, really?",[923,924,450],"web-components","vue","uuhD7uZApeqtDxniXoNjOG62525ZKL7a5z-s8tZFWQk",1776925021381]