thingsinjars

  • 19 Jul 2019

    Web Components vs Vue Components

    I've been doing a lot of work in Vue recently so when I was asked to evaluate using Web Components on an upcoming project, I approached it with a Vue-ish mindset.

    I've not really kept my eye on Web Components for the last couple of years beyond seeing original proposals being superceded and import specs being replaced. Just seeing those things on the periphery were enough to make me think "Meh... I'll have a look later when it's all died down".

    Now, I know that Web Components !== Vue. But, I was interested in what knowledge could be migrated from one technology to the other. If I were building an actual web app, I'd definitely use Vue. Building a boxful of reusable, shareable UI elements, though... let's find out.


    I'm not going to build anything too complex to start with. How about an "Planet Summary" panel? A simple panel that renders summary information about a planet given a JSON object.

    I have an API that returns JSON information about where in the sky to find planets when given your latitude and longitude. For example, if you're standing slightly south of the centre of Berlin and want to know where Venus is, you'd make this request:

    https://planets-api.awsm.st/venus/52.5/13.4

    And the response would be:

    
    {
      "name": "Venus",
      "number": 1,
      "colour": 1,
      "colleft": 24,
      "colright": 25,
      "alt": 13.043427032890424,
      "az": 290.3495756869397,
      "dec": 22.661411404345362,
      "ra": 110.21545618074397,
      "H": 98.18491228623316,
      "eclon": 108.59563862950628,
      "eclat": 0.5200939814134588,
      "illum": 0.9918628383385676,
      "r": 0.7192422869900328,
      "dist": 1.7155717469739922,
      "mag": -3.909377586961354,
      "elong": 0,
      "pa": 0,
      "p": 1,
      "description": {
        "altitude": "Barely above the horizon",
        "azimuth": "West"
      },
      "visible": false
    }
    

    In this case, it determines Venus isn't visible because, even though it's above the horizon, it's not bright enough given the time of day (about 6pm).

    We want to make a little UI card that displays this information.

    Planet panel

    Mapping Vue features to Web Components

    VueWeb ComponentNotes
    nameclass name
    datainstance properties
    propsattributesthese are not reactive by default. Attributes have to be specifically observed (see watch).
    watchattributeChangedCallbackfirst, register your watched attributes with `observedAttributes` then process them in attributeChangedCallback
    computedgetters
    methodsclass methods
    mountedconnectedCallbackcalled async so the component may not be fully ready or may have been detached. Use Node.isConnected to protect against calling a dead node
    componentWillUnmountdisconnectedCallback
    style blockstyle block inside templatestyles are scoped by default
    template blockliteral templateJS literal templates (backtick strings) are nowhere near as powerful for templating as an actual template library. Vue template features such as `v-for` can be replicated with vanilla JS but a single-purpose template library (such as `lit-html`) is a good idea.

    NOTE: I am deliberately not using Webpack. I realise that actual applications would be using additional tooling but I want to see what we can do without it.


    The first thing that clicked with me was when I realised that computed properties and getters are identical. Nice.

    Here's Vue code to return the planet name or a default string:

    
    computed: {
      name() {
        return this.planet.name || '';
      },
    }
    

    And Web Component:

    
    get name() {
      return this.planet.name || '';
    }
    

    Well, that was easy (and trivial).

    The same goes for defining the custom element for use in the DOM

    Vue:

    
    components: { 
      "planet-summary": PlanetSummary
    }
    

    Web Components:

    
    customElements.define("planet-summary", PlanetSummary);
    

    The only real difference at this level is the data binding. In Vue, props passed from a parent element to a child are automatically updated. If you change the data passed in, the child updates by default. With Web Components, you need to explicitly say you want to be notified of changes.

    This is basically the same as setting a watch in Vue. Data that changes in a slightly less tightly-bound fashion can be watched and the changes trigger updates further down.

    Watches

    Watches in Vue:

    
    watch: {
      altitude(newValue, oldValue) {
        ...
      }
    }
    

    With Web Components, registering a watch and reacting to changes are separate:

    
    static get observedAttributes() {
      return ['altitude'];
    }
    
    attributeChangedCallback(name, oldValue, newValue) {
      if(name === 'altitude') {
        ...
      }
    }
    

    Templating

    Vue contains full templating support – for loops, conditional rendering, seamless passing around of data. Natively, you have literal templates and that's about it.

    To create a list of planets, you'd use the v-for directive and loop over your planets array.

    Vue:

    
    <ul>
      <li v-for="planet in planets">
        <planet-summary :planet="planet"></planet-summary>
      </li>
    </ul>
    

    Web Component

    
    <ul>
      ${this.planets.map(planet => `
      <li>
        <planet-summary planet='${JSON.stringify(planet)}'></planet-summary>
      </li>
      `).join('')}
    </ul>
    

    The join is there because we're creating an HTML string out of an array of list items. You could also accomplish this with a reduce.

    Boilerplate

    With Web Components, your component lives in the Shadow DOM so you are responsible for updating it yourself. Vue handles DOM updates for you.

    Here is a basic render setup:

    
      constructor() {
        super();
        this._shadowRoot = this.attachShadow({ mode: "open" });
        this.render();
      }
      render() {
        this._shadowRoot.innerHTML = '';
        this._shadowRoot.appendChild(this.template().content.cloneNode(true));
      }
    

    This needs to be explicitly included in every component as they are standalone whereas Vue automatically handles DOM updates.

    CSS

    Due to the fact that Web Components live in a separate document fragment, There are complications around sharing styles between the host page and the component which are nicely explained on CSS Tricks. The biggest benefit, on the other hand, is that all styles are scoped by default.

    Vue without Webpack (or other tooling) also has its own complications around styles (specifically scoping styles) but if you're building a Vue application, it is much more straightforward to specify which styles are global and which are scoped.

    Summary

    Here is the Vue Planet Summary and the source of planet-summary-vue.js.

    Here is the Web Component Planet Summary and the source of planet-summary.js.

    Bonus: here's a Planet List Web Component which includes the Planet Summary component. And the source of planet-list.js

    All in all, pretty much everything between basic Vue Components and Web Components can be mapped one-to-one. The differences are all the stuff around the basic construction of the components.

    I'd say that if you're looking to build completely standalone, framework-free reusable components, you'll be able to accomplish it with the Web Components standard. You just might have a bit of extra lifting and boilerplate to deal with.

    On the other hand, if you're already planning on building a full web application with data management and reactive components, use the tools available to you.

    Geek, Development, CSS, Javascript

  • 15 Sep 2013

    Hardy, meet Travis

    While developing the website for Hardy, it seemed obvious that I should be writing Hardy-based tests for it. What better way to figure out how to simplify testing than see what bits of the process were the hardest?

    The site is hosted on GitHub using the gh-pages branch of the hardy.io project. I've played with various toolchains in GitHub pages in the past - csste.st uses Wintersmith, for example - but wanted to follow my own best practice suggestions and automate the process of getting an idea from inside my head, through the text editor, through a bunch of tests and out onto production as much as possible. The main Hardy project uses Travis CI to run unit and acceptance tests on every commit so it seemed obvious to use it for this. What I've ended up with is what I think is a nice, simple process. This is designed for sites hosted on GitHub Pages but is applicable to other sites run through Travis,

    The manual steps of the process are:

    • Make change in your website master branch
    • Preview changes locally
    • Commit changes and push to GitHub

    After this, the automation takes over:

    • Travis pulls latest commit
    • Builds website project to dist folder in master branch
    • Launch web server on Travis
    • Run Hardy tests against local web server
    • On successful test run, push (via git) to gh-pages branch

    The full process is used to build hardy.io. Have a look at the .travis.yml, package.json and post_build.sh files mostly.

    CSS, Development, Javascript

  • 22 Aug 2013

    Hardy - Rigorous Testing

    When GhostStory first came out, it grabbed a fair bit of interest. Unfortunately, despite the number of downloads, I got the impression actual usage was low. There were quite a few stars on the project, a couple of forks and no issues. That's not a good sign. If your project has no issues, it doesn't mean your codes are perfect, it means nobody's using them.

    After asking on Twitter and generally 'around', it emerged that, although people liked the idea,

    1. initial setup was too tricky
    2. test maintenance was a hassle
    3. it’s not WebKit that needs tested most

    Number 3 might seem odd but it has become a fact that, due to the excellent tooling, most web developers use a WebKit variant (chrome, chromium, safari, etc) as their main browser when building. This means they do generally see the problems there where they might miss the ones in IE. This isn't to say WebKit shouldn't also be tested, but GhostStory was built on PhantomJS - a WebKit variant - and therefore only picked up problems that occurred there.

    I've been working evenings and weekends for the last couple of several months to improve the CSS testing setup on here.com and I think we've gotten somewhere. For a start, the name has changed...

    Hardy

    A.K.A. GhostStory 2 - The Ghostening

    This is the bulk of the original GhostStory project but instead of running through PhantomJS, it now uses Selenium via the Webdriver protocol. This means you can run your same CSS tests - still written in Cucumber - against any Webdriver-capable browser. This means Chrome and Firefox and Opera and PhantomJS and Internet Explorer and Mobile Safari on iOS and the Android Browser and and and…. you get the idea.

    Installation

    It’s a simple npm-based install:

    npm install -g hardy
    

    You can then call it by passing a folder of .feature files and step definitions.

    hardy testfolder/
    

    Any relevant files it finds will be automatically parsed.

    Okay, that's most of the difficulty out of the way. A simple install, simple test run and, hopefully, a simple integration into your build system. The only thing left to solve is how to make it easier to make the Cucumber files in the first place.

    Hardy Chrome Extension

    It’s now as simple to make your initial test cases as opening Chrome DevTools and clicking around a bit. Well, almost. Still under development, the Chrome extension helps you make feature files by navigating around the site you want to test and capturing the styles you want to test for. A Hardy-compatible Cucumber file and the accompanying element selector map is generated ready to be dropped into your features folder.

    What's missing?

    Is there anything missing from this flow that you'd like added? A Grunt task to set it up? Prefer something else over Cucumber? Want a Maven Plugin instead of a grunt one? Just let me know. I'm not promising I'll do it, just that I'd like to know.

    Let me know by filing an issue on GitHub.

    Visit hardy.io to learn more.

    Credits and notes

    The image diff code is heavily borrowed from PhantomCSS. In fact, if PhantomCSS could be run through Selenium, I'd switch over to it straight away.

    Project structure and the automatic way Cucumber files, steps and suchlike all play nicely together mostly comes from WebDriverJS and CucumberJS.

    Note: It’s just Hardy, not Hardy.JS. The idea here is just to show how to setup basic CSS tests and provide one (JS) solution to the problem. It’d be nice if Hardy could include common JBehave steps for CSS testing, too. And whatever it is Ruby people use.

    CSS, Javascript, Development

  • 19 Jun 2013

    Chrome Devtools Extension Starter Kit

    I've been working on a new little side project recently (more info soon) which involves a Chrome Devtools extension. These are Chrome extensions that aren't targeted at changing your usual browsing experience like a normal extension but are actually aimed at modifying or providing extra behaviour for the Chrome Devtools panel. It's a little bit meta.

    The theory behind them isn't that tricky and if you have any experience with standard Chrome extensions, you'd probably 'get' devtools extensions quite easily. Unfortunately, I was coming in with no prior knowledge at all and got quite lost for a few days. The docs are very helpful and the Google Group is handy but it still took me a while.

    The main area I had difficulty with was the message passing. Sending info from my panel in the devtools window to the page being inspected and vice versa proved to be... complicated. I've used message events before but it still took me a while to figure out what was going where. It seems there are three individual execution contexts:

    • The Panel
    • The Background
    • The Page

    They each have access to different bits of the Chrome Extension API and the inspected page. I won't go into details of that here as others have explained it better. What I will do, though, is share this project - Devtools Extension Starter Kit. This does nothing more than create a panel containing three buttons. The first executes a snippet of JS in the context of the inspected page, the second attaches a complete JS file to the inspected page (allowing execution) and the third replaces the entire inspected page with a button. When you click on this button, it will send data back from the inspected page to the devtools panel.

    Essentially, it's all the communication channels I found I needed for my extension and should be a useful starting point for anyone wanting to do something similar.

    I also heard today about a new Yeoman generator that makes building Chrome Extensions easier. It would probably have made the first bit of my investigations a lot easier if I'd known about that beforehand.

    • Devtools Extension Starter Kit

    Development, Geek, Javascript

  • newer posts
  • older posts

Categories

Toys, Guides, Opinion, Geek, Non-geek, Development, Design, CSS, JS, Open-source Ideas, Cartoons, Photos

Shop

Colourful clothes for colourful kids

I'm currently reading

Projects

  • Awsm Street – Kid's clothing
  • Stickture
  • Explanating
  • Open Source Snacks
  • My life in sans-serif
  • My life in monospace
Simon Madine (thingsinjars)

@thingsinjars

Hi, I’m Simon Madine and I make music, write books and code.

I’m the CTO for workpin.

© 2022 Simon Madine