why-we-use-web-components

It’s August, 2018. I’m at the office, sitting by the window staring rain pouring down from the sky. A warm cup of tea in my hand, about to sip it, but the phone suddenly rings. I don’t recognize the number. I hesitate for a moment whether to pick it up or not. Maybe it’s again one of those telemarketers trying to sell me something?

We had the urge to create a tech-agnostic instead of tech-specific system. A system that is based on web standards and would survive the births and deaths of JavaScript frameworks.

Thinking of this particular autumn evening today, a year and a half later, I’m delighted I picked up the phone. This one phone call ended up resulting in the biggest personal project I have worked on so far.

While I used to work with bigger clients and projects when we lived in United States, this felt different. I personally sold the project and were responsible for most of the things from initial research all the way to the design system’s overall architecture.

A few months went by after our first call. I went to see the client during a couple of occasions to plan the possible collaboration. After some back and forth negotiation we ultimately started working together in the beginning of January, 2019.

And so Duet Design System was born.

Controversial opinions

As you might have guessed based on the headline, Duet Design System has something to do with Web Components. Quite a lot actually. Our whole frontend component architecture is based on Web Components. But how and why did we end up using them? Seems like a very controversial tech, at least if you solely base your opinion on what’s happening on Twitter.

Well if anything, we didn’t actually start by choosing Web Components. It was more that they chose us. Truth speaking, we weren’t even considering them until only later on in the spring once the research I was working on started suggesting us that they might be the most viable option.

One of the biggest challenges for us turned out to be the number of different tech stacks in use. My client had applications built using Angular, React, Ember, Vanilla JavaScript, basic HTML, native code. And more.

We couldn’t just choose [a framework] because the different tech stacks were picked by different teams (or even different sub-companies) for different reasons and we would have never succeeded if we would have started trying to force everyone to use a specific technology like React.

Instead, we had the urge to create a technology-agnostic instead of technology-specific system. A system that is based on web standards and would survive the births and deaths of JavaScript frameworks.

What are Web Components?

Web Components are a set of technologies that provide a standards based component model for the Web. They allow you to create reusable custom elements with their functionality encapsulated away from the rest of the code. Primary technologies used to create them include:

Best parts of Web Components

This past year has made me a big advocate for Web Components. What previously seemed like a distant black box that I tried to stay away from, has now become an important part of my toolset. If I would have to list the main reasons why Web Components work so greatly for Duet Design System, it would be these four things:

Tech-agnostic instead of tech-specific

In order to create modular interfaces, a design system needs to be technology-agnostic instead of technology-specific. Web Components offer this benefit and make it easier to reduce our design system’s complexity and improve its reusability.

Future proofing with Web Standards

Web Standards are more future proof than any given JavaScript framework. I’ve seen different frameworks come and go during my almost two decade long career on the web, but Web Standards keep thriving and evolving.

Any framework or no framework

Web Components can be used with basically any JavaScript framework or no framework at all. This means we’re able to support all our product teams from a single codebase. This makes it possible for our small design system team to be very efficient. Currently we can offer support for all of the following with just one set of components:

Full Encapsulation

Shadow DOM allows components to have their own DOM tree that can’t be accidentally accessed from the main document. For us this means that “everything just works” when the components are implemented onto different environments and platforms. Styles cannot penetrate a component from the outside, and styles inside a component won’t bleed out.

Debunking Myths

I guess you really can’t talk about Web Components without also talking about their negative sides. They’re built on evolving standards which have their flaws and limitations. I’ve seen a few myths go around though which I’d like to clear just a bit for anyone wondering:

Shadow DOM doesn’t work with forms

This is true to some extent and one of the drawbacks of Shadow DOM. It makes sense though if you think about it. Shadow DOM isn’t a part of the normal DOM tree/document. Whether this is an issue for you or not depends on what you’re building.

There’re a few approaches on how to get around this, but the easiest is to not use Shadow DOM for components that host form inputs. The other option would be to create an invisible input and append that with JavaScript to the parent document.

Web components can’t be server side rendered

Not true. We’ve built support for server side rendering into our Web Components. While it might be hard to do if you would have to build a solution from scratch, there are tools like Stencil.js that solve this for you.

Duet’s Web Components package includes a hydrate app that is a bundle of the same components, but compiled so that they can be hydrated on a Node.js server and generate static HTML and CSS.

Using web components means duplicate CSS

It can if you don’t make your components modular enough. But if your frontend architecture is designed around reusability and modularity then this isn’t really an issue. Yes, you might have to set margin and padding once to zero for each Shadow DOM, but let’s not exaggerate the impact of this. We use one Sass mixin for this in Duet.

Web components aren’t accessible

Not true at all. Of course you can construct them in a way which isn’t accessible. But so you can do by building things with [insert a JavaScript framework] as well. We provide support for WCAG 2.1 Spec and have tested our Web Components to work with all common screen reader and OS combinations like:

Web components don’t work with React

This is both true and false. Web Components themselves work fine, but by default React passes all data to Custom Elements in the form of attributes, meaning you can’t pass objects or arrays without workarounds. Additionally, because React has its own synthetic event system, it cannot listen for DOM events coming from Custom Elements without the use of refs.

Both of these issues can be fixed, but for some they might be showstoppers. For our team the benefits of Web Components far surpassed these and we decided to work our way around these issues by automatically wrapping our Web Components with React based wrappers.

Web Components aren’t production ready

This last myth I probably hear the most often. People seem to assume that because Web Components are built on evolving standards they can’t be used for anything else except experiments. Well guess what, the whole World Wide Web is a set of standards that are constantly transitioning and evolving.

If you need to see some real life examples, take a look of Firefox’s user interface that is now built with Web Components. Or Apple who uses Web Components in their new Apple Music service.

The future is bright

This is an incredibly exciting time to be a web developer. New standards[1] like the Web Components have made it so much easier to build systems that can support a range of platforms and frameworks via a single code base. To be completely honest; five years ago I would have probably laughed if someone would have claimed that all of this would be easy.

Enthusiastic from our positive experiences, the next step is to expand towards native apps and create browser based tools around Duet’s Web Components that allow us to move further from static design tools. ❦

Web Components out in the wild

[1] If we can really consider something introduced in 2011 to be new anymore.

Get in touch

I’m an independent design systems architect specialized in helping organizations to build design systems. Get in touch.

Share

styled-components,-styled-systems-and-how-they-work

Styled Components, Styled Systems and How They Work

Sometimes the libraries that we use daily can seem a little bit like magic. By taking the time to understand the fundamental JavaScript features that make these libraries possible, we become better equipped to improve our use of those libraries, applying their functionality in unique and relevant ways.

In React, there are two libraries that work together to make the creation of presentational components very simple: styled-components and styled-system.

Here is an example of these libraries working together, courtesy of the styled-system documentation.

import styled from 'styled-components'
import { color } from 'styled-system'

const Box = styled.div`
	${color}
`

This code created a React component named Box that receives the props color and bg.

<Box color="#fff" bg="tomato">  
	Tomato
</Box>

In this article I am going to cover:

  • Generating React Components with Tagged Templates
  • Implementing a simple version of Styled Components
  • Diving into how styled-components and styled-systems actually operate together

Template Literals

In order to understand how styled-components and styled-systems work, it’s best to first understand where these libraries get their power from: Template Literals.

The most common use-case of Template Literals is string concatenation.


const string = `I am a template literal`;


const expressions = 'dynamic values';
const string = `I can contain ${expressions}`;

Template Literals are clean, they’re effective and they have been so well adopted for string concatenation that some of us (e.g. me) don’t even care how this syntax works, or realize that its functionality can be expanded upon.

Template Literals and the preceding function

When we implement Template Literals, something unintuitive happens: The contents of the Template Literal (e.g. the text and placeholders) are passed to a function.

Which function? In the two examples above, a default function with the job of concatenating the text and placeholders into a single string.

const expressions = 'dynamic values';
const example = `I can contain ${expressions}`;

console.log(example);  

But Template Literals are not confined to performing only string concatenations. JavaScript lets us use our own custom functions to do whatever we want with the text and placeholders within the Template Literal. This type of function is called a Tag and to use it, you simply reference the function name — the Tag — in front of the Template Literal. Doing this results in the creation of a Tagged Template.

For example, here is a simple function that does not accept any parameters and prints a static message to the console.

const printStaticMessage = () => { 
	console.log('My static message'); 
}

We can invoke this function in two ways: As a traditional function call and as a Tagged Template.

printStaticMessage();  
printStaticMessage``;  

Notice that each invocation generated the same result. So, we can conclude that Tagged Templates are just an alternative way to invoke a function.

Using the Template Literal as Arguments for the Tag

A more useful Tagged Template would utilize the text and placeholders within the Template Literal. Let’s create a Tag that prints out its arguments.

const printArguments = (...args) => { console.log(...args); }

const var1 = "my";
const var2 = "message"

printArguments`This is ${var1} custom ${var2}!`;

//   ["This is "," custom ","!"],
//   "my",
//   "message"

The first argument is an array of strings that represent the text in the Template Literal, separated into elements based on the location of the placeholders.

The remaining n arguments are strings with the value of each placeholder, ordered based on when they are defined in the Template Literal.

Knowing that these are the arguments that Tags receive, we can guess what the implementation of the default Template Literals concatenation function looks like:

const defaultFunction = (stringArray, ...values) => {  
  return stringArray.reduce((acc, str, i) => {    
     return values[i] ? acc   str   values[i] : acc   str;  
  }, '');
}

const var1 = "my";
const var2 = "message"

const example = defaultFunction`This is ${var1} custom ${var2}!`;

console.log(example);  

Passing functions into the Tagged Template

Since the Tag is simply receiving the Template Literal placeholders as argument variables, those variables can contain any JavaScript object, like a number, string or function. Here is an non-useful example where we pass a function in the Template Literal placeholder and execute it in the Tag.

const executeFirstPlaceholder = (textArray, placeholder) => {  			
	placeholder();
}

executeFirstPlaceholder`${() => { console.log('first placeholder')}}`; 

// >>> first placeholder

Returning functions from a Tagged Template

Like any other function in JavaScript, a Tagged Template can return objects, strings, numbers, and, of course, functions.

const multiply = (multiple) => (num) =>	
	parseInt(num[0]) * parseInt(multiple[0]);
    
const double = multiply`2`;
const result = double`4`;    

console.log(result);  // >>> 8

Making the leap to React

React’s “function components” are very simply JavaScript functions that can be rendered into the DOM. Here is an example of a Tagged Template returning a React function component.

const withGreeting = ([greeting]) => 
	({ name }) => 

{greeting}, {name}!

; const Greet = withGreeting`Greetings`; // Render component // Renders in DOM

Greetings, Chris

This is the crux for how Styled Components generate React components.

Styled Components

Utilising tagged template literals (a recent addition to JavaScript) and the power of CSS, styled-components allows you to write actual CSS code to style your components. It also removes the mapping between components and styles — using components as a low-level styling construct could not be easier!

https://www.styled-components.com

Styled Components use Tagged Templates to return React components.

In the following example, styled.h1 is used to create a simple React component containing a

HTML tag, display using the CSS styles specified within the Template Literal

import styled from 'styled-components';

const Title = styled.h1`color: blue;`;

// Render
Regular Title

// Renders to DOM

Regular Title

The styled object contains keys named after common HTML tags — like H1, H2, and div. These keys reference a function that can be used as the Tag in a Tagged Template.

A simple implementation of styled.h1

Let’s try to make a simple implementation of styled.h1. At it’s simplest, the styled.h1 function receives the CSS styles in the back-ticks and transforms them into a style object that it attaches to the underlying element (e.g. h1).

const styledH1 = ([styles]) => ({ children })=> {
  const lines = styles
                  .split(';')
                  .map(line => line.trim())
                  .filter(line => line !== "")

  // Function courtesy of mck89 on StackOverflow
  const convertToCamelCase = (key) =>
    key.replace(/-([a-z])/g, (x, up) => up.toUpperCase())

  const style = lines.reduce((acc, line) => {
    const lineParsed = line.split(':');
    const key = convertToCamelCase(lineParsed[0]);
    const val = lineParsed[1];
    return { ...acc, [key]: val };
  }, {});

  return 

{children}

} const H1 = styledH1` color: red; font-size: 18px; `; // Render

Hello

// Renders in DOM

Hello

At this point, the style we are passing to the function is hard coded and fixed; not able to dynamically change, based on the prop values the component receives.

Let’s look at how the ability to pass functions into our tagged templates can enable things to become more dynamic.

Using functions to access Props in Template Literals and Styled Components

As discussed we discussed, a function passed to a Template Literal placeholder can be executed. Styled Components utilize this feature to generate dynamic styles.

import styled from 'styled-components';

const Button = styled.button`
  color: ${ (props) => props.primary ? 'blue' : 'red' };
`;

class StyledComponentsDemo extends Component { 
  render() {
    return(
      <>
      
      
	  
     
    )
  }
}

When the Styled Component is rendered, each function in the Template Literal is passed the component’s props and those props can be used to impact the presentation of the component.

Note: not all props passed to a Styled Component need to impact the presentation (e.g. onSubmit); they could also be used only by the underlying HTML element.

Styling regular, custom components

Styled Components allow you to style any custom component that you’ve created. First, the custom component must receive the prop className and pass it to the underlying DOM element. Once that is done, pass the custom component to the styled function and invoke it as a Tagged Template to receive a new Styled Component.

import styled from 'styled-components';

const Button = ({ className, children }) => 
  

const ButtonBlue = styled(Button)`color: blue`;

// Render
Blue Button

Styling the Styled Components

Styled Components uses the CSS preprocessor stylis, supporting SCSS-like syntax for automatically nesting styles.

const Thing = styled.button`
  color: black;
    
  :hover {    
    color: blue;  
  }
`

Within SCSS syntax, & references the current component. You can also reference other components like you would reference any other type of selector (e.g. .class or #id) by simply referencing ${OtherComponentName} , but only if it is a Styled Component.

import styled from 'styled-components';

const Item = styled.div`
	color: red;
`
const Container = styled.div`
	& > ${Item} {
		font-size: 2rem;
	}
`
class StyledComponentsDemo extends Component { 
  render() {
    return(
      
        Item 1
      
    )
  }
}

As you can see, we have the ability to not only specify the styles in our components, but also the ability to add some dynamic functionality. Building on this, we are able to better accommodate some common use cases such as adding themes to our applications.

Using Themes

Theming is accomplished by exporting the ThemeProvider component, passing an object to its theme prop, and wrapping the entire app in the ThemeProvider component. This will give every Styled Component access to the theme object.

import styled, { ThemeProvider } from 'styled-components';

const Item = styled.div`
	color: ${( props ) => props.theme.color.primary}
`

const theme = {
  color: {
    primary: 'red'
  }
}

class StyledComponentsDemo extends Component { 
  render() {
    return(
      
	      Item 1
      
    )
  }
}

Components that are not Styled Components can also access theme by using the withTheme function.

import { withTheme } from 'styled-components';

class MyComponent extends React.Component {
  render() {
    return 

{this.props.theme.color.primary}

} } export default withTheme(MyComponent);

Styled System

Styled System is a collection of utility functions that add style props to your React components and allows you to control styles based on a global theme object with typographic scales, colors, and layout properties.

https://styled-system.com

If you created a Button component from Styled Components and you want it to receive foreground and background color props, you can use the styled-system utility function color and pass it as a placeholder function in the Template Literal to enable these props.

import styled, { ThemeProvider } from 'styled-components';
import { color } from 'styled-system'

const theme = {
  colors: {
    primary: 'blue'
  }
}

const Box = styled.div`
  ${color}
`

class StyledSystemDemo extends Component {
  render() {
    return (
      
        <>
          Tomato
          Tomato
        
      
    )
  }
}

Note: The name of the generated props are all outlined in the styled system API.

If there is a theme available, the utility function will try to match the prop value to the theme before using the value as the raw value (e.g. #fff).

Structuring Theme objects

The structure of the theme object and styled-system are tightly coupled. The structure follows a Work-in-Progress specification called System UI Theme Specification.

For instance, the fontSizes and colors keys follow this specification, and their values (arrays or objects) also follow this specification.

export const theme = {
	fontSizes: [
	  12, 14, 16, 20, 24, 32
	]
	fontSizes.body = fontSizes[1]
	colors: {
	  blue: '#07c',
	  green: '#0fa',
	}
}

With the above theme, the fontSize prop on a component could receive the index value of the array, or the alias body.

Under the hood of color

Let’s look at how styled-system implements the utility function color. Remember that a utility function is called like this:

const Button = styled.div`
	${color}
`

This is what the function looks like.

// https://github.com/styled-system/styled-system/blob/v2.3.6/src/styles.js

export const color = props => ({
  ...textColor(props),
  ...bgColor(props)
})

Which is akin to writing this in the Template Literal:

const Button = styled.div`
	${(props) => ({
	  ...textColor(props),
	  ...bgColor(props)
	})}
`

The textColor and bgColor functions will return style objects that are spread within the function. These functions look like this.

// https://github.com/styled-system/styled-system/blob/v2.3.6/src/styles.js

export const textColor = responsiveStyle({
  prop: 'color',
  key: 'colors', // theme key
})

export const bgColor = responsiveStyle({
  prop: 'bg',
  cssProperty: 'backgroundColor',
  key: 'colors'
})

The responsiveStyle function handles all the breakpoints, fallbacks and prop naming. Below, I simplified the styled-system code for demonstrative purposes.

// https://github.com/styled-system/styled-system/blob/v2.3.6/src/util.js
// I simplified this for demonstrative purposes

export const responsiveStyle = ({
  prop,
  cssProperty,
  key,
}) => {
  const fn = props => {
    cssProperty = cssProperty || prop
    const n = props[prop];
    const theme = props.theme;
		
    // ...
		
    return {
      [cssProperty]: theme[key][n]
    }

    // ...

  }

  return fn
}

Which can be represented to look something like this:

const Button = styled.div`
  {
    ${...(props) => (
      { color: props.theme['colors'][props.color] }
    )}
	${...(props) => (
      { backgroundColor: props.theme['colors'][props.bg] }
    )}
  }
`

Breakpoints and responsive themes

For responsive themes, styled-system lets you establish breakpoints  in the theme and then lets you pass an array as a prop with different values for each breakpoint. styled-system takes a mobile first approach, so the first index will always be the smallest breakpoint.



// theme.js
const breakpoints = ['40em', '52em', '64em', '80em']

export default { breakpoints };

Conclusion

I was inspired to see how the developers of styled-components and styled-system employed the extended functionality of Template Literals and Tagged Templates to provide users with an intuitive way for adding SCSS to React components.

Have you seen any interesting uses of common functionality in your work recently? Please share!

Sources

Styled System

Styled Components

The magic behind ? styled-components

MDN Web Docs: Template Literals

serverless-components

Serverless Components

Forget infrastructure — Serverless Components enables you to deploy entire serverless use-cases, like a blog, a user registration system, a payment system or an entire application — without managing complex cloud infrastructure configurations.

You can use them now with the Serverless Framework.



Install the Serverless Framework via NPM:

Then, clone one of the pre-made Templates and deploy it, to rapidly create a serverless REST API, Websockets API, Website, Scheduled Task, and much more! Each Template has a README.md with clear instructions on what it does and how to get started.

Check out more Serverless Components here.


Simplicity

Serverless Components are built around higher-order use-cases (e.g. a website, blog, payment system). Irrelevant low-level infrastructure details are abstracted away, and simpler configuration is offered instead.

For example, with minimal configuration, you can deploy…

  • A serverless website hosted on AWS S3, delivered globally and quickly w/ AWS Cloudfront, via a custom domain on AWS Route 53, secured by a free AWS ACM SSL Certificate:

    # serverless.yml
    
    website:
      component: @serverless/website
      inputs:
        code:
          src: ./src
        domain: www.serverless-app.com
  • A serverless API hosted on AWS Lambda, accessible via an AWS API Gateway endpoint under a custom domain on AWS Route 53, secured by a free AWS ACM SSL Certificate:

    # serverless.yml
    
    api:
      component: @serverless/backend
      inputs:
        code:
          src: ./src
        domain: api.serverless-app.com
  • A serverless real-time websockets API hosted on AWS Lambda, accessible via an AWS API Gateway Websockets endpoint:

    # serverless.yml
    
    api:
      component: @serverless/backend-socket
      inputs:
        code:
          src: ./src
  • and much more!

Reusability

While Serverless Components can be easily composed in YAML (serverless.yml), they are written as reusable javascript libraries (serverless.js), with simple syntax inspired by component-based frameworks, like React.

// serverless.js

const { Component } = require('@serverless/core')

class MyBlog extends Component {
  async default(inputs) {
    this.context.status('Deploying a serverless blog')
    const website = await this.load('@serverless/website') // Load a component
    const outputs = await website({ code: { src: './blog-code' } }) // Deploy it
    this.state.url = outputs.url
    await this.save()
    return outputs
  }
}

module.exports = MyBlog

Anyone can build a Serverless Component and share it in our upcoming Registry.

Fast Deployments

Most Serverless Components deploy 20x faster than traditional cloud provisioning tools. Our intention is to design Serverless Components’ that deploy almost instantly, removing the need to emulate cloud services locally.

Vendor-Agnostic

Serverless Components favor cloud infrastructure with serverless qualities (shocker!). We also believe in order to deliver the best product, you must be free to use the best services.

Serverless Components are being designed entirely vendor agnostic, enabling you to easily use services from different vendors, together! Like, AWS Lambda, AWS S3, Azure Functions, Google Big Query, Twilio, Stripe, Algolia, Cloudflare Workers and more.

Vanilla Javascript

Serverless Components are written in vanilla javascript and seek to use the least amount of dependencies, making the entire project as approachable as possible to beginners (and fatigued veterans).


Serverless Components are merely Javascript libraries that provision something/anything.

They are focused primarily on back-end use-cases, and cloud infrastructure with serverless qualities, enabling you to deliver software with radically less overhead and cost. Serverless Components are to serverless back-end use-cases, what React Components are to front-end use-cases.

A Component can be designed to provision low-level infrastructure (e.g. an AWS S3 bucket). However, they can also provision higher-order outcomes — which is when they are at their best. Examples of a higher-order outcome are:

  1. A group of infrastructure with a purpose, like a type of data processing pipeline.
  2. A software feature, like user registration, comments, or a payment system.
  3. An entire application, like a blog, video streaming service, or landing page.

The syntax for writing a Serverless Component makes it trivial to load child Components and deploy them, enablng you to lean on low-level Components to handle difficult infrastructure provisioning tasks, while you rapidly create a higher-order abstraction.

Serverless Components can be used declaratively (via the Serverless Framework’s serverless.yml file) or programatically (via a serverless.js file).

Using Components declaratively is great if you want to build a serverless application as easily as possible, but not re-use it.

Using Components programmatically is also great for building serverless applications easily. And if you’d like to build a reusable Serverless Component, this is currently the only way.

In the Using Components section, we’ll focus on the declarative experience (serverless.yml). In the Building Components section, we’ll focus on the programmatic expereince (serverless.js).


Serverless.yml Basics

serverless.yml is the easiest way to compose Serverless Components into an application.

The syntax for using Serverless Components looks like this:

name: my-serverless-website

website: # An instance of a component is declared here.
  component: @serverless/website@2.0.5 # This is the component you want to create an instance of.
  inputs: # These are inputs to pass into the component's "default()" function
    code:
      src: ./src

You can deploy this easily via the Serverless Framework with the $ serverless command.

$ serverless # Installs and deploys the components...

There is nothing to install when using Serverless Components via serverless.yml. Instead, when you deploy a serverless.yml, its Components are downloaded automatically at the beginning of that deployment (if they aren’t already downloaded), and stored in a central folder at the root of your machine. This effectively caches the Components in one location, so you don’t clutter your project files with Component libraries and don’t download duplicates.

Serverless Components are distributed via NPM. When Components are downloaded, a basic NPM installation is happening behind the scenes.

Because of this, you use the NPM name in the component: property.

website: # An instance of a component.
  component: @serverless/website # This is the NPM package name

You can also use the same semantic versioning strategy that NPM uses.

website: # An instance of a component.
  component: @serverless/website@3.0.5 # This is the NPM package name and version

When you add a version, only that Component version is used. When you don’t add a version, upon every deployment, the Serverless Framework will check for a newer version, and use that, if it exists.

Note: While in Beta, you cannot currently use Serverless Components within an existing Serverless Framework project file (i.e. a project file that contains functions, events, resources and plugins).

Inputs

Every Serverless Component has one main function to make it deploy something. This is known as the default() function (you can learn more about it in the “Building Components” section). This default() function takes an inputs object.

When you specify inputs for a Component in serverless.yml, they are simply arguments that are being passed into that Serverless Component’s default() function.

name: my-serverless-website

website:
  component: @serverless/website@3.0.5
  inputs: # Inputs to pass into the component's default function
    code:
      src: ./src

Outputs

When a Component function (e.g. the default() function) is finished running, it returns an outputs object.

You can reference values of this outputs object in serverless.yml to pass data into Components, like this:

backend:
  component: @serverless/backend@1.0.2
  inputs:
    code:
      src: ./src
    env:
      dbName: ${database.name}
      dbName: ${database.region}
      
database:
  component: @serverless/aws-dynamodb@4.3.1
  inputs:
    name: users-database

This tells the Serverless Framework to pass a few of the outputs from the database instance into the backend instance. Specifically, the name and region of the database are being added as environment variables to the backend instance, so that it can interact with that database.

This also tells the Serverless Framework what depends on what. The Framework builds a graph based on this, and deploys everything in that order. Circular references however do not work and the Framework will throw an error.

Credentials

Upon deployment, whether it’s a serverless.yml or serverless.js, Serverless Components’ core looks for a .env file in the current working directory.

Upon deployment, if a .env file exists, Serverless Components will add the content of it as environment variables. If you use specific environment variable names that match that of a cloud infrastructure vendor’s access keys/tokens, upon deployment, Serverless Components will automatically inject that into the Components that need that vendor to provision infrastructure.

Here are the keys that are currently supported (keep in mind Components are in Beta and we’ve mostly been focused on AWS infrastructure up until now):

AWS_ACCESS_KEY_ID=123456789
AWS_SECRET_ACCESS_KEY=123456789

These credentials will be used by any and all Components in your serverless.yml or serverless.js — as well as their child Components — if you specify the environment variables exactly like this.


If you want to build resuable Serverless Components, it starts and ends with a serverless.js file.

Serverless.js Basics

In your current working directory, install the Serverless Components core (@serverless/core) as a local dependency.

npm i --save @serverless/core

Create a serverless.js file, extend the Component class and add a default method, to make a bare minimum Serverless Component, like this:

// serverless.js

const { Component } = require('@serverless/core')

class MyComponent extends Component {
  async default(inputs = {}) { return {} } // The default functionality to run/provision/update your Component
}

module.exports = MyComponent;

default() is always required. It is where the logic resides in order for your Component to make something. Whenever you run the $ serverless command, it’s always calling the default() method.

You can also any other methods to this class. A remove() method is often the next logical choice, if you want your Serverless Component to remove the things it creates.

You can add as many methods as you want. This is interesting because it enables you to ship more automation with your Component, than logic that merely deploys and removes something.

It’s still early days for Serverless Components, but we are starting to work on Components that ship with their own test() function, or their own logs() and metrics() functions, or seed() for establishing initial values in a database Component. Overall, there is a lot of opportunity here to deliver outcomes that are loaded with useful automation.

All methods other than the default() method are optional. All methods take a single inputs object, not individual arguments, and return a single outputs object.

Here is what it looks like to add a remove method, as well as a custom method.

// serverless.js

const { Component } = require('@serverless/core')

class MyComponent extends Component {

  /*
   * Default (Required)
   * - The default functionality to run/provision/update your Component
   * - You can run this function by running the "$ serverless" command
   */

  async default(inputs = {}) { return {} }

  /*
   * Remove (Optional)
   * - If your Component removes infrastructure, this is recommended.
   * - You can run this function by running "$ serverless remove"
   */

  async remove(inputs = {}) { return {} }

  /*
   * Anything (Optional)
   * - If you want to ship your Component w/ extra functionality, put it in a method.
   * - You can run this function by running "$ serverless anything"
   */

  async anything(inputs = {}) { return {} }
}

module.exports = MyComponent;

When inside a Component method, this comes with utilities which you can use. Here is a guide to what’s available to you within the context of a Component.

// serverless.js

const { Component } = require('@serverless/core')

class MyComponent extends Component {

  async default(inputs = {}) {
  
    // this.context features useful information
    console.log(this.context)

    // Common provider credentials are identified in the environment or .env file and added to this.context.credentials
    // when you run "components", then the credentials in .env will be used
    // when you run "components --stage prod", then the credentials in .env.prod will be used...etc
    // if you don't have any .env files, then global aws credentials will be used
    const dynamodb = new AWS.DynamoDB({ credentials: this.context.credentials.aws })
    
    // You can easily create a random ID to name cloud infrastruture resources with using this utility.
    const s3BucketName = `my-bucket-${this.context.resourceId()}`
    // This prevents name collisions.

    // Components have built-in state storage.
    // Here is how to save state to your Component:
    this.state.name = 'myComponent'
    await this.save()

    // Here is how to load a child Component. 
    // This assumes you have the "@serverless/website" component in your "package.json" file and you've run "npm install"
    let website = await this.load('@serverless/website')
    
    // You can run the default method of a child Component two ways:
    let websiteOutputs = website({ code: { src: './src' }})
    let websiteOutputs = website.default({ code: { src: './src' }})

    // If you are deploying multiple instances of the same Component, include an instance id.
    let website1 = await this.load('@serverless/website', 'website1')
    let website2 = await this.load('@serverless/website', 'website2')
    
    // Child Components save their state automatically.

    // You can also load a local component that is not yet published to npm
    // just reference the root dir that contains the serverless.js file
    // You can also use similar syntax in serverless.yml to run local Components
    let localComponent = await this.load('../my-local-component')

    // Here is how you can easily remove a Component.
    let websiteRemoveOutputs = await website.remove()
    
    // Here is how you can call any custom method on a Component.
    let websiteRemoveOutputs = await website.test({})

    // If you want to show a status update to the CLI in an elegant way, use this.
    this.context.status('Uploading')

    // If you want to show a log statement in the CLI in an elegant way, use this.
    this.context.log('this is a log statement')

    // Return your results
    return { url: websiteOutputs.url }
  }
}

module.exports = MyComponent;

Just run serverless in the directory that contains the serverless.js file to run your new component. You’ll will see all the logs and outputs of your new component. Logs and outputs of any child component you use will not be shown, unless you run in debug mode: serverless --debug. You can also run any custom method/command you’ve defined with serverless .

For complete real-world examples on writing components, check out our official components

Development Tips

Here are some development tips when it comes to writing Serverless Components:

Use Local References

When writing a Serverless Component, you can reference it locally via a serverless.yml, or another serverless.js. Keep in mind, a directory can only contain 1 serverless.yml or serverless.js. A directory cannot contain a both a serverless.yml and a serverless.js.

Here’s how to reference a local Component via serverless.yml:

name: my-project

myComponent:
  component: ../src
  inputs:
    foo: bar

Here’s how to reference a local Component via serverless.js:

class myFirstComponent extends Component {

  default() {
    const mySecondComponent = this.load('../components/my-second-component')
  }
 
}

Start With The Outcome

When making a Serverless Component, it can be tempting to break it down into several levels of child Components, to maintain separation of concerns and increase the ways your work could be re-used.

However, provisioniong back-end logic can be more complicated than designing a front-end React Component. We’ve learned over-optimizing for granular separation of concerns is a fast way to burn yourself out!

We recommend starting with a focus on your desired outcome. Create one Serverless Component that solves that problem first. After you’ve achieved your initial goal, then start breaking it down into child Components.

The Outcome Is Your Advantage

Provisioning infrastructure can be quite complicated. However, Serverless Components have one powerful advantage over general infrastructure provisiong tools that seek to enable every possible option and combination (e.g. AWS Cloudformation) — Serverless Components know the specific use-case they are trying to deliver.

One of the most important lessons we’ve learned about software development tools is that once you know the use-case, you can create a much better tool.

Components know their use-case. You can use that knowledge to: 1) provision infrastructure more reliably, because you have a clear provisioning path and you can program around the pitfalls. 2) provision infrastructure more quickly 3) add use-case specific automation to your Component in the form of custom methods.

Keep Most State On The Cloud Provider

Serverless Components save remarkably little state. In fact, many powerful Components have less than 10 properties in their state objects.

Components rely on the state saved within the cloud services they use as the source of truth. This prevents drift issues that break inrastructure provisioning tools. It also opens up the possibility of working with existing resources, that were not originally managed by Serverless Components.

Store State Immediately After A Successful Operation

If you do need to store state, try to store it immediately after a successful operation. This way, if anything after that operation fails, your Serverless Component can pick up where it left off, when the end user tries to deploy it again.

Optimize For Accessibility

We believe serverless infrastructure and architectures will empower more people to develop software than ever before.

Because of this, we’re designing all of our projects to be as approachable as possible. Please try to use simple, vanilla Javascript. Additionally, to reduce security risks and general bloat, please try to use the least amount of NPM dependencies as possible.