Creating web components using Microsoft FAST elements

srungta

Shaswat Rungta

Posted on September 21, 2021

Creating web components using Microsoft FAST elements

What is FAST?

Microsoft FAST is a collection of technologies built on Web Components and modern Web Standards It helps you write your custom HTML elements with ease.

You can read more about web components on this mozilla docs page

What we will try to achieve?

  • Set up a managed typescript project to write the components.
  • Have hot reload and watch mode enabled.
  • Create a web component that displays the name in uppercase based on an input attribute with an optional greeting.

Setting up the environment.

  1. Install Node from https://nodejs.org/en/download/ page.
  2. After the installation is complete, open a command line window (cmd, bash, powershell anything is fine) and type
node -v
Enter fullscreen mode Exit fullscreen mode

If all is good you should see something like

~/srungta>node -v
v12.3.1
Enter fullscreen mode Exit fullscreen mode
  1. Next, type
npm -v
Enter fullscreen mode Exit fullscreen mode

If all is good you should see something like

~/srungta>npm -v
6.9.0
Enter fullscreen mode Exit fullscreen mode

Creating the package

  1. Create a workspace folder.
mkdir FAST-playground
cd FAST-playground
Enter fullscreen mode Exit fullscreen mode
  1. Initialize the npm package using
npm init
Enter fullscreen mode Exit fullscreen mode

This should ask you a couple of questions about your package.

This is what i used.

~/FAST-playground>npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See 'npm help json' for definitive documentation on these fields
and exactly what they do.
Use 'npm install <pkg>' afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (FAST-playground) FAST-playground
version: (1.0.0)
description: Test package to create FAST components
entry point: (index.js) index.js
test command:
git repository:
keywords: FAST
author: srungta
license: (ISC) MIT
About to write to ~/FAST-playground\package.json:
Enter fullscreen mode Exit fullscreen mode
{
  "name": "FAST-playground",
  "version": "1.0.0",
  "description": "Test package to create FAST components",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "FAST"
  ],
  "author": "srungta",
  "license": "MIT"
}
Enter fullscreen mode Exit fullscreen mode
Is this OK? (yes) yes
~/FAST-playground>
Enter fullscreen mode Exit fullscreen mode

At this point it would also be advisable to initialize a Git repo in the same directory using git init so that you can track changes across files easily.

Adding typescript

Typescript adds much needed type support for javascript.
Webpack makes it easier to create bundles for JS, CSS and HTML files. It also helps us setup the TS to JS transpilation.
So we add typescript to the package and use that instead of plain JS.

  1. Install typescript using
npm install --save typescript
Enter fullscreen mode Exit fullscreen mode

If you are using git, better add a .gitignore file with node_modules as one of the omissions.

  1. Initialize a tsconfig.json file using
~/FAST-playground>.\node_modules\.bin\tsc --init
Enter fullscreen mode Exit fullscreen mode
  1. Edit your tsconfig.json to set your preferences. I am using the below configuration. You can check the other options mentioned in the official documentation
{
  "compilerOptions": {
    "allowJs": true,
    "allowSyntheticDefaultImports": true,
    "experimentalDecorators": true,
    "module": "ES2015",
    "moduleResolution": "Node",
    "noImplicitAny": true,
    "target": "ES2015"
  },
  "files": ["src/index.ts"]
}
Enter fullscreen mode Exit fullscreen mode

Add dummy typescript files

  1. Create a file src/index.ts with following contents.
const adder = (a: number, b: number):number => {
    return a+ b;
}
console.log("Stuff printed from js file");
export {adder};
Enter fullscreen mode Exit fullscreen mode
  1. In package.json, add a build script that we will use to build this package.
...
"scripts": {
    "build": "tsc",
    ...
  },    
...
Enter fullscreen mode Exit fullscreen mode
  1. Run the build command at the package root.
npm run build
Enter fullscreen mode Exit fullscreen mode

You should see a new folder called dist that has the transpiled js file.

You should also add this dist folder to your .gitignore.

Adding a dummy HTML file

Add a index.html file with the following contents.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Webpack App</title>
  </head>
  <body>
    <h1>Hello world!</h1>
    <h2>Tip: Check your console</h2>
  </body>
</html>

Enter fullscreen mode Exit fullscreen mode

Adding webpack

While the tsc is sufficient for now, running it again and again after every change is annoying.
We will setup webpack as our build system, so that it can watch the changed files, generate the js files, bundle them as a single file and serve up the html (yet to be added) files.

tsc comes with a default --watch flag that we could use to watch the files for compilation. We are using webpack as it helps add plugins for other things also.

  1. Install webpack as a dependency.
npm install webpack webpack-cli webpack-dev-server --save-dev
Enter fullscreen mode Exit fullscreen mode
  1. Install the loaders that we will use. Since we want to use webpack to transpile typescript, we need a loader that can do that. We will use ts-loader.
npm install ts-loader --save-dev
Enter fullscreen mode Exit fullscreen mode
  1. Add webpack.config.js Add a new file next to package.json called webpack.config.js. We will try to do a few things.
  2. Process SCSS files to CSS
  3. Transpile TS files to JS
  4. Inject the bundled files in an html file to test.
  5. Process assets.
  6. Minify CSS and JS files.

We can do this using the below webpack file.

// Generated using webpack-cli https://github.com/webpack/webpack-cli
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const isProduction = process.env.NODE_ENV == "production";
const stylesHandler = isProduction
  ? MiniCssExtractPlugin.loader
  : "style-loader";
const config = {
  entry: "./src/index.ts",
  output: {
    path: path.resolve(__dirname, "dist"),
  },
  devServer: {
    open: true,
    host: "localhost",
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "index.html",
    }),

    // Add your plugins here
    // Learn more about plugins from https://webpack.js.org/configuration/plugins/
  ],
  module: {
    rules: [
      {
        test: /\.(ts|tsx)$/i,
        loader: "ts-loader",
        exclude: ["/node_modules/"],
      },
      {
        test: /\.css$/i,
        use: [stylesHandler, "css-loader"],
      },
      {
        test: /\.s[ac]ss$/i,
        use: [stylesHandler, "css-loader", "sass-loader"],
      },
      {
        test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
        type: "asset",
      },

      // Add your rules for custom modules here
      // Learn more about loaders from https://webpack.js.org/loaders/
    ],
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js"],
  },
};

module.exports = () => {
  if (isProduction) {
    config.mode = "production";
    config.plugins.push(new MiniCssExtractPlugin());
  } else {
    config.mode = "development";
  }
  return config;
};

Enter fullscreen mode Exit fullscreen mode

For this webpack config to work, you need to install the dependencies as well.

npm install -D css-loader html-webpack-plugin mini-css-extract-plugin sass sass-loader style-loader
Enter fullscreen mode Exit fullscreen mode
  1. Add webpack script to npm commands. In your package.json file add the following scripts
...
"scripts": {
  "build": "webpack --mode=production --node-env=production",
  "build:dev": "webpack --mode=development",
  "build:prod": "webpack --mode=production --node-env=production",
  "watch": "webpack --watch",
  "serve": "webpack serve",
  "start": "webpack serve"
}
...
Enter fullscreen mode Exit fullscreen mode
  1. Type npm run build:dev to see a dist folder getting created with an index.html file and a main.js file.

  2. Type npm run start to see a dev server start and your html file should pop up. Changes to your files should automatically refresh the browser.

Adding FAST element.

Now that we have the basic dev experience setup, we will start with FAST element development.
Install the FAST element package using

npm install @microsoft/fast-element
Enter fullscreen mode Exit fullscreen mode

Adding the custom component.

  1. Create a new file called src/PersonCard.ts with following contents.
import { attr, customElement, FASTElement, html} from "@microsoft/fast-element";
const template = html<PersonCard>`<h1>${(x) => x.shouldGreet ? "Hello" : ""} ${(x) => x.name?.toUpperCase()}</h1>`;
@customElement({
  name: "person-card",
  template: template,
})
class PersonCard extends FASTElement {
  @attr name: string;
  @attr({ mode: 'boolean' }) shouldGreet: boolean;
}
export { PersonCard };
Enter fullscreen mode Exit fullscreen mode
  1. Export the newly created web component. Update your index.ts with the following
export * from "./PersonCard";
Enter fullscreen mode Exit fullscreen mode

You can remove rest of the dummy code.

  1. Update your index.html file to use your new web component.
<!DOCTYPE html>
<html>
  ...
  <body>
    <h1>Hello world!</h1>
    <h2>Tip: Check your console</h2>
    <person-card name="Kirk" shouldGreet></person-card>
  </body>
</html>

Enter fullscreen mode Exit fullscreen mode

Run npm run start to see the web component in action.
Voila. Your first web component works.
Webpack demo

Keep committing your changes at regular intervals as checkpoints.

BONUS : Setup up storybook

Storybook JS is a nifty tool for UI component testing.
We will setup a storybook so that we can test the web component in isolation.

  1. Add storybook js
    We will use the storybook init command to add storybook dependencies automatically.
    At project root, run npx sb init

  2. Choose the html option.

√ Do you want to manually choose a Storybook project type to install? ... yes
√ Please choose a project type from the following list: » html
Enter fullscreen mode Exit fullscreen mode
  1. This command should add the dependencies in package.json, it will also add the related scripts and some sample stories.

Adding stories for PersonCard

  1. Create a file named src/PersonCard.stories.ts with following contents.
import { Story, Meta } from "@storybook/html";
import { PersonCard } from ".";
PersonCard;
export default {
  title: "Components/PersonCard",
  argTypes: {
    name: { control: "text" },
    shouldGreet: { control: "boolean" },
  },
} as Meta;
const Template: Story<{ name: string; shouldGreet: boolean }> = (args) => {
  return `<person-card  name="${args.name}" shouldGreet="${args.shouldGreet}"></person-card>`;
};
export const Primary = Template.bind({});
Primary.args = {
  name: "Captain Kirk",
  shouldGreet: true
};
Enter fullscreen mode Exit fullscreen mode
  1. Run npm run build-storybook to build and then npm run storybook to start your storybook. You will get an error if the sort TypeError: Cannot read property 'get' of undefined This is being tracked as a Github issue on the storybookjs repo. DefinePlugin cannot read property 'get' of undefined

You should add storybook-static to your .gitignore

  1. Based on the suggestion in the issue, run this command to install dotenv-webpack
npm install dotenv-webpack
Enter fullscreen mode Exit fullscreen mode
  1. Run npm run storybook again to start your storybook. You should see an option in left nav with PersonCard title. Click on it and you should see a UI like below. Storybook for person card

Click on person card. But why does the UI does not show the text?

This is because there is an existing issue with storybook.
Storybook uses babel as a transpiler for typescript instead of ts-loader.
We can force storybook to use ts-loader by updating the .storybook/main.js.
This is what is also done in the official FAST repo

module.exports = {
  stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
  addons: ["@storybook/addon-links", "@storybook/addon-essentials"],
  webpackFinal: async (config) => {
    config.module.rules.push({
      test: /\.ts$/,
      use: [
        {
          loader: require.resolve("ts-loader"),
        },
      ],
    });
    return config;
  },
};
Enter fullscreen mode Exit fullscreen mode

The UI will still not pick up. By default installing ts-loader adds the latest version to package.json.
However FAST elements in storybook do not work with that version for some reason 😢.
This can be fixed by running

npm install -D ts-loader@^7.0.2
Enter fullscreen mode Exit fullscreen mode

This is the same version that the official FAST repo uses. Link to the Github repo

Rerun npm run storybook and things should work now with a UI like below. 😊
Storybook for person card

Change the text in the controls and see it live in action.

Fin.

💖 💪 🙅 🚩
srungta
Shaswat Rungta

Posted on September 21, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related