#

## Pages

For content-driven websites with low interactivity a multi-page approach is best suited.

Abandoning the use of frameworks means writing out those HTML pages from scratch. For this it is important to understand what a good minimal HTML page should look like.

An explanation of each element:

`<!doctype html>`
  The doctype is required to have the HTML parsed as HTML5 instead of an older version.
`<html lang="en">`
  The lang attribute is recommended to make sure browsers don't misdetect the language used in the page.
`<head><title>`
  The title will be used for the browser tab and when bookmarking, making it effectively non-optional.
`<head><meta charset="utf-8">`
  This is borderline unnecessary, but just to make sure a page is properly interpreted as UTF-8 this line should be included. Obviously the editor used to make the page should equally be set to UTF-8.
`<head><meta name="viewport">`
  The viewport meta is necessary to have mobile-friendly layout.
`<head><link rel="stylesheet" href="index.css">`
  By convention the stylesheet is loaded from `<head>` in a blocking way to ensure the page's markup does not have a flash of unstyled content.
`<head><script type="module" src="index.js" defer>`
  The main JavaScript file is in the `<head>`, and will bootstrap the web components as explained on the components page. The use of `defer` allows it to download while the rest of the page is loading and get executed at the end.
`<body><noscript>`
  Because web components don't work without JavaScript it is good practice to include a noscript warning to users that have JavaScript disabled. This warning only needs to be on pages with web components. If you don't want to show anything except the warning, see the template pattern below.
`<body><header/main/footer>`
  The page's markup should be organized using HTML landmarks. Landmarks when used properly help organize the page into logical blocks and make the page's structure accessible. Because they are standards-based, compatibility with present and future accessibility products is more likely.

Pages that should only show their contents if JavaScript is enabled can use this template pattern:

### Semantics matter

The markup in the page should default to using semantic HTML, to improve accessibility and SEO. Web components should only be used in those cases where the level of complexity and interaction exceeds the capabilities of plain HTML markup.

Familiarize yourself with these aspects of semantic HTML:

Landmarks
  As mentioned above, landmarks are the backbone of a page's structure and deliver good structure and accessibility by default.
Elements
  Being familiar with HTML's set of built-in elements saves time, both in avoiding the need for custom elements and in easing implementation of the custom elements that are needed. When properly used HTML elements are accessible by default. Pay special attention to the newly baseline features that enable complex UI patterns with minimal JavaScript, like how popover and dialog can be used to implement HTML-native tooltips, dialogs and menus, and how the details element can be used to implement accordions.
Forms
  HTML's built-in forms can implement many interactivity use cases when used to their full extent. Be aware of capabilities like rich input types, client-side validation and UI pseudo classes. When a suitable form input type for a use case cannot be found, consider using form-associated custom elements. To dive into this topic read the article on making a new form control.

### Favicons

There is one thing that you will probably want to add to the HTML that is not standards-based and that is a reference to a favicon:

- To keep it really simple, put a `favicon.ico` in the root path of the site and link to it from your HTML: `<link rel="icon" href="favicon.ico">`
- Consider SVG favicons, but know that Safari does not support them. Embed dark mode in the favicon SVG itself or use a generator like RealFaviconGenerator for more convience.
- Be aware that because favicons are not based on published web standards it is cumbersome to implement the de facto standard fully.

#

## Project

A suggested project layout for a vanilla multi-page website:

/
  The project root contains the files that will not be published, such as `README.md`, `LICENSE` or `.gitignore`.
/public
  The public folder is published as-is, without build steps. It is the whole website.
/public/index.html
  The main landing page of the website, not particularly different from the other pages, except for its path.
/public/index.[js/css]
  The main stylesheet and javascript. These contain the shared styles and code for all pages. `index.js` loads and registers the web components used on all pages. By sharing these across multiple HTML pages unnecessary duplication and inconsistencies between pages can be avoided.
/public/pages/[name].html
  All of the site's other pages, each including the same `index.js` and `index.css`, and ofcourse containing the content directly as markup in the HTML, leveraging the web components.
/public/components/[name]/
  One folder per web component, containing a `[name].js` and `[name].css` file. The `.js` file is imported into the `index.js` file to register the web component. The `.css` file is imported into the global `index.css` or in the shadow DOM, as explained on the styling page.
/public/lib/
  For all external libraries used as dependencies. See below for how to add and use these dependencies.
/public/styles/
  The global styles referenced from `index.css`, as explained on the styling page.

Configuration files for a smoother workflow in programmer's editors also belong in the project's root. Most of the development experience of a framework-based project is possible without a build step through editor extensions. See the Visual Studio Code setup for this site for an example.

#

## Routing

The old-school routing approach of standard HTML pages and `<a>` tags to link them together has the advantages of being easily indexed by search engines and fully supporting browser history and bookmarking functionality out of the box. 😉

#

## Dependencies

At some point you may want to pull in third-party libraries. Without npm and a bundler this is still possible.

### Unpkg

To use libraries without a bundler they need to be prebuilt in either ESM or UMD format. These libraries can be obtained from unpkg.com:

1. Browse to `unpkg.com/[library]/` (trailing slash matters), for example unpkg.com/microlight/
2. Look for and download the library js file, which may be in a subfolder, like `dist`, `esm` or `umd`
3. Place the library file in the `lib/` folder

Alternatively, the library may be loaded directly from CDN.

### UMD

The UMD module format is an older format for libraries loaded from script tag, and it is the most widely supported, especially among older libraries. It can be recognized by having `typeof define === 'function' && define.amd` somewhere in the library JS.

To include it in your project:

1. Include it in a script tag: `<script src="lib/microlight.js"></script>`
2. Obtain it off the window: `const { microlight } = window;`

### ESM

The ESM module format (also known as JavaScript modules) is the format specified by the ECMAScript standard, and newer or well-behaved libraries will typically provide an ESM build. It can be recognized by the use of the `export` keyword.

To include it in your project:

- Load it from CDN:
  `import('https://unpkg.com/web-vitals@4.2.2/dist/web-vitals.js').then((webVitals) => ...)`
- Or load it from a local copy:
  `import webVitals from 'lib/web-vitals.js'`

### imports.js

To neatly organize libraries and separate them from the rest of the codebase, they can be loaded and exported from an `imports.js` file. For example, here is a page that uses a UMD build of Day.js and an ESM build of web-vitals:

The text is rendered by the `<x-metrics>` component:

In the `/lib` folder we find these files:

- web-vitals.js - the ESM build of web-vitals
- dayjs/
  - dayjs.min.js - the UMD build of Day.js
  - relativeTime.js - the UMD build of this Day.js plugin
- imports.js

Digging deeper into this last file we see how it bundles loading of third-party dependencies:

It imports the ESM library directly, but it pulls the UMD libraries off the `Window` object. These are loaded in the HTML.

Here is the combined example:

Regrettably not all libraries have a UMD or ESM build, but more and more do.

### Import Maps

An alternative to the `imports.js` approach are import maps. These define a unique mapping between importable module name and corresponding library file in a special script tag in the HTML head. This allows a more traditional module-based import syntax in the rest of the codebase.

The previous example adapted to use import maps:

Some things to take into account when using import maps:

- Import maps can only map to ESM modules, so for UMD libraries a wrapper must be provided, as with the `module.js` wrapper for `dayjs` in this example.
- External import maps of the form `<script type="importmap" src="importmap.json">` are not yet supported in all browsers. This means the import map must be duplicated in every HTML page.
- The import map must be defined before the `index.js` script is loaded, preferably from the `<head>` section.
- Import maps can be used to more easily load libraries from a `node_modules` folder or from a CDN. JSPM generator can be used to quickly create an import map for CDN dependencies. Use this with caution, as adding such external dependencies makes a vanilla codebase rely on the continued availability of that service.

#

## Browser support

Vanilla web sites are supported in all modern browsers. But what does that mean?

- Everything on this site works in current versions of Safari, Chrome, Edge and Firefox.
- Everything on this site has 95% support or more on caniuse.com, with the exception of Popover (91%), mutually-exclusive details (91%), and CSS Nesting (92%), and it won't be long before those catch up.
- That in turn means you can safely rely on HTTP/2, HTML5 semantic elements, Custom Elements, Templates, Shadow DOM, MutationObserver, CustomEvent, FormData, and the Element.closest API.
- It's also safe to use JavaScript Modules, ECMAScript 6 / 2015, ECMAScript 8 / 2017 and ECMAScript 11 / 2020.
- In CSS you can rely on @import, variables, calc(), flexbox, grid, display: contents and so much more.

To keep up with new web standards, keep an eye on these projects:

- Baseline keeps track of which features are widely available in browsers and lets you know when they are safe to use.
- Interop is a yearly initiative between browser makers to bring new web platform features to all browsers, or fix compatibility of existing ones. Consider it a preview of what will become baseline.

#

## Deploying

Any provider that can host static websites can be used for deployment.

An example using GitHub Pages:

1. Upload the project as a repository on GitHub
2. Go to Settings, Pages
3. Source: GitHub Actions
4. Static Website, Configure
5. Scroll down to `path`, and change it to `./public`
6. Commit changes...
7. Go to the Actions page for the repository, wait for the site to deploy

#

## Testing

Popular testing frameworks are all designed to run in build pipelines. However, a plain vanilla web site has no build. To test web components an old-fashioned approach can be used: testing in the browser using the Mocha framework.

For example, these are the live unit tests for the `<x-tab-panel>` component used to present tabbed source code panels on this site:

And for ultimate coding inception, here is that tabpanel component showing the testing source code:

Some working notes for how this is set up:

- The entire code for the unit tests, including the testing libraries, is isolated to a `public/tests/` subfolder. The tests will therefore be available live by adding `/tests` to the deployed site's URL. If you don't want to deploy the tests on the live website, exclude the tests folder during the deploy step.
- Mocha and Chai are used as test and assertion frameworks, because they work in-browser without a build step.
- DOM Testing Library is used to more easily query the DOM. The `imports-test.js` file configures it for vanilla use.
- An important limitation is that DOM Testing Library cannot query inside shadow roots. To test something inside a shadow root it is necessary to first query for the containing web component, get a handle to its `shadowRoot` property, and then query inside of that.
- Web Components initialize asynchronously, which can make them tricky to test. Use the async methods of DOM Testing Library.

#

## Example

This website is the example. Check out the project on GitHub.

## Up next

Learn how to build single-page applications using vanilla techniques.

Make Applications