Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
January 18, 2023 08:00 am GMT

I Asked ChatGPT to Code Web Components and This is What Happened

The past couple months and I've heard of web engineers tinkering with ChatGPT while another engineer told me they relied on the output for the basis of every coding decision at work. I found myself in the first camp, poking at ChatGPT to generate anything from code examples to blog outlines, ideas for novels, or generative artwork. I was genuinely curious.

My short take is we are doomed, at least in the immediate future. Content providers will probably use the ChatGPT to generate blog posts. The rest of us won't get the content we deserve. We'll be forced to sift through cursory explanations of code. I suppose that was already happening before ChatGPT, but I'm predicting it's only getting worse. Read on to learn more.

I was primarily interested if ChatGPT could be used to generate anything close to production-ready code. I had talked with an engineer using ChatGPT as a basis for coding anything at their job. I wanted to understand what that experience was like. Granted, I'm a ChatGPT novice, so this article won't get overly technical about using ChatGPT by any means. I highly doubt I unlocked the tool's full potential.

I'm a front-end web engineer that codes Web Components and have coded applications with several JavaScript frameworks. I asked ChatGPT to generate some Web Components, because that's what I know extremely well. I wanted to see how precise ChatGPT could produce a simple view comprised of Web Components. I wrote a book about Web Components recently, so I know first-hand how the code should be formatted.

Getting Started

I logged into https://chat.openai.com and prompted ChatGPT with a simple request to generate an application login form with Web Components.

Image description

ChatGPT responded with the following code snippet that used the popular Web Components library Lit. I didn't specifically ask for ChatGPT to use a JavaScript library, but here we are.

Image description

<template>  <form>    <label for="username">Username:</label>    <input type="text" id="username" @input="${this._onUsernameInput}">    <br>    <label for="password">Password:</label>    <input type="password" id="password" @input="${this._onPasswordInput}">    <br>    <button @click="${this._onLoginButtonClick}">Login</button>  </form></template><script>  import { LitElement, html } from 'lit-element';  class LoginForm extends LitElement {    static get properties() {      return {        username: { type: String },        password: { type: String }      };    }    constructor() {      super();      this.username = '';      this.password = '';    }    _onUsernameInput(event) {      this.username = event.target.value;    }    _onPasswordInput(event) {      this.password = event.target.value;    }    _onLoginButtonClick() {      // Send a login request with the username and password      console.log(`Attempting to login with username: ${this.username} and password: ${this.password}`);    }    render() {      return html`        <form>          <label for="username">Username:</label>          <input type="text" id="username" @input="${this._onUsernameInput}">          <br>          <label for="password">Password:</label>          <input type="password" id="password" @input="${this._onPasswordInput}">          <br>          <button @click="${this._onLoginButtonClick}">Login</button>        </form>      `;    }  }  customElements.define('login-form', LoginForm);</script>

The duplicate templates surprised me and seem like an error. One declared as a HTML template, the other declared in the render method. The HTML template seems superfluous.

ChatGPT followed the code snippet with an explanation and example of how to implement the LoginForm custom element defined in the <script>. Most of that explanation describing how LitElement behaves is omitted here for sake of brevity. The last bit of the explanation was interesting. A callout the form needed further measures to address security, to keep the transmission of the password secure.

Image description

ChatGPT generated a boilerplate example with Lit. It's interesting that was the first choice, given I hadn't prompted ChatGPT to generate any code with a library, just Web Components which are a set of browser specifications.

I wasn't satisfied with the example using LitElement. Nothing against Lit. Lit is actually the first library I would adopt for Web Components that need to be developed at scale. I merely don't want any dependencies in a login view, which should be highly performant. Zero dependencies is a benefit of coding Web Components, especially for a view application like a login form which could be code split from the main bundle.

I prompted ChatGPT to produce the same example but without a JavaScript library.

Image description

Image description

<template id="login-form-template">  <form>    <label for="username">Username:</label>    <input type="text" id="username">    <br>    <label for="password">Password:</label>    <input type="password" id="password">    <br>    <button id="login-button">Login</button>  </form></template><script>  class LoginForm extends HTMLElement {    constructor() {      super();      // Attach the template to the element      const template = document.getElementById('login-form-template');      const templateContent = template.content;      this.appendChild(templateContent.cloneNode(true));      // Get references to the form elements      this._usernameInput = this.querySelector('#username');      this._passwordInput = this.querySelector('#password');      this._loginButton = this.querySelector('#login-button');      // Add event listeners      this._usernameInput.addEventListener('input', this._onUsernameInput.bind(this));      this._passwordInput.addEventListener('input', this._onPasswordInput.bind(this));      this._loginButton.addEventListener('click', this._onLoginButtonClick.bind(this));    }    _onUsernameInput(event) {      this._username = event.target.value;    }    _onPasswordInput(event) {      this._password = event.target.value;    }    _onLoginButtonClick() {      // Send a login request with the username and password      console.log(`Attempting to login with username: ${this._username} and password: ${this._password}`);    }  }  customElements.define('login-form', LoginForm);</script>

The usage of HTML templates in this example make a lot more sense. LoginForm references each HTML template to append the username and password inputs and the button to display a login form. Event listeners are attached to methods on the class that store the username and password as private properties, denoted by the underscore. A call to customElements.define registers the component class with the browser.

ChatGPT followed the code example with a similar explanation (omitted here).

The above example is alright, although I wanted to follow best practices of making the input and button separate components from the login form. This promotes reusability. The input and button could be exported from a UI library. I asked ChatGPT to generate the same code but this time allowing for a separation of concerns.

Image description

Image description

ChatGPT produced three separate HTML templates and classes for the form, input, and button.

<template id="login-form-template">  <form>    <login-input label="Username"></login-input>    <login-input label="Password" type="password"></login-input>    <login-button>Login</login-button>  </form></template><template id="login-input-template">  <label for="input"></label>  <input id="input"></template><template id="login-button-template">  <button></button></template><script>  class LoginForm extends HTMLElement {    constructor() {      super();      // Attach the template to the element      const template = document.getElementById('login-form-template');      const templateContent = template.content;      this.appendChild(templateContent.cloneNode(true));      // Get references to the form elements      this._usernameInput = this.querySelector('login-input[label="Username"]');      this._passwordInput = this.querySelector('login-input[label="Password"]');      this._loginButton = this.querySelector('login-button');      // Add event listeners      this._usernameInput.addEventListener('input', this._onUsernameInput.bind(this));      this._passwordInput.addEventListener('input', this._onPasswordInput.bind(this));      this._loginButton.addEventListener('click', this._onLoginButtonClick.bind(this));    }    _onUsernameInput(event) {      this._username = event.target.value;    }    _onPasswordInput(event) {      this._password = event.target.value;    }    _onLoginButtonClick() {      // Send a login request with the username and password      console.log(`Attempting to login with username: ${this._username} and password: ${this._password}`);    }  }  customElements.define('login-form', LoginForm);  class LoginInput extends HTMLElement {    constructor() {      super();      // Attach the template to the element      const template = document.getElementById('login-input-template');      const templateContent = template.content;      this.appendChild(templateContent.cloneNode(true));      // Get references to the input element      this._input = this.querySelector('#input');      this._label = this.querySelector('label');      // Set attributes      this._input.name = this.getAttribute('label');      this._input.type = this.getAttribute('type') || 'text';      this._label.innerText = this.getAttribute('label');    }  }  customElements.define('login-input', LoginInput);  class LoginButton extends HTMLElement {    constructor() {      super();      // Attach the template to the element      const template = document.getElementById('login-button-template');      const templateContent = template.content;      this.appendChild(templateContent.cloneNode(true));      this.innerText = this.textContent;    }  }  customElements.define('login-button', LoginButton);

Interesting callouts from the above code.

  1. LoginInput sets the label using attributes, but doesn't utilize observedAttributes or attributeChangedCallback, two methods available to every custom element that enable engineers to effectively respond to changes in attribute values. Using those callbacks are the only reliable means to track changes in attribute values.
  2. LoginButton sets in the label of the button using a strange line: this.innerText = this.textContent; I would have preferred a method that set the content with a <slot> via the HTML template or an attribute.

Some engineers may be put off by the usage of HTML templates here, but I appreciate how ChatGPT used specifications in this case to provide a stable template for each component. Instead you would declare the templates in the constructor of each component class, a lot like many examples out there.

Adding TypeScript

Next, I asked ChatGPT to produce the same code, but this time with TypeScript. This would give me an idea of how to make the components type safe.

Image description

class LoginForm extends HTMLElement {  private _usernameInput: HTMLInputElement;  private _passwordInput: HTMLInputElement;  private _loginButton: HTMLButtonElement;  private _username: string;  private _password: string;  constructor() {    super();    // Attach the template to the element    const template = document.getElementById('login-form-template') as HTMLTemplateElement;    const templateContent = template.content;    this.appendChild(templateContent.cloneNode(true));    // Get references to the form elements    this._usernameInput = this.querySelector('#username') as HTMLInputElement;    this._passwordInput = this.querySelector('#password') as HTMLInputElement;    this._loginButton = this.querySelector('#login-button') as HTMLButtonElement;    // Add event listeners    this._usernameInput.addEventListener('input', this._onUsernameInput.bind(this));    this._passwordInput.addEventListener('input', this._onPasswordInput.bind(this));    this._loginButton.addEventListener('click', this._onLoginButtonClick.bind(this));  }  private _onUsernameInput(event: Event) {    this._username = (event.target as HTMLInputElement).value;  }  private _onPasswordInput(event: Event) {    this._password = (event.target as HTMLInputElement).value;  }  private _onLoginButtonClick() {    // Send a login request with the username and password    console.log(`Attempting to login with username: ${this._username} and password: ${this._password}`);  }}customElements.define('login-form', LoginForm);

ChatGPT only output the LoginForm class and failed to generate the other components in Typescript, so I asked again. There were a few other instances where I had to prompt ChatGPT a second, or even third time, to get an acceptable answer. ChatGPT struggled with large code examples. Finally ChatGPT delivered a full TypeScript implementation.

Image description

class LoginInput extends HTMLElement {  private _input: HTMLInputElement;  private _label: HTMLLabelElement;  constructor() {    super();    // Attach the template to the element    const template = document.getElementById('login-input-template') as HTMLTemplateElement;    const templateContent = template.content;    this.appendChild(templateContent.cloneNode(true));    // Get references to the input element    this._input = this.querySelector('#input') as HTMLInputElement;    this._label = this.querySelector('label') as HTMLLabelElement;    // Set attributes    this._input.name = this.getAttribute('label');    this._input.type = this.getAttribute('type') || 'text';    this._label.innerText = this.getAttribute('label');  }}customElements.define('login-input', LoginInput);class LoginButton extends HTMLElement {  constructor() {    super();    // Attach the template to the element    const template = document.getElementById('login-button-template') as HTMLTemplateElement;    const templateContent = template.content;    this.appendChild(templateContent.cloneNode(true));    this.innerText = this.textContent;  }}customElements.define('login-button', LoginButton);class LoginForm extends HTMLElement {  private _usernameInput: HTMLInputElement;  private _passwordInput: HTMLInputElement;  private _loginButton: HTMLButtonElement;  private _username: string;  private _password: string;  constructor() {    super();    // Attach the template to the element    const template = document.getElementById('login-form-template') as HTMLTemplateElement;    const templateContent = template.content;    this.appendChild(templateContent.cloneNode(true));    // Get references to the form elements    this._usernameInput = this.querySelector('login-input[label="Username"]') as HTMLInputElement;    this._passwordInput = this.querySelector('login-input[label="Password"]') as HTMLInputElement;    this._loginButton = this.querySelector('login-button') as HTMLButtonElement;    // Add event listeners    this._usernameInput.addEventListener('input', this._onUsernameInput.bind(this));    this._passwordInput.addEventListener('input', this._onPasswordInput.bind(this));    this._loginButton.addEventListener('click', this._onLoginButtonClick.bind(this));  }  private _onUsernameInput(event: Event) {    this._username = (event.target as HTMLInputElement).value;  }  private _onPasswordInput(event: Event) {    this._password = (event.target as HTMLInputElement).value;  }  private _onLoginButtonClick() {    // Send a login request with the username and password    console.log(`Attempting to login with username: ${this._username} and password: ${this._password}`);  }}customElements.define('login-form', LoginForm);

The TypeScript output was spot on. I was particularly glad to see strict typing of the event.target in a style that was familiar. Pertinent properties on each class are labeled private and typed appropriately.

Styling The Components

I wanted to add styling to the input and button so I prompted ChatGPT to add CSS to the components, although it kept setting styling through this.style and not by injecting CSS through the <style> tag which I am accustomed. This is due to the assumption the code examples relied on the previous generated HTML templates. The styling could be provided in the HTML templates, although ChatGPT never chose to place the styles there. I generally prefer declaring CSS in the constructor of each class.

I started a new prompt, this time leading with the styling and ChatGPT generated a completely different solution that aligned to my expectations. To achieve this I was more descriptive.

Image description

ChatGPT chose to default to LitElement again for this request despite the instruction to "not require a JavaScript library".

Image description

I asked again, this time calling out LitElement and ChatGPT responded accordingly even though I had a slight typo.

Image description

Image description

ChatGPT output three separate code samples, one for each component this time around. This made the output way more reliable than when ChatGPT generated one long code snippet with everything.

export class MyInput extends HTMLElement {    constructor() {        super();        this.attachShadow({ mode: 'open' });        this.shadowRoot.innerHTML = `            <style>                :host {                    display: block;                    background-color: #333;                    color: #fff;                }            </style>            <input type="text" />        `;    }}customElements.define('my-input', MyInput);

Each component declares the specified styles in the constructor, setting the innerHTML of each shadowRoot to include a <style> tag. I greatly prefer this format over setting this.style. The following MyButton class even sets the cursor: pointer to enhance accessibility.

export class MyButton extends HTMLButtonElement {    constructor() {        super();        this.attachShadow({ mode: 'open' });        this.shadowRoot.innerHTML = `            <style>                :host {                    background-color: #868e96;                    color: #000;                    padding: 0.5em 1em;                    border: none;                    border-radius: 0.25em;                    cursor: pointer;                }            </style>            <slot></slot>        `;    }}customElements.define('my-button', MyButton, { extends: 'button' });

ChatGPT chose to extend HTMLButtonElement, making the above component a customized built-in element. This allows any instance of MyButton to take on the characteristics of HTMLButtonElement, meaning you don't have to declare event listeners for accessibility because they are baked in. With this change, ChatGPT failed to provide an example of how to use MyButton in HTML, which differs from any custom element extended from HTMLElement. ChatGPT didn't also call out this code would be inoperable in Safari due to WebKit's stance that blocks customized built-ins from working in Safari (which is currently being reconsidered for inclusion, although WebKit has not signaled support).

Finally, ChatGPT declared a custom element for the form itself, setting the shadowRoot with boilerplate styling for the labels, although omitting the is attribute from the instance of <button>, which is required of customized built-in elements.

export class MyFormLayout extends HTMLElement {    constructor() {        super();        this.attachShadow({ mode: 'open' });        this.shadowRoot.innerHTML = `            <style>                :host {                    display: block;                }                label {                    display: block;                    font-size: 0.8em;                    margin-bottom: 0.5em;                }            </style>            <form>                <label>Username</label>                <my-input></my-input>                <label>Password</label>                <my-input type="password"></my-input>                <button>                    <slot name="submit"></slot>                </button>            </form>        `;    }}customElements.define('my-form-layout', MyFormLayout);

The above examples were derived from a request to ChatGPT to generate a separation of concerns between the components and style them. One problem is found on the last line of the MyFormLayout example. Element tag names can only include one dash according to spec, so my-form-layout should be my-formlayout to be parsable by the browser. Otherwise, the HTMLButtonElement doesn't employ the is attribute which is necessary due to MyButton being declared as a customized built-in element. An interesting part of the example is that the template includes a <slot> although lacks support for HTML templates which are necessary to use <slot>.

In order to take advantage of the <slot>, the example shouldn't set innerHTML directly on the shadowRoot. Instead, the example should instantiate a new HTMLTemplateElement, then clone the template's content into the shadowRoot, like below:

const shadowRoot = this.attachShadow({ mode: 'open' });const template = document.createElement('template');template.innerHTML = `            <style>                :host {                    display: block;                }                label {                    display: block;                    font-size: 0.8em;                    margin-bottom: 0.5em;                }            </style>            <form>                <label>Username</label>                <my-input></my-input>                <label>Password</label>                <my-input type="password"></my-input>                <button>                    <slot name="submit"></slot>                </button>            </form>`;shadowRoot.appendChild(template.content.cloneNode(true));

The logic for authentication is missing from the prior examples. I ask ChatGPT to add the functionality to MyFormLayout and was surprised the output was different than before.

Image description

Image description

export class MyFormLayout extends HTMLElement {    constructor() {        super();        this.attachShadow({ mode: 'open' });        this.shadowRoot.innerHTML = `            <style>                :host {                    display: block;                }                label {                    display: block;                    font-size: 0.8em;                    margin-bottom: 0.5em;                }            </style>            <form>                <label>Username</label>                <my-input id="username"></my-input>                <label>Password</label>                <my-input type="password" id="password"></my-input>                <button id="submit-button">                    <slot name="submit"></slot>                </button>            </form>        `;    }    connectedCallback() {        this.shadowRoot.getElementById("submit-button").addEventListener("click", (e) => this.handleSubmit(e));    }    handleSubmit(e: Event) {        e.preventDefault();        let username = this.shadowRoot.getElementById("username") as HTMLInputElement;        let password = this.shadowRoot.getElementById("password") as HTMLInputElement;        // logic to check the entered credentials against a user database or API        // if the credentials are valid, redirect the user to the home page        // else display an error message        if (username.value === "user" && password.value === "password") {            window.location.href = "home.html";        } else {            alert("Invalid username or password");        }    }}customElements.define('my-form-layout', MyFormLayout);

ChatGPT added a single event listener for the submit button, then read the value of the username and password from each input, and implemented some boilerplate validation that checks the value of username and password fields, along with inline documentation to explain the changes. Additional documentation followed the code snippet.

Image description

It's nice there is a warning the example "is not recommended for production" considering the boilerplate code doesn't secure the password with any sort of hashing algorithm. The description of the code is suitable to gain a basic understanding of how everything works.

The pseudo code validating the username and password doesn't make much sense. There would most likely be much more robust validation logic here. You could coax ChatGPT into producing an algorithm close to your intended business logic, but it's unlikely to get it just right.

Conclusion

When I heard an engineer was using ChatGPT to generate code everyday for his job, I was interested to see what that was like. Tools like this are no substitute for the knowledge gained by coding something from scratch. While it's entirely possible to generate boilerplate code with ChatGPT, I can't recommend it. Like other responses for "factual" content, ChatGPT will often answer with either completely wrong information or base part of an answer on incorrect assumptions. The same is true of code snippets. As we observed, ChatGPT omitted valuable information about the generated code and generated incorrectly formatted code. Overall though, ChatGPT did an alright job at generating Web Components. With some alterations the generated code could be used as the basis for coding a login view.

There's possibly more advanced queries I could have made to generate the login view code. I'm just a novice.

It's fairly interesting that ChatGPT defaulted to generating Web Components with LitElement. This is a testament to how synonymous the library is becoming with Web Components.

In another query (not featured here), I prompted ChatGPT to make the input and button form-associated custom elements which the service promptly ignored. In a separate query from that, ChatGPT struggled to generate a single example that implemented formAssociated or ElementInternals.

ChatGPT could get better over time, but for now I feel quite secure in my job as a front-end web developer. If I were completely new to Web Components, the incorrect and omitted code would be awfully confusing. Due to the inaccuracy of the code samples, In the future, maybe our robot overloads will take our jobs, but I don't think we will be replaced by A.I. anytime soon.

Fullstack Web Components Book

Want to learn how to code UI Libraries and Web Applications with Web Components?

Fullstack Web Components is the complete guide to building UI Libraries with Web Components. Through ten chapters of hands-on exercises, you'll learn every aspect to the set of specifications in the context of developing a UI Library and Web Application.

Front cover of Fullstack Web Components Book

Purchase a copy of Fullstack Web Components today and begin your journey into the wonderful world of front-end development with Web Components.


Original Link: https://dev.to/steveblue/i-asked-chatgpt-to-code-web-components-and-this-is-what-happened-13a1

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To