Skip to content

Instantly share code, notes, and snippets.

@aelbore
Last active December 7, 2023 20:14
Show Gist options
  • Star 19 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save aelbore/d80c98bde558987c045e4798b570afdf to your computer and use it in GitHub Desktop.
Save aelbore/d80c98bde558987c045e4798b570afdf to your computer and use it in GitHub Desktop.
Step by Step creating web components in typescript using rollup

Getting Started

  • Install Dependencies
    npm init
    npm install --save-dev ts-node typescript tslib express @types/express
    

Create your web server

  • Create server.ts in root folder of your app.
    import * as express from 'express';
    import * as http from 'http';
    import * as path from 'path';
    
    import { AddressInfo } from 'net';
    
    const app = express();
    
    const PORT = 3000;
    const HOST_NAME = `localhost`;
    const PUBLIC_FOLDER = 'public';
    
    app.use(`/${PUBLIC_FOLDER}`, express.static(path.resolve(PUBLIC_FOLDER)));
    
    app.all('/*', function(req: express.Request, res: express.Response) {
      res.sendFile('index.html', { root: path.resolve(PUBLIC_FOLDER) })
    })
    
    const server = http.createServer(app);
    server.listen(PORT, HOST_NAME)
      .on('listening', function() {
        const { port, address } = server.address() as AddressInfo;
        console.log(`Express server started on port ${port} at ${address}.`); 
      })
  • Create public folder
  • Create public/index.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>TypeScript Web Component</title>
    </head>
    <body>
      <h1>Hello World.</h1>
    </body>
    </html>
  • Create scripts in package.json
     "scripts": {
      "serve": "ts-node -O '{ \"module\": \"commonjs\" }' server.ts"
     } 
  • Run your server
    npm run serve
    
  • Browse your application
    http://localhost:3000
    

Create Web Components

  • Create src folder
  • Create src/hello-world.ts
  • "Hello World" Custom Elements
     class HelloWorld extends HTMLElement {
    
      connectedCallback() {
        this.innerHTML = `<h1>Hello World.</h1>`
      }
    
    }
    
    customElements.define('hello-world', HelloWorld);
  • Update public/index.html file
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>TypeScript Web Component</title>
    </head>
    <body>
      <hello-world></hello-world>
      <script src="public/bundle.js"></script>
    </body>
    </html>

Setup Build tools using Rollup

  • Add build dependencies

    npm install --save-dev rollup rollup-plugin-typescript2 rollup-plugin-node-resolve
    
  • Create build.ts

    import * as path from 'path';
    
    import { rollup } from 'rollup';
    const typescript2 = require('rollup-plugin-typescript2');
    const resolve = require('rollup-plugin-node-resolve');
    
    const ENTRY_FILE = `src/hello-world.ts`;
    
    const rollupConfig = {
      inputOptions: {
        treeshake: true,
        input: ENTRY_FILE,
        external: [],
        plugins: [
          typescript2({
            check: false,
            cacheRoot: path.join(path.resolve(), 'node_modules/.tmp/.rts2_cache'), 
            useTsconfigDeclarationDir: true       
          }),
          resolve()
        ],
        onwarn (warning) {
          if (warning.code === 'THIS_IS_UNDEFINED') { return; }
          console.log("Rollup warning: ", warning.message);
        }
      },
      outputOptions: {
        sourcemap: true,
        exports: 'named',
        file: 'public/bundle.js',
        name: 'hello-world', 
        format: 'es'
      }
    }
    
    function rollupBuild({ inputOptions, outputOptions }): Promise<any> {
      return rollup(inputOptions).then(bundle => bundle.write(outputOptions));
    }
    
    rollupBuild(rollupConfig);
  • Add scripts to your package.json

     "scripts": {
      ...
      "build": "ts-node -O '{ \"module\": \"commonjs\" }' build.ts"
     } 
  • Build & Run your application

    npm run build && npm run serve
    
  • Browse your app

    http://localhost:3000
    
  • Expected error when running the application

    • In the browser (chrome) open the DevTools (right click the browser then Inspect)
    • Go to console tab of the browser
    • Check the error (in red color)
      bundle.js:17 Uncaught TypeError: Failed to construct 'HTMLElement': Please use the 'new' operator, this DOM object constructor cannot be called as a function.
      at new HelloWorld (bundle.js:17)
      at bundle.js:24
      
  • Cause of the error.

    This error means, that you need a polyfill for browser that do support (!) Custom Elements. 
    That sounds a bit strange, but Custom Elements are defined for EcmaScript 2015+ 
    (bases upon classes) and for supporting older browser, we normally compile down to EcmaScript 5 today. 
    The output of your javascript is in es5 format. 
    
  • How to resolve or fix?

    • Install custom-elements polyfill.
      npm i @webcomponents/custom-elements
      
    • In your server.ts file add node_modules folder as one of your static folders
       app.use(`/node_modules`, express.static(path.resolve('node_modules')));
    • Add polyfill as script tag in your public/index.html
        <script src="node_modules/@webcomponents/custom-elements/src/native-shim.js"></script>
        <script src="public/bundle.js"></script>
  • Run your application

    npm run serve
    
  • Browse your app

    http://localhost:3000
    
@n2o
Copy link

n2o commented Mar 9, 2020

My application dies at the step where the Script should be build:

  "build": "ts-node -O '{ \"module\": \"commonjs\" }' build.ts"

How does build.ts look like?

Log Output:

λ npm run build                                                                                                                   09:34:10

> webcomponents-demo@1.0.0 build /Users/n2o/repos/webcomponents-demo
> ts-node -O '{ "module": "commonjs" }' build.ts

Error: Cannot find module '/Users/n2o/repos/webcomponents-demo/build.ts'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:980:15)
    at Function.Module._load (internal/modules/cjs/loader.js:862:27)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
    at main (/Users/n2o/repos/webcomponents-demo/node_modules/ts-node/src/bin.ts:226:14)
    at Object.<anonymous> (/Users/n2o/repos/webcomponents-demo/node_modules/ts-node/src/bin.ts:485:3)
    at Module._compile (internal/modules/cjs/loader.js:1151:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1171:10)
    at Module.load (internal/modules/cjs/loader.js:1000:32)
    at Function.Module._load (internal/modules/cjs/loader.js:899:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! webcomponents-demo@1.0.0 build: `ts-node -O '{ "module": "commonjs" }' build.ts`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the webcomponents-demo@1.0.0 build script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/n2o/.npm/_logs/2020-03-09T08_35_49_090Z-debug.log

@aelbore
Copy link
Author

aelbore commented Mar 9, 2020

@n2o can you try again? i update the gist instead rollup.config.ts change to build.ts

@n2o
Copy link

n2o commented Mar 9, 2020

Thanks, that helped (sorry, I am new to ts).

Glad to have it running. Now i copied twice the line <script src="public/bundle.js"></script>, but that was my fault. Thanks for this!

@n2o
Copy link

n2o commented Mar 9, 2020

Maybe I'll work on a version with does not use RollUp. Seems quite a mess and disturbs the development flow 🤔 It's too clumsy to see the results in the browser. But gives a good overview how to write WebComponents. Now, I want to know a bit more about customized built-in elements :-)

@aelbore
Copy link
Author

aelbore commented Mar 9, 2020

@n2o if your using typescript you will using any build tools like rollup, webpack or parcel.

you can check this repository i created (it is easy to follow) it uses cli (behind the scenes it uses rollup with some plugins)
https://github.com/aelbore/typescript-webcomponents.git

@n2o
Copy link

n2o commented Mar 9, 2020

Hm okay... Thanks for the repo, I will check it out in a minute :-)

Do you always call npm run build from the console when you changed files?

@aelbore
Copy link
Author

aelbore commented Mar 9, 2020

@n2o i updated the repo, when you execute npm start it will automatically watch the src folder for changes and automatically build typescript code, you dont need to call the npm run build
just manually refresh the page to see the changes
enjoy coding ^^

@n2o
Copy link

n2o commented Mar 10, 2020

Excited to test it, thank you :-)

@fselcukcan
Copy link

fselcukcan commented Feb 6, 2021

It is great as it watches the source files to build the latest code, but that is kind of half of the story :). It would be even greater and more complete, after building if it would replace the modules in web app (HMR) or just refresh the browser page. Or does it?

@mxm-web-develop
Copy link

Uncaught SyntaxError: Unexpected token '<' in bundle.js:1

i copied everyline from your code,but not work as expected

@AndyButland
Copy link

Thanks for sharing this gist, was very useful for someone getting started.

I found I had to change a couple of things to get things working with Windows/Powershell, that I'll note for anyone else trying the same.

scripts section of package.json needs to be:

"serve": "ts-node -O \"{ \\\"module\\\": \\\"commonjs\\\" }\" server.ts",
"build": "ts-node -O \"{ \\\"module\\\": \\\"commonjs\\\", \\\"suppressImplicitAnyIndexErrors\\\": true }\" build.ts"

The following didn't work but I could run them separately.

npm run build && npm run serve

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment