[{"data":1,"prerenderedAt":2713},["ShallowReactive",2],{"home-blog":3,"home-projects":2421},[4,452,926],{"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",{"id":927,"title":928,"body":929,"date":2407,"description":2408,"extension":439,"featuredImage":2409,"featuredImageAlt":2410,"keywords":2411,"meta":2412,"navigation":443,"path":2413,"seo":2414,"stem":2415,"subtitle":2416,"tags":2417,"__hash__":2420},"blog\u002Fblog\u002Fnode-cli-tutorial.md","Creating a CLI tool using Node.js",{"type":8,"value":930,"toc":2399},[931,939,941,946,961,967,971,974,1259,1266,1269,1272,1494,1501,1505,1512,1846,1860,1867,2336,2372,2376,2388,2395],[16,932,933,934,935,938],{},"In an attempt to learn something new about Node.js, and to improve my productivity, I began developing a CLI tool for generating React components. This would be very useful for the Next.js project that I talked about in my last post, as I could customize the file structure to the pattern I’ve already implemented (Model, View, ViewModel-ish).","  Through the use of the ",[67,936,937],{},"oclif"," CLI framework, it was actually pretty easy to not only develop this tool, but also to publish it on NPM so that my team members could utilize it as well. I decided to create this tutorial to share my experience and hopefully help you write tools that can make you more productive.",[11,940,570],{"id":569},[942,943,945],"h3",{"id":944},"initialize-the-oclif-project","Initialize the Oclif project",[16,947,948,949,952,953,956,957,960],{},"The easiest way to get a new project started is by using npx:\n",[67,950,951],{},"npx oclif single [project name]","\nYou will then be prompted with several configuration options for your project. For example, in this tutorial I chose to use TypeScript. This will create a folder for your project with all of the required configuration.  Once you’re done, cd into the new directory and run\n",[67,954,955],{},"npm install","  or ",[67,958,959],{},"yarn install"," to install the required dependencies.",[16,962,963,964],{},"To make sure that everything is situated correctly, run your CLI tool:\n",[67,965,966],{},".\u002Fbin\u002Frun",[942,968,970],{"id":969},"define-the-required-arguments","Define the required arguments",[16,972,973],{},"Our freshly-generated project gives us predefined variables to store our arguments and flags related to our cli:",[975,976,980],"pre",{"className":977,"code":978,"filename":979,"language":502,"meta":428,"style":428},"language-ts shiki shiki-themes github-light github-dark","\u002F\u002F *** Add these imports to the beginning of the file! ***\nimport * as inquirer from \"inquirer\";\nimport * as fs from 'fs';\nimport cli from 'cli-ux';\nimport { createCipheriv, randomBytes, scryptSync } from 'crypto';\n\nclass Crypto extends Command {\n    static description = 'describe the command here';\n\n    static flags = {\n        \u002F\u002F add --version flag to show CLI version\n        version: flags.version({char: 'v'}),\n        help: flags.help({char: 'h'}),\n        \u002F\u002F flag with a value (-n, --name=VALUE)\n        name: flags.string({char: 'n', description: 'name to print'}),\n        \u002F\u002F flag with no value (-f, --force)\n        force: flags.boolean({char: 'f'}),\n    };\n\n    static args = [{name: 'file'}];\n    ...\n}\n","\u002Fsrc\u002Findex.ts",[67,981,982,991,1018,1037,1052,1067,1073,1092,1110,1115,1127,1133,1151,1167,1173,1195,1201,1217,1223,1228,1247,1253],{"__ignoreMap":428},[983,984,987],"span",{"class":985,"line":986},"line",1,[983,988,990],{"class":989},"sJ8bj","\u002F\u002F *** Add these imports to the beginning of the file! ***\n",[983,992,993,997,1001,1004,1008,1011,1015],{"class":985,"line":429},[983,994,996],{"class":995},"szBVR","import",[983,998,1000],{"class":999},"sj4cs"," *",[983,1002,1003],{"class":995}," as",[983,1005,1007],{"class":1006},"sVt8B"," inquirer ",[983,1009,1010],{"class":995},"from",[983,1012,1014],{"class":1013},"sZZnC"," \"inquirer\"",[983,1016,1017],{"class":1006},";\n",[983,1019,1021,1023,1025,1027,1030,1032,1035],{"class":985,"line":1020},3,[983,1022,996],{"class":995},[983,1024,1000],{"class":999},[983,1026,1003],{"class":995},[983,1028,1029],{"class":1006}," fs ",[983,1031,1010],{"class":995},[983,1033,1034],{"class":1013}," 'fs'",[983,1036,1017],{"class":1006},[983,1038,1040,1042,1045,1047,1050],{"class":985,"line":1039},4,[983,1041,996],{"class":995},[983,1043,1044],{"class":1006}," cli ",[983,1046,1010],{"class":995},[983,1048,1049],{"class":1013}," 'cli-ux'",[983,1051,1017],{"class":1006},[983,1053,1055,1057,1060,1062,1065],{"class":985,"line":1054},5,[983,1056,996],{"class":995},[983,1058,1059],{"class":1006}," { createCipheriv, randomBytes, scryptSync } ",[983,1061,1010],{"class":995},[983,1063,1064],{"class":1013}," 'crypto'",[983,1066,1017],{"class":1006},[983,1068,1070],{"class":985,"line":1069},6,[983,1071,1072],{"emptyLinePlaceholder":443},"\n",[983,1074,1076,1079,1083,1086,1089],{"class":985,"line":1075},7,[983,1077,1078],{"class":995},"class",[983,1080,1082],{"class":1081},"sScJk"," Crypto",[983,1084,1085],{"class":995}," extends",[983,1087,1088],{"class":1081}," Command",[983,1090,1091],{"class":1006}," {\n",[983,1093,1095,1098,1102,1105,1108],{"class":985,"line":1094},8,[983,1096,1097],{"class":995},"    static",[983,1099,1101],{"class":1100},"s4XuR"," description",[983,1103,1104],{"class":995}," =",[983,1106,1107],{"class":1013}," 'describe the command here'",[983,1109,1017],{"class":1006},[983,1111,1113],{"class":985,"line":1112},9,[983,1114,1072],{"emptyLinePlaceholder":443},[983,1116,1118,1120,1123,1125],{"class":985,"line":1117},10,[983,1119,1097],{"class":995},[983,1121,1122],{"class":1100}," flags",[983,1124,1104],{"class":995},[983,1126,1091],{"class":1006},[983,1128,1130],{"class":985,"line":1129},11,[983,1131,1132],{"class":989},"        \u002F\u002F add --version flag to show CLI version\n",[983,1134,1136,1139,1142,1145,1148],{"class":985,"line":1135},12,[983,1137,1138],{"class":1006},"        version: flags.",[983,1140,1141],{"class":1081},"version",[983,1143,1144],{"class":1006},"({char: ",[983,1146,1147],{"class":1013},"'v'",[983,1149,1150],{"class":1006},"}),\n",[983,1152,1154,1157,1160,1162,1165],{"class":985,"line":1153},13,[983,1155,1156],{"class":1006},"        help: flags.",[983,1158,1159],{"class":1081},"help",[983,1161,1144],{"class":1006},[983,1163,1164],{"class":1013},"'h'",[983,1166,1150],{"class":1006},[983,1168,1170],{"class":985,"line":1169},14,[983,1171,1172],{"class":989},"        \u002F\u002F flag with a value (-n, --name=VALUE)\n",[983,1174,1176,1179,1182,1184,1187,1190,1193],{"class":985,"line":1175},15,[983,1177,1178],{"class":1006},"        name: flags.",[983,1180,1181],{"class":1081},"string",[983,1183,1144],{"class":1006},[983,1185,1186],{"class":1013},"'n'",[983,1188,1189],{"class":1006},", description: ",[983,1191,1192],{"class":1013},"'name to print'",[983,1194,1150],{"class":1006},[983,1196,1198],{"class":985,"line":1197},16,[983,1199,1200],{"class":989},"        \u002F\u002F flag with no value (-f, --force)\n",[983,1202,1204,1207,1210,1212,1215],{"class":985,"line":1203},17,[983,1205,1206],{"class":1006},"        force: flags.",[983,1208,1209],{"class":1081},"boolean",[983,1211,1144],{"class":1006},[983,1213,1214],{"class":1013},"'f'",[983,1216,1150],{"class":1006},[983,1218,1220],{"class":985,"line":1219},18,[983,1221,1222],{"class":1006},"    };\n",[983,1224,1226],{"class":985,"line":1225},19,[983,1227,1072],{"emptyLinePlaceholder":443},[983,1229,1231,1233,1236,1238,1241,1244],{"class":985,"line":1230},20,[983,1232,1097],{"class":995},[983,1234,1235],{"class":1100}," args",[983,1237,1104],{"class":995},[983,1239,1240],{"class":1006}," [{name: ",[983,1242,1243],{"class":1013},"'file'",[983,1245,1246],{"class":1006},"}];\n",[983,1248,1250],{"class":985,"line":1249},21,[983,1251,1252],{"class":995},"    ...\n",[983,1254,1256],{"class":985,"line":1255},22,[983,1257,1258],{"class":1006},"}\n",[16,1260,1261,1262,1265],{},"The difference between flags and arguments is that flags are provided when the command is executed, ie: ",[67,1263,1264],{},".\u002Fbin\u002Frun --force",", whereas an argument is a value that the CLI prompts the user for during runtime.",[16,1267,1268],{},"For this example project, I am going to be making a CLI tool that takes a user input, and return a hashed string of that input. We will also be giving the user choices on what encryption algorithm they want to use.",[16,1270,1271],{},"From this example, we can see that there are two user inputs that are going to be recorded: the input string, and the encryption algorithm. Let's add those fields to our flags and arguments:",[975,1273,1275],{"className":977,"code":1274,"filename":979,"language":502,"meta":428,"style":428},"static flags = {\n  \u002F\u002F add --version flag to show CLI version\n  version: flags.version({char: 'v'}),\n  help: flags.help({char: 'h'}),\n  \u002F\u002F flag with a value (-n, --name=VALUE)\n  name: flags.string({char: 'n', description: 'name to print'}),\n  \u002F\u002F flag with no value (-f, --force)\n  force: flags.boolean({char: 'f'}),\n\n  \u002F\u002F Our new input flags\n  input: flags.string({ default: '' }),\n  algorithm: flags.string({\n    options: ['aes-192-cbc', 'aes-256-cbc', 'des3', 'rc2']\n  }),\n  password: flags.string({}),\n};\n\nstatic args = [\n  \u002F\u002F Our new input arguments\n  {name: 'input'},\n  {name: 'algorithm'},\n  {name: 'password'}\n];\n",[67,1276,1277,1287,1292,1305,1318,1323,1340,1345,1358,1362,1367,1383,1393,1420,1425,1435,1440,1444,1454,1459,1470,1479,1488],{"__ignoreMap":428},[983,1278,1279,1282,1285],{"class":985,"line":986},[983,1280,1281],{"class":1006},"static flags ",[983,1283,1284],{"class":995},"=",[983,1286,1091],{"class":1006},[983,1288,1289],{"class":985,"line":429},[983,1290,1291],{"class":989},"  \u002F\u002F add --version flag to show CLI version\n",[983,1293,1294,1297,1299,1301,1303],{"class":985,"line":1020},[983,1295,1296],{"class":1006},"  version: flags.",[983,1298,1141],{"class":1081},[983,1300,1144],{"class":1006},[983,1302,1147],{"class":1013},[983,1304,1150],{"class":1006},[983,1306,1307,1310,1312,1314,1316],{"class":985,"line":1039},[983,1308,1309],{"class":1006},"  help: flags.",[983,1311,1159],{"class":1081},[983,1313,1144],{"class":1006},[983,1315,1164],{"class":1013},[983,1317,1150],{"class":1006},[983,1319,1320],{"class":985,"line":1054},[983,1321,1322],{"class":989},"  \u002F\u002F flag with a value (-n, --name=VALUE)\n",[983,1324,1325,1328,1330,1332,1334,1336,1338],{"class":985,"line":1069},[983,1326,1327],{"class":1006},"  name: flags.",[983,1329,1181],{"class":1081},[983,1331,1144],{"class":1006},[983,1333,1186],{"class":1013},[983,1335,1189],{"class":1006},[983,1337,1192],{"class":1013},[983,1339,1150],{"class":1006},[983,1341,1342],{"class":985,"line":1075},[983,1343,1344],{"class":989},"  \u002F\u002F flag with no value (-f, --force)\n",[983,1346,1347,1350,1352,1354,1356],{"class":985,"line":1094},[983,1348,1349],{"class":1006},"  force: flags.",[983,1351,1209],{"class":1081},[983,1353,1144],{"class":1006},[983,1355,1214],{"class":1013},[983,1357,1150],{"class":1006},[983,1359,1360],{"class":985,"line":1112},[983,1361,1072],{"emptyLinePlaceholder":443},[983,1363,1364],{"class":985,"line":1117},[983,1365,1366],{"class":989},"  \u002F\u002F Our new input flags\n",[983,1368,1369,1372,1374,1377,1380],{"class":985,"line":1129},[983,1370,1371],{"class":1006},"  input: flags.",[983,1373,1181],{"class":1081},[983,1375,1376],{"class":1006},"({ default: ",[983,1378,1379],{"class":1013},"''",[983,1381,1382],{"class":1006}," }),\n",[983,1384,1385,1388,1390],{"class":985,"line":1135},[983,1386,1387],{"class":1006},"  algorithm: flags.",[983,1389,1181],{"class":1081},[983,1391,1392],{"class":1006},"({\n",[983,1394,1395,1398,1401,1404,1407,1409,1412,1414,1417],{"class":985,"line":1153},[983,1396,1397],{"class":1006},"    options: [",[983,1399,1400],{"class":1013},"'aes-192-cbc'",[983,1402,1403],{"class":1006},", ",[983,1405,1406],{"class":1013},"'aes-256-cbc'",[983,1408,1403],{"class":1006},[983,1410,1411],{"class":1013},"'des3'",[983,1413,1403],{"class":1006},[983,1415,1416],{"class":1013},"'rc2'",[983,1418,1419],{"class":1006},"]\n",[983,1421,1422],{"class":985,"line":1169},[983,1423,1424],{"class":1006},"  }),\n",[983,1426,1427,1430,1432],{"class":985,"line":1175},[983,1428,1429],{"class":1006},"  password: flags.",[983,1431,1181],{"class":1081},[983,1433,1434],{"class":1006},"({}),\n",[983,1436,1437],{"class":985,"line":1197},[983,1438,1439],{"class":1006},"};\n",[983,1441,1442],{"class":985,"line":1203},[983,1443,1072],{"emptyLinePlaceholder":443},[983,1445,1446,1449,1451],{"class":985,"line":1219},[983,1447,1448],{"class":1006},"static args ",[983,1450,1284],{"class":995},[983,1452,1453],{"class":1006}," [\n",[983,1455,1456],{"class":985,"line":1225},[983,1457,1458],{"class":989},"  \u002F\u002F Our new input arguments\n",[983,1460,1461,1464,1467],{"class":985,"line":1230},[983,1462,1463],{"class":1006},"  {name: ",[983,1465,1466],{"class":1013},"'input'",[983,1468,1469],{"class":1006},"},\n",[983,1471,1472,1474,1477],{"class":985,"line":1249},[983,1473,1463],{"class":1006},[983,1475,1476],{"class":1013},"'algorithm'",[983,1478,1469],{"class":1006},[983,1480,1481,1483,1486],{"class":985,"line":1255},[983,1482,1463],{"class":1006},[983,1484,1485],{"class":1013},"'password'",[983,1487,1258],{"class":1006},[983,1489,1491],{"class":985,"line":1490},23,[983,1492,1493],{"class":1006},"];\n",[16,1495,1496,1497,1500],{},"The reason I am defining these variables in both the flags and args options is so that a user can provide values when running the command, and the CLI will prompt the user for any undefined arguments. For example, someone could run ",[67,1498,1499],{},"crypto --input=test",", because they know they want to encrypt \"test\" but aren't sure which algorithm they wish to use.",[942,1502,1504],{"id":1503},"create-input-prompts","Create input prompts",[16,1506,1507,1508,1511],{},"Next, we need to define the logic that will both pull values from any provided flags, and prompt for any arguments not provided already. The \"main\" function of an Oclif project is ",[67,1509,1510],{},"run()",", which is where we will be doing the majority of our work.",[975,1513,1515],{"className":977,"code":1514,"filename":979,"language":502,"meta":428,"style":428},"async run() {\n  \u002F\u002F Pulls args and flags variables from Crypto class\n  const {args, flags} = this.parse(Crypto)\n  \u002F\u002F Destructure flags object to get input and algorithm\n  let { input, algorithm, password } = flags;\n\n  \u002F\u002F If input variable is undefined...\n  if (!input) {\n    \u002F\u002F cli-ux and 'cli' object provide utilities to interact\n    \u002F\u002F  with the Oclif API.\n    input = await cli.prompt('String to be encrypted');\n  }\n  \u002F\u002F If algorithm variable is undefined...\n  if (!algorithm) {\n    \u002F\u002F inquirer is an add-on for Oclif that allows us to\n    \u002F\u002F  take different kinds of input - in this case a list\n    const select = await inquirer.prompt([{\n      name: 'algorithm',\n      message: 'Select encryption algorithm',\n      type: 'list',\n      choices: [\n        { name: 'aes-192-cbc' },\n        { name: 'aes-256-cbc' },\n        { name: 'des3' },\n        { name: 'rc2' },\n      ],\n    }]);\n    \u002F\u002F Assign user input to algorithm variable\n    algorithm = select.algorithm;\n  }\n  \u002F\u002F If password is undefined...\n  if (!password) {\n    password = await cli.prompt('Password for encryption key');\n  }\n}\n",[67,1516,1517,1528,1533,1565,1570,1583,1587,1592,1606,1611,1616,1641,1646,1651,1662,1667,1672,1692,1702,1712,1722,1727,1737,1745,1754,1763,1769,1775,1781,1792,1797,1803,1815,1836,1841],{"__ignoreMap":428},[983,1518,1519,1522,1525],{"class":985,"line":986},[983,1520,1521],{"class":1006},"async ",[983,1523,1524],{"class":1081},"run",[983,1526,1527],{"class":1006},"() {\n",[983,1529,1530],{"class":985,"line":429},[983,1531,1532],{"class":989},"  \u002F\u002F Pulls args and flags variables from Crypto class\n",[983,1534,1535,1538,1541,1544,1546,1549,1552,1554,1557,1559,1562],{"class":985,"line":1020},[983,1536,1537],{"class":995},"  const",[983,1539,1540],{"class":1006}," {",[983,1542,1543],{"class":999},"args",[983,1545,1403],{"class":1006},[983,1547,1548],{"class":999},"flags",[983,1550,1551],{"class":1006},"} ",[983,1553,1284],{"class":995},[983,1555,1556],{"class":999}," this",[983,1558,168],{"class":1006},[983,1560,1561],{"class":1081},"parse",[983,1563,1564],{"class":1006},"(Crypto)\n",[983,1566,1567],{"class":985,"line":1039},[983,1568,1569],{"class":989},"  \u002F\u002F Destructure flags object to get input and algorithm\n",[983,1571,1572,1575,1578,1580],{"class":985,"line":1054},[983,1573,1574],{"class":995},"  let",[983,1576,1577],{"class":1006}," { input, algorithm, password } ",[983,1579,1284],{"class":995},[983,1581,1582],{"class":1006}," flags;\n",[983,1584,1585],{"class":985,"line":1069},[983,1586,1072],{"emptyLinePlaceholder":443},[983,1588,1589],{"class":985,"line":1075},[983,1590,1591],{"class":989},"  \u002F\u002F If input variable is undefined...\n",[983,1593,1594,1597,1600,1603],{"class":985,"line":1094},[983,1595,1596],{"class":995},"  if",[983,1598,1599],{"class":1006}," (",[983,1601,1602],{"class":995},"!",[983,1604,1605],{"class":1006},"input) {\n",[983,1607,1608],{"class":985,"line":1112},[983,1609,1610],{"class":989},"    \u002F\u002F cli-ux and 'cli' object provide utilities to interact\n",[983,1612,1613],{"class":985,"line":1117},[983,1614,1615],{"class":989},"    \u002F\u002F  with the Oclif API.\n",[983,1617,1618,1621,1623,1626,1629,1632,1635,1638],{"class":985,"line":1129},[983,1619,1620],{"class":1006},"    input ",[983,1622,1284],{"class":995},[983,1624,1625],{"class":995}," await",[983,1627,1628],{"class":1006}," cli.",[983,1630,1631],{"class":1081},"prompt",[983,1633,1634],{"class":1006},"(",[983,1636,1637],{"class":1013},"'String to be encrypted'",[983,1639,1640],{"class":1006},");\n",[983,1642,1643],{"class":985,"line":1135},[983,1644,1645],{"class":1006},"  }\n",[983,1647,1648],{"class":985,"line":1153},[983,1649,1650],{"class":989},"  \u002F\u002F If algorithm variable is undefined...\n",[983,1652,1653,1655,1657,1659],{"class":985,"line":1169},[983,1654,1596],{"class":995},[983,1656,1599],{"class":1006},[983,1658,1602],{"class":995},[983,1660,1661],{"class":1006},"algorithm) {\n",[983,1663,1664],{"class":985,"line":1175},[983,1665,1666],{"class":989},"    \u002F\u002F inquirer is an add-on for Oclif that allows us to\n",[983,1668,1669],{"class":985,"line":1197},[983,1670,1671],{"class":989},"    \u002F\u002F  take different kinds of input - in this case a list\n",[983,1673,1674,1677,1680,1682,1684,1687,1689],{"class":985,"line":1203},[983,1675,1676],{"class":995},"    const",[983,1678,1679],{"class":999}," select",[983,1681,1104],{"class":995},[983,1683,1625],{"class":995},[983,1685,1686],{"class":1006}," inquirer.",[983,1688,1631],{"class":1081},[983,1690,1691],{"class":1006},"([{\n",[983,1693,1694,1697,1699],{"class":985,"line":1219},[983,1695,1696],{"class":1006},"      name: ",[983,1698,1476],{"class":1013},[983,1700,1701],{"class":1006},",\n",[983,1703,1704,1707,1710],{"class":985,"line":1225},[983,1705,1706],{"class":1006},"      message: ",[983,1708,1709],{"class":1013},"'Select encryption algorithm'",[983,1711,1701],{"class":1006},[983,1713,1714,1717,1720],{"class":985,"line":1230},[983,1715,1716],{"class":1006},"      type: ",[983,1718,1719],{"class":1013},"'list'",[983,1721,1701],{"class":1006},[983,1723,1724],{"class":985,"line":1249},[983,1725,1726],{"class":1006},"      choices: [\n",[983,1728,1729,1732,1734],{"class":985,"line":1255},[983,1730,1731],{"class":1006},"        { name: ",[983,1733,1400],{"class":1013},[983,1735,1736],{"class":1006}," },\n",[983,1738,1739,1741,1743],{"class":985,"line":1490},[983,1740,1731],{"class":1006},[983,1742,1406],{"class":1013},[983,1744,1736],{"class":1006},[983,1746,1748,1750,1752],{"class":985,"line":1747},24,[983,1749,1731],{"class":1006},[983,1751,1411],{"class":1013},[983,1753,1736],{"class":1006},[983,1755,1757,1759,1761],{"class":985,"line":1756},25,[983,1758,1731],{"class":1006},[983,1760,1416],{"class":1013},[983,1762,1736],{"class":1006},[983,1764,1766],{"class":985,"line":1765},26,[983,1767,1768],{"class":1006},"      ],\n",[983,1770,1772],{"class":985,"line":1771},27,[983,1773,1774],{"class":1006},"    }]);\n",[983,1776,1778],{"class":985,"line":1777},28,[983,1779,1780],{"class":989},"    \u002F\u002F Assign user input to algorithm variable\n",[983,1782,1784,1787,1789],{"class":985,"line":1783},29,[983,1785,1786],{"class":1006},"    algorithm ",[983,1788,1284],{"class":995},[983,1790,1791],{"class":1006}," select.algorithm;\n",[983,1793,1795],{"class":985,"line":1794},30,[983,1796,1645],{"class":1006},[983,1798,1800],{"class":985,"line":1799},31,[983,1801,1802],{"class":989},"  \u002F\u002F If password is undefined...\n",[983,1804,1806,1808,1810,1812],{"class":985,"line":1805},32,[983,1807,1596],{"class":995},[983,1809,1599],{"class":1006},[983,1811,1602],{"class":995},[983,1813,1814],{"class":1006},"password) {\n",[983,1816,1818,1821,1823,1825,1827,1829,1831,1834],{"class":985,"line":1817},33,[983,1819,1820],{"class":1006},"    password ",[983,1822,1284],{"class":995},[983,1824,1625],{"class":995},[983,1826,1628],{"class":1006},[983,1828,1631],{"class":1081},[983,1830,1634],{"class":1006},[983,1832,1833],{"class":1013},"'Password for encryption key'",[983,1835,1640],{"class":1006},[983,1837,1839],{"class":985,"line":1838},34,[983,1840,1645],{"class":1006},[983,1842,1844],{"class":985,"line":1843},35,[983,1845,1258],{"class":1006},[16,1847,1848,1849,1851,1852,1855,1856,1859],{},"What I've done here is pretty simple - after getting the user input variables by destructuring ",[67,1850,1548],{},", we simply check if each item is undefined, and if so we prompt the user with the appropriate method. In this case I am using ",[67,1853,1854],{},"cli.prompt()"," to get basic text inputs, and ",[67,1857,1858],{},"inquirer.prompt()"," to give the user a list of items to select from.",[16,1861,1862,1863,1866],{},"Now that we have collected the required variables, we can generate a hash from the input string and print the resulting hash to the terminal. We are going to use the imported function ",[67,1864,1865],{},"createHash"," to do this:",[975,1868,1870],{"className":977,"code":1869,"filename":979,"language":502,"meta":428,"style":428},"async run() {\n  ...\n\n  \u002F\u002F Encryption key length is dependent on algorithm\n  let keyLength: number;\n  switch(algorithm) {\n    case \"aes-192-cbc\":\n      keyLength = 24;\n      break;\n    case \"aes-256-cbc\":\n      keyLength = 32;\n      break;\n    case \"des-ede3-cbc\":\n      keyLength = 7;\n      break;\n    default:\n      keyLength = 24;\n      break;\n  }\n  \u002F\u002F Generate encryption key from password and keyLength\n  const key = scryptSync(password ? password : 'password', 'salt', keyLength);\n  \u002F\u002F Define IV (Initialization vector)\n  const iv = Buffer.alloc(16, 0);\n  \u002F\u002F Create Cipher object\n  const cipher = createCipheriv(\n    algorithm ? algorithm : 'aes-192-cbc',\n    key,\n    \u002F\u002F des-ede3 algorithm does not use iv\n    algorithm === 'des-ede3' ? '' : iv\n  );\n\n  this.log(`\\n\u002F\u002F Input string: ${input}`);\n  this.log(`\u002F\u002F Algorithm: ${algorithm}`);\n  this.log(`\u002F\u002F Cipher password: ${password}\\n`);\n\n  \u002F\u002F Create the encrypted string by updating the cipher\n  let encrypted: string = cipher.update(input, 'utf8', 'hex');\n  encrypted += cipher.final('hex');\n  this.log(`OUTPUT: ${encrypted}`);\n}\n",[67,1871,1872,1880,1885,1889,1894,1908,1916,1927,1939,1946,1955,1966,1972,1981,1992,1998,2005,2015,2021,2025,2030,2064,2069,2095,2100,2115,2131,2136,2141,2163,2168,2172,2201,2221,2246,2250,2256,2290,2310,2331],{"__ignoreMap":428},[983,1873,1874,1876,1878],{"class":985,"line":986},[983,1875,1521],{"class":1006},[983,1877,1524],{"class":1081},[983,1879,1527],{"class":1006},[983,1881,1882],{"class":985,"line":429},[983,1883,1884],{"class":995},"  ...\n",[983,1886,1887],{"class":985,"line":1020},[983,1888,1072],{"emptyLinePlaceholder":443},[983,1890,1891],{"class":985,"line":1039},[983,1892,1893],{"class":989},"  \u002F\u002F Encryption key length is dependent on algorithm\n",[983,1895,1896,1898,1901,1903,1906],{"class":985,"line":1054},[983,1897,1574],{"class":995},[983,1899,1900],{"class":1006}," keyLength",[983,1902,26],{"class":995},[983,1904,1905],{"class":999}," number",[983,1907,1017],{"class":1006},[983,1909,1910,1913],{"class":985,"line":1069},[983,1911,1912],{"class":995},"  switch",[983,1914,1915],{"class":1006},"(algorithm) {\n",[983,1917,1918,1921,1924],{"class":985,"line":1075},[983,1919,1920],{"class":995},"    case",[983,1922,1923],{"class":1013}," \"aes-192-cbc\"",[983,1925,1926],{"class":1006},":\n",[983,1928,1929,1932,1934,1937],{"class":985,"line":1094},[983,1930,1931],{"class":1006},"      keyLength ",[983,1933,1284],{"class":995},[983,1935,1936],{"class":999}," 24",[983,1938,1017],{"class":1006},[983,1940,1941,1944],{"class":985,"line":1112},[983,1942,1943],{"class":995},"      break",[983,1945,1017],{"class":1006},[983,1947,1948,1950,1953],{"class":985,"line":1117},[983,1949,1920],{"class":995},[983,1951,1952],{"class":1013}," \"aes-256-cbc\"",[983,1954,1926],{"class":1006},[983,1956,1957,1959,1961,1964],{"class":985,"line":1129},[983,1958,1931],{"class":1006},[983,1960,1284],{"class":995},[983,1962,1963],{"class":999}," 32",[983,1965,1017],{"class":1006},[983,1967,1968,1970],{"class":985,"line":1135},[983,1969,1943],{"class":995},[983,1971,1017],{"class":1006},[983,1973,1974,1976,1979],{"class":985,"line":1153},[983,1975,1920],{"class":995},[983,1977,1978],{"class":1013}," \"des-ede3-cbc\"",[983,1980,1926],{"class":1006},[983,1982,1983,1985,1987,1990],{"class":985,"line":1169},[983,1984,1931],{"class":1006},[983,1986,1284],{"class":995},[983,1988,1989],{"class":999}," 7",[983,1991,1017],{"class":1006},[983,1993,1994,1996],{"class":985,"line":1175},[983,1995,1943],{"class":995},[983,1997,1017],{"class":1006},[983,1999,2000,2003],{"class":985,"line":1197},[983,2001,2002],{"class":995},"    default",[983,2004,1926],{"class":1006},[983,2006,2007,2009,2011,2013],{"class":985,"line":1203},[983,2008,1931],{"class":1006},[983,2010,1284],{"class":995},[983,2012,1936],{"class":999},[983,2014,1017],{"class":1006},[983,2016,2017,2019],{"class":985,"line":1219},[983,2018,1943],{"class":995},[983,2020,1017],{"class":1006},[983,2022,2023],{"class":985,"line":1225},[983,2024,1645],{"class":1006},[983,2026,2027],{"class":985,"line":1230},[983,2028,2029],{"class":989},"  \u002F\u002F Generate encryption key from password and keyLength\n",[983,2031,2032,2034,2037,2039,2042,2045,2048,2051,2053,2056,2058,2061],{"class":985,"line":1249},[983,2033,1537],{"class":995},[983,2035,2036],{"class":999}," key",[983,2038,1104],{"class":995},[983,2040,2041],{"class":1081}," scryptSync",[983,2043,2044],{"class":1006},"(password ",[983,2046,2047],{"class":995},"?",[983,2049,2050],{"class":1006}," password ",[983,2052,26],{"class":995},[983,2054,2055],{"class":1013}," 'password'",[983,2057,1403],{"class":1006},[983,2059,2060],{"class":1013},"'salt'",[983,2062,2063],{"class":1006},", keyLength);\n",[983,2065,2066],{"class":985,"line":1255},[983,2067,2068],{"class":989},"  \u002F\u002F Define IV (Initialization vector)\n",[983,2070,2071,2073,2076,2078,2081,2084,2086,2088,2090,2093],{"class":985,"line":1490},[983,2072,1537],{"class":995},[983,2074,2075],{"class":999}," iv",[983,2077,1104],{"class":995},[983,2079,2080],{"class":1006}," Buffer.",[983,2082,2083],{"class":1081},"alloc",[983,2085,1634],{"class":1006},[983,2087,129],{"class":999},[983,2089,1403],{"class":1006},[983,2091,2092],{"class":999},"0",[983,2094,1640],{"class":1006},[983,2096,2097],{"class":985,"line":1747},[983,2098,2099],{"class":989},"  \u002F\u002F Create Cipher object\n",[983,2101,2102,2104,2107,2109,2112],{"class":985,"line":1756},[983,2103,1537],{"class":995},[983,2105,2106],{"class":999}," cipher",[983,2108,1104],{"class":995},[983,2110,2111],{"class":1081}," createCipheriv",[983,2113,2114],{"class":1006},"(\n",[983,2116,2117,2119,2121,2124,2126,2129],{"class":985,"line":1765},[983,2118,1786],{"class":1006},[983,2120,2047],{"class":995},[983,2122,2123],{"class":1006}," algorithm ",[983,2125,26],{"class":995},[983,2127,2128],{"class":1013}," 'aes-192-cbc'",[983,2130,1701],{"class":1006},[983,2132,2133],{"class":985,"line":1771},[983,2134,2135],{"class":1006},"    key,\n",[983,2137,2138],{"class":985,"line":1777},[983,2139,2140],{"class":989},"    \u002F\u002F des-ede3 algorithm does not use iv\n",[983,2142,2143,2145,2148,2151,2154,2157,2160],{"class":985,"line":1783},[983,2144,1786],{"class":1006},[983,2146,2147],{"class":995},"===",[983,2149,2150],{"class":1013}," 'des-ede3'",[983,2152,2153],{"class":995}," ?",[983,2155,2156],{"class":1013}," ''",[983,2158,2159],{"class":995}," :",[983,2161,2162],{"class":1006}," iv\n",[983,2164,2165],{"class":985,"line":1794},[983,2166,2167],{"class":1006},"  );\n",[983,2169,2170],{"class":985,"line":1799},[983,2171,1072],{"emptyLinePlaceholder":443},[983,2173,2174,2177,2179,2182,2184,2187,2190,2193,2196,2199],{"class":985,"line":1805},[983,2175,2176],{"class":999},"  this",[983,2178,168],{"class":1006},[983,2180,2181],{"class":1081},"log",[983,2183,1634],{"class":1006},[983,2185,2186],{"class":1013},"`",[983,2188,2189],{"class":999},"\\n",[983,2191,2192],{"class":1013},"\u002F\u002F Input string: ${",[983,2194,2195],{"class":1006},"input",[983,2197,2198],{"class":1013},"}`",[983,2200,1640],{"class":1006},[983,2202,2203,2205,2207,2209,2211,2214,2217,2219],{"class":985,"line":1817},[983,2204,2176],{"class":999},[983,2206,168],{"class":1006},[983,2208,2181],{"class":1081},[983,2210,1634],{"class":1006},[983,2212,2213],{"class":1013},"`\u002F\u002F Algorithm: ${",[983,2215,2216],{"class":1006},"algorithm",[983,2218,2198],{"class":1013},[983,2220,1640],{"class":1006},[983,2222,2223,2225,2227,2229,2231,2234,2237,2240,2242,2244],{"class":985,"line":1838},[983,2224,2176],{"class":999},[983,2226,168],{"class":1006},[983,2228,2181],{"class":1081},[983,2230,1634],{"class":1006},[983,2232,2233],{"class":1013},"`\u002F\u002F Cipher password: ${",[983,2235,2236],{"class":1006},"password",[983,2238,2239],{"class":1013},"}",[983,2241,2189],{"class":999},[983,2243,2186],{"class":1013},[983,2245,1640],{"class":1006},[983,2247,2248],{"class":985,"line":1843},[983,2249,1072],{"emptyLinePlaceholder":443},[983,2251,2253],{"class":985,"line":2252},36,[983,2254,2255],{"class":989},"  \u002F\u002F Create the encrypted string by updating the cipher\n",[983,2257,2259,2261,2264,2266,2269,2271,2274,2277,2280,2283,2285,2288],{"class":985,"line":2258},37,[983,2260,1574],{"class":995},[983,2262,2263],{"class":1006}," encrypted",[983,2265,26],{"class":995},[983,2267,2268],{"class":999}," string",[983,2270,1104],{"class":995},[983,2272,2273],{"class":1006}," cipher.",[983,2275,2276],{"class":1081},"update",[983,2278,2279],{"class":1006},"(input, ",[983,2281,2282],{"class":1013},"'utf8'",[983,2284,1403],{"class":1006},[983,2286,2287],{"class":1013},"'hex'",[983,2289,1640],{"class":1006},[983,2291,2293,2296,2299,2301,2304,2306,2308],{"class":985,"line":2292},38,[983,2294,2295],{"class":1006},"  encrypted ",[983,2297,2298],{"class":995},"+=",[983,2300,2273],{"class":1006},[983,2302,2303],{"class":1081},"final",[983,2305,1634],{"class":1006},[983,2307,2287],{"class":1013},[983,2309,1640],{"class":1006},[983,2311,2313,2315,2317,2319,2321,2324,2327,2329],{"class":985,"line":2312},39,[983,2314,2176],{"class":999},[983,2316,168],{"class":1006},[983,2318,2181],{"class":1081},[983,2320,1634],{"class":1006},[983,2322,2323],{"class":1013},"`OUTPUT: ${",[983,2325,2326],{"class":1006},"encrypted",[983,2328,2198],{"class":1013},[983,2330,1640],{"class":1006},[983,2332,2334],{"class":985,"line":2333},40,[983,2335,1258],{"class":1006},[16,2337,2338,2339,2342,2343,164,2346,2349,2350,2355,2356,2359,2360,2363,2364,2367,2368,2371],{},"There's a little bit of extra work that had to go into this. Each encryption algorithm requires a different key length, so I used a switch\u002Fcase statement to properly assign the ",[67,2340,2341],{},"keyLength"," variable. Next, we create the ",[67,2344,2345],{},"key",[67,2347,2348],{},"iv"," variables (more info about Initialization Vectors ",[20,2351,2354],{"href":2352,"rel":2353},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FInitialization_vector",[24],"here","). We then define the ",[67,2357,2358],{},"cipher"," variable by using the ",[67,2361,2362],{},"createCipheriv"," method from the Node.js crypto library. To actually get an encrypted value, we use the ",[67,2365,2366],{},"cipher.update()"," method to run the encryption, and ",[67,2369,2370],{},"cipher.final()"," to pull the resulting encrypted string.",[942,2373,2375],{"id":2374},"running-the-program","Running the program",[16,2377,2378,2379,2381,2382,2387],{},"To test our program, we can run ",[67,2380,966],{}," in the terminal which will launch our CLI application. Congratulations! You have created your very own CLI tool to use as you please. CLI applications can be extremely powerful - and learning how to make them yourself is a great learning experience that can also improve your productivity. You can also publish your CLI tool to ",[20,2383,2386],{"href":2384,"rel":2385},"https:\u002F\u002Fnpmjs.org\u002F",[24],"npmjs.org"," so anyone can install and use your work!",[16,2389,2390,2391,168],{},"The full code for index.ts can be found ",[20,2392,2354],{"href":2393,"rel":2394},"https:\u002F\u002Fgist.github.com\u002Fdan-valinotti\u002F6f7499d8e7a59d32f85740486ef4ba28",[24],[2396,2397,2398],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":428,"searchDepth":429,"depth":429,"links":2400},[2401],{"id":569,"depth":429,"text":570,"children":2402},[2403,2404,2405,2406],{"id":944,"depth":1020,"text":945},{"id":969,"depth":1020,"text":970},{"id":1503,"depth":1020,"text":1504},{"id":2374,"depth":1020,"text":2375},"2021-03-11","In an attempt to learn something new about Node.js, and to improve my productivity, I began developing a CLI tool for generating React components. This would be very useful for the Next.js project that I talked about in my last post, as I could customize the file structure to the pattern I’ve already implemented (Model, View, ViewModel-ish).  Through the use of the oclif CLI framework, it was actually pretty easy to not only develop this tool, but also to publish it on NPM so that my team members could utilize it as well. I decided to create this tutorial to share my experience and hopefully help you write tools that can make you more productive.","cli-tool.jpg","A picture of a computer terminal window","web development,nodejs,node,tutorial,cli,command line interface,oclif,typescript,javascript,cli-ux,npm,github,git,npm package",{},"\u002Fblog\u002Fnode-cli-tutorial",{"title":928,"description":2408},"blog\u002Fnode-cli-tutorial","Spend time learning to save time working.",[2418,2419],"tutorial","nodejs","xP8LkkByJKdNc8g_qkeNLA9lMcv9GpLNQ8wpYY4Drkk",[2422,2443,2473,2493,2521,2539,2557,2576,2594,2627,2658,2676,2694],{"id":2423,"title":2424,"body":2425,"company":2432,"description":2429,"extension":439,"github":2433,"image":2434,"imageAlt":2435,"isBeta":443,"liveUrl":2433,"meta":2436,"navigation":443,"npm":2433,"path":2437,"position":986,"readMoreUrl":2438,"seo":2439,"stem":2440,"tag":2441,"__hash__":2442},"projects\u002Fprojects\u002Fads-creative-studio.md","Ads Creative Studio",{"type":8,"value":2426,"toc":2430},[2427],[16,2428,2429],{},"A unified home for Google’s creative advertising tools, to help you build compelling experiences for video, display and audio ads. Ads Creative Studio will allow creative teams to build and innovate together while increasing productivity and simplifying the overall process to bring a great ad to life.",{"title":428,"searchDepth":429,"depth":429,"links":2431},[],"Google",null,"acs.jpg","Ads Creative studio illustration",{},"\u002Fprojects\u002Fads-creative-studio","https:\u002F\u002Fblog.google\u002Fproducts\u002Fads-commerce\u002Fads-creative-studio-launch-cannes\u002F",{"title":2424,"description":2429},"projects\u002Fads-creative-studio","professional","k7EylzfSEfgWaPb4cDRBZQGw4oYf2DeMhdV6BIhnXVc",{"id":2444,"title":2445,"body":2446,"company":2432,"description":2464,"extension":439,"github":2433,"image":2434,"imageAlt":2465,"isBeta":2466,"liveUrl":2433,"meta":2467,"navigation":443,"npm":2433,"path":2468,"position":429,"readMoreUrl":2469,"seo":2470,"stem":2471,"tag":2441,"__hash__":2472},"projects\u002Fprojects\u002Fcampaign-manager.md","Google Ads Campaign Manager",{"type":8,"value":2447,"toc":2462},[2448],[16,2449,2450,2451,2453,2454,2457,2458,2461],{},"Built new surfaces for recommendations in ",[34,2452,2445],{}," that help advertisers increase campaign performance through actionable asset insights. Developed with ",[34,2455,2456],{},"Angular TypeScript"," on the frontend and ",[34,2459,2460],{},"Java"," on the backend.",{"title":428,"searchDepth":429,"depth":429,"links":2463},[],"Built new surfaces for recommendations in Google Ads Campaign Manager that help advertisers increase campaign performance through actionable asset insights. Developed with Angular TypeScript on the frontend and Java on the backend.","Google Ads Creative Studio illustration",false,{},"\u002Fprojects\u002Fcampaign-manager","https:\u002F\u002Fads.google.com\u002Fintl\u002Fen_us\u002Fhome\u002Fcampaigns\u002F",{"title":2445,"description":2464},"projects\u002Fcampaign-manager","7ITnGbMF8hJ2qykE7zAD8vTmmNzBo-lYYKt2iQlMrUE",{"id":2474,"title":2475,"body":2476,"company":2433,"description":2480,"extension":439,"github":2483,"image":2484,"imageAlt":2485,"isBeta":2466,"liveUrl":2433,"meta":2486,"navigation":443,"npm":2487,"path":2488,"position":1054,"readMoreUrl":2433,"seo":2489,"stem":2490,"tag":2491,"__hash__":2492},"projects\u002Fprojects\u002Fcgen-cli.md","React Component Generator CLI",{"type":8,"value":2477,"toc":2481},[2478],[16,2479,2480],{},"CLI tool built with OCLIF that allows developers to quickly generate files for React components. Supports JavaScript or Typescript, and mulitple component styling options.",{"title":428,"searchDepth":429,"depth":429,"links":2482},[],"https:\u002F\u002Fgithub.com\u002Fdvalinotti\u002Fcgen-cli","cgen-cli.png","React logo on a terminal icon.",{},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002F@dvalinotti\u002Fcgen-cli","\u002Fprojects\u002Fcgen-cli",{"title":2475,"description":2480},"projects\u002Fcgen-cli","personal","lM96ysVQyrGUda_8kJrUyjsx-O9e5LbaLlihSYeMJv4",{"id":2494,"title":2495,"body":2496,"company":2511,"description":2512,"extension":439,"github":2433,"image":2513,"imageAlt":2514,"isBeta":2466,"liveUrl":2515,"meta":2516,"navigation":443,"npm":2433,"path":2517,"position":1039,"readMoreUrl":2433,"seo":2518,"stem":2519,"tag":2441,"__hash__":2520},"projects\u002Fprojects\u002Fco-by-colgate.md","CO. by Colgate",{"type":8,"value":2497,"toc":2509},[2498],[16,2499,2500,2501,2504,2505,2508],{},"Colgate's second \"sub-brand\" launch of CO. by Colgate, with a Gen-Z focused oral care brand on a headless DTC E-commerce site. Built with ",[34,2502,2503],{},"Nuxt.js"," on the frontend, and ",[34,2506,2507],{},"Shopify"," as a backend CMS for products, orders, customers etc. A very unique design that also keeps accessibility in mind.",{"title":428,"searchDepth":429,"depth":429,"links":2510},[],"Colgate-Palmolive","Colgate's second \"sub-brand\" launch of CO. by Colgate, with a Gen-Z focused oral care brand on a headless DTC E-commerce site. Built with Nuxt.js on the frontend, and Shopify as a backend CMS for products, orders, customers etc. A very unique design that also keeps accessibility in mind.","co.png","CO. by Colgate logo on beige background","https:\u002F\u002Fco.colgate.com",{},"\u002Fprojects\u002Fco-by-colgate",{"title":2495,"description":2512},"projects\u002Fco-by-colgate","kPxFBd19EeBO6qz1LOO3hkRhCPyDjBhWIKdTY5tgBYo",{"id":2522,"title":2523,"body":2524,"company":2432,"description":2528,"extension":439,"github":2433,"image":2531,"imageAlt":2532,"isBeta":2466,"liveUrl":2433,"meta":2533,"navigation":443,"npm":2433,"path":2534,"position":986,"readMoreUrl":2535,"seo":2536,"stem":2537,"tag":2441,"__hash__":2538},"projects\u002Fprojects\u002Fdv3.md","Google Ads Display & Video 360",{"type":8,"value":2525,"toc":2529},[2526],[16,2527,2528],{},"Display & Video 360 is the evolution and consolidation of DoubleClick Bid Manager, Campaign Manager, Studio, and Audience Center. It offers a single tool for planning campaigns, designing and managing creative, organizing and applying audience data, finding and buying inventory, and measuring and optimizing campaigns.",{"title":428,"searchDepth":429,"depth":429,"links":2530},[],"dv3.png","Google Ads Display & Video 360 logo on dark gray background",{},"\u002Fprojects\u002Fdv3","https:\u002F\u002Fmarketingplatform.google.com\u002Fabout\u002Fdisplay-video-360\u002F",{"title":2523,"description":2528},"projects\u002Fdv3","LwY470V9lnvwLhX_kumyBNRipEB1qT92_UsTrYwNVPU",{"id":2540,"title":2541,"body":2542,"company":2433,"description":2546,"extension":439,"github":2549,"image":2550,"imageAlt":2551,"isBeta":2466,"liveUrl":2433,"meta":2552,"navigation":443,"npm":2433,"path":2553,"position":986,"readMoreUrl":2433,"seo":2554,"stem":2555,"tag":2491,"__hash__":2556},"projects\u002Fprojects\u002Fexpress-babel-starter.md","Express.js Babel Starter Project",{"type":8,"value":2543,"toc":2547},[2544],[16,2545,2546],{},"A GitHub repo template that makes it easy to start building a Node.js backend application. Includes support for modern ES6 syntax, input validation, GitHub Action templates, Google App Engine deployment, and more. Comes with a Model-View-Controller directory structure for better project organization.",{"title":428,"searchDepth":429,"depth":429,"links":2548},[],"https:\u002F\u002Fgithub.com\u002Fdvalinotti\u002Fexpress-babel-starter","express-babel-node.png","Express, Babel, NodeJS and JavaScript logos",{},"\u002Fprojects\u002Fexpress-babel-starter",{"title":2541,"description":2546},"projects\u002Fexpress-babel-starter","CbiH2YtrBIN0mIYzEVjXCkV21l6ASr3XgtsveRAQVv0",{"id":2558,"title":2559,"body":2560,"company":2567,"description":2564,"extension":439,"github":2433,"image":2568,"imageAlt":2569,"isBeta":2466,"liveUrl":2570,"meta":2571,"navigation":443,"npm":2433,"path":2572,"position":1054,"readMoreUrl":2433,"seo":2573,"stem":2574,"tag":2441,"__hash__":2575},"projects\u002Fprojects\u002Ffarrell-partnership.md","Farrell Partnership Website",{"type":8,"value":2561,"toc":2565},[2562],[16,2563,2564],{},"Designed, developed and deployed a new brand website for Farrell Partnership, LLC., an architecture firm based in New Jersey. I used Vue + Nuxt for server-side rendering, and Strapi.io for a CMS which greatly improved speed, user experience, and site reliability compared to the previous WordPress site.",{"title":428,"searchDepth":429,"depth":429,"links":2566},[],"Freelance","FPLogo.png","Farrell Partnership company logo on orange background","https:\u002F\u002Ffarrellpartnership.com",{},"\u002Fprojects\u002Ffarrell-partnership",{"title":2559,"description":2564},"projects\u002Ffarrell-partnership","LzU8tjj5UZ1ihgqKB9ipqrNZQQyjG5yIp9onXPYtKu0",{"id":2577,"title":2578,"body":2579,"company":2433,"description":2583,"extension":439,"github":2586,"image":2587,"imageAlt":2588,"isBeta":2466,"liveUrl":2433,"meta":2589,"navigation":443,"npm":2433,"path":2590,"position":1020,"readMoreUrl":2433,"seo":2591,"stem":2592,"tag":2491,"__hash__":2593},"projects\u002Fprojects\u002Fhue-morse-messenger.md","Hue Morse Messenger",{"type":8,"value":2580,"toc":2584},[2581],[16,2582,2583],{},"My first React Native + Expo app, which utilized Philips' Hue Smart Lightbulb API, enabling users to send Morse Code messages to rooms in their house by flashing the lights. This also involved building a desktop app with React\u002FElectron that starts an Express app to connect to Philips' Hue API. Not practical, but very fun!",{"title":428,"searchDepth":429,"depth":429,"links":2585},[],"https:\u002F\u002Fgithub.com\u002Fdvalinotti\u002FHue-Morse-Messenger","ExpoLogo.png","Expo logo on black background",{},"\u002Fprojects\u002Fhue-morse-messenger",{"title":2578,"description":2583},"projects\u002Fhue-morse-messenger","AgHht1dNAw5LLLqb_SD4fGpRj5PjizvGYNkMre-BKpU",{"id":2595,"title":2596,"body":2597,"company":2511,"description":2618,"extension":439,"github":2433,"image":2619,"imageAlt":2620,"isBeta":2466,"liveUrl":2621,"meta":2622,"navigation":443,"npm":2433,"path":2623,"position":1020,"readMoreUrl":2433,"seo":2624,"stem":2625,"tag":2441,"__hash__":2626},"projects\u002Fprojects\u002Fhum-by-colgate.md","hum by Colgate",{"type":8,"value":2598,"toc":2616},[2599],[16,2600,2601,2602,2605,2606,2608,2609,2611,2612,2615],{},"Launched a new \"sub-brand\" of Colgate called ",[122,2603,2604],{},"hum"," with a headless DTC E-commerce website. Built with ",[34,2607,2503],{}," on the frontend, using ",[34,2610,2507],{}," as a backend CMS for handling products, customers, orders and checkouts. We also used ",[34,2613,2614],{},"Three.js and GSAP"," to create cool, fluid animations throughout the site.",{"title":428,"searchDepth":429,"depth":429,"links":2617},[],"Launched a new \"sub-brand\" of Colgate called hum with a headless DTC E-commerce website. Built with Nuxt.js on the frontend, using Shopify as a backend CMS for handling products, customers, orders and checkouts. We also used Three.js and GSAP to create cool, fluid animations throughout the site.","hum.png","hum by Colgate logo on a red background","https:\u002F\u002Fhum.colgate.com",{},"\u002Fprojects\u002Fhum-by-colgate",{"title":2596,"description":2618},"projects\u002Fhum-by-colgate","Fz-bfr2j44TmuOV8ud0eoWqoUJQ35DwjGc0dI0O6AEo",{"id":2628,"title":2629,"body":2630,"company":2647,"description":2648,"extension":439,"github":2433,"image":2649,"imageAlt":2650,"isBeta":2466,"liveUrl":2651,"meta":2652,"navigation":443,"npm":2433,"path":2653,"position":2654,"readMoreUrl":2433,"seo":2655,"stem":2656,"tag":2441,"__hash__":2657},"projects\u002Fprojects\u002Fnetflix-ads-suite.md","Netflix Ads Suite",{"type":8,"value":2631,"toc":2645},[2632],[16,2633,2634,2635,1403,2638,2641,2642,168],{},"Building the UI for Netflix's ad platform creative tools, enabling internal ad account managers to build, develop, and manage ad creatives. Built with ",[34,2636,2637],{},"React",[34,2639,2640],{},"TypeScript",", and ",[34,2643,2644],{},"GraphQL",{"title":428,"searchDepth":429,"depth":429,"links":2646},[],"Netflix","Building the UI for Netflix's ad platform creative tools, enabling internal ad account managers to build, develop, and manage ad creatives. Built with React, TypeScript, and GraphQL.","netflix-ads.png","Netflix Ads Suite logo on dark background","https:\u002F\u002Fadvertising.netflix.com\u002Fen-us",{},"\u002Fprojects\u002Fnetflix-ads-suite",0,{"title":2629,"description":2648},"projects\u002Fnetflix-ads-suite","kJ1Rq49TpcdossO9-hctIyisUQobeOINIT9HNAaRAtw",{"id":2659,"title":2660,"body":2661,"company":2433,"description":2665,"extension":439,"github":2668,"image":2669,"imageAlt":2670,"isBeta":2466,"liveUrl":2433,"meta":2671,"navigation":443,"npm":2433,"path":2672,"position":2654,"readMoreUrl":2433,"seo":2673,"stem":2674,"tag":2491,"__hash__":2675},"projects\u002Fprojects\u002Frescue-group.md","RescueGroup Mobile App",{"type":8,"value":2662,"toc":2666},[2663],[16,2664,2665],{},"I’m working on a React Native-based iOS app aimed at making it easier to rescue animals in need of a home, and make it easier for rescue organizations to find homes for their furry friends.",{"title":428,"searchDepth":429,"depth":429,"links":2667},[],"https:\u002F\u002Fgithub.com\u002Fdvalinotti\u002FRescueGroupApp","RescueGroup.png","RescueGroup mobile app icon",{},"\u002Fprojects\u002Frescue-group",{"title":2660,"description":2665},"projects\u002Frescue-group","2O8GU7erc1kNiLIAswGX3ofNrH38eBvEUSAImVByVMU",{"id":2677,"title":2678,"body":2679,"company":2433,"description":2683,"extension":439,"github":2686,"image":2687,"imageAlt":2688,"isBeta":2466,"liveUrl":2433,"meta":2689,"navigation":443,"npm":2433,"path":2690,"position":1039,"readMoreUrl":2433,"seo":2691,"stem":2692,"tag":2491,"__hash__":2693},"projects\u002Fprojects\u002Frust-sandbox.md","Rust Sandbox",{"type":8,"value":2680,"toc":2684},[2681],[16,2682,2683],{},"Web app that allows users to write and execute Rust code in their browser. Built with React + Next.js with TypeScript.",{"title":428,"searchDepth":429,"depth":429,"links":2685},[],"https:\u002F\u002Fgithub.com\u002Fdvalinotti\u002Frust-sandbox","rust-sandbox.png","Rust logo on purple background",{},"\u002Fprojects\u002Frust-sandbox",{"title":2678,"description":2683},"projects\u002Frust-sandbox","YyDVm2GcmNii2xLjSSx56EYr69UpvKGRWFIB0x3lunQ",{"id":2695,"title":2696,"body":2697,"company":2433,"description":2701,"extension":439,"github":2704,"image":2705,"imageAlt":2706,"isBeta":2466,"liveUrl":2433,"meta":2707,"navigation":443,"npm":2708,"path":2709,"position":1039,"readMoreUrl":2433,"seo":2710,"stem":2711,"tag":2491,"__hash__":2712},"projects\u002Fprojects\u002Fvue-vimeo.md","Vue Vimeo Player",{"type":8,"value":2698,"toc":2702},[2699],[16,2700,2701],{},"Vue.js component for embedding Vimeo videos, with several options and event hooks for custom behavior.",{"title":428,"searchDepth":429,"depth":429,"links":2703},[],"https:\u002F\u002Fgithub.com\u002Fdan-valinotti\u002Fvue-vimeo-player#readme","Vimeo-512.png","Blue image of Vimeo logo",{},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002F@dvalinotti\u002Fvue-vimeo-player","\u002Fprojects\u002Fvue-vimeo",{"title":2696,"description":2701},"projects\u002Fvue-vimeo","Y7PAf49HfN8EDtYkk5flcJeADvWTNa7LpfvCZjca7Pw",1775849977172]