chore: new website using rocket
Co-authored-by: Lars den Bakker <larsdenbakker@gmail.com>
19
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## Expected behavior
|
||||
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
## Actual Behavior
|
||||
|
||||
Describe what happens instead.
|
||||
|
||||
## Additional context
|
||||
|
||||
Which packages and versions are you using.
|
||||
9
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: You have an idea about an additional feature
|
||||
title: '[Feature Request] please use the discussions tab'
|
||||
labels: ''
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
Please use [discussions](https://github.com/open-wc/open-wc/discussions/new) for new feature requests.
|
||||
3
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
## What I did
|
||||
|
||||
1.
|
||||
8
.gitignore
vendored
@@ -46,3 +46,11 @@ local.log
|
||||
|
||||
## generated codelabs
|
||||
docs/.vuepress/public/codelabs
|
||||
|
||||
# Local Netlify folder
|
||||
.netlify
|
||||
|
||||
## Rocket ignore files (need to be the full relative path to the folders)
|
||||
docs/_merged_data/
|
||||
docs/_merged_assets/
|
||||
docs/_merged_includes/
|
||||
|
||||
88
cli-build.js
@@ -1,88 +0,0 @@
|
||||
/* eslint-disable */
|
||||
|
||||
const path = require('path');
|
||||
const copy = require('rollup-plugin-copy');
|
||||
const { rollup } = require('rollup');
|
||||
const { generateSW } = require('rollup-plugin-workbox');
|
||||
const Eleventy = require('@11ty/eleventy');
|
||||
const { createMpaConfig } = require('./docs/_building-rollup/createMpaConfig.js');
|
||||
|
||||
const elev = new Eleventy('./docs', './_site');
|
||||
elev.setConfigPathOverride('./docs/.eleventy.js');
|
||||
elev.setDryRun(true); // do not write to file system
|
||||
|
||||
/**
|
||||
* @param {object} config
|
||||
*/
|
||||
async function buildAndWrite(config) {
|
||||
const bundle = await rollup(config);
|
||||
|
||||
if (Array.isArray(config.output)) {
|
||||
await bundle.write(config.output[0]);
|
||||
await bundle.write(config.output[1]);
|
||||
} else {
|
||||
await bundle.write(config.output);
|
||||
}
|
||||
}
|
||||
|
||||
async function productionBuild(html) {
|
||||
const mpaConfig = createMpaConfig({
|
||||
outputDir: '_site',
|
||||
legacyBuild: false,
|
||||
html: { html },
|
||||
injectServiceWorker: false,
|
||||
workbox: false,
|
||||
});
|
||||
|
||||
mpaConfig.plugins.push(
|
||||
generateSW({
|
||||
globIgnores: ['polyfills/*.js', 'legacy-*.js', 'nomodule-*.js'],
|
||||
swDest: path.join(process.cwd(), '_site', 'service-worker.js'),
|
||||
globDirectory: path.join(process.cwd(), '_site'),
|
||||
globPatterns: ['**/*.{html,js,json,css,webmanifest,png,gif}'],
|
||||
skipWaiting: true,
|
||||
clientsClaim: true,
|
||||
runtimeCaching: [
|
||||
{
|
||||
urlPattern: 'polyfills/*.js',
|
||||
handler: 'CacheFirst',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
const dest = '_site/';
|
||||
mpaConfig.plugins.push(
|
||||
copy({
|
||||
targets: [
|
||||
{ src: 'docs/styles.css', dest },
|
||||
{ src: 'docs/demoing/demo/custom-elements.json', dest },
|
||||
{ src: 'docs/manifest.json', dest },
|
||||
{ src: 'docs/**/*.{png,gif}', dest },
|
||||
],
|
||||
flatten: false,
|
||||
}),
|
||||
);
|
||||
|
||||
await buildAndWrite(mpaConfig);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const htmlFiles = [];
|
||||
|
||||
elev.config.filters['hook-for-rocket'] = (html, outputPath, inputPath) => {
|
||||
htmlFiles.push({
|
||||
html,
|
||||
name: outputPath.substring(8),
|
||||
rootDir: path.dirname(path.resolve(inputPath)),
|
||||
});
|
||||
return html;
|
||||
};
|
||||
|
||||
await elev.init();
|
||||
await elev.write();
|
||||
|
||||
await productionBuild(htmlFiles);
|
||||
}
|
||||
|
||||
main();
|
||||
68
cli-start.js
@@ -1,68 +0,0 @@
|
||||
/* eslint-disable */
|
||||
|
||||
const { createConfig, startServer } = require('es-dev-server');
|
||||
const Eleventy = require('@11ty/eleventy');
|
||||
|
||||
const elev = new Eleventy('./docs', './__site');
|
||||
elev.setConfigPathOverride('./docs/.eleventy.js');
|
||||
elev.setDryRun(true); // do not write to file system
|
||||
|
||||
async function run() {
|
||||
await elev.init();
|
||||
|
||||
const config = {
|
||||
nodeResolve: true,
|
||||
watch: true,
|
||||
open: './docs/README.md',
|
||||
middlewares: [
|
||||
async (ctx, next) => {
|
||||
if (ctx.path.endsWith('index.html')) {
|
||||
ctx.path = ctx.path.replace('index.html', 'README.md');
|
||||
} else if (ctx.path.endsWith('.html')) {
|
||||
ctx.path = ctx.path.replace('.html', '.md');
|
||||
} else if (ctx.path.endsWith('/')) {
|
||||
ctx.path += 'README.md';
|
||||
}
|
||||
return next();
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
{
|
||||
async transform(context) {
|
||||
if (context.path.endsWith('md')) {
|
||||
const serverPath = context.path;
|
||||
let { body } = 'File not found';
|
||||
elev.config.filters['hook-for-rocket'] = (content, outputPath, inputPath) => {
|
||||
if (inputPath === `.${serverPath}`) {
|
||||
body = content;
|
||||
}
|
||||
return content;
|
||||
};
|
||||
await elev.write();
|
||||
return {
|
||||
body: body.replace(/href="\//g, 'href="/docs/').replace(/src="\//g, 'src="/docs/'),
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
resolveMimeType(context) {
|
||||
if (context.path.endsWith('md')) {
|
||||
return 'text/html';
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
startServer(createConfig(config));
|
||||
|
||||
['exit', 'SIGINT'].forEach(event => {
|
||||
// @ts-ignore
|
||||
process.on(event, () => {
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
run();
|
||||
@@ -1,27 +0,0 @@
|
||||
const pluginMdjs = require('./_plugin-mdjs/index.js');
|
||||
|
||||
module.exports = function (eleventyConfig) {
|
||||
eleventyConfig.addPlugin(pluginMdjs);
|
||||
|
||||
eleventyConfig.addPassthroughCopy('./styles.css');
|
||||
eleventyConfig.addPassthroughCopy('./demoing/demo/custom-elements.json');
|
||||
eleventyConfig.addPassthroughCopy('./manifest.json');
|
||||
eleventyConfig.addPassthroughCopy('./**/*.{png,gif}');
|
||||
|
||||
eleventyConfig.addCollection('section', function (collection) {
|
||||
// This works _because_ of our current content. Something like https://github.com/Polymer/lit-html/blob/master/docs/.eleventy.js#L37
|
||||
// would be more robust, but there are likely other answers here.
|
||||
return collection.getFilteredByTag('section').reverse();
|
||||
});
|
||||
|
||||
// 11ty needs this as it apparently reads this config from multiple files
|
||||
// and only if we provide this hook we can actually override later when we
|
||||
// programmatically trigger 11ty
|
||||
// @TODO: create an issue and find a nicer way to add this transformer
|
||||
eleventyConfig.addTransform('hook-for-rocket');
|
||||
|
||||
return {
|
||||
dir: { input: './', output: '../_site-dev' },
|
||||
passthroughFileCopy: true,
|
||||
};
|
||||
};
|
||||
8
docs/404.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Open Web Components 404 Page
|
||||
permalink: 404.html
|
||||
---
|
||||
|
||||
This page could not be found.
|
||||
|
||||
Return [home](/) to start over.
|
||||
@@ -1,30 +0,0 @@
|
||||
---
|
||||
permalink: 'index.html'
|
||||
title: Open Web Components
|
||||
layout: home.njk
|
||||
home: true
|
||||
actionText: Get Started →
|
||||
actionLink: /guide/
|
||||
features:
|
||||
- title: Smart Defaults
|
||||
details: Enjoy the peace of mind that comes from having a well-known default solution for almost everything. From linting to testing to demos to publishing - have the full experience.
|
||||
- title: Awesome Generators
|
||||
details: Get up and running quickly with opinionated generators, or add recommended tools to existing projects. Our comprehensive fleet of generators have got you covered.
|
||||
- title: Open Source Love
|
||||
details: Open Web Components is a community-effort, independent of any framework or company. We use mostly open-source tools and services.
|
||||
footer: MIT Licensed | Copyright © 2018-2020 open-wc
|
||||
---
|
||||
|
||||
<hr>
|
||||
<p align="center" style="margin: 50px; color: #4e6e8e;">
|
||||
<q><i>I used open-wc to get testing working, it was so helpful, works like a charm!</i></q>
|
||||
<br><br> - Ikira
|
||||
</p>
|
||||
<hr>
|
||||
<p align="center" style="margin: 50px; color: #4e6e8e;">
|
||||
<q><i>Just now visiting open-wc.org site for the first time. You guys are killing it! Amazing stuff, and IMO totally on target. Many thanks.</i></q>
|
||||
<br><br> - Petecarapetyan
|
||||
</p>
|
||||
<p align="center">
|
||||
hosted by <a href="http://netlify.com/" rel="noopener" target="_blank">netlify</a>
|
||||
</p>
|
||||
9
docs/_assets/head.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<!-- <link
|
||||
href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/> -->
|
||||
<meta name="twitter:creator" content="@open-wc" />
|
||||
BIN
docs/_assets/icons/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
docs/_assets/icons/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
docs/_assets/icons/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
docs/_assets/icons/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
docs/_assets/icons/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
docs/_assets/icons/maskable-icon.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
docs/_assets/icons/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
60
docs/_assets/icons/safari-pinned-tab.svg
Normal file
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M3238 6965 c-2 -2 -35 -7 -73 -10 -75 -6 -131 -12 -174 -20 -14 -2
|
||||
-42 -7 -61 -10 -69 -11 -196 -37 -255 -52 -33 -8 -67 -16 -75 -18 -8 -2 -61
|
||||
-18 -118 -35 -290 -87 -643 -254 -897 -423 -781 -522 -1315 -1321 -1494 -2232
|
||||
-20 -106 -47 -304 -56 -420 -6 -80 -6 -422 0 -475 2 -19 6 -69 10 -110 11
|
||||
-136 47 -344 90 -520 22 -93 96 -323 106 -335 5 -5 9 -15 9 -23 0 -39 166
|
||||
-388 261 -551 45 -76 179 -276 214 -320 8 -9 37 -45 65 -81 96 -121 236 -270
|
||||
376 -400 66 -61 249 -210 292 -237 15 -10 29 -20 32 -24 8 -10 198 -131 275
|
||||
-176 261 -151 598 -289 860 -353 129 -31 306 -68 365 -75 30 -4 62 -9 70 -10
|
||||
8 -2 51 -6 95 -10 44 -3 96 -8 115 -10 92 -11 462 -4 590 10 665 73 1288 329
|
||||
1805 740 156 124 333 293 452 431 l52 61 -41 33 c-22 19 -46 39 -52 45 -6 5
|
||||
-40 34 -76 63 -79 65 -71 58 -255 211 -82 69 -152 128 -155 131 -3 3 -70 59
|
||||
-150 125 -80 66 -156 129 -169 141 l-23 21 -137 -136 c-442 -442 -998 -675
|
||||
-1611 -675 -236 0 -412 26 -646 94 -674 197 -1232 719 -1479 1384 -40 110
|
||||
-101 338 -110 413 -3 27 -7 59 -10 71 -9 51 -19 194 -19 297 0 105 12 303 19
|
||||
315 1 3 6 25 9 50 18 133 82 369 127 466 10 24 19 45 19 48 0 3 20 48 45 101
|
||||
159 334 414 640 720 862 333 241 728 387 1140 420 602 48 1183 -138 1645 -525
|
||||
95 -79 256 -241 286 -288 9 -13 20 -24 24 -24 7 0 293 226 570 450 19 15 109
|
||||
87 200 160 91 72 166 134 168 138 4 13 -173 211 -291 326 -185 180 -395 341
|
||||
-622 479 -225 136 -491 257 -740 337 -91 29 -304 84 -365 95 -14 2 -68 11
|
||||
-120 20 -52 9 -120 19 -150 21 -30 3 -66 8 -80 11 -29 7 -596 14 -602 8z"/>
|
||||
<path d="M6240 5255 c-96 -63 -229 -148 -295 -190 -445 -282 -537 -345 -527
|
||||
-358 11 -14 64 -106 89 -154 9 -18 21 -33 26 -33 5 0 250 120 543 267 l534
|
||||
266 -44 81 c-60 112 -136 236 -144 235 -4 0 -86 -51 -182 -114z"/>
|
||||
<path d="M6645 4681 c-55 -21 -282 -106 -505 -190 -223 -83 -412 -155 -420
|
||||
-158 -8 -3 -17 -7 -20 -7 -3 -1 -20 -8 -38 -15 -38 -16 -38 -5 5 -148 23 -75
|
||||
31 -92 45 -87 9 2 25 6 35 8 10 2 88 22 173 44 234 62 464 121 565 147 252 64
|
||||
369 95 373 100 8 8 -97 345 -107 344 -3 0 -51 -17 -106 -38z"/>
|
||||
<path d="M6880 4002 c-27 -3 -342 -51 -376 -57 -12 -2 -48 -7 -80 -10 -33 -4
|
||||
-68 -9 -79 -11 -38 -8 -70 -13 -120 -19 -27 -3 -75 -10 -105 -15 -30 -5 -75
|
||||
-12 -100 -15 -76 -10 -234 -35 -257 -40 -13 -3 -20 -11 -18 -22 3 -21 17 -157
|
||||
20 -193 l1 -25 110 1 c107 1 379 10 604 20 184 8 319 13 409 15 l84 2 -7 101
|
||||
c-3 55 -8 110 -10 121 -2 11 -7 50 -10 87 -8 71 -5 68 -66 60z"/>
|
||||
<path d="M5759 3328 c-1 -26 -3 -54 -14 -153 -4 -38 -5 -67 -1 -64 4 4 25 3
|
||||
47 -2 21 -5 56 -12 77 -15 32 -4 82 -13 177 -28 14 -3 33 -7 43 -10 9 -2 37
|
||||
-7 62 -10 25 -3 54 -8 65 -10 11 -2 40 -7 65 -11 25 -4 54 -9 65 -11 35 -7
|
||||
309 -53 350 -59 22 -4 47 -8 55 -10 8 -2 35 -7 60 -10 25 -3 60 -10 77 -15 18
|
||||
-4 35 -5 37 -2 2 4 7 31 11 61 4 30 8 62 10 72 6 28 18 150 19 189 1 32 -1 35
|
||||
-29 36 -28 1 -94 6 -295 19 -110 8 -203 13 -310 20 -52 3 -120 7 -150 10 -68
|
||||
5 -195 13 -322 20 -97 6 -98 6 -99 -17z"/>
|
||||
<path d="M5649 2780 l-37 -111 37 -15 c20 -9 77 -31 126 -51 557 -219 876
|
||||
-346 903 -358 18 -8 38 -15 45 -15 10 0 70 144 82 198 2 9 13 45 24 79 l19 63
|
||||
-41 11 c-23 5 -55 15 -72 20 -27 8 -547 153 -735 204 -8 2 -46 13 -85 24 -81
|
||||
23 -223 61 -227 61 -2 0 -19 -50 -39 -110z"/>
|
||||
<path d="M5454 2353 c-27 -48 -50 -90 -52 -93 -1 -4 55 -44 125 -91 70 -46
|
||||
292 -192 493 -324 201 -133 370 -244 375 -247 10 -6 41 42 132 197 51 88 51
|
||||
90 -4 118 -27 13 -73 38 -103 54 -30 17 -127 67 -215 113 -88 45 -173 90 -190
|
||||
100 -28 16 -97 53 -382 201 -62 33 -116 59 -121 59 -5 0 -31 -39 -58 -87z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
1
docs/_assets/logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><svg viewBox="0 0 244 244" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" alt="open-wc" class="logo"><defs><linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1"><stop stop-color="#9B00FF" offset="0%"></stop><stop stop-color="#0077FF" offset="100%"></stop></linearGradient><g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" id="logo"><path d="M205.639259,176.936244 C207.430887,174.217233 209.093339,171.405629 210.617884,168.510161 M215.112174,158.724316 C216.385153,155.50304 217.495621,152.199852 218.433474,148.824851 M220.655293,138.874185 C221.231935,135.482212 221.637704,132.03207 221.863435,128.532919 M222,118.131039 C221.860539,114.466419 221.523806,110.85231 221.000113,107.299021 M218.885321,96.8583653 C218.001583,93.4468963 216.942225,90.1061026 215.717466,86.8461994 M211.549484,77.3039459 C209.957339,74.1238901 208.200597,71.0404957 206.290425,68.0649233 M200.180513,59.5598295 C181.848457,36.6639805 153.655709,22 122.036748,22 C66.7879774,22 22,66.771525 22,122 C22,177.228475 66.7879774,222 122.036748,222 C152.914668,222 180.52509,208.015313 198.875424,186.036326" stroke="url(#linearGradient-1)" stroke-width="42.0804674"></path></g></defs><use xlink:href="#logo"></use></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
BIN
docs/_assets/social-media-image.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
130
docs/_assets/style.css
Normal file
@@ -0,0 +1,130 @@
|
||||
body[layout='home'] .markdown-body .call-to-action:nth-of-type(2) {
|
||||
--primary-color: #222;
|
||||
--primary-color-lighter: #333;
|
||||
--primary-color-darker: #000;
|
||||
}
|
||||
|
||||
rocket-navigation {
|
||||
font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;
|
||||
}
|
||||
|
||||
header {
|
||||
font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;
|
||||
}
|
||||
|
||||
/* TODO: should be in rocket? */
|
||||
#sidebar-nav .logo-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
body[layout='home'][home-layout='background'] .page-background {
|
||||
top: -111px;
|
||||
right: -360px;
|
||||
}
|
||||
}
|
||||
|
||||
body,
|
||||
#sidebar-nav {
|
||||
background: var(--page-background);
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: var(--footer-background);
|
||||
}
|
||||
|
||||
#newsletter {
|
||||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.101562);
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
background-color: var(--blog-ba) #fafafa;
|
||||
border-top: 1px solid #eaeaea;
|
||||
border-bottom: 1px solid #eaeaea;
|
||||
}
|
||||
|
||||
#newsletter h4 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
header {
|
||||
background: var(--header-color);
|
||||
}
|
||||
|
||||
.call-to-action {
|
||||
background: var(--button-one)!important;
|
||||
text-shadow: none!important;
|
||||
border-radius: 5px!important;
|
||||
padding-top: 15px!important;
|
||||
padding-bottom: 15px!important;
|
||||
border: none!important;
|
||||
}
|
||||
|
||||
.call-to-action:hover,
|
||||
.call-to-action:focus,
|
||||
.call-to-action:active {
|
||||
background: var(--button-one-hover)!important;
|
||||
}
|
||||
|
||||
.call-to-action:nth-child(2) {
|
||||
background: var(--button-two)!important;
|
||||
}
|
||||
|
||||
.call-to-action:nth-child(2):hover,
|
||||
.call-to-action:nth-child(2):focus,
|
||||
.call-to-action:nth-child(2):active {
|
||||
background: var(--button-two-hover)!important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Guides styles: */
|
||||
#sidebar-nav > rocket-navigation > ul > li.menu-item.current > a {
|
||||
color: var(--owc-active-color)!important;
|
||||
}
|
||||
|
||||
#sidebar-nav > rocket-navigation > ul > li.menu-item.current > ul > li.menu-item.anchor.current a {
|
||||
color: var(--owc-active-color)!important;
|
||||
}
|
||||
|
||||
|
||||
/* Docs styles: */
|
||||
#sidebar-nav > rocket-navigation > ul > li.menu-item.active > a {
|
||||
color: var(--owc-active-color)!important;
|
||||
}
|
||||
|
||||
#sidebar-nav > rocket-navigation > ul > li.menu-item.active > ul > li.menu-item.current > a {
|
||||
color: var(--owc-active-color)!important;
|
||||
}
|
||||
|
||||
#sidebar-nav > rocket-navigation > ul > li.menu-item.active > ul > li.menu-item.current > ul > li.menu-item.anchor.current:before {
|
||||
background: var(--owc-active-color)!important;
|
||||
}
|
||||
|
||||
#sidebar-nav > rocket-navigation > ul > li.menu-item.active > ul > li.menu-item.current > ul > li.menu-item.anchor.current a {
|
||||
color: var(--owc-active-color)!important;
|
||||
}
|
||||
|
||||
|
||||
footer {
|
||||
border-top: 0px!important;
|
||||
}
|
||||
|
||||
rocket-navigation > ul > li > a {
|
||||
text-transform: none!important;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.markdown-body {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.sidebar-tags .tag {
|
||||
background: var(--footer-background);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
#sidebar {
|
||||
max-width: 280px;
|
||||
}
|
||||
}
|
||||
140
docs/_assets/variables.css
Normal file
@@ -0,0 +1,140 @@
|
||||
html {
|
||||
--page-background: white;
|
||||
--footer-background: rgba(0, 0, 0, 0.02);
|
||||
--primary-color: rgb(44, 62, 80);
|
||||
--primary-color-lighter: #449ad7;
|
||||
--primary-color-darker: #1a5285;
|
||||
--primary-color-accent: #cee5f6;
|
||||
--contrast-color-light: #fff;
|
||||
--contrast-color-dark: #1d3557;
|
||||
--primary-text-color: #2c3e50;
|
||||
--primary-lines-color: #ccc;
|
||||
--markdown-link-color: #2758ff;
|
||||
|
||||
--header-color: white;
|
||||
|
||||
--owc-active-color: #2758ff;
|
||||
--owc-hover-color: #436eff;
|
||||
|
||||
--button-one: #2758ff;
|
||||
--button-one-hover: #436eff;
|
||||
|
||||
--button-two: black;
|
||||
--button-two-hover: #444444;
|
||||
}
|
||||
|
||||
/* TODO: markdown Vars */
|
||||
|
||||
/* SEE: https://github.com/daKmoR/rocket/pull/18 */
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
--header-color: #2f3136;
|
||||
|
||||
--footer-background: rgba(255, 255, 255, 0.1);
|
||||
--page-background: #36393e;
|
||||
--primary-text-color: #eee;
|
||||
--primary-color: white;
|
||||
--primary-color-lighter: #449ad7;
|
||||
--primary-color-darker: #1a5285;
|
||||
--primary-color-accent: #cee5f6;
|
||||
--contrast-color-light: #fff;
|
||||
--contrast-color-dark: #1d3557;
|
||||
--primary-lines-color: #333;
|
||||
|
||||
--owc-active-color: #41ffb0;
|
||||
--owc-hover-color: #6dffc2;
|
||||
|
||||
--button-one: #9b03fe;
|
||||
--button-one-hover: #a724ff;
|
||||
|
||||
--button-two: black;
|
||||
--button-two-hover: rgb(36, 36, 36);
|
||||
|
||||
|
||||
/* Markdown */
|
||||
--markdown-octicon-link: var(--primary-text-color);
|
||||
--markdown-body: #24292e;
|
||||
--markdown-link-color: #41ffb0;
|
||||
--markdown-divider-color: #e1e4e8;
|
||||
--markdown-blockquote-border-color: #dfe2e5;
|
||||
--markdown-blockquote-color: #90aac7;
|
||||
--markdown-kbd-background-color: #fafbfc;
|
||||
--markdown-kbd-border-color: #c6cbd1;
|
||||
--markdown-kbd-border-bottom-color: #959da5;
|
||||
--markdown-kbd-color: #444d56;
|
||||
--markdown-heading-color-6: #6a737d;
|
||||
--markdown-table-background-color: #fff;
|
||||
--markdown-table-border-color: #c6cbd1;
|
||||
--markdown-table-row-odd-background-color: #f6f8fa;
|
||||
--markdown-code-background-color: rgba(27, 31, 35, 0.05);
|
||||
--markdown-pre-background-color: rgb(49, 49, 49);
|
||||
|
||||
/* syntax */
|
||||
--markdown-syntax-color: #f8f8f2;
|
||||
--markdown-syntax-background-color: #2e3440;
|
||||
--markdown-syntax-atrule-color: #88c0d0;
|
||||
--markdown-syntax-attr-name-color: #a3be8c;
|
||||
--markdown-syntax-attr-value-color: #88c0d0;
|
||||
--markdown-syntax-builtin-color: #a3be8c;
|
||||
--markdown-syntax-boolean-color: #81a1c1;
|
||||
--markdown-syntax-class-name-color: #88c0d0;
|
||||
--markdown-syntax-constant-color: #81a1c1;
|
||||
--markdown-syntax-char-color: #a3be8c;
|
||||
--markdown-syntax-deleted-color: #81a1c1;
|
||||
--markdown-syntax-entity-color: #81a1c1;
|
||||
--markdown-syntax-function-color: #88c0d0;
|
||||
--markdown-syntax-inserted-color: #a3be8c;
|
||||
--markdown-syntax-keyword-color: #81a1c1;
|
||||
--markdown-syntax-number-color: #b48ead;
|
||||
--markdown-syntax-operator-color: #81a1c1;
|
||||
--markdown-syntax-property-color: #81a1c1;
|
||||
--markdown-syntax-punctuation-color: #81a1c1;
|
||||
--markdown-syntax-regex-color: #81a1c1;
|
||||
--markdown-syntax-important-color: #81a1c1;
|
||||
--markdown-syntax-selector-color: #a3be8c;
|
||||
--markdown-syntax-symbol-color: #81a1c1;
|
||||
--markdown-syntax-string-color: #a3be8c;
|
||||
--markdown-syntax-tag-color: #81a1c1;
|
||||
--markdown-syntax-url-color: #81a1c1;
|
||||
--markdown-syntax-variable-color: #81a1c1;
|
||||
--markdown-syntax-hotkey-selector-color: #d73a49;
|
||||
--markdown-syntax-keyword-color: #22863a;
|
||||
--markdown-table-background-color: #212121;
|
||||
--markdown-table-row-odd-background-color: #515151;
|
||||
--markdown-table-border-color: #8e8e8e;
|
||||
|
||||
--markdown-syntax-background-color: rgb(27, 29, 35);
|
||||
--markdown-syntax-atrule-color: rgb(198, 120, 221);
|
||||
--markdown-syntax-attr-name-color: rgb(198, 120, 221);
|
||||
--markdown-syntax-boolean-color: rgb(209, 154, 102);
|
||||
--markdown-syntax-class-name-color: rgb(97, 175, 239);
|
||||
--markdown-syntax-constant-color: rgb(220, 220, 170);
|
||||
--markdown-syntax-entity-color: rgb(220, 220, 170);
|
||||
--markdown-syntax-function-color: rgb(97, 175, 239);
|
||||
--markdown-syntax-inserted-color: rgb(220, 220, 170);
|
||||
--markdown-syntax-keyword-color: rgb(198, 120, 221);
|
||||
--markdown-syntax-number-color: rgb(220, 220, 170);
|
||||
--markdown-syntax-operator-color: rgb(220, 220, 170);
|
||||
--markdown-syntax-property-color: rgb(220, 220, 170);
|
||||
--markdown-syntax-punctuation-color: white;
|
||||
--markdown-syntax-regex-color: rgb(209, 154, 102);
|
||||
--markdown-syntax-selector-color: rgb(86, 156, 214);
|
||||
--markdown-syntax-symbol-color: rgb(220, 220, 170);
|
||||
--markdown-syntax-tag-color: rgb(86, 156, 214);
|
||||
--markdown-syntax-url-color: rgb(220, 220, 170);
|
||||
--markdown-syntax-variable-color: rgb(220, 220, 170);
|
||||
}
|
||||
|
||||
.string {
|
||||
color: rgb(152, 195, 121);
|
||||
}
|
||||
|
||||
.comment {
|
||||
color: #7d7d7d;
|
||||
}
|
||||
|
||||
.language-css {
|
||||
--markdown-syntax-string-color: #81a1c1;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/** @typedef {import('./types').SpaOptions} SpaOptions */
|
||||
|
||||
const merge = require('deepmerge');
|
||||
const { createSpaConfig } = require('@open-wc/building-rollup');
|
||||
|
||||
/**
|
||||
* @param {SpaOptions} options
|
||||
*/
|
||||
function createMpaConfig(options) {
|
||||
const userOptions = merge(
|
||||
{
|
||||
html: { flatten: false },
|
||||
},
|
||||
options,
|
||||
);
|
||||
return createSpaConfig(userOptions);
|
||||
}
|
||||
|
||||
module.exports = { createMpaConfig };
|
||||
90
docs/_data/baseLibraries.js
Normal file
@@ -0,0 +1,90 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
const baseLibraries = [
|
||||
{
|
||||
name: 'lit-element',
|
||||
package: 'lit-element',
|
||||
description: 'A simple base class for creating fast, lightweight web components',
|
||||
url: 'https://lit-element.polymer-project.org/',
|
||||
},
|
||||
{
|
||||
name: 'FAST',
|
||||
package: '@microsoft/fast-element',
|
||||
description: 'The adaptive interface system for modern web experiences',
|
||||
url: 'https://www.fast.design/',
|
||||
},
|
||||
{
|
||||
package: 'atomico',
|
||||
description:
|
||||
'Atomico a micro-library for creating webcomponents using only functions, hooks and virtual-dom.',
|
||||
},
|
||||
{
|
||||
package: 'haunted',
|
||||
description: "React's Hooks API but for standard web components and lit-html or hyperHTML",
|
||||
},
|
||||
{
|
||||
package: 'hybrids',
|
||||
description: 'The simplest way to create web components with plain objects and pure functions!',
|
||||
},
|
||||
{
|
||||
package: 'hyperhtml-element',
|
||||
description: 'An extensible class to define hyperHTML based Custom Elements',
|
||||
},
|
||||
{
|
||||
name: 'Joist',
|
||||
package: '@joist/component',
|
||||
description:
|
||||
'A small (~2kb) library to help with the creation of web components and web component based applications',
|
||||
},
|
||||
{
|
||||
name: 'Lightning Web Components',
|
||||
package: 'lwc',
|
||||
description: 'A Blazing Fast, Enterprise-Grade Web Components Foundation',
|
||||
},
|
||||
{
|
||||
name: 'preact-custom-element',
|
||||
package: 'preact-custom-element',
|
||||
description: 'Generate/register a custom element from a preact component',
|
||||
},
|
||||
{
|
||||
package: 'skatejs',
|
||||
description:
|
||||
'Skate is a library built on top of the W3C web component specs that enables you to write functional and performant web components with a very small footprint.',
|
||||
url: 'https://skatejs.netlify.com/',
|
||||
},
|
||||
{
|
||||
package: 'slimjs',
|
||||
description:
|
||||
'a lightweight web component authoring library that provides extended capabilities for components (such as data binding) using es6 native class inheritance',
|
||||
},
|
||||
{
|
||||
package: 'solid-js',
|
||||
description: 'The Deceptively Simple User Interface Library',
|
||||
},
|
||||
{
|
||||
package: 'stencil',
|
||||
description: 'web component with typescript and JSX (requires a build step)',
|
||||
url: 'https://stenciljs.com/',
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = async function getBaseLibraries() {
|
||||
const result = await Promise.all(
|
||||
baseLibraries.map(async lib => {
|
||||
const pkg = encodeURIComponent(lib.package);
|
||||
const response = await fetch(`https://api.npmjs.org/downloads/point/last-week/${pkg}`);
|
||||
const { downloads } = await response.json();
|
||||
|
||||
return {
|
||||
...lib,
|
||||
downloads,
|
||||
downloadsFormatted: new Intl.NumberFormat('de-DE').format(downloads),
|
||||
name: lib.name || lib.package,
|
||||
url: lib.url || `https://www.npmjs.com/package/${pkg}`,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
return result.sort((a, b) => b.downloads - a.downloads);
|
||||
};
|
||||
156
docs/_data/componentLibraries.js
Normal file
@@ -0,0 +1,156 @@
|
||||
const componentLibraries = [
|
||||
{
|
||||
name: 'aybolit',
|
||||
url: 'https://web-padawan.github.io/aybolit/',
|
||||
description:
|
||||
'A growing family of elements with default styling for Bootstrap, Bulma, and Material, as well as a solid white-label basis for extending the underlying functionality with your own custom designs.',
|
||||
},
|
||||
{
|
||||
name: 'Auro',
|
||||
url: 'https://auro.alaskaair.com',
|
||||
description:
|
||||
"The Auro Design System and by extension, Auro Web Components are the core components for a new cross-team, multi-platform development strategy to ensure consistency of UI and experience for all of Alaska's customers. Auro is an eco-system of design, core utilities, and web components designed to reduce design and development efforts.",
|
||||
},
|
||||
{
|
||||
name: 'Bolt',
|
||||
url: 'https://boltdesignsystem.com/',
|
||||
description:
|
||||
'A family of web components built with first-class participation in the Twig templating system for PHP in mind. This set is backed by an expansive catalog of usage variants.',
|
||||
},
|
||||
{
|
||||
name: 'Carbon Design System',
|
||||
url: 'https://github.com/carbon-design-system/carbon-custom-elements',
|
||||
description:
|
||||
'Carbon is an open-source design system built by IBM. With the IBM Design Language as its foundation, the system consists of working code, design tools and resources, human interface guidelines, and a vibrant community of contributors.',
|
||||
},
|
||||
{
|
||||
name: 'Clever Components',
|
||||
url: 'https://github.com/CleverCloud/clever-components',
|
||||
description:
|
||||
'A collection of low-level (atoms) and high-level (domain specific) Web Components made by Clever Cloud for its different Web UIs (public and internal).',
|
||||
},
|
||||
{
|
||||
name: 'Elix',
|
||||
url: 'https://component.kitchen/elix',
|
||||
description:
|
||||
'The Elix project aims to create a universal library of all general-purpose user interface patterns commonly found in desktop and mobile UIs, where each pattern is implemented as a well-designed, attractive, high-quality, performant, accessible, localizable, and extensively customizable web component.',
|
||||
},
|
||||
{
|
||||
name: 'FAST',
|
||||
url: 'https://fast.design',
|
||||
description:
|
||||
'FAST is a web component library built by Microsoft. The core, `fast-element`, is a lightweight means to easily build performant and standards-compliant Web Components. `fast-foundation` is a library of Web Component classes, templates, and other utilities built on fast-element intended to be composed into registered web components by design systems.',
|
||||
},
|
||||
{
|
||||
name: 'HelixUI',
|
||||
url: 'https://helixdesignsystem.github.io/helix-ui/',
|
||||
description:
|
||||
'The HelixUI library provides front-end developers a set of reusable CSS classes and HTML Custom Elements that adhere to Helix design standards, as outlined by Rackspace.',
|
||||
},
|
||||
{
|
||||
name: 'Ink Components',
|
||||
url: 'https://components.ink/',
|
||||
description:
|
||||
'Web components for interactive scientific writing, reactive documents and explorable explanations. The Ink Components library can bring your math and science documents to the next level by breathing life into charts, equations, and variables that can be used throughout your application or content.',
|
||||
},
|
||||
{
|
||||
name: 'Io GUI',
|
||||
url: 'https://io-gui.dev/#page=elements',
|
||||
description:
|
||||
'Io is a UI framework for web applications and custom elements. It supports virtual DOM, reactive rendering and data binding. It includes a design system composed of UI elements ranging from simple input fields, to menu systems and responsive layouts. Its unique feature is the ability to create visually complex, yet fast and GPU-optimized elements using WebGL shaders.',
|
||||
},
|
||||
{
|
||||
name: 'Ionic UI Components',
|
||||
url: 'https://ionicframework.com/docs/components',
|
||||
description:
|
||||
'Take advantage of the component system that powers Ionic applications with a large ecosystem to choose from and in-depth usage instructions, no matter the framework you use.',
|
||||
},
|
||||
{
|
||||
name: 'iooxa.dev',
|
||||
url: 'https://iooxa.dev/',
|
||||
description:
|
||||
'The goal of [iooxa.dev](https://iooxa.dev/) is to provide open source tools to promote and enable interactive scientific writing, reactive documents and explorable explanations.',
|
||||
},
|
||||
{
|
||||
name: 'Kor UI',
|
||||
url: 'https://kor-ui.com/',
|
||||
description:
|
||||
'A Design System and complete UI component library built to facilitate the design and development of intuitive, coherent and pleasing applications based on Web technologies (HTML5). It contains 35+ components which are compatible with any framework, form factor, input type and modern browser.',
|
||||
},
|
||||
{
|
||||
name: 'Lightning Web Components by SalesForce',
|
||||
url: 'https://developer.salesforce.com/docs/component-library/overview/components',
|
||||
description:
|
||||
'Lightning Web Components are now the go-to standard for developing applications in the SalesForce ecosystem, and with the power of web components, they can be the basis of applications outside of their ecosystem, too!',
|
||||
},
|
||||
{
|
||||
name: 'Lion Web Components by ING Bank',
|
||||
url: 'https://github.com/ing-bank/lion',
|
||||
description:
|
||||
'Lion web components is a set of highly performant, accessible and flexible Web Components. They provide an unopinionated, white label layer that can be extended to your own layer of components.',
|
||||
},
|
||||
{
|
||||
name: 'Material Web Components',
|
||||
url:
|
||||
'https://material-components.github.io/material-components-web-components/demos/index.html',
|
||||
description:
|
||||
"Material Design Components from Material Design team themselves. Stay as close as possible to the changing specification with these components from Google's own Material Design team.",
|
||||
},
|
||||
{
|
||||
name: 'Morningstar',
|
||||
url: 'http://designsystem.morningstar.com/components/component-status.html',
|
||||
description:
|
||||
'The Morningstar Design System combines vanilla HTML/CSS with web components in just the right proportions to empower the design and development of wide reaching content and functionality.',
|
||||
},
|
||||
{
|
||||
name: 'Shoelace',
|
||||
url: 'https://shoelace.style/',
|
||||
description:
|
||||
'Shoelace provides a collection of professionally designed, every day UI components built on a framework-agnostic technology. Every web component is built with accessibility and developer experience in mind.',
|
||||
},
|
||||
{
|
||||
name: 'Smart HTML Elements',
|
||||
url: 'https://www.htmlelements.com',
|
||||
description:
|
||||
'With the goal of helping to deliver modern responsive websites and applications that work on any device and are pleasure to use, the Smart HTML Elements collection is one the most feature rich and comprehensive javascript user interface frameworks.',
|
||||
},
|
||||
{
|
||||
name: 'Spectrum Web Components',
|
||||
url: 'https://opensource.adobe.com/spectrum-web-components/',
|
||||
description:
|
||||
"The Spectrum Web Components project is an implementation of Spectrum, Adobe’s design system. It's designed to work with any web framework — or even without one.",
|
||||
},
|
||||
{
|
||||
name: 'UI5 Web Components',
|
||||
url: 'https://sap.github.io/ui5-webcomponents/',
|
||||
description:
|
||||
"Get the power and flexibility of SAP's UI5 rendering stack with the ergonomics and ease of use of web components, and bring enterprise-grade features, Fiori UX and themeability home to your application.",
|
||||
},
|
||||
{
|
||||
name: 'Vaadin',
|
||||
url: 'https://vaadin.com/components',
|
||||
description:
|
||||
'Vaadin has a comprehensive set of beautifully crafted, performant, and adaptable UI components for modern mobile-first Web apps. They are the ideal building blocks for Progressive Web Applications.',
|
||||
},
|
||||
{
|
||||
name: 'Weightless',
|
||||
url: 'https://weightless.dev/',
|
||||
description:
|
||||
'A lightweight component library featuring a wide array of design concepts. It surfaces a good amount of customizability via comprehensive component and CSS Custom Property APIs with a small footprint, just short of 30KB.',
|
||||
},
|
||||
{
|
||||
name: 'Wired Elements',
|
||||
url: 'https://wiredjs.com/',
|
||||
description:
|
||||
'A set of common UI elements with a hand-drawn, sketchy look. These can be used for wireframes, mockups, or just the fun hand-drawn look.',
|
||||
},
|
||||
{
|
||||
name: 'Zooplus Web Components',
|
||||
url: 'https://zooplus.github.io/zoo-web-components/',
|
||||
description: 'A set of web components that implement Z+ shop style guide.',
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = async function getComponentLibraries() {
|
||||
return componentLibraries.sort((a, b) => a.name.localeCompare(b.name));
|
||||
};
|
||||
49
docs/_data/footer.json
Normal file
@@ -0,0 +1,49 @@
|
||||
[
|
||||
{
|
||||
"name": "Discover",
|
||||
"children": [
|
||||
{
|
||||
"text": "Blog",
|
||||
"href": "/blog/"
|
||||
},
|
||||
{
|
||||
"text": "About",
|
||||
"href": "/discover/about/"
|
||||
},
|
||||
{
|
||||
"text": "Help and Feedback",
|
||||
"href": "https://github.com/open-wc/open-wc/issues"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Follow",
|
||||
"children": [
|
||||
{
|
||||
"text": "Github",
|
||||
"href": "https://github.com/open-wc/open-wc"
|
||||
},
|
||||
{
|
||||
"text": "Twitter",
|
||||
"href": "https://twitter.com/openwc"
|
||||
},
|
||||
{
|
||||
"text": "Slack",
|
||||
"href": "/discover/slack/"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Support",
|
||||
"children": [
|
||||
{
|
||||
"text": "Sponsor",
|
||||
"href": "https://opencollective.com/modern-web"
|
||||
},
|
||||
{
|
||||
"text": "Contribute",
|
||||
"href": "https://github.com/open-wc/open-wc/blob/master/CONTRIBUTING.md"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = 'layout.njk';
|
||||
4
docs/_data/rocketLaunch.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"homeLayout": "home",
|
||||
"newsletter": false
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
let url = '/';
|
||||
/* eslint-disable */
|
||||
|
||||
switch (process.env.CONTEXT) {
|
||||
case 'production':
|
||||
url = process.env.URL;
|
||||
break;
|
||||
case 'deploy-preview':
|
||||
url = process.env.DEPLOY_URL;
|
||||
break;
|
||||
case 'branch-deploy':
|
||||
url = process.env.DEPLOY_PRIME_URL;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
name: 'Open Web Components',
|
||||
shortDesc:
|
||||
'Open Web Components provides a set of defaults, recommendations and tools to help facilitate your web component project. Our recommendations include: developing, linting, testing, building, tooling, demoing, publishing and automating.',
|
||||
url,
|
||||
module.exports = async function () {
|
||||
return {
|
||||
name: 'Open Web Components',
|
||||
description:
|
||||
'Open Web Components provides a set of defaults, recommendations and tools to help facilitate your Web Component.',
|
||||
socialLinks: [
|
||||
{
|
||||
name: 'GitHub',
|
||||
url: 'https://github.com/open-wc/open-wc',
|
||||
},
|
||||
],
|
||||
helpUrl: 'https://github.com/open-wc/open-wc/issues',
|
||||
logoAlt: 'O with occluded right edge to appear also as a C',
|
||||
iconColorMaskIcon: '#3f93ce',
|
||||
iconColorMsapplicationTileColor: '#1d3557',
|
||||
iconColorThemeColor: '#1d3557',
|
||||
analytics: 'UA-131782693-2',
|
||||
socialMediaImage: '/_assets/social-media-image.jpg',
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
{% include 'partials/meta.njk' %}
|
||||
<link rel="stylesheet" href="/styles.css">
|
||||
</head>
|
||||
|
||||
<body data-gr-c-s-loaded="true">
|
||||
<div id="app">
|
||||
<div class="theme-container no-sidebar">
|
||||
{% include 'partials/header.njk' %}
|
||||
<div class="sidebar-mask"></div>
|
||||
<aside class="sidebar">
|
||||
<nav class="nav-links">
|
||||
<div class="nav-item">
|
||||
<a href="/" class="nav-link router-link-exact-active router-link-active">
|
||||
Home
|
||||
</a>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<a href="/guide/" class="nav-link">
|
||||
Guide
|
||||
</a>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<a href="/faq/" class="nav-link">
|
||||
FAQ
|
||||
</a>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<a href="/about/" class="nav-link">
|
||||
About
|
||||
</a>
|
||||
</div>
|
||||
<a href="https://github.com/open-wc/open-wc" target="_blank" rel="noopener noreferrer" class="repo-link">
|
||||
GitHub
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewbox="0 0 100 100" width="15" height="15" class="icon outbound">
|
||||
<path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path>
|
||||
<polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon>
|
||||
</svg>
|
||||
</a>
|
||||
</nav>
|
||||
<!---->
|
||||
</aside>
|
||||
<main aria-labelledby="main-title" class="home">
|
||||
<header class="hero">
|
||||
<svg viewbox="0 0 244 244" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" alt="open-wc" class="logo">
|
||||
<use xlink:href="#logo"></use>
|
||||
</svg>
|
||||
<h1 id="main-title">open-wc</h1>
|
||||
<p class="description">
|
||||
Open Web Component Recommendations
|
||||
</p>
|
||||
<clipboard-copy for="homepage-npm-init-command" tabindex="0" role="button">
|
||||
<code id="homepage-npm-init-command" class="language-bash">npm init @open-wc</code>
|
||||
</clipboard-copy>
|
||||
<p class="action">
|
||||
<a href="/guide/" class="nav-link action-button">
|
||||
Get Started →
|
||||
</a>
|
||||
</p>
|
||||
</header>
|
||||
<div class="features">
|
||||
<div class="feature">
|
||||
<h2>Smart Defaults</h2>
|
||||
<p>Enjoy the peace of mind that comes from having a well-known default solution for almost everything. From linting to testing to demos to publishing - have the full experience.</p>
|
||||
</div>
|
||||
<div class="feature">
|
||||
<h2>Awesome Generators</h2>
|
||||
<p>Get up and running quickly with opinionated generators, or add recommended tools to existing projects. Our comprehensive fleet of generators have got you covered.</p>
|
||||
</div>
|
||||
<div class="feature">
|
||||
<h2>Open Source Love</h2>
|
||||
<p>Open Web Components is a community-effort, independent of any framework or company. We use mostly open-source tools and services.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="theme-default-content custom content__default">
|
||||
{{ content.html | safe }}
|
||||
<div class="footer">
|
||||
MIT Licensed | Copyright © 2018-2020 open-wc
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<div class="global-ui"></div>
|
||||
</div>
|
||||
|
||||
{% include "partials/sidebar-script.njk" %}
|
||||
{% include 'partials/service-worker.njk' %}
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,33 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
{% include 'partials/meta.njk' %}
|
||||
<link rel="stylesheet" href="/styles.css">
|
||||
</head>
|
||||
|
||||
<body data-gr-c-s-loaded="true">
|
||||
<div id="app">
|
||||
<div class="theme-container">
|
||||
{% include 'partials/header.njk' %}
|
||||
{% include 'partials/sidebar.njk' %}
|
||||
<main class="page">
|
||||
<div class="theme-default-content content__default">
|
||||
{{ content.html | safe }}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<div class="global-ui"></div>
|
||||
</div>
|
||||
|
||||
{% include "partials/sidebar-script.njk" %}
|
||||
|
||||
{% if content.jsCode %}
|
||||
<script type="module">
|
||||
{{ content.jsCode | safe }}
|
||||
</script>
|
||||
{% endif %}
|
||||
{% include 'partials/service-worker.njk' %}
|
||||
</body>
|
||||
|
||||
</html>
|
||||
6
docs/_includes/partials/content-footer.njk
Normal file
@@ -0,0 +1,6 @@
|
||||
<div class="content-footer">
|
||||
<p>
|
||||
Caught a mistake or want to contribute to the documentation?
|
||||
<a href="https://github.com/open-wc/open-wc/edit/chore/newSiteWithRocket/{{ page.inputPath }}">Edit this page on GitHub!</a>
|
||||
</p>
|
||||
</div>
|
||||
@@ -1,44 +0,0 @@
|
||||
<header class="navbar">
|
||||
<div class="sidebar-button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewbox="0 0 448 512" class="icon">
|
||||
<path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<a href="/index.html" class="home-link router-link-active" aria-label="Open Web Components">
|
||||
<svg viewbox="0 0 244 244" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" alt="open-wc" class="logo">
|
||||
<defs>
|
||||
<lineargradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1">
|
||||
<stop stop-color="#9B00FF" offset="0%"></stop>
|
||||
<stop stop-color="#0077FF" offset="100%"></stop>
|
||||
</lineargradient>
|
||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" id="logo">
|
||||
<path d="M205.639259,176.936244 C207.430887,174.217233 209.093339,171.405629 210.617884,168.510161 M215.112174,158.724316 C216.385153,155.50304 217.495621,152.199852 218.433474,148.824851 M220.655293,138.874185 C221.231935,135.482212 221.637704,132.03207 221.863435,128.532919 M222,118.131039 C221.860539,114.466419 221.523806,110.85231 221.000113,107.299021 M218.885321,96.8583653 C218.001583,93.4468963 216.942225,90.1061026 215.717466,86.8461994 M211.549484,77.3039459 C209.957339,74.1238901 208.200597,71.0404957 206.290425,68.0649233 M200.180513,59.5598295 C181.848457,36.6639805 153.655709,22 122.036748,22 C66.7879774,22 22,66.771525 22,122 C22,177.228475 66.7879774,222 122.036748,222 C152.914668,222 180.52509,208.015313 198.875424,186.036326" stroke="url(#linearGradient-1)" stroke-width="42.0804674"></path>
|
||||
</g>
|
||||
</defs>
|
||||
<use xlink:href="#logo"></use>
|
||||
</svg>
|
||||
<span class="site-name can-hide">open-wc</span></a>
|
||||
<div class="links" style="max-width: 1532px;">
|
||||
<nav class="nav-links can-hide">
|
||||
<div class="nav-item">
|
||||
<a href="/index.html" class="nav-link{% if permalink == 'index.html' %} router-link-active{% endif %}">
|
||||
Home
|
||||
</a>
|
||||
</div>
|
||||
{% for navItem in collections.section %}
|
||||
<div class="nav-item">
|
||||
<a href="/{{ navItem.data.permalink }}" class="nav-link{% if section == navItem.data.section %} router-link-active{% endif %}">
|
||||
{{ navItem.data.title }}
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<a href="https://github.com/open-wc/open-wc" target="_blank" rel="noopener noreferrer" class="repo-link">
|
||||
GitHub
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewbox="0 0 100 100" width="15" height="15" class="icon outbound">
|
||||
<path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path>
|
||||
<polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon>
|
||||
</svg>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
@@ -1,34 +0,0 @@
|
||||
{% set pageTitle = title %}
|
||||
{% if title != site.name %}
|
||||
{% set pageTitle = title + ': ' + site.name %}
|
||||
{% endif %}
|
||||
{% set currentUrl = site.url + page.url %}
|
||||
|
||||
{% set metaDesc = site.shortDesc %}
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ pageTitle }}</title>
|
||||
<meta name="generator" content="11ty 0.10.0">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link rel="icon" type="image/png" href="/favicon.png"/>
|
||||
<link rel="apple-touch-icon" href="/icons/icon-128x128.png"/>
|
||||
<meta name="theme-color" content="#127afb"/>
|
||||
{% if site.url !== '/' %}
|
||||
<link rel="canonical" href="{{ currentUrl }}"/>
|
||||
{% endif %}
|
||||
|
||||
<meta property="og:site_name" content="{{ site.name }}"/>
|
||||
<meta property="og:title" content="{{ pageTitle }}"/>
|
||||
<meta property="og:type" content="website"/>
|
||||
<meta property="og:image" content="/logo.png">
|
||||
<meta property="og:url" content="{{ currentUrl }}"/>
|
||||
|
||||
<meta name="twitter:creator" content="@OpenWc"/>
|
||||
<meta name="twitter:card" content="summary">
|
||||
|
||||
{% if metaDesc %}
|
||||
<meta name="description" content="{{ metaDesc }}"/>
|
||||
<meta name="twitter:description" content="{{ metaDesc }}"/>
|
||||
<meta property="og:description" content="{{ metaDesc }}"/>
|
||||
{% endif %}
|
||||
@@ -1,42 +0,0 @@
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', function() {
|
||||
navigator.serviceWorker
|
||||
.register(`${window.location.pathname.split('/').length - 1 > 1 ? '../service-worker.js' : './service-worker.js' }`)
|
||||
.then(function() {
|
||||
console.log('ServiceWorker registered.');
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.log('ServiceWorker registration failed: ', err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (sessionStorage.getItem('openItems')) {
|
||||
const openItems = JSON.parse(sessionStorage.getItem('openItems'));
|
||||
const menuItems = [...document.querySelectorAll('section > p.sidebar-heading')];
|
||||
|
||||
if (openItems.length === menuItems.length) {
|
||||
menuItems.forEach((item, i) => {
|
||||
if (openItems[i]) {
|
||||
item.click();
|
||||
}
|
||||
});
|
||||
|
||||
sessionStorage.removeItem('openItems');
|
||||
}
|
||||
}
|
||||
|
||||
let refreshing;
|
||||
navigator.serviceWorker.addEventListener('controllerchange',
|
||||
function() {
|
||||
if (refreshing) return;
|
||||
refreshing = true;
|
||||
const openItems = [...document.querySelectorAll('section > p.sidebar-heading')]
|
||||
.map(item => item.classList.contains('open'));
|
||||
|
||||
sessionStorage.setItem('openItems', JSON.stringify(openItems));
|
||||
window.location.reload();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
@@ -1,14 +0,0 @@
|
||||
<ul class="sidebar-links">
|
||||
<li>
|
||||
<a href="/about/" class="{% if permalink == 'about/index.html' %}active {% endif %}sidebar-link">About</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/about/contact.html" class="{% if permalink == 'about/contact.html' %}active {% endif %}sidebar-link">Contact</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/about/rationales.html" class="{% if permalink == 'about/rationales.html' %}active {% endif %}sidebar-link">Rationales</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/about/blog.html" class="{% if permalink == 'about/blog.html' %}active {% endif %} sidebar-link">Blog</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -1,30 +0,0 @@
|
||||
<ul class="sidebar-links">
|
||||
<li>
|
||||
<a href="/faq/index.html" class="{% if permalink == 'faq/index.html' %}active {% endif %}sidebar-link">Faq</a>
|
||||
</li>
|
||||
<li>
|
||||
<section class="sidebar-group depth-0">
|
||||
<p class="sidebar-heading open">
|
||||
<span>Deep dives</span>
|
||||
<!---->
|
||||
</p>
|
||||
<ul class="sidebar-links sidebar-group-items">
|
||||
<li>
|
||||
<a href="/faq/events.html" class="{% if permalink == 'faq/events.html' %}active {% endif %}sidebar-link">Managing events in your custom elements</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/faq/rerender.html" class="{% if permalink == 'faq/rerender.html' %}active {% endif %}sidebar-link">Rerender not triggered</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/faq/unit-testing-custom-events.html" class="{% if permalink == 'faq/unit-testing-custom-events.html' %}active {% endif %}sidebar-link">Unit Testing Events</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/faq/unit-testing-init-error.html" class="{% if permalink == 'faq/unit-testing-init-error.html' %}active {% endif %}sidebar-link">Testing for errors thrown in initialization</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/faq/lit-element-lifecycle.html" class="{% if permalink == 'faq/lit-element-lifecycle.html' %}active {% endif %}sidebar-link">LitElement lifecycle</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -1,233 +0,0 @@
|
||||
<ul class="sidebar-links">
|
||||
<li>
|
||||
<a href="/guide/" class="{% if page.url == '/guide/' %}active {% endif %}sidebar-link">Introduction</a>
|
||||
</li>
|
||||
<li>
|
||||
<section class="sidebar-group collapsable depth-0">
|
||||
<p class="sidebar-heading">
|
||||
<span>Guides & Docs</span>
|
||||
<span class="arrow right"></span></p>
|
||||
<ul class="sidebar-links sidebar-group-items" style="">
|
||||
{% for item in collections.guide %}
|
||||
<li>
|
||||
<a href="{{ item.url }}" class="{% if page.url == item.url %}active {% endif %}sidebar-link">{{ item.data.title }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
</li>
|
||||
<li>
|
||||
<section class="sidebar-group collapsable depth-0">
|
||||
<p class="sidebar-heading">
|
||||
<span>Configs & Recommendations</span>
|
||||
<span class="arrow right"></span></p>
|
||||
<ul class="sidebar-links sidebar-group-items" style="">
|
||||
<li>
|
||||
<section class="sidebar-group is-sub-group depth-1">
|
||||
<p class="sidebar-heading open">
|
||||
<a href="/linting/" class="{% if permalink == 'linting/index.html' %}active {% endif %} sidebar-heading-link">Linting</a>
|
||||
<!---->
|
||||
</p>
|
||||
<ul class="sidebar-links sidebar-group-items">
|
||||
<li>
|
||||
<a href="/linting/linting-eslint.html" class="{% if permalink == 'linting/linting-eslint.html' %}active {% endif %}sidebar-link">ESLint</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</li>
|
||||
<li>
|
||||
<section class="sidebar-group is-sub-group depth-1">
|
||||
<p class="sidebar-heading">
|
||||
<a href="/developing/" class="{% if permalink == 'developing/index.html' %}active {% endif %} sidebar-heading-link">Developing</a>
|
||||
<!---->
|
||||
</p>
|
||||
</section>
|
||||
</li>
|
||||
<li>
|
||||
<section class="sidebar-group is-sub-group depth-1">
|
||||
<p class="sidebar-heading">
|
||||
<a href="/testing/" class="{% if permalink == 'testing/index.html' %}active {% endif %} sidebar-heading-link">Testing</a>
|
||||
<!---->
|
||||
</p>
|
||||
<ul class="sidebar-links sidebar-group-items">
|
||||
<li>
|
||||
<a href="/testing/testing.html" class="{% if permalink == 'testing/testing.html' %}active {% endif %}sidebar-link">Testing library</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</li>
|
||||
<li>
|
||||
<section class="sidebar-group is-sub-group depth-1">
|
||||
<p class="sidebar-heading">
|
||||
<a href="/publishing/" class="{% if page.url == '/publishing/' %}active {% endif %}sidebar-heading-link">Publishing Web Components</a>
|
||||
</p>
|
||||
</section>
|
||||
</li>
|
||||
<li>
|
||||
<section class="sidebar-group is-sub-group depth-1">
|
||||
<p class="sidebar-heading">
|
||||
<a href="/building/" class="{% if permalink == 'building/index.html' %}active {% endif %} sidebar-heading-link">Building</a>
|
||||
<!---->
|
||||
</p>
|
||||
<ul class="sidebar-links sidebar-group-items">
|
||||
<li>
|
||||
<a href="/building/building-rollup.html" class="{% if permalink == 'building/building-rollup.html' %}active {% endif %}sidebar-link">Rollup</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</li>
|
||||
<li>
|
||||
<section class="sidebar-group is-sub-group depth-1">
|
||||
<p class="sidebar-heading">
|
||||
<a href="/deploying/" class="{% if permalink == 'deploying/index.html' %}active {% endif %} sidebar-heading-link">Deploying apps</a>
|
||||
<!---->
|
||||
</p>
|
||||
</section>
|
||||
</li>
|
||||
<li>
|
||||
<section class="sidebar-group is-sub-group depth-1">
|
||||
<p class="sidebar-heading">
|
||||
<a href="/demoing/" class="{% if permalink == 'demoing/index.html' %}active {% endif %} sidebar-heading-link">Demoing</a>
|
||||
<!---->
|
||||
</p>
|
||||
</section>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</li>
|
||||
<li>
|
||||
<section class="sidebar-group collapsable depth-0">
|
||||
<p class="sidebar-heading">
|
||||
<span>Tools & Libraries</span>
|
||||
<span class="arrow right"></span></p>
|
||||
<ul class="sidebar-links sidebar-group-items" style="">
|
||||
<li>
|
||||
<section class="sidebar-group is-sub-group depth-1">
|
||||
<p class="sidebar-heading open">
|
||||
<span>Developing</span>
|
||||
</p>
|
||||
<ul class="sidebar-links sidebar-group-items">
|
||||
<li>
|
||||
<a href="/developing/es-dev-server.html" class="{% if permalink == 'developing/es-dev-server.html' %}active {% endif %}sidebar-link">ES dev server</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/init/" class="{% if permalink == 'init/index.html' %}active {% endif %}sidebar-link">Generators</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/developing/lit-helpers.html" class="{% if permalink == 'developing/lit-helpers.html' %}active {% endif %}sidebar-link">Lit Helpers</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/guide/dedupe-mixin.html" class="{% if permalink == 'guide/dedupe-mixin.html' %}active {% endif %}sidebar-link">Dedupe Mixin</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/scoped-elements/" class="{% if permalink == 'scoped-elements/index.html' %}active {% endif %}sidebar-link">Scoped Elements</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</li>
|
||||
<li>
|
||||
<section class="sidebar-group is-sub-group depth-1">
|
||||
<p class="sidebar-heading">
|
||||
<span>Testing</span>
|
||||
</p>
|
||||
<ul class="sidebar-links sidebar-group-items">
|
||||
<li>
|
||||
<a href="/testing/testing-helpers.html" class="{% if permalink == 'testing/testing-helpers.html' %}active {% endif %}sidebar-link">Testing Helpers</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/testing/testing-chai-a11y-axe.html" class="{% if permalink == 'testing/testing-chai-a11y-axe.html' %}active {% endif %}sidebar-link">Chai A11y aXe</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/testing/semantic-dom-diff.html" class="{% if permalink == 'testing/semantic-dom-diff.html' %}active {% endif %}sidebar-link">Semantic Dom Diff</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</li>
|
||||
<li>
|
||||
<section class="sidebar-group is-sub-group depth-1">
|
||||
<p class="sidebar-heading">
|
||||
<span>Building</span>
|
||||
</p>
|
||||
<ul class="sidebar-links sidebar-group-items">
|
||||
<li>
|
||||
<a href="/building/rollup-plugin-html.html" class="{% if permalink == 'building/rollup-plugin-html.html' %}active {% endif %}sidebar-link">Rollup Plugin HTML</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/building/rollup-plugin-polyfills-loader.html" class="{% if permalink == 'building/rollup-plugin-polyfills-loader.html' %}active {% endif %}sidebar-link">Rollup Plugin Polyfills Loader</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/building/polyfills-loader.html" class="{% if permalink == 'building/polyfills-loader.html' %}active {% endif %}sidebar-link">Polyfills loader</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</li>
|
||||
<li>
|
||||
<section class="sidebar-group is-sub-group depth-1">
|
||||
<p class="sidebar-heading">
|
||||
<span>Demoing</span>
|
||||
</p>
|
||||
<ul class="sidebar-links sidebar-group-items">
|
||||
<li>
|
||||
<a href="/demoing/storybook-addon-markdown-docs.html" class="{% if permalink == 'demoing/storybook-addon-markdown-docs.html' %}active {% endif %}sidebar-link">Storybook Addon: Markdown Docs</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</li>
|
||||
<li>
|
||||
<section class="sidebar-group collapsable depth-0">
|
||||
<p class="sidebar-heading">
|
||||
<span>Experiments</span>
|
||||
<span class="arrow right"></span></p>
|
||||
<ul class="sidebar-links sidebar-group-items" style="">
|
||||
<li>
|
||||
<a href="/mdjs/" class="{% if permalink == 'mdjs/index.html' %}active {% endif %}sidebar-link">Markdown JavaScript (mdjs) Format</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</li>
|
||||
</ul>
|
||||
<script>
|
||||
const groups = [...document.querySelectorAll('.sidebar-group:not(.is-sub-group)')];
|
||||
groups.map(group => {
|
||||
group
|
||||
.querySelector('.sidebar-heading')
|
||||
.addEventListener('click', e => {
|
||||
e
|
||||
.currentTarget
|
||||
.classList
|
||||
.toggle('open');
|
||||
const arrow = e
|
||||
.currentTarget
|
||||
.querySelector('.arrow');
|
||||
arrow
|
||||
.classList
|
||||
.toggle('down');
|
||||
arrow
|
||||
.classList
|
||||
.toggle('right');
|
||||
e
|
||||
.currentTarget
|
||||
.nextElementSibling
|
||||
.classList
|
||||
.toggle('hidden');
|
||||
});
|
||||
const active = group.querySelector('.active');
|
||||
if (active) {
|
||||
const arrow = group.querySelector('.arrow');
|
||||
arrow
|
||||
.classList
|
||||
.toggle('down');
|
||||
arrow
|
||||
.classList
|
||||
.toggle('right');
|
||||
} else {
|
||||
group
|
||||
.querySelector('.sidebar-group-items')
|
||||
.classList
|
||||
.toggle('hidden');
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -1,18 +0,0 @@
|
||||
<script>
|
||||
document
|
||||
.querySelector('.sidebar-button')
|
||||
.addEventListener('click', () => {
|
||||
document
|
||||
.querySelector('.theme-container')
|
||||
.classList
|
||||
.toggle('sidebar-open');
|
||||
});
|
||||
document
|
||||
.querySelector('.sidebar-mask')
|
||||
.addEventListener('click', () => {
|
||||
document
|
||||
.querySelector('.theme-container')
|
||||
.classList
|
||||
.toggle('sidebar-open');
|
||||
});
|
||||
</script>
|
||||
@@ -1,41 +0,0 @@
|
||||
<div class="sidebar-mask"></div>
|
||||
<aside class="sidebar">
|
||||
<nav class="nav-links">
|
||||
<div class="nav-item">
|
||||
<a href="/" class="nav-link">
|
||||
Home
|
||||
</a>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<a href="/guide/index.html" class="nav-link">
|
||||
Guide
|
||||
</a>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<a href="/faq/index.html" class="nav-link active">
|
||||
FAQ
|
||||
</a>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<a href="/about/index.html" class="nav-link">
|
||||
About
|
||||
</a>
|
||||
</div>
|
||||
<a href="https://github.com/open-wc/open-wc" target="_blank" rel="noopener noreferrer" class="repo-link">
|
||||
GitHub
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewbox="0 0 100 100" width="15" height="15" class="icon outbound">
|
||||
<path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path>
|
||||
<polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon>
|
||||
</svg>
|
||||
</a>
|
||||
</nav>
|
||||
{% if section == 'about' %}
|
||||
{% include 'partials/sidebar-about.njk' %}
|
||||
{% endif %}
|
||||
{% if section == 'faq' %}
|
||||
{% include 'partials/sidebar-faq.njk' %}
|
||||
{% endif %}
|
||||
{% if section == 'guides' %}
|
||||
{% include 'partials/sidebar-guide.njk' %}
|
||||
{% endif %}
|
||||
</aside>
|
||||
@@ -1,46 +0,0 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
const { mdjsProcess, mdjsProcessPlugins } = require('@mdjs/core');
|
||||
|
||||
const plugins = mdjsProcessPlugins.map(pluginObj => {
|
||||
if (pluginObj.name === 'htmlHeading') {
|
||||
return {
|
||||
...pluginObj,
|
||||
options: {
|
||||
properties: {
|
||||
className: ['header-anchor'],
|
||||
},
|
||||
content: [{ type: 'text', value: '#' }],
|
||||
},
|
||||
};
|
||||
}
|
||||
return pluginObj;
|
||||
});
|
||||
|
||||
function eleventyUnified() {
|
||||
return {
|
||||
set: () => {},
|
||||
render: async str => {
|
||||
const result = await mdjsProcess(str, {
|
||||
plugins,
|
||||
});
|
||||
return result;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const defaultEleventyUnifiedOptions = {
|
||||
plugins: [],
|
||||
};
|
||||
|
||||
const _eleventy = {
|
||||
initArguments: {},
|
||||
configFunction: (eleventyConfig, pluginOptions = {}) => {
|
||||
const options = {
|
||||
...defaultEleventyUnifiedOptions,
|
||||
...pluginOptions,
|
||||
};
|
||||
eleventyConfig.setLibrary('md', eleventyUnified(options));
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = _eleventy;
|
||||
42
docs/_redirects
Normal file
@@ -0,0 +1,42 @@
|
||||
/guide/ /guides/
|
||||
/guide/component-libraries.html /guides/community/component-libraries/
|
||||
/developing/code-examples.html /guides/developing-components/code-examples/
|
||||
/developing/ /guides/developing-components/getting-started/
|
||||
/developing/es-dev-server.html /docs/legacy/legacy-projects/
|
||||
/init/ /guides/developing-components/getting-started/
|
||||
/codelabs/ /guides/developing-components/codelabs/
|
||||
/testing/ /guides/developing-components/testing/
|
||||
/guide/index.html /guides/
|
||||
/testing/testing-helpers.html /docs/testing/helpers/
|
||||
/demoing/ /docs/demoing/storybook/
|
||||
/developing/best-practices.html /guides/knowledge/lit-element/lifecycle/
|
||||
/building/building-rollup.html /docs/building/rollup/
|
||||
/index.html /
|
||||
/faq/events.html /guides/knowledge/events/
|
||||
/building/ /docs/building/
|
||||
/mdjs/ /docs/experimental/mdjs/
|
||||
/scoped-elements/ /docs/development/scoped-elements/
|
||||
/testing/testing.html /guides/developing-components/testing/
|
||||
/testing/testing-karma.html /docs/legacy/legacy-projects/
|
||||
/developing/lit-html.html /guides/developing-components/getting-started/
|
||||
/publishing/ /guides/developing-components/publishing/
|
||||
/developing/lit-helpers.html /docs/development/lit-helpers/
|
||||
/developing/ide.html /guides/tools/ide/
|
||||
/building/rollup-plugin-html.html /docs/legacy/legacy-projects/
|
||||
/faq/rerender.html /guides/knowledge/lit-element/rendering/
|
||||
/faq/unit-testing-custom-events.html /guides/knowledge/events/
|
||||
/guide/dedupe-mixin.html /docs/development/dedupe-mixin/
|
||||
/faq/index.html /guides/knowledge/lit-element/lifecycle/
|
||||
/testing/semantic-dom-diff.html /docs/testing/semantic-dom-diff/
|
||||
/linting/ /guides/tools/linting-and-formatting/
|
||||
/building/polyfills-loader.html /docs/legacy/legacy-projects/
|
||||
/about/index.html /discover/about/
|
||||
/demoing/storybook-addon-markdown-docs.html /docs/legacy/legacy-projects/
|
||||
/testing/testing-chai-a11y-axe.html /docs/testing/chai-a11y-axe/
|
||||
/linting/linting-eslint.html /guides/tools/linting-and-formatting/
|
||||
/testing/karma-esm.html /docs/legacy/legacy-projects/
|
||||
/faq/lit-element-lifecycle.html /guides/knowledge/lit-element/lifecycle/
|
||||
/about/ /discover/about/
|
||||
/faq/ /guides/knowledge/lit-element/lifecycle/
|
||||
/building/rollup-plugin-polyfills-loader.html /docs/legacy/legacy-projects/
|
||||
/testing/testing-karma-bs.html /docs/legacy/legacy-projects/
|
||||
@@ -1,33 +0,0 @@
|
||||
---
|
||||
permalink: 'about/blog.html'
|
||||
title: Blog
|
||||
section: about
|
||||
tags:
|
||||
- about
|
||||
---
|
||||
|
||||
# Blog
|
||||
|
||||
## [Announcing Open Web Components](https://dev.to/thepassle/announcing-open-web-components-5h7)
|
||||
|
||||
by [open-wc](https://github.com/open-wc)
|
||||
|
||||
## [Cross Browser Testing your Stencil Web Component like a boss](https://medium.com/vincent-ogloblinsky/cross-browser-testing-your-stencil-web-component-like-a-boss-93c1b154bfd9)
|
||||
|
||||
by [Vincent Ogloblinsky](https://github.com/vogloblinsky)
|
||||
|
||||
## [npm Convos: open-wc](https://blog.npmjs.org/post/182917093835/npm-convos-open-wc?utm_campaign=newsletter20190228&utm_source=newsletter_mailer&utm_medium=email&utm_source=npm+Weekly&utm_campaign=72f3f1c998-npm_weekly_186_2019_02_26_08_58&utm_medium=email&utm_term=0_e17fe5d778-72f3f1c998-303236901)
|
||||
|
||||
by [npm](https://npmjs.com)
|
||||
|
||||
## [Not Another To Do App](https://medium.com/@westbrook/not-another-to-do-app-169c14bb7ef9)
|
||||
|
||||
by [Westbrook Johnson](https://github.com/westbrook)
|
||||
|
||||
## [Web Components: spoils of the browser wars](https://www.andrewzigler.com/blog/2019/02/28/web-components-spoils-of-the-browser-wars/)
|
||||
|
||||
by [Andrew Zigler](https://github.com/azigler)
|
||||
|
||||
## [Shared Behaviors best practices with Mocha](https://dev.to/open-wc/shared-behaviors-best-practices-with-mocha-519d)
|
||||
|
||||
by [Noël Macé](https://github.com/noelmace)
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
permalink: 'about/contact.html'
|
||||
title: Contact
|
||||
section: about
|
||||
tags:
|
||||
- about
|
||||
---
|
||||
|
||||
# Contact
|
||||
|
||||
Feel free to reach out to us on [twitter](https://twitter.com/OpenWc) or create [a github issue](https://github.com/open-wc/open-wc/issues/new) for any feedback or questions you might have.
|
||||
|
||||
You can also find us on the Polymer slack in the [#open-wc](https://slack.com/share/IUQNUPWUF/awabyN8iYH4dXX6aGpu16ES9/enQtOTc2Nzc2ODEyOTY3LWM5ZGExNGFiMmM4NDY2YWI2MzYwOGY5ZTNlZjk4OGU4NTFhMGJjNmVhNGI4MzVlNTMwNGRiNGIxNjc4MGJhNDg) channel.
|
||||
|
||||
You can join the Polymer slack by visiting [this link](https://www.polymer-project.org/slack-invite).
|
||||
@@ -1,75 +0,0 @@
|
||||
---
|
||||
permalink: 'about/rationales.html'
|
||||
title: Rationales
|
||||
section: about
|
||||
tags:
|
||||
- about
|
||||
---
|
||||
|
||||
# Rationales
|
||||
|
||||
Our recommendations must fulfill certain criteria before we publish them.
|
||||
|
||||
1. Language or platform features/APIs must be released without a flag in the stable version of at least one browser
|
||||
1. Libraries must offer an ES module version
|
||||
|
||||
**Note**: <a id="bare-specifiers"></a>We currently have ONE exception to this rule and that is 'bare modules'.
|
||||
This is such a powerful and widely-used pattern in the current JavaScript ecosystem, that if you don't use it you basically need to implement everything yourself.
|
||||
We want to help you build your apps as easily and efficiently as possible, so for now we've adopted this practice as our only exception to the above rules.
|
||||
The [import maps](https://github.com/WICG/import-maps) proposal aims to bring bare modules to the web browser. You can follow that repository to stay up to date.
|
||||
|
||||
## Workflows
|
||||
|
||||
We recommend implementing the following three workflows for specific tasks while developing your component or application.
|
||||
You are encouraged to freely switch between them depending on what you are working on.
|
||||
|
||||
### Development Workflow
|
||||
|
||||
The ideal development environment uses no tools, just an up-to-date browser and a simple HTTP server.
|
||||
|
||||
<div class="custom-block warning"><p class="custom-block-title">WARNING</p> <p>Unfortunately we are not fully there yet, because of the <a href="#bare-specifiers">bare modules exception</a> you will still need to have a server that at least supports them.
|
||||
We recommend our <a href="../developing/es-dev-server.html" class="">ES Dev Server</a> as it does nothing more/nothing less.</p></div>
|
||||
|
||||
When would you choose this workflow:
|
||||
|
||||
- When developing new features
|
||||
|
||||
Why would you choose this:
|
||||
|
||||
- Simple to setup
|
||||
- As fast as it gets
|
||||
|
||||
### Bundling Workflow
|
||||
|
||||
Chances are that the web components you're building will need to run in more than just the latest browser.
|
||||
In these cases it's time to open your toolbox and make sure everything works in all supported browsers.
|
||||
|
||||
When would you choose this workflow:
|
||||
|
||||
- To verify everything works in all supported browsers
|
||||
- To debug legacy browsers
|
||||
|
||||
We recommend our [Open Web Components Building Setup](../building).
|
||||
|
||||
Why would you choose it:
|
||||
|
||||
- As good as it gets when you need to work with legacy browsers
|
||||
- Auto-bundling/reloading
|
||||
|
||||
### Production Workflow
|
||||
|
||||
Once you're happy with your web components, it's time to put them somewhere more useful.
|
||||
Most likely a publicly available web server.
|
||||
Before you do that let's apply all the optimizations magic we can cook up.
|
||||
|
||||
When would you use this:
|
||||
|
||||
- Optimize your build
|
||||
- Publishing to production
|
||||
|
||||
We recommend our [Open Web Components Building Setup](../building).
|
||||
|
||||
Why would you choose it:
|
||||
|
||||
- Multiple bundles for differential serving
|
||||
- Conditional auto-loader for those bundles (without a special server)
|
||||
@@ -1,47 +0,0 @@
|
||||
---
|
||||
permalink: 'automating/index.html'
|
||||
title: Automating
|
||||
section: guides
|
||||
tags:
|
||||
- guides
|
||||
---
|
||||
|
||||
# Automating
|
||||
|
||||
Having continuous integration in your project can provide valuable insights, and we consider it an essential in your projects.
|
||||
|
||||
## Circle ci
|
||||
|
||||
If you use the default generator you will already have CircleCi setup with a .circleci folder.
|
||||
It also contains a config that takes care of linting and testing.
|
||||
|
||||
<div class="custom-block tip"><p class="custom-block-title">Info</p> <p>This is part of the default <a href="https://open-wc.org/" target="_blank" rel="noopener noreferrer">open-wc<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a> recommendation.</p></div>
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
npm init @open-wc automating
|
||||
```
|
||||
|
||||
### Manual Setup
|
||||
|
||||
- copy [.circleci/config.yml](https://github.com/open-wc/open-wc/blob/master/packages/create/src/generators/tools-circleci/templates/static/.circleci/config.yml) to `.circleci/config.yml`
|
||||
|
||||
## Usage
|
||||
|
||||
- Register via [https://circleci.com/signup/](https://circleci.com/signup/)
|
||||
- Use Sign Up with Github and select your user
|
||||
- If you already have an account, simply login
|
||||
- In the sidebar click "Add Projects"
|
||||
- Click "Set up Project"
|
||||
- Config is already present so you can simply click "Start building"
|
||||
|
||||
## Run Tests via Browserstack
|
||||
|
||||
- If you do not have an account yet, please create a Browserstack account
|
||||
- For open source projects you can request a sponsorhip (like we have for open-wc) => You only need to add the logo + link to browserstack and write an E-Mail.
|
||||
- Go to [https://www.browserstack.com/accounts/settings](https://www.browserstack.com/accounts/settings)
|
||||
- Look for "Automate", and write down the "Access Key" and "Username"
|
||||
- Open your [circleci App](https://circleci.com/dashboard) or direclty via https://circleci.com/gh/{groupname}/{reponame}/edit
|
||||
- Go to the project settings -> Environment Variables
|
||||
- Add Variable: BROWSER_STACK_USERNAME + \${username from url above}
|
||||
- Add Variable: BROWSER_STACK_ACCESS_KEY + \${key from url above}
|
||||
BIN
docs/blog/announcing-open-web-components/images/blog-header.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
docs/blog/announcing-open-web-components/images/media-image.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
176
docs/blog/announcing-open-web-components/index.md
Normal file
@@ -0,0 +1,176 @@
|
||||
---
|
||||
title: 'Announcing Open Web Components'
|
||||
pageTitle: 'Announcing Open Web Components'
|
||||
date: 2019-02-11
|
||||
published: true
|
||||
description: 'Open Web Components provides a set of defaults, recommendations and tools to help facilitate your Web Component'
|
||||
tags: [webcomponents, lithtml, litelement, javascript]
|
||||
canonical_url: https://open-wc.org/blog/announcing-open-web-components/
|
||||
cover_image: /blog/announcing-open-web-components/images/blog-header.jpg
|
||||
socialMediaImage: /blog/announcing-open-web-components/images/social-media-image.jpg
|
||||
---
|
||||
|
||||
Hi all! 👋
|
||||
|
||||
We are `open-wc`, a collective of open source and web component enthusiasts. We consider it our goal to empower everyone with a powerful and battle-tested setup for creating and sharing open source web components.
|
||||
|
||||
Many web developers have experienced the dreaded "Javascript Fatigue". With our recommendations, we hope you'll enjoy the peace of mind that comes from having a well-known default solution for almost everything. From IDE to CI, open-wc has got you covered.
|
||||
|
||||
We want web component development to be accessible and approachable for everyone, regardless of your background or previous experience. Therefore, our recommendations aim to be easy to use, be ready to use, and provide that "it just works" developer experience we all crave for the various aspects of web component development.
|
||||
|
||||
We strongly believe that staying close to browser standards will be the best long term investment for your code. It is the basis for all our recommendations, and it means that sometimes we will not recommend a popular feature or functionality. It also means we can be faster to adopt and recommend new browser standards.
|
||||
|
||||
|
||||
|
||||
## Why Web Components
|
||||
|
||||
|
||||
In the last several years, the component-based model for web application development was popularized, and the JavaScript community blossomed with a wide variety of libraries and approaches. Work on standardizing the web’s native component model began at Google in 2012, and after several years of open development, was successfully implemented across all major browsers in 2019. At the time of writing, over 10% of all page views contain web components.
|
||||
|
||||
We believe web components provide a standards-based solution to problems like reusability, interopability, and encapsulation. Furthermore, we believe using the browser's native component model will increase the longevity of your application. The web has an extremely strong tradition of backwards-compatibility, as standards bodies have consistently gone out of their way to maintain legacy APIs.
|
||||
|
||||
At `open-wc` you'll find anything you might need to get started developing web components.
|
||||
|
||||
|
||||
## Developing
|
||||
|
||||
|
||||
In our [developing](https://open-wc.org/developing/) section you'll find anything you need to know about writing your code; from practical code demos, to accessibility, to mixins, to tutorials and blog posts.
|
||||
|
||||
We have a wide range of interactive demo's available to help you get started writing code quickly:
|
||||
|
||||
[](https://open-wc-lit-demos.stackblitz.io/)
|
||||
|
||||
If you want to learn more about web components first, we recommend the following blog posts to give you a solid basis to start developing web components:
|
||||
|
||||
|
||||
- [Lets Build Web Components!](https://dev.to/bennypowers/lets-build-web-components-part-1-the-standards-3e85) by Benny Powers
|
||||
- [Web Components: from Zero to Hero](https://dev.to/thepassle/web-components-from-zero-to-hero-4n4m) by Pascal Schilp
|
||||
|
||||
|
||||
## Testing
|
||||
|
||||
|
||||
Not only do we provide a [testing setup](https://open-wc.org/testing/testing-karma.html) with Karma, Browserstack, and Wallaby, we also provide a set of testing helpers that help you:
|
||||
|
||||
|
||||
### Make fixtures:
|
||||
|
||||

|
||||
|
||||
```js
|
||||
CAPTION: code snippet
|
||||
import { html, fixture } from '@open-wc/testing-helpers';
|
||||
|
||||
it('can instantiate an element with properties', async () => {
|
||||
const el = await fixture(html`<my-el .foo=${'bar'}></my-el>`);
|
||||
expect(el.foo).to.equal('bar');
|
||||
}
|
||||
```
|
||||
|
||||
### Compare DOM:
|
||||
|
||||

|
||||
|
||||
```js
|
||||
CAPTION: code snippet
|
||||
import { html, fixture } from '@open-wc/testing-helpers';
|
||||
|
||||
it('has the following dom', async () => {
|
||||
const el = await fixture(`<div><!-- comment --><h1>${'Hey'} </h1> </div>`);
|
||||
expect(el).dom.to.equal('<div><h1>Hey</h1></div>');
|
||||
});
|
||||
```
|
||||
|
||||
### Manage timings:
|
||||
|
||||

|
||||
|
||||
```js
|
||||
CAPTION: code snippet
|
||||
import { nextFrame, aTimeout, html, fixture } from '@open-wc/testing-helpers';
|
||||
|
||||
const el = await fixture(html`<my-el .foo=${'bar'}></my-el>`);
|
||||
el.foo = 'baz';
|
||||
await nextFrame();
|
||||
|
||||
expect(el.shadowRoot.querySelector('#foo').innerText).to.equal('baz');
|
||||
|
||||
```
|
||||
|
||||
### Define multiple custom elements:
|
||||
|
||||

|
||||
|
||||
```js
|
||||
CAPTION: code snippet
|
||||
import { fixture, defineCE } from '@open-wc/testing-helpers';
|
||||
|
||||
const tag = defineCE(class extends MyMixin(HTMLElement) {
|
||||
constructor() {
|
||||
super();
|
||||
this.foo = true;
|
||||
}
|
||||
});
|
||||
const el = await fixture(`<${tag}></${tag}>`);
|
||||
expect(el.foo).to.be.true;
|
||||
```
|
||||
|
||||
|
||||
|
||||
## App Starter
|
||||
|
||||
|
||||
Many developers have experienced what has often been described as "JavaScript Fatigue", the overwhelming feeling of having to keep up with every new technology in the JavaScript ecosystem. JavaScript tooling can have an intimidating learning curve and can often be frustrating to configure. What are the right tools? Which tools should I be using? How do these tools work?
|
||||
|
||||
Our recommendations aim to relieve you some of that painful setup so you can skip right to the fun part; development. So if you want to get straight to developing, with a powerful setup that leverages the best of browser standards in no time, our [open-wc-app-starter](https://github.com/open-wc/open-wc-starter-app) might be for you!
|
||||
|
||||
[](https://github.com/open-wc/open-wc-starter-app)
|
||||
|
||||
Live demo [here](https://open-wc-starter-app.netlify.com/).
|
||||
|
||||
Our open-wc-app-starter will set you up with a full configuration, with the following features:
|
||||
|
||||
|
||||
- **Module resolution**
|
||||
- **Automatic module type selection**
|
||||
- **HTML, JS and CSS minifications**
|
||||
- **es2015 and es5 output** - Using [webpack-babel-multi-target-plugin](https://www.npmjs.com/package/webpack-babel-multi-target-plugin), our build outputs an es5 and es2015 version of your app. Using the [nomodule trick](https://jakearchibald.com/2017/es-modules-in-browsers/), we can serve es2015 code on modern browsers and es5 on older browsers (IE11 specifically). This significantly reduces the size of your app on modern browsers.
|
||||
- **No regenerator runtime / transform**
|
||||
- **Polyfills by usage** - Language polyfills are added based on browser support and usage. This leads to a significantly smaller initial bundle of your app.
|
||||
- **Syntax and javascript APIs** - Our config only supports standard javascript syntax and browser APIs. We support stage 3 proposals when they add significant value and are easy to support without major performance penalties. Some of the proposals we support are: - Dynamic import - import.meta.url
|
||||
- **Testing suite with Karma**
|
||||
- **Linting with ESLint, Prettier and commitlint**
|
||||
|
||||
You can find more documentation on our `open-wc-app-starter` [here](https://github.com/open-wc/open-wc-starter-app). We try to provide the best, user friendly set up available and your feedback is extremely valuable to us, so if you feel like anything is missing or you have any kind of feedback, please feel free to create an issue on our repo.
|
||||
|
||||
|
||||
## And much, much more
|
||||
|
||||
|
||||
Other recommendations include anything betwixt and between: [linting](https://open-wc.org/linting/), [demoing](https://open-wc.org/demoing/), [building](https://open-wc.org/building/), [publishing](https://open-wc.org/publishing/) and [automating](https://open-wc.org/automating/). We also have a fleet of [generators](https://open-wc.org/developing/generator) to plug and play any of our setups in your current project.
|
||||
|
||||
If you're interested in learning more about our philosophy and the rationale for our recommendations, you can do so [here](https://open-wc.org/about/).
|
||||
|
||||
It is our goal to help you get set up as quickly, and effortlessly as possible. If you feel like our recommendations are missing something, feel free to contact us. Please note that our recommendations and best practices are subject to change and may evolve over time.
|
||||
|
||||
|
||||
|
||||
## Join the conversation!
|
||||
|
||||
|
||||
We'd love to hear any feedback or questions you might have. You can reach us at:
|
||||
|
||||
|
||||
- Feel free to open an issue on our [Github](https://github.com/open-wc/open-wc) if you have a question or feedback.
|
||||
|
||||
- You can also find us on the Polymer slack in the [#open-wc](https://polymer.slack.com/messages/CE6D9DN05) channel.
|
||||
You can join the Polymer slack by visiting [this link](https://join.slack.com/t/polymer/shared_invite/enQtNTAzNzg3NjU4ODM4LTkzZGVlOGIxMmNiMjMzZDM1YzYyMzdiYTk0YjQyOWZhZTMwN2RlNjM5ZDFmZjMxZWRjMWViMDA1MjNiYWFhZWM).
|
||||
|
||||
- You can find our recommendations and documentation over at: [open-wc](https://open-wc.org).
|
||||
|
||||
You can also find some of us on twitter: [BennyP](https://twitter.com/PowersBenny), [daKmoR](https://twitter.com/daKmoR), [passle](https://twitter.com/passle_)
|
||||
|
||||
<br>
|
||||
<p align="center">
|
||||
🚽 Made with love by <a href="https://github.com/open-wc/open-wc">open-wc</a>. </p>
|
||||
19
docs/blog/index.md
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
title: Open Web Components Blog
|
||||
pageTitle: Open Web Components Blog
|
||||
layout: blog-overview.njk
|
||||
eleventyNavigation:
|
||||
key: Blog
|
||||
order: 30
|
||||
pagination:
|
||||
data: collections.blog
|
||||
size: 10
|
||||
reverse: true
|
||||
alias: posts
|
||||
---
|
||||
|
||||
Discover articles from the core team and contributors about Open Web Components, tips, and tricks included!
|
||||
|
||||
Do you want to write a blog post, or want us to write about a specific topic? [Suggest it!](https://github.com/open-wc/open-wc/issues/new?title=[blog%20post]%20Write%20about%20...&labels=blog%20post&body=I%20would%20like%20to%20write%20about...)
|
||||
|
||||
_Note: blog posts may age while our guides & docs section will always be up to date_
|
||||
|
After Width: | Height: | Size: 421 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 12 KiB |
971
docs/blog/testing-workflow-for-web-components/index.md
Normal file
@@ -0,0 +1,971 @@
|
||||
---
|
||||
title: 'Testing Workflow for Web Components'
|
||||
pageTitle: 'Testing Workflow for Web Components'
|
||||
published: true
|
||||
description: 'Learn to test and debug your web component with open-wc tools so you can deploy or share your production-ready code with confidence.'
|
||||
tags: [webcomponents, javascript, testing, karma]
|
||||
date: 2019-05-09
|
||||
canonical_url: https://open-wc.org/blog/testing-workflow-for-web-components/
|
||||
cover_image: /blog/testing-workflow-for-web-components/images/blog-header.jpg
|
||||
socialMediaImage: /blog/testing-workflow-for-web-components/images/social-media-image.jpg
|
||||
---
|
||||
|
||||
Whenever you ship something to be used by others, you take on a responsibility to deliver safe and stable code. One way to address this is by testing your code.
|
||||
|
||||
No matter how small - no matter how simple your project, there should always ideally be tests.
|
||||
|
||||
> Yes, I know reality hits hard and there will be many cases where that doesn't happen - but you should always strive to have tests
|
||||
|
||||
### Disclaimer
|
||||
|
||||
In this tutorial, we're going to make a simple version of an input element. By the end of it, you'll gain the skills and knowledge to put open-wc testing tools to practice; and build a solid, accessible, and well-tested input component.
|
||||
|
||||
### Warning
|
||||
|
||||
This is an in-depth tutorial showing a few pitfalls and tough cases when working with web components. This is definitely for more advanced users. You should have a basic knowledge about [LitElement](https://lit-element.polymer-project.org/) and [JSDoc Types](https://dev.to/dakmor/type-safe-web-components-with-jsdoc-4icf). Having an idea what [Mocha](https://mochajs.org/), [Chai BDD](https://www.chaijs.com/api/bdd/), [Karma](https://karma-runner.github.io/latest/index.html) is might help a little as well.
|
||||
|
||||
> We are thinking about posting an easier digestible version of this so if that is something you would like to see - let us know in the comments.
|
||||
|
||||
If you want to play along - all the code is on [github](https://github.com/daKmoR/testing-workflow-for-web-components).
|
||||
|
||||
## Let's Get Started!
|
||||
|
||||
Run in your console
|
||||
|
||||
```bash
|
||||
$ npm init @open-wc
|
||||
|
||||
# Results in this flow
|
||||
✔ What would you like to do today? › Scaffold a new project
|
||||
✔ What would you like to scaffold? › Web Component
|
||||
# Select with space! "Testing" => just enter will move one with no selection
|
||||
✔ What would you like to add? › Testing
|
||||
✔ Would you like to scaffold examples files for? › Testing
|
||||
✔ What is the tag name of your application/web component? … a11y-input
|
||||
✔ Do you want to write this file structure to disk? › Yes
|
||||
Writing..... done
|
||||
✔ Do you want to install dependencies? › No
|
||||
```
|
||||
|
||||
For more details please see [https://open-wc.org/testing/](https://open-wc.org/testing/).
|
||||
|
||||
Delete `src/A11yInput.js`
|
||||
|
||||
Modify `src/a11y-input.js` to:
|
||||
|
||||
```js
|
||||
import { LitElement, html, css } from 'lit-element';
|
||||
|
||||
export class A11yInput extends LitElement {}
|
||||
|
||||
customElements.define('a11y-input', A11yInput);
|
||||
```
|
||||
|
||||
and `test/a11y-input.test.js` to:
|
||||
|
||||
```js
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import { html, fixture, expect } from '@open-wc/testing';
|
||||
|
||||
import '../src/a11y-input.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../src/a11y-input.js').A11yInput} A11yInput
|
||||
*/
|
||||
|
||||
describe('a11y input', () => {
|
||||
it('has by default an empty string as label', async () => {
|
||||
const el = /** @type {A11yInput} */ (await fixture('<a11y-input></a11y-input>'));
|
||||
expect(el.label).to.equal('');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Our tests so far consist of a single feature (the `label` property) and a single assertion (`expect`). We're using karma and chai's <abbr title="behaviour/business-driven-development">BDD</abbr> syntax, so we group sets of tests (`it`) under the features or APIs they relate to (`describe`).
|
||||
|
||||
Let's see if everything works correctly by running: `npm run test`.
|
||||
|
||||
```
|
||||
SUMMARY:
|
||||
✔ 0 tests completed
|
||||
✖ 1 test failed
|
||||
|
||||
FAILED TESTS:
|
||||
a11y input
|
||||
✖ has by default an empty string as label
|
||||
HeadlessChrome 73.0.3683 (Windows 10.0.0)
|
||||
AssertionError: expected undefined to equal ''
|
||||
|
||||
+ expected - actual
|
||||
|
||||
-[undefined]
|
||||
+""
|
||||
```
|
||||
|
||||
Awesome - just as expected (🥁), we have a failing test :)
|
||||
|
||||
Let's switch into watch mode, which will run the tests continuously whenever you make changes to your code.
|
||||
|
||||
`npm run test:watch`
|
||||
|
||||

|
||||
|
||||
The following code was added in the video above to `src/a11y-input.js`:
|
||||
|
||||
```js
|
||||
static get properties() {
|
||||
return {
|
||||
label: { type: String },
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.label = '';
|
||||
}
|
||||
```
|
||||
|
||||
So far, so good? Still with us? Great! Let's up the game a little...
|
||||
|
||||
### Adding a Test for Shadow DOM
|
||||
|
||||
Let's add an assertion to test the contents of our element's shadow root.
|
||||
|
||||
If we want to be sure that our element behaves/looks the same we should make sure its dom structure remains the same.
|
||||
So let's compare the actual shadow dom to what we want it to be.
|
||||
|
||||
```js
|
||||
it('has a static shadowDom', async () => {
|
||||
const el = /** @type {A11yInput} */ (await fixture(html` <a11y-input></a11y-input> `));
|
||||
expect(el.shadowRoot.innerHTML).to.equal(`
|
||||
<slot name="label"></slot>
|
||||
<slot name="input"></slot>
|
||||
`);
|
||||
});
|
||||
```
|
||||
|
||||
As expected, we get:
|
||||
|
||||
```
|
||||
✖ has a static shadowDom
|
||||
AssertionError: expected '' to equal '\n <slot name="label"></slot>\n <slot name="input"></slot>\n '
|
||||
|
||||
+ expected - actual
|
||||
|
||||
+
|
||||
+ <slot name="label"></slot>
|
||||
+ <slot name="input"></slot>
|
||||
+
|
||||
```
|
||||
|
||||
So let's implement that in our element.
|
||||
|
||||
```js
|
||||
render() {
|
||||
return html`
|
||||
<slot name="label"></slot>
|
||||
<slot name="input"></slot>
|
||||
`;
|
||||
}
|
||||
```
|
||||
|
||||
Interesting, the test should be green... but it's not :thinking: Let's take a look.
|
||||
|
||||
```
|
||||
✖ has a static shadowDom
|
||||
AssertionError: expected '<!---->\n <slot name="label"></slot>\n <slot name="input"></slot>\n <!---->' to equal '\n <slot name="label"></slot>\n <slot name="input"></slot>\n '
|
||||
|
||||
+ expected - actual
|
||||
|
||||
-<!---->
|
||||
- <slot name="label"></slot>
|
||||
- <slot name="input"></slot>
|
||||
- <!---->
|
||||
+
|
||||
+ <slot name="label"></slot>
|
||||
+ <slot name="input"></slot>
|
||||
+
|
||||
```
|
||||
|
||||
You may have noticed those weird empty comment `<!---->` tags. They are markers that `lit-html` uses to remember where dynamic parts are, so it can be updated efficiently. For testing, however, this can be a little annoying to deal with.
|
||||
|
||||
If we use `innerHTML` to compare the DOM, we'd have to rely on simple string equality. Under those circumstances, we'd have to exactly match the generated DOM's whitespace, comments, etc; in other words: it will need to be a perfect match. Really all we need to test is that the elements we want to render are rendered. We want to test the _semantic_ contents of the shadow root.
|
||||
|
||||
Fortunately, we've got you covered. If you're using `@open-wc/testing` then it automatically loads the `@open-wc/semantic-dom-diff` chai plugin for us to use.
|
||||
|
||||
So let's try it out :muscle:
|
||||
|
||||
```js
|
||||
// old:
|
||||
expect(el.shadowRoot.innerHTML).to.equal(`...`);
|
||||
|
||||
// new:
|
||||
expect(el).shadowDom.to.equal(`
|
||||
<slot name="label"></slot>
|
||||
<slot name="input"></slot>
|
||||
`);
|
||||
```
|
||||
|
||||
Bam :tada:
|
||||
|
||||
```
|
||||
a11y input
|
||||
✔ has by default an empty string as a label
|
||||
✔ has a static shadowDom
|
||||
```
|
||||
|
||||
### How does shadowDom.to.equal() work?
|
||||
|
||||
1. It gets the `innerHTML` of the shadow root
|
||||
2. Parses it (actually, the browser parses it - no library needed)
|
||||
3. Normalizes it (potentially every tag/property on its own line)
|
||||
4. Parses and normalizes the expected HTML string
|
||||
5. Passes both normalized DOM strings on to chai's default compare function
|
||||
6. In case of failure, groups, and displays any differences in a clear manner
|
||||
|
||||
If you want to know more, please check out the documentation of [semantic-dom-diff](https://open-wc.org/testing/semantic-dom-diff.html).
|
||||
|
||||
### Testing the "Light" DOM
|
||||
|
||||
We can do exactly the same thing with the light DOM. (The DOM which will be provided by our user or our defaults, i.e. the element's `children`).
|
||||
|
||||
```js
|
||||
it('has 1 input and 1 label in light-dom', async () => {
|
||||
const el = /** @type {A11yInput} */ (await fixture(html`
|
||||
<a11y-input .label=${'foo'}></a11y-input>
|
||||
`));
|
||||
expect(el).lightDom.to.equal(`
|
||||
<label slot="label">foo</label>
|
||||
<input slot="input">
|
||||
`);
|
||||
});
|
||||
```
|
||||
|
||||
And let's implement it.
|
||||
|
||||
```js
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.labelEl = document.createElement('label');
|
||||
this.labelEl.innerText = this.label;
|
||||
this.labelEl.setAttribute('slot', 'label');
|
||||
this.appendChild(this.labelEl);
|
||||
|
||||
this.inputEl = document.createElement('input');
|
||||
this.inputEl.setAttribute('slot', 'input');
|
||||
this.appendChild(this.inputEl);
|
||||
}
|
||||
```
|
||||
|
||||
So we tested our light and shadow dom :muscle: and our tests run clean :tada:
|
||||
|
||||
> Note: Using the DOM API in a lit-element's lifecycle is an anti-pattern, however to allow for <abbr title="accessibility">a11y</abbr> it might be a real use case - anyways it's great for illustration purposes
|
||||
|
||||
### Using Our Element in an App
|
||||
|
||||
So now that we have a basic a11y input let's use it - and test it - in our application.
|
||||
|
||||
Again we start with a skeleton `src/my-app.js`
|
||||
|
||||
```js
|
||||
/* eslint-disable class-methods-use-this */
|
||||
import { LitElement, html, css } from 'lit-element';
|
||||
|
||||
export class MyApp extends LitElement {}
|
||||
|
||||
customElements.define('my-app', MyApp);
|
||||
```
|
||||
|
||||
And our test in `test/my-app.test.js`;
|
||||
|
||||
```js
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import { html, fixture, expect } from '@open-wc/testing';
|
||||
|
||||
import '../src/my-app.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../src/my-app.js').MyApp} MyApp
|
||||
*/
|
||||
|
||||
describe('My Filter App', () => {
|
||||
it('has a heading and a search field', async () => {
|
||||
const el = /** @type {MyApp} */ (await fixture(html` <my-app .label=${'foo'}></my-app> `));
|
||||
expect(el).shadowDom.to.equal(`
|
||||
<h1>My Filter App</h1>
|
||||
<a11y-input></a11y-input>
|
||||
`);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Run the test => fails and then we add the implementation to `src/a11y-input.js`
|
||||
|
||||
```js
|
||||
render() {
|
||||
return html`
|
||||
<h1>My Filter App</h1>
|
||||
<a11y-input></a11y-input>
|
||||
`;
|
||||
}
|
||||
```
|
||||
|
||||
But oh no! That should be green now...
|
||||
|
||||
```
|
||||
SUMMARY:
|
||||
✔ 3 tests completed
|
||||
✖ 1 test failed
|
||||
|
||||
FAILED TESTS:
|
||||
My Filter App
|
||||
✖ has a heading and a search field
|
||||
AssertionError: expected '<h1>\n My Filter App\n</h1>\n<a11y-input>\n <label slot="label">\n </label>\n <input slot="input">\n</a11y-input>\n' to equal '<h1>\n My Filter App\n</h1>\n<a11y-input>\n</a11y-input>\n'
|
||||
|
||||
+ expected - actual
|
||||
|
||||
<h1>
|
||||
My Filter App
|
||||
</h1>
|
||||
<a11y-input>
|
||||
- <label slot="label">
|
||||
- </label>
|
||||
- <input slot="input">
|
||||
</a11y-input>
|
||||
```
|
||||
|
||||
What is happening?
|
||||
Do you remember that we had a specific test to ensure the light-dom of a11y-input?
|
||||
So even if the users only puts `<a11y-input></a11y-input>` in his code - what actually comes out is
|
||||
|
||||
```html
|
||||
<a11y-input>
|
||||
<label slot="label"></label>
|
||||
<input slot="input" />
|
||||
</a11y-input>
|
||||
```
|
||||
|
||||
e.g. `a11y-input` is actually creating nodes inside of your `my-app` shadow dom. Preposterous! For our example here we say that's what we want.
|
||||
So how can we still test it?
|
||||
|
||||
Luckily `.shadowDom` has another ace up its sleeve; it allows us to ignore parts of dom.
|
||||
|
||||
```js
|
||||
expect(el).shadowDom.to.equal(
|
||||
`
|
||||
<h1>My Filter App</h1>
|
||||
<a11y-input></a11y-input>
|
||||
`,
|
||||
{ ignoreChildren: ['a11y-input'] },
|
||||
);
|
||||
```
|
||||
|
||||
We can even specify the following properties as well:
|
||||
|
||||
- `ignoreChildren`
|
||||
- `ignoreTags`
|
||||
- `ignoreAttributes` (globally or for specific tags)
|
||||
|
||||
For more details please see [semantic-dom-diff](https://open-wc.org/testing/semantic-dom-diff.html).
|
||||
|
||||
#### Snapshot testing
|
||||
|
||||
If you have a lot of big dom trees writing/maintaining all those manually written expects is going to be tough.
|
||||
To help you with that there are semi/automatic snapshots.
|
||||
|
||||
So if we change our code
|
||||
|
||||
```js
|
||||
// from
|
||||
expect(el).shadowDom.to.equal(`
|
||||
<slot name="label"></slot>
|
||||
<slot name="input"></slot>
|
||||
`);
|
||||
|
||||
// to
|
||||
expect(el).shadowDom.to.equalSnapshot();
|
||||
```
|
||||
|
||||
If we now execute `npm run test` it will create a file `__snapshots__/a11y input.md` and fill it with something like this
|
||||
|
||||
````
|
||||
## `a11y input`
|
||||
|
||||
#### `has a static shadowDom`
|
||||
|
||||
```html
|
||||
<slot name="label">
|
||||
</slot>
|
||||
<slot name="input">
|
||||
</slot>
|
||||
````
|
||||
|
||||
````
|
||||
|
||||
What we wrote before by hand can now be auto-generated on init or forcefully via `npm run test:update-snapshots`.
|
||||
|
||||
If the file `__snapshots__/a11y input.md` already exists it will compare it with the output and you will get errors if your html output changed.
|
||||
|
||||
```
|
||||
FAILED TESTS:
|
||||
a11y input
|
||||
✖ has a static shadowDom
|
||||
HeadlessChrome 73.0.3683 (Windows 10.0.0)
|
||||
AssertionError: Received value does not match stored snapshot 0
|
||||
|
||||
+ expected - actual
|
||||
|
||||
-<slot name="label-wrong">
|
||||
+<slot name="label">
|
||||
</slot>
|
||||
<slot name="input">
|
||||
-</slot>
|
||||
+</slot>
|
||||
```
|
||||
|
||||
For more details please see [semantic-dom-diff](https://open-wc.org/testing/semantic-dom-diff.html).
|
||||
|
||||
I think that's now enough about comparing dom trees...
|
||||
It's time for a change :hugs:
|
||||
|
||||
### Code coverage
|
||||
|
||||
Another useful metric we get when testing with the open-wc setup is code coverage.
|
||||
So what does it mean and how can we get it? [Code coverage](https://www.wikiwand.com/en/Code_coverage) is a measure of *how much* of our code is checked by tests. If there's a line, statement, function, or branch (e.g. `if`/`else` statement) that our tests don't cover our coverage score will be affected.
|
||||
A simple `npm run test` is all we need and you will get the following:
|
||||
|
||||
```
|
||||
=============================== Coverage summary ===============================
|
||||
Statements : 100% ( 15/15 )
|
||||
Branches : 100% ( 0/0 )
|
||||
Functions : 100% ( 5/5 )
|
||||
Lines : 100% ( 15/15 )
|
||||
================================================================================
|
||||
```
|
||||
|
||||
Which means that 100% of our code's statements, branches, functions, and lines are covered by tests. Pretty neat!
|
||||
|
||||
So let's go the other way and add code to `src/a11y-input.js` before adding a test. Let's say we want to access the value of our input directly via our custom element and whenever its value is 'cat' we want to log something.
|
||||
|
||||
```js
|
||||
get value() {
|
||||
return this.inputEl.value;
|
||||
}
|
||||
|
||||
set value(newValue) {
|
||||
if (newValue === 'cat') {
|
||||
console.log('We like cats too :)');
|
||||
}
|
||||
this.inputEl.value = newValue;
|
||||
}
|
||||
```
|
||||
|
||||
It's a vastly different result
|
||||
```
|
||||
SUMMARY:
|
||||
✔ 4 tests completed
|
||||
TOTAL: 4 SUCCESS
|
||||
|
||||
=============================== Coverage summary ===============================
|
||||
Statements : 81.82% ( 18/22 )
|
||||
Branches : 0% ( 0/2 )
|
||||
Functions : 75% ( 6/8 )
|
||||
Lines : 81.82% ( 18/22 )
|
||||
================================================================================
|
||||
06 04 2019 10:40:45.380:ERROR [reporter.coverage-istanbul]: Coverage for statements (81.82%) does not meet global threshold (90%)
|
||||
06 04 2019 10:40:45.381:ERROR [reporter.coverage-istanbul]: Coverage for lines (81.82%) does not meet global threshold (90%)
|
||||
06 04 2019 10:40:45.381:ERROR [reporter.coverage-istanbul]: Coverage for branches (0%) does not meet global threshold (90%)
|
||||
06 04 2019 10:40:45.381:ERROR [reporter.coverage-istanbul]: Coverage for functions (75%) does not meet global threshold (90%)
|
||||
```
|
||||
|
||||
Our coverage is way lower than before. Our test command even fails, even though all the tests run successfully.
|
||||
This is because by default open-wc's config sets a 90% threshold for code coverage.
|
||||
|
||||
If we want to improve coverage we need to add tests - so let's do it
|
||||
```js
|
||||
it('can set/get the input value directly via the custom element', async () => {
|
||||
const el = /** @type {A11yInput} */ (await fixture(html`
|
||||
<a11y-input .value=${'foo'}></a11y-input>
|
||||
`));
|
||||
expect(el.value).to.equal('foo');
|
||||
});
|
||||
```
|
||||
|
||||
uh oh :scream: we wanted to improve coverage but now we need fix an actual bug first :disappointed:
|
||||
```
|
||||
FAILED TESTS:
|
||||
a11y input
|
||||
✖ can set/get the input value directly via the custom element
|
||||
TypeError: Cannot set property 'value' of null at HTMLElement.set value [as value]
|
||||
// ... => long error stack
|
||||
```
|
||||
|
||||
That was unexpected... on first glance, I don't really know what that means... better to check some actual nodes and inspect them in the browser.
|
||||
|
||||
### Debugging in the Browser
|
||||
|
||||
When we run our test with watch, karma sets up a persistent browser environment to run tests in.
|
||||
|
||||
- Be sure you started with `npm run test:watch`
|
||||
- visit [http://localhost:9876/debug.html](http://localhost:9876/debug.html)
|
||||
|
||||
|
||||
You should see something like this
|
||||

|
||||
|
||||
You can click on the circled play button to only run one individual test.
|
||||
|
||||
So let's open the Chrome Dev Tools (F12) and put a debugger in the test code.
|
||||
|
||||
```js
|
||||
it('can set/get the input value directly via the custom element', async () => {
|
||||
const el = /** @type {A11yInput} */ (await fixture(html`
|
||||
<a11y-input .value=${'foo'}></a11y-input>
|
||||
`));
|
||||
debugger;
|
||||
expect(el.value).to.equal('foo');
|
||||
});
|
||||
```
|
||||
|
||||
Dang.. the error happens even before that point...
|
||||
"Fatal" errors like this are a little tougher as they are not failing tests but sort of a complete meltdown of your full component.
|
||||
|
||||
Ok, let's put some code in the `setter` directly.
|
||||
|
||||
```js
|
||||
set value(newValue) {
|
||||
debugger;
|
||||
```
|
||||
|
||||
Alright, that worked so our chrome console we write `console.log(this)` let's see what we have here
|
||||
```js
|
||||
<a11y-input>
|
||||
#shadow-root (open)
|
||||
</a11y-input>
|
||||
```
|
||||
|
||||
Ahh there we have it - the shadow dom is not yet rendered when the setter is called.
|
||||
So let's be safe and add a check before
|
||||
|
||||
```js
|
||||
set value(newValue) {
|
||||
if (newValue === 'cat') {
|
||||
console.log('We like cats too :)');
|
||||
}
|
||||
if (this.inputEl) {
|
||||
this.inputEl.value = newValue;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Fatel error is gone :tada:
|
||||
But we now have a failing test :sob:
|
||||
|
||||
```
|
||||
✖ can set/get the input value directly via the custom element
|
||||
AssertionError: expected '' to equal 'foo'
|
||||
```
|
||||
|
||||
We may need a change of tactic :thinking:
|
||||
We can add it as a separate `value` property and sync when needed.
|
||||
|
||||
```js
|
||||
static get properties() {
|
||||
return {
|
||||
label: { type: String },
|
||||
value: { type: String },
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.label = '';
|
||||
this.value = '';
|
||||
// ...
|
||||
}
|
||||
|
||||
update(changedProperties) {
|
||||
super.update(changedProperties);
|
||||
if (changedProperties.has('value')) {
|
||||
if (this.value === 'cat') {
|
||||
console.log('We like cats too :)');
|
||||
}
|
||||
this.inputEl.value = this.value;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And we're finally back in business! :tada:
|
||||
|
||||
ok bug fixed - can we please get back to coverage? thank you :pray:
|
||||
|
||||
### Back to coverage
|
||||
|
||||
With this added test we made some progress.
|
||||
|
||||
```
|
||||
=============================== Coverage summary ===============================
|
||||
Statements : 95.83% ( 23/24 )
|
||||
Branches : 50% ( 2/4 )
|
||||
Functions : 100% ( 7/7 )
|
||||
Lines : 95.83% ( 23/24 )
|
||||
================================================================================
|
||||
06 04 2019 13:18:54.902:ERROR [reporter.coverage-istanbul]: Coverage for branches (50%) does not meet global threshold (90%)
|
||||
```
|
||||
|
||||
However we are still not fully there - the question is why?
|
||||
|
||||
To find out open `coverage/index.html` in your browser. No web server needed just open the file in your browser - on a mac you can do that from the command line with `open coverage/index.html`
|
||||
|
||||
You will see something like this
|
||||
|
||||

|
||||
|
||||
Once you click on `a11y-input.js` you get a line by line info how often they got executed.
|
||||
So we can immediately see which lines are not executed yet by our tests.
|
||||
|
||||

|
||||
|
||||
So let's add a test for that
|
||||
```js
|
||||
it('logs "We like cats too :)" if the value is "cat"', async () => {
|
||||
const el = /** @type {A11yInput} */ (await fixture(html`
|
||||
<a11y-input .value=${'cat'}></a11y-input>
|
||||
`));
|
||||
// somehow check that console.log was called
|
||||
});
|
||||
```
|
||||
|
||||
```
|
||||
=============================== Coverage summary ===============================
|
||||
Statements : 100% ( 24/24 )
|
||||
Branches : 75% ( 3/4 )
|
||||
Functions : 100% ( 7/7 )
|
||||
Lines : 100% ( 24/24 )
|
||||
================================================================================
|
||||
```
|
||||
|
||||
With that, we are back at 100% on statements but we still have something missing on branches.
|
||||
Let's see why?
|
||||
|
||||

|
||||
|
||||
This `E` means `else path not taken`.
|
||||
So whenever the function `update` gets called there is always a property `value` in the changedProperties.
|
||||
|
||||
We have `label` as well so it's a good idea to test it. :+1:
|
||||
|
||||
```js
|
||||
it('can update its label', async () => {
|
||||
const el = /** @type {A11yInput} */ (await fixture('<a11y-input label="foo"></a11y-input>'));
|
||||
expect(el.label).to.equal('foo');
|
||||
el.label = 'bar';
|
||||
expect(el.label).to.equal('bar');
|
||||
});
|
||||
```
|
||||
|
||||
boom 100% :muscle: we win :1st_place_medal:
|
||||
|
||||
```
|
||||
=============================== Coverage summary ===============================
|
||||
Statements : 100% ( 24/24 )
|
||||
Branches : 100% ( 4/4 )
|
||||
Functions : 100% ( 7/7 )
|
||||
Lines : 100% ( 24/24 )
|
||||
================================================================================
|
||||
```
|
||||
|
||||
But wait we didn't even finish the test above - the code is still
|
||||
```js
|
||||
// somehow check that console.log was called
|
||||
```
|
||||
|
||||
#### How come we have 100% test coverage?
|
||||
|
||||
Lets first try to understand how code coverage works :thinking:
|
||||
The way code coverage gets measured is by applying a form of `instrumentation`. In short, before our code is executed it gets changed (`instrumented`) and it behaves something like this:
|
||||
|
||||
**Note:** This is a super simplified version for illustration purposes.
|
||||
```js
|
||||
if (this.value === 'cat') {
|
||||
console.log('We like cats too :)');
|
||||
}
|
||||
|
||||
// becomes something like this (psoido code)
|
||||
__instrumented['functionUpdate'] += 1;
|
||||
if (this.value === 'cat') {
|
||||
__instrumented['functionUpdateBranch1yes'] += 1;
|
||||
console.log('We like cats too :)');
|
||||
} else {
|
||||
__instrumented['functionUpdateBranch1no'] += 1;
|
||||
}
|
||||
```
|
||||
|
||||
Basically, your code gets littered with many many flags. Based on which flags get trigger a statistic gets created.
|
||||
|
||||
So 100% test coverage only means that every line you have in your code was executed at least once after all your tests finished. It does *not* mean that you tested everything, or if your tests make the correct assertions.
|
||||
|
||||
So even though we already have 100% code coverage we are still going to improve our log test.
|
||||
|
||||
You should, therefore, see code coverage as a tool that only gives you guidance and help on spotting some missing tests, rather than a hard guarantee of code quality.
|
||||
|
||||
### Spying on Code
|
||||
|
||||
If you want to check how often or with which parameters a function gets called, that's called spying.
|
||||
open-wc recommends the venerable [sinon](https://sinonjs.org/) package, which provides many tools for spying and other related tasks.
|
||||
|
||||
```bash
|
||||
npm i -D sinon
|
||||
```
|
||||
|
||||
So you create a spy on a specific object and then you can check how often it gets called.
|
||||
|
||||
```js
|
||||
import sinon from 'sinon';
|
||||
|
||||
it('outputs "We like cats too :)" if the value is set to "cat"', async () => {
|
||||
const logSpy = sinon.spy(console, 'log');
|
||||
const el = /** @type {A11yInput} */ (await fixture(html`
|
||||
<a11y-input></a11y-input>
|
||||
`));
|
||||
|
||||
el.value = 'cat';
|
||||
expect(logSpy.callCount).to.equal(1);
|
||||
});
|
||||
```
|
||||
|
||||
Uh oh... the test fails:
|
||||
```
|
||||
AssertionError: expected 0 to equal 1
|
||||
```
|
||||
|
||||
Messing with global objects like `console` might have [side effects](https://gyandeeps.com/console-stubbing/) so let's better refactor using a dedicated log function.
|
||||
|
||||
```js
|
||||
update(changedProperties) {
|
||||
super.update(changedProperties);
|
||||
if (changedProperties.has('value')) {
|
||||
if (this.value === 'cat') {
|
||||
this.log('We like cats too :)');
|
||||
}
|
||||
this.inputEl.value = this.value;
|
||||
}
|
||||
}
|
||||
|
||||
log(msg) {
|
||||
console.log(msg);
|
||||
}
|
||||
```
|
||||
|
||||
This result in no global object in our test code - sweet :hugs:
|
||||
|
||||
```js
|
||||
it('logs "We like cats too :)" if the value is set to "cat"', async () => {
|
||||
const el = /** @type {A11yInput} */ (await fixture(html`
|
||||
<a11y-input></a11y-input>
|
||||
`));
|
||||
const logSpy = sinon.spy(el, 'log');
|
||||
|
||||
el.value = 'cat';
|
||||
expect(logSpy.callCount).to.equal(1);
|
||||
});
|
||||
```
|
||||
|
||||
However, we still get the same error. Let's debug... boohoo apparently `update` is not sync - a wrong assumption I made :see_no_evil: I am saying *assumptions are dangerous* quite often - still I fall for it from time to time :cry:.
|
||||
|
||||
So what can we do? Sadly there seems to be no public api to do some sync actions triggered by an property update.
|
||||
Let's create an issue for it https://github.com/Polymer/lit-element/issues/643.
|
||||
|
||||
For now apparently, the only way is to rely on a *private* api. :see_no_evil:
|
||||
Also, we needed to move the value sync to `updated` so it gets executed after every dom render.
|
||||
|
||||
```js
|
||||
_requestUpdate(name, oldValue) {
|
||||
super._requestUpdate(name, oldValue);
|
||||
if (name === 'value') {
|
||||
if (this.value === 'cat') {
|
||||
this.log('We like cats too :)');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updated(changedProperties) {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has('value')) {
|
||||
this.inputEl.value = this.value;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
and here is the updated test for the logging
|
||||
```js
|
||||
it('logs "We like cats too :)" if the value is set to "cat"', async () => {
|
||||
const el = /** @type {A11yInput} */ (await fixture(html`
|
||||
<a11y-input></a11y-input>
|
||||
`));
|
||||
const logSpy = sinon.spy(el, 'log');
|
||||
|
||||
el.value = 'cat';
|
||||
expect(logSpy.callCount).to.equal(1);
|
||||
expect(logSpy.calledWith('We like cats too :)')).to.be.true;
|
||||
|
||||
// different values do NOT log
|
||||
el.value = 'foo';
|
||||
expect(logSpy.callCount).to.equal(1);
|
||||
|
||||
el.value = 'cat';
|
||||
expect(logSpy.callCount).to.equal(2);
|
||||
});
|
||||
```
|
||||
|
||||
wow, that was a little tougher than expected but we did it :muscle:
|
||||
|
||||
```
|
||||
SUMMARY:
|
||||
✔ 7 tests completed
|
||||
TOTAL: 7 SUCCESS
|
||||
```
|
||||
|
||||
### Running Tests Without Karma Framework
|
||||
|
||||
The Karma framework is powerful and feature-rich, but sometimes we might want to pare-down our testing regiment. The nice thing with everything we proposed so far is that we only used browser-standard es modules with no need for transpilation, [with the one exception of bare modules specifiers](https://open-wc.org/about/rationales.html#workflows).
|
||||
So just by creating a `test/index.html`.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link href="../node_modules/mocha/mocha.css" rel="stylesheet" />
|
||||
<script src="../node_modules/mocha/mocha.js"></script>
|
||||
<script src="../node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
<script>
|
||||
mocha.setup('bdd');
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
import './a11y-input.test.js';
|
||||
import './my-app.test.js';
|
||||
|
||||
mocha.checkLeaks();
|
||||
mocha.run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
and opening it via `owc-dev-server` in chrome, it will work perfectly fine.
|
||||
We got everything up and running without `webpack` or `karma` - sweet :hugs:
|
||||
|
||||
### Do the Cross-Browser Thing
|
||||
|
||||
We now feel pretty comfortable with our web component. It's tested and covered; there's just one more step - we want to make sure it runs and is tested in all browsers.
|
||||
|
||||
Open WC recommends [Browserstack](https://www.browserstack.com/) for cross-browser testing. If you haven't set it up yet, you can do it now - here is the link again - [https://open-wc.org/testing/](https://open-wc.org/testing/).
|
||||
|
||||
So let's just run it
|
||||
```
|
||||
npm run test:bs
|
||||
|
||||
SUMMARY:
|
||||
✔ 42 tests completed
|
||||
TOTAL: 42 SUCCESS
|
||||
```
|
||||
|
||||
Yeah, that works nicely! :hugs:
|
||||
|
||||
If there are failing tests it will output them in the summary with the specific browser where it failed.
|
||||
```
|
||||
SUMMARY:
|
||||
✔ 40 tests completed
|
||||
✖ 2 tests failed
|
||||
|
||||
FAILED TESTS:
|
||||
a11y input
|
||||
✖ has a static shadowDom
|
||||
Firefox 64.0.0 (Windows 10.0.0)
|
||||
Safari 12.0.0 (Mac OS X 10.14.0)
|
||||
expected '<slot name="label">\n</slot>\n<slot name="input">\n</slot>\n<style>\n</style>\n' to equal '<slot name="label">\n</slot>\n<slot name="input">\n</slot>\n'
|
||||
|
||||
+ expected - actual
|
||||
|
||||
<slot name="label">
|
||||
</slot>
|
||||
<slot name="input">
|
||||
</slot>
|
||||
-<style>
|
||||
-</style>
|
||||
```
|
||||
|
||||
If you need to debug a particular browser:
|
||||
- `npm run test:legacy:watch`
|
||||
- visit [http://localhost:9876/debug.html](http://localhost:9876/debug.html) with that browser (either it locally or via browserstack)
|
||||
- select a specific test (or use `it.only()` in code)
|
||||
- start debugging
|
||||
|
||||
Also if you want to adjust the browser that gets tested you can adjust your `karma.bs.config.js`.
|
||||
|
||||
For example, if you want to add the `Firefox ESR` to your list.
|
||||
|
||||
```js
|
||||
module.exports = config => {
|
||||
config.set(
|
||||
merge(bsSettings(config), createBaseConfig(config), {
|
||||
browserStack: {
|
||||
project: 'testing-workflow-for-web-components',
|
||||
},
|
||||
browsers: [
|
||||
'bs_win10_firefox_ESR',
|
||||
],
|
||||
// define browsers
|
||||
// https://www.browserstack.com/automate/capabilities
|
||||
customLaunchers: {
|
||||
bs_win10_firefox_ESR: {
|
||||
base: 'BrowserStack',
|
||||
browser: 'Firefox',
|
||||
browser_version: '60',
|
||||
os: 'Windows',
|
||||
os_version: '10',
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
return config;
|
||||
};
|
||||
```
|
||||
|
||||
Or maybe you want to test only 2 specific browsers?
|
||||
|
||||
```js
|
||||
merge.strategy({
|
||||
browsers: 'replace',
|
||||
})(bsSettings(config), createBaseConfig(config), {
|
||||
browserStack: {
|
||||
project: 'testing-workflow-for-web-components',
|
||||
},
|
||||
browsers: [
|
||||
'bs_win10_ie_11',
|
||||
'bs_win10_firefox_ESR',
|
||||
],
|
||||
}),
|
||||
```
|
||||
|
||||
**Note:** This uses the [webpack merge strategies](https://github.com/survivejs/webpack-merge#merging-with-strategies) replace.
|
||||
|
||||
## Quick Recap
|
||||
- Testing is important for every project. Be sure to write as many as you can.
|
||||
- Try to keep your code coverage high, but remember it's not a magic guarantee, so it doesn't always need to be 100%.
|
||||
- Debug in the browser via `npm run test:watch`. For legacy browsers, use `npm run test:legacy.watch`.
|
||||
|
||||
## What's Next?
|
||||
- Run the tests in your CI (works perfectly well together with browserstack). See our recommendations at [automating](https://open-wc.org/automating/).
|
||||
|
||||
Follow us on [Twitter](https://twitter.com/openwc), or follow me on my personal [Twitter](https://twitter.com/dakmor).
|
||||
Make sure to check out our other tools and recommendations at [open-wc.org](https://open-wc.org).
|
||||
|
||||
Thanks to [Pascal](https://dev.to/thepassle) and [Benny](https://dev.to/bennypowers) for feedback and helping turn my scribbles to a followable story.
|
||||
````
|
||||
BIN
docs/blog/the-all-new-open-web-components/images/blog-header.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
docs/blog/the-all-new-open-web-components/images/media-image.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
83
docs/blog/the-all-new-open-web-components/index.md
Normal file
@@ -0,0 +1,83 @@
|
||||
---
|
||||
title: 'The all new Open Web Components'
|
||||
pageTitle: 'The all new Open Web Components'
|
||||
date: 2020-10-29
|
||||
published: true
|
||||
description: 'A lot has happend in Open Web Components - new website, repo cleanup, change of setup and we joined Modern Web'
|
||||
tags: [webcomponents, open-wc, javascript]
|
||||
canonical_url: https://open-wc.org/blog/the-all-new-open-web-components/
|
||||
cover_image: /blog/the-all-new-open-web-components/images/blog-header.jpg
|
||||
socialMediaImage: /blog/the-all-new-open-web-components/images/social-media-image.jpg
|
||||
---
|
||||
|
||||
It has been an incredible busy year for Open Web Components. Lots has happened behind the scenes and there is still more to come.
|
||||
|
||||
Let's talk about the obvious first.
|
||||
|
||||
## The all-new Open Web Components website
|
||||
|
||||
As you may have noticed we completely restructured all our content and there is now a clear distinction between Guides and Documentation.
|
||||
|
||||
In [Guides](../../guides/index.md) we focus more on followable step by step explanations where as [Documentation](../../docs/index.md) is meant as a reference book if you need to look up which options are available or what they are doing.
|
||||
|
||||
In Guides you can find our current best sellers like [Codelabs](../../guides/developing-components/codelabs.md), [Code Examples](../../guides/developing-components/code-examples.md) or [Publishing](../../guides/developing-components/publishing.md). However we also added a complete new [Community](../../guides/community/getting-started.md) section which shows communities you can join or Base Libraries and Component Libraries you should check out.
|
||||
|
||||
Additionally we now share web components knowledge like how [attributes and properties](../../guides/knowledge/attributes-and-properties.md) or [events](../../guides/knowledge/events.md) work.
|
||||
|
||||
Technically the new website is using [eleventy](11ty.dev), [rollup](https://rollupjs.org/) and our own tools like Web Dev Server and Mdjs.
|
||||
|
||||
## Cleanup our repo
|
||||
|
||||
Over the last years we have created different projects and recommendations. During this time certain projects have become deprecated as we moved on to different tools or approaches.
|
||||
|
||||
This doesn't mean that we've completely dropped support for these projects. While we don't feature them on the main website, we still maintain and support these projects. We don't develop any new features or functionalities, but we will continue to support bugfixes and in some cases update along with the dependent tooling.
|
||||
|
||||
The documentation for our legacy projects is maintained in the github readmes:
|
||||
|
||||
### Legacy projects
|
||||
|
||||
- [es-dev-server](https://github.com/open-wc/es-dev-server) is now replaced by [web-dev-server](https://modern-web.dev/docs/dev-server/overview/)
|
||||
- [testing-karma](https://github.com/open-wc/legacy/tree/master/packages/testing-karma) and [karma-esm](https://github.com/open-wc/legacy/tree/master/packages/karma-esm) we now recommend [web-test-runner](https://modern-web.dev/docs/test-runner/overview/)
|
||||
- [testing-karma-bs](https://github.com/open-wc/legacy/tree/master/packages/testing-karma-bs) we now recommend [web-test-runner](https://modern-web.dev/docs/test-runner/overview/) & [@web/test-runner-browserstack](https://modern-web.dev/docs/test-runner/browser-launchers/browserstack/)
|
||||
- [rollup-plugin-index-html](https://github.com/open-wc/legacy/tree/master/packages/rollup-plugin-index-html) we now recommend `@web/rollup-plugin-html`
|
||||
- [webpack-import-meta-loader](https://github.com/open-wc/legacy/tree/master/packages/webpack-import-meta-loader) we now recommend [babel-plugin-bundled-import-meta](https://www.npmjs.com/package/babel-plugin-bundled-import-meta)
|
||||
- [building-webpack](https://github.com/open-wc/legacy/tree/master/packages/building-webpack) we now recommend rollup over webpack
|
||||
- [webpack-index-html-plugin](https://github.com/open-wc/legacy/tree/master/packages/webpack-index-html-plugin) we now recommend rollup over webpack
|
||||
- [storybook-addon-web-components-knobs](https://github.com/open-wc/legacy/tree/master/packages/storybook-addon-web-components-knobs) storybook v6 has a new better knobs system
|
||||
|
||||
We also move out our [create](https://github.com/open-wc/create) Generators into a dedicated repository - which is only our first step as we will later automate updating it's dependencies via a bot so you can always be sure you get the latest versions.
|
||||
|
||||
## Change our setup
|
||||
|
||||
On top of moving out all "dusty" code we additionally change our setup.
|
||||
|
||||
We are now using [changesets](https://github.com/atlassian/changesets) to gives us more control about what gets released and how.
|
||||
With [github actions](https://github.com/features/actions) we run our tests on multiple node versions (12 & 14) and windows at the same time so we can make sure our tools don't break.
|
||||
|
||||
Additionally our web testing is now performed by [Web Test Runner](https://modern-web.dev/docs/test-runner/overview/) which runs web tests in all evergreen browsers within a github action.
|
||||
|
||||
## Return focus to Web Components
|
||||
|
||||
With all those general web development packages moved to [Modern Web](https://modern-web.dev) and all those legacy packages move out of the repo we can bring our focus back to web components.
|
||||
|
||||
You will see more web component specific guides and tools coming up.
|
||||
|
||||
One of those is our just recently released [eslint-plugin-lit-a11y](../../docs/linting/eslint-plugin-lit-a11y/overview.md). It features more then 20 rules that will help you write more accessible lit-html templates.
|
||||
|
||||
## Issues and discussions
|
||||
|
||||
We do have a fair share of open issues which makes it sometimes hard to see/understand what are actual bugs/issues and what are feature request or questions. Additionally with all these package some issues probably are not relevant anymore. We plan to clean this up in the upcoming weeks by
|
||||
|
||||
1. Moving issues to the appropriate repository (if it's code got moved)
|
||||
2. Moving feature requests into [github discussions](https://github.com/open-wc/open-wc/discussions)
|
||||
3. This will leave only actual bugs in our [issue list](https://github.com/open-wc/open-wc/issues) 💪
|
||||
|
||||
We do hope this will make navigating out Github Page easier.
|
||||
|
||||
## Joining Modern Web
|
||||
|
||||
This now also makes it more apparent that we are part of the [Modern Web Family](https://modern-web.dev/discover/about/).
|
||||
|
||||
So be sure to follow Modern Web on [Twitter](https://twitter.com/modern_web_dev) and if you like what you see please consider sponsoring the project on [Open Collective](https://opencollective.com/modern-web).
|
||||
|
||||
Written with ♥️ by the Open Web Components Core Team
|
||||
9
docs/browserconfig.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/mstile-150x150.png"/>
|
||||
<TileColor>#9b03fe</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
@@ -1,41 +0,0 @@
|
||||
---
|
||||
permalink: 'building/index.html'
|
||||
title: Building
|
||||
section: guides
|
||||
tags:
|
||||
- guides
|
||||
---
|
||||
|
||||
# Building
|
||||
|
||||
Building is a necessary optimization when shipping apps to production. By using a build you reduce the total size and amount of files transferred to the end-user, and you ensure your code runs on all supported browsers.
|
||||
|
||||
We recommend doing most of the building only in projects which deploy the final result to production, such as apps or websites. This is where you can make the best decisions about supported browsers and optimizations.
|
||||
|
||||
## Build types
|
||||
|
||||
Depending on the type of project, we recommend different approaches to building.
|
||||
|
||||
### Building single page apps (SPA)
|
||||
|
||||
If you are building a single page application we recommend [rollup](https://rollupjs.org/guide/en/) to build your app.
|
||||
|
||||
Take a look at our dedicated [building-rollup](/building/building-rollup.html) page which explains how to set up rollup. We ship a default config that you can use to set up your project, or you can use it as inspiration for your custom config.
|
||||
|
||||
### Building reusable components and libraries
|
||||
|
||||
If you are building a reusable component or library we recommend publishing code that runs without modifications on the latest browsers. You should not bundle in any dependencies or polyfills, or build to a very old version of JavaScript such as ES5. This way, consuming projects can decide which polyfills to load and which JavaScript version to target based on browser support.
|
||||
|
||||
In practical terms, this means publishing standard ES modules and standard JavaScript features implemented in modern browsers, like Chrome, Safari, Firefox, and Edge. We recommend [buildless development](/developing/), so unless you are using very cutting edge features, you can actually just publish your source code as is. See [this blog post](https://justinfagnani.com/2019/11/01/how-to-publish-web-components-to-npm/) for a general guideline.
|
||||
|
||||
If you are using very new or non-standard features, such as TypeScript, you will need to set up a lightweight build. For this, we recommend tools such as [babel](https://babeljs.io/) with the [preset-env](https://babeljs.io/docs/en/babel-preset-env) plugin or the [TypeScript compiler](https://www.typescriptlang.org/). Make sure to set the target to modern browsers, and publish ES modules.
|
||||
|
||||
### Building websites or multi page apps (MPA)
|
||||
|
||||
Single page apps are great for a snappy user experience when you have highly dynamic content, but a lot of content on the web does not fall into this category. It still makes sense to build websites or apps consisting of multiple pages. This also requires a different approach to your build system.
|
||||
|
||||
We are still in the process of investigating and documenting our recommendations for this. In the meantime both [building-rollup](/building/building-rollup.html) and [@open-wc/rollup-plugin-html](https://open-wc.org/building/rollup-plugin-html.html) have tips for these types of projects.
|
||||
|
||||
## Webpack
|
||||
|
||||
We recommend [rollup](https://rollupjs.org/guide/en/) as a build tool. It is designed specifically for standard es modules and it's very easy to use. But sometimes you are not in control of choosing the tools to use on a project, and you need to make things work with other tools. For webpack, we have a legacy [standard config](https://github.com/open-wc/legacy/tree/master/packages/building-webpack) which can be used to build apps with web components.
|
||||
@@ -1 +0,0 @@
|
||||
../../packages/building-rollup/README.md
|
||||
@@ -1 +0,0 @@
|
||||
../../packages/polyfills-loader/README.md
|
||||
@@ -1 +0,0 @@
|
||||
../../packages/rollup-plugin-html/README.md
|
||||
@@ -1 +0,0 @@
|
||||
../../packages/rollup-plugin-polyfills-loader/README.md
|
||||
@@ -1,59 +0,0 @@
|
||||
---
|
||||
permalink: 'developing/index.html'
|
||||
title: Developing
|
||||
section: guides
|
||||
tags:
|
||||
- guides
|
||||
---
|
||||
|
||||
# Developing
|
||||
|
||||
## Going buildless
|
||||
|
||||
Browsers have improved a lot over the past years. It's now possible to do web development without requiring any build tools, using the native module loader of the browser. We think this is a great fit for web components, and we recommend this as a general starting point.
|
||||
|
||||
Build tools can quickly add a lot of complexity to your code, and make your code reliant on a specific build setup. We think it's best to avoid them during development, or only add them for light transformations if you know what you're doing.
|
||||
|
||||
Read [this article](https://dev.to/open-wc/developing-without-a-build-1-introduction-26ao) to learn more about this approach.
|
||||
|
||||
## Development server
|
||||
|
||||
We created [es-dev-server](https://open-wc.org/developing/es-dev-server.html) to help developing without build tools.
|
||||
|
||||
## Web component libraries
|
||||
|
||||
You can write web components using just the basic web component APIs. This can be a great choice when you're looking to keep dependencies low. But generally, we recommend using lightweight libraries to help improve the developer experience and reduce boilerplate.
|
||||
|
||||
We recommend [lit-html](https://www.npmjs.com/package/lit-html) with the [lit-element](https://www.npmjs.com/package/lit-element) base class as a general-purpose library for building web components. `lit-html` is feature complete, extremely lightweight and offers a great development experience. Check out the [lit-html page](/developing/lit-html.html) for code examples and more information.
|
||||
|
||||
In the code snippets throughout our documentation we use `lit-html` and `lit-element`, but our recommendations and tools are not specific to them. You should be able to use them with any web component library that follows browser standards. If you do run into issues, or have any questions, let us know about it!
|
||||
|
||||
### Alternative libraries
|
||||
|
||||
Besides `lit-html`, there are other great options available:
|
||||
|
||||
- [haunted](https://www.npmjs.com/package/haunted) functional-style web components, with react-like hooks
|
||||
- [hybrids](https://www.npmjs.com/package/hybrids) another functional web component library
|
||||
- [SkateJS](https://skatejs.netlify.com/) wraps libraries like react, preact or lit-html in a web component
|
||||
- [slim.js](https://slimjs.com/) declarative web components
|
||||
- [stencil](https://stenciljs.com/) web component with typescript and JSX (requires a build step)
|
||||
|
||||
## Codelabs
|
||||
|
||||
See the [codelabs](/codelabs/) page for step by step tutorials for development.
|
||||
|
||||
## Code examples
|
||||
|
||||
Check out the [code examples](/developing/code-examples.html) page for a collection of best practices and design patterns.
|
||||
|
||||
## Testing
|
||||
|
||||
Check out our [testing documentation](/testing/) for help with setting up testing.
|
||||
|
||||
## Building
|
||||
|
||||
When you are ready to ship your app to production, or when you need to test your app on older browsers, take a look at our [building documentation](/building/) to get you started.
|
||||
|
||||
## Further reading
|
||||
|
||||
See: [awesome lit-html](https://github.com/web-padawan/awesome-lit-html) for a great collection of resources.
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
permalink: 'developing/best-practices.html'
|
||||
title: Best Practices
|
||||
section: guides
|
||||
tags:
|
||||
- guides
|
||||
- guide
|
||||
---
|
||||
|
||||
# Best Practices
|
||||
|
||||
If you're using our [linting setup](/linting), you may already follow some Web Component best practices, but unfortunately, linters do not cover everything. On this page, you can find our recommended best practices.
|
||||
|
||||
In general, we recommend you read [Custom Element Best Practices](https://developers.google.com/web/fundamentals/web-components/best-practices), and [Guidelines for creating web platform compatible components](https://w3ctag.github.io/webcomponents-design-guidelines/).
|
||||
|
||||
<div class="custom-block tip"><p class="custom-block-title">Looking for contributors!</p> <p>We're actively looking to extend this page with common best practices. If you have anything to contribute, please make an issue or a pull request on our <a href="https://github.com/open-wc/" target="_blank" rel="noopener noreferrer">github repository<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a>.</p></div>
|
||||
|
||||
## Upwards Data
|
||||
|
||||
> ⬇️ Properties/attributes down
|
||||
>
|
||||
> ⬆️ Events up
|
||||
|
||||
Every developer at some point will run into the problem of moving some data upwards to a parent element. While there are many ways to manage state in your application like Redux, or passing down a bound function to a child element to mutate state in the parent, our default recommendation for moving data upwards is simply using events. The benefits of using events are that it's close to the platform, and will always have the same expected behavior across frameworks. You can find a demo of using `CustomEvents` [here](https://stackblitz.com/edit/open-wc-lit-demos?file=01-basic%2F12-firing-events.js).
|
||||
|
||||
If you find yourself moving a lot of data around, it may be time to look into some state management libraries like [Redux](https://redux.js.org/) or consider some more advanced composition techniques with [slotting](https://webcomponents.dev/edit/collection/fOta0aCFgRQqMtyXJjXT/pNsN9JVAiNHOAHpvkL6c).
|
||||
@@ -1,942 +0,0 @@
|
||||
---
|
||||
permalink: 'developing/es-dev-server.html'
|
||||
title: ES dev server
|
||||
section: guides
|
||||
tags:
|
||||
- guides
|
||||
---
|
||||
|
||||
> **Notice**
|
||||
>
|
||||
> Development of es-dev-server continues under a new name: [web dev server](https://modern-web.dev/docs/dev-server/overview/). We recommend using it for new projects, and upgrading for existing projects.
|
||||
>
|
||||
> We will continue to support fixing bugs for es-dev-server.
|
||||
|
||||
# es dev server
|
||||
|
||||
A web server for development without bundling.
|
||||
|
||||
```bash
|
||||
npx es-dev-server --node-resolve --watch
|
||||
```
|
||||
|
||||
**Quick overview**
|
||||
|
||||
- efficient browser caching for fast reloads
|
||||
- [transform code on older browsers for compatibility](#compatibility-mode)
|
||||
- [resolve bare module imports for use in the browser](#node-resolve) (`--node-resolve`)
|
||||
- auto-reload on file changes with the (`--watch`)
|
||||
- history API fallback for SPA routing (`--app-index index.html`)
|
||||
- [plugin API for extensions](#plugins)
|
||||
|
||||
[See all commands](#command-line-flags-and-configuration)
|
||||
|
||||
## Getting started
|
||||
|
||||
We recommend [following this guide](https://dev.to/open-wc/developing-without-a-build-2-es-dev-server-1cf5) for a step by step overview of different workflows with `es-dev-server`.
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
npm i --save-dev es-dev-server
|
||||
```
|
||||
|
||||
Add scripts to your `package.json`, modify the flags as needed:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"start": "es-dev-server --app-index index.html --node-resolve --watch --open"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Run the server:
|
||||
|
||||
```bash
|
||||
npm run start
|
||||
```
|
||||
|
||||
## Node version
|
||||
|
||||
es-dev-server requires node v10 or higher
|
||||
|
||||
## Command-line flags and Configuration
|
||||
|
||||
### Server configuration
|
||||
|
||||
| name | type | description |
|
||||
| --------- | -------------- | ----------------------------------------------------------------------- |
|
||||
| port | number | The port to use, uses a random free port if not set. |
|
||||
| hostname | string | The hostname to use. Default: localhost |
|
||||
| open | boolean/string | Opens the browser on app-index, root dir or a custom path |
|
||||
| app-index | string | The app's index.html file, sets up history API fallback for SPA routing |
|
||||
| root-dir | string | The root directory to serve files from. Default: working directory |
|
||||
| base-path | string | Base path the app is served on. Example: /my-app |
|
||||
| config | string | The file to read configuration from (JS or JSON) |
|
||||
| cors | boolean | Enable CORS |
|
||||
| help | none | See all options |
|
||||
|
||||
### Development help
|
||||
|
||||
| name | type | description |
|
||||
| ----- | ------- | ------------------------------------------------------------------- |
|
||||
| watch | boolean | Reload the browser when files are edited |
|
||||
| http2 | boolean | Serve files over HTTP2. Sets up HTTPS with self-signed certificates |
|
||||
|
||||
### Code transformation
|
||||
|
||||
| name | type | description |
|
||||
| -------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| compatibility | string | Compatibility mode for older browsers. Can be: `auto`, `always`, `min`, `max` or `none` Default `auto` |
|
||||
| node-resolve | boolean | Resolve bare import imports using node resolve |
|
||||
| dedupe | boolean/array | Deduplicates all modules, or modules from specified packages if the value is an array |
|
||||
| preserve-symlinks | boolean | Preserve symlinks when resolving modules. Set to true, if using tools that rely on symlinks, such as `npm link`. Default false. |
|
||||
| module-dirs | string/array | Directories to resolve modules from. Used by node-resolve |
|
||||
| babel | boolean | Transform served code through babel. Requires .babelrc |
|
||||
| file-extensions | string/array | Extra file extensions to use when transforming code. |
|
||||
| babel-exclude | number/array | Patterns of files to exclude from babel compilation. |
|
||||
| babel-modern-exclude | number/array | Patterns of files to exclude from babel compilation on modern browsers. |
|
||||
| babel-module-exclude | number/array | Patterns of files to exclude from babel compilation for modules only. |
|
||||
| event-stream | boolean | Whether to inject event stream script. Defaults to true. |
|
||||
|
||||
Most commands have an alias/shorthand. You can view them by using `--help`.
|
||||
|
||||
### Configuration files
|
||||
|
||||
We pick up an `es-dev-server.config.js` file automatically if it is present in the current working directory. You can specify a custom config path using the `config` flag.
|
||||
|
||||
Configuration options are the same as command line flags, using their camelCased names. Example:
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
port: 8080,
|
||||
watch: true,
|
||||
nodeResolve: true,
|
||||
appIndex: 'demo/index.html',
|
||||
plugins: [],
|
||||
moduleDirs: ['node_modules', 'web_modules'],
|
||||
};
|
||||
```
|
||||
|
||||
In addition to the command-line flags, the configuration file accepts these additional options:
|
||||
|
||||
| name | type | description |
|
||||
| --------------- | ------- | --------------------------------------------------------- |
|
||||
| middlewares | array | Koa middlewares to add to the server. (read more below) |
|
||||
| plugins | array | Plugins to add to the server. (read more below) |
|
||||
| babelConfig | object | Babel config to run with the server. |
|
||||
| polyfillsLoader | object | Configuration for the polyfills loader. (read more below) |
|
||||
| debug | boolean | Whether to turn on debug mode on the server. |
|
||||
|
||||
## Serving files
|
||||
|
||||
es-dev-server is a static web server. When a request is made from the browser for `/foo/bar.js` it will try and find this file from the root directory. It cannot serve any files outside of your root directory because the browser has no way to request them, and the path on the file system must always be reflected in the path of the browser.
|
||||
|
||||
### index.html in the root
|
||||
|
||||
The simplest setup, making sure that all files are accessible, is to place your index.html at the root of your project
|
||||
|
||||
<details>
|
||||
<summary>Read more</summary>
|
||||
|
||||
Consider this example directory structure:
|
||||
|
||||
```
|
||||
node_modules/...
|
||||
src/...
|
||||
index.html
|
||||
```
|
||||
|
||||
If you run the `es-dev-server` command from the root of the project, you can access your app at `/` or `/index.html` in the browser.
|
||||
|
||||
</details>
|
||||
|
||||
### index.html in a folder
|
||||
|
||||
If you move your `index.html` outside the root of your project, you have some different options.
|
||||
|
||||
<details>
|
||||
<summary>Read more</summary>
|
||||
|
||||
Use the `--open` parameter for when you'd like to keep you index.html in a subfolder.
|
||||
|
||||
```
|
||||
node_modules/...
|
||||
src/...
|
||||
src/index.html
|
||||
```
|
||||
|
||||
You can access your app in the browser at `/src/` or `/src/index.html`. You can tell es-dev-server to explicitly open at this path:
|
||||
|
||||
```bash
|
||||
# with app-index flag
|
||||
es-dev-server --app-index src/index.html --open
|
||||
# without app-index flag
|
||||
es-dev-server --open src/
|
||||
```
|
||||
|
||||
You can also change the root directory of the dev server:
|
||||
|
||||
```bash
|
||||
es-dev-server --root-dir src --open
|
||||
```
|
||||
|
||||
Now your `index.html` is accessible at `/` or `/index.html`. However, the dev server cannot serve any files outside of the root directory. So if your app uses any node modules, they will no longer because accessible.
|
||||
|
||||
If you want your index in a subfolder without this being visible in the browser URL, you can set up a file rewrite rule. [Read more here](#rewriting-request-urls)
|
||||
|
||||
</details>
|
||||
|
||||
### Monorepos
|
||||
|
||||
If you are using es-dev-server in a monorepo, your node modules are in two different locations. In a package's folder and at the repository root. You need to make sure that es-dev-server can serve from both directories.
|
||||
|
||||
<details>
|
||||
<summary>Read more</summary>
|
||||
|
||||
For example, a typical monorepo setup looks like this:
|
||||
|
||||
```
|
||||
node_modules/...
|
||||
packages/my-package/node_modules/...
|
||||
packages/my-package/index.html
|
||||
```
|
||||
|
||||
You will need to make sure the root node_modules folder is accessible to the dev server.
|
||||
|
||||
If your working directory is `packages/my-package` you can use this command:
|
||||
|
||||
```bash
|
||||
# with app-index (for SPA)
|
||||
es-dev-server --root-dir ../../ --app-index packages/my-package/index.html --open
|
||||
# without app-index
|
||||
es-dev-server --root-dir ../../ --open packages/my-package/index.html
|
||||
```
|
||||
|
||||
If your working directory is the root of the repository you can use this command:
|
||||
|
||||
```bash
|
||||
# with app index (for SPA)
|
||||
es-dev-server --app-index packages/my-package/index.html --open
|
||||
# without app index
|
||||
es-dev-server --open packages/my-package/index.html
|
||||
```
|
||||
|
||||
This is the same approach as serving an index.html in a subdirectory, so the section above applies here as well.
|
||||
|
||||
</details>
|
||||
|
||||
### Base Element
|
||||
|
||||
<details>
|
||||
<summary>Read more</summary>
|
||||
|
||||
If you are building a single page application with client-side routing, we recommend adding a base element to set the base URL of your document.
|
||||
|
||||
The base URL of the document can be accessed through `document.baseURI` and is used by the browser to resolve relative paths (anchors, images, links, scripts, etc.). By default, it is set to the browser's URL.
|
||||
|
||||
You can add `<base href="">` element to modify how files are resolved relatively to your index.html. This can be very useful when your index.html is not at the root of your project.
|
||||
|
||||
[Read more about this on MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base)
|
||||
|
||||
</details>
|
||||
|
||||
## Node resolve
|
||||
|
||||
"Bare imports" are imports which don't specify a full path to a file:
|
||||
|
||||
```js
|
||||
import foo from 'bar';
|
||||
```
|
||||
|
||||
The browser doesn't know where to find this file called `bar`. The `--node-resolve` flag resolves this bare import to the actual file path before serving it to the browser:
|
||||
|
||||
```js
|
||||
import foo from './node_modules/bar/bar.js';
|
||||
```
|
||||
|
||||
Because we use [es-module-lexer](https://github.com/guybedford/es-module-lexer) for blazing fast analysis to find the imports in a file without booting up a full-blown parser like babel, we can do this without a noticeable impact on performance.
|
||||
|
||||
For the actual resolve logic, we internally use [@rollup/plugin-node-resolve](https://github.com/rollup/plugins/tree/master/packages/node-resolve) so that you can keep the resolve logic in sync between development and production. When using a config file, the `nodeResolve` can also be an object which accepts the same options as the rollup plugin. options.
|
||||
|
||||
<details>
|
||||
<summary>Example config</summary>
|
||||
|
||||
See [the rollup docs](https://github.com/rollup/plugins/tree/master/packages/node-resolve) for all options and what they do.
|
||||
|
||||
Some options like `dedupe`, `fileExtensions`, `preserveSymlinks` and `moduleDirs` are mapped to options for `nodeResolve` internally. You can overwrite them with your custom config.
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
nodeResolve: {
|
||||
jsnext: true,
|
||||
browser: true,
|
||||
// set default to false because es-dev-server always
|
||||
// runs in the browser
|
||||
preferBuiltins: true,
|
||||
// will overwrite es-dev-server's fileExtensions option
|
||||
extensions: ['.mjs', '.js'],
|
||||
// will overwrite es-dev-server's dedupe option
|
||||
dedupe: ['lit-html'],
|
||||
customResolveOptions: {
|
||||
// will overwrite es-dev-server's moduleDirs option
|
||||
moduleDirectory: ['node_modules'],
|
||||
preserveSymlinks: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
In the future, we are hoping that [import maps](https://github.com/WICG/import-maps) will make this step unnecessary.
|
||||
|
||||
## Middleware
|
||||
|
||||
You can add your own middleware to es-dev-server using the `middlewares` property. The middleware should be a standard koa middleware. [Read more about koa here.](https://koajs.com/)
|
||||
|
||||
You can use middleware to modify respond to any request from the browser, for example to rewrite a URL or proxy to another server. For serving or manipulating files it's recommended to use plugins.
|
||||
|
||||
### Proxying requests
|
||||
|
||||
<details>
|
||||
<summary>Read more</summary>
|
||||
|
||||
```javascript
|
||||
const proxy = require('koa-proxies');
|
||||
|
||||
module.exports = {
|
||||
port: 9000,
|
||||
middlewares: [
|
||||
proxy('/api', {
|
||||
target: 'http://localhost:9001',
|
||||
}),
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Rewriting request urls
|
||||
|
||||
You can rewrite certain file requests using a simple middleware. This can be useful for example to serve your `index.html` from a different file location or to alias a module.
|
||||
|
||||
<details>
|
||||
<summary>Read more</summary>
|
||||
|
||||
Serve `/index.html` from `/src/index.html`:
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
middlewares: [
|
||||
function rewriteIndex(context, next) {
|
||||
if (context.url === '/' || context.url === '/index.html') {
|
||||
context.url = '/src/index.html';
|
||||
}
|
||||
|
||||
return next();
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Plugins
|
||||
|
||||
Plugins are objects with lifecycle hooks called by es-dev-server as it serves files to the browser. They can be used to serve virtual files, transform files, or resolve module imports.
|
||||
|
||||
### Adding plugins
|
||||
|
||||
A plugin is just an object that you add to the `plugins` array in your configuration file. You can add an object directly, or create one from a function somewhere:
|
||||
|
||||
<details>
|
||||
<summary>Read more</summary>
|
||||
|
||||
```js
|
||||
const awesomePlugin = require('awesome-plugin');
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
// use a plugin
|
||||
awesomePlugin({ someOption: 'someProperty' }),
|
||||
// create an inline plugin
|
||||
{
|
||||
transform(context) {
|
||||
if (context.response.is('html')) {
|
||||
return { body: context.body.replace(/<base href=".*">/, '<base href="/foo/">') };
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
See the full type interface for all options:
|
||||
|
||||
<details>
|
||||
<summary>Read more</summary>
|
||||
|
||||
```ts
|
||||
import Koa, { Context } from 'koa';
|
||||
import { FSWatcher } from 'chokidar';
|
||||
import { Server } from 'net';
|
||||
import { ParsedConfig } from './config';
|
||||
|
||||
type ServeResult = void | { body: string; type?: string; headers?: Record<string, string> };
|
||||
type TransformResult = void | { body?: string; headers?: Record<string, string> };
|
||||
type ResolveResult = void | string | Promise<void> | Promise<string>;
|
||||
|
||||
interface ServerArgs {
|
||||
config: ParsedConfig;
|
||||
app: Koa;
|
||||
server: Server;
|
||||
fileWatcher: FSWatcher;
|
||||
}
|
||||
|
||||
export interface Plugin {
|
||||
serverStart?(args: ServerArgs): void | Promise<void>;
|
||||
serve?(context: Context): ServeResult | Promise<ServeResult>;
|
||||
transform?(context: Context): TransformResult | Promise<TransformResult>;
|
||||
resolveImport?(args: { source: string; context: Context }): ResolveResult;
|
||||
resolveMimeType?(context: Context): undefined | string | Promise<undefined | string>;
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Hook: serve
|
||||
|
||||
The serve hook can be used to serve virtual files from the server. The first plugin to respond with a body is used. It can return a Promise.
|
||||
|
||||
<details>
|
||||
<summary>Read more</summary>
|
||||
|
||||
Serve an auto generated `index.html`:
|
||||
|
||||
```js
|
||||
const indexHTML = generateIndexHTML();
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
{
|
||||
serve(context) {
|
||||
if (context.path === '/index.html') {
|
||||
return { body: indexHTML };
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
Serve a virtual module:
|
||||
|
||||
```js
|
||||
const indexHTML = generateIndexHTML();
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
{
|
||||
serve(context) {
|
||||
if (context.path === '/messages.js') {
|
||||
return { body: 'export default "Hello world";' };
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
The file extension is used to infer the mime type to respond with. If you are using a non-standard file extension you can use the `type` property to set it explicitly:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
plugins: [
|
||||
{
|
||||
serve(context) {
|
||||
if (context.path === '/foo.xyz') {
|
||||
return { body: 'console.log("foo bar");', type: 'js' };
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Hook: resolveMimeType
|
||||
|
||||
Browsers don't use file extensions to know how to interpret files. Instead, they use [media or MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) which is set using the `content-type` header.
|
||||
|
||||
es-dev-server guesses the MIME type based on the file extension. When serving virtual files with non-standard file extensions, you can set the MIME type in the returned result (see the examples above). If you are transforming code from one format to another, you need to use the `resolveMimeType` hook.
|
||||
|
||||
<details>
|
||||
<summary>Read more</summary>
|
||||
|
||||
The returned MIME type can be a file extension, this will be used to set the corresponding default MIME type. For example `js` resolves to `application/javascript` and `css` to `text/css`.
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
plugins: [
|
||||
{
|
||||
resolveMimeType(context) {
|
||||
// change all MD files to HTML
|
||||
if (context.response.is('md')) {
|
||||
return 'html';
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
resolveMimeType(context) {
|
||||
// change all CSS files to JS, except for a specific file
|
||||
if (context.response.is('css') && context.path !== '/global.css') {
|
||||
return 'js';
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
It is also possible to set the full mime type directly:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
plugins: [
|
||||
{
|
||||
resolveMimeType(context) {
|
||||
if (context.response.is('md')) {
|
||||
return 'text/html';
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Hook: transform
|
||||
|
||||
The transform hook is called for each file and can be used to transform a file. Multiple plugins can transform a single file. It can return a Promise.
|
||||
|
||||
This hook is useful for small modifications, such as injecting environment variables, or for compiling files to JS before serving them to the browser.
|
||||
|
||||
If you are transforming non-standard file types, you may also need to include a `resolveMimeType` hook.
|
||||
|
||||
<details>
|
||||
<summary>Read more</summary>
|
||||
|
||||
Rewrite the base path of your application for local development;
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
plugins: [
|
||||
{
|
||||
transform(context) {
|
||||
if (context.path === '/index.html') {
|
||||
const transformedBody = context.body.replace(/<base href=".*">/, '<base href="/foo/">');
|
||||
return { body: transformedBody };
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
Inject a script to set global variables during local development:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
plugins: [
|
||||
{
|
||||
transform(context) {
|
||||
if (context.path === '/index.html') {
|
||||
const transformedBody = context.body.replace(
|
||||
'</head>',
|
||||
'<script>window.process = { env: { NODE_ENV: "development" } }</script></head>',
|
||||
);
|
||||
return { body: transformedBody };
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
Inject environment variables into a JS module:
|
||||
|
||||
```js
|
||||
const packageJson = require('./package.json');
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
{
|
||||
transform(context) {
|
||||
if (context.path === '/src/environment.js') {
|
||||
return { body: `export const version = '${packageJson.version}';` };
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
Transform markdown to HTML:
|
||||
|
||||
```js
|
||||
const markdownToHTML = require('markdown-to-html-library');
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
{
|
||||
resolveMimeType(context) {
|
||||
// this ensures the browser interprets .md files as .html
|
||||
if (context.path.endsWith('.md')) {
|
||||
return 'html';
|
||||
}
|
||||
},
|
||||
|
||||
async transform(context) {
|
||||
// this will transform all MD files. if you only want to transform certain MD files
|
||||
// you can check context.path
|
||||
if (context.path.endsWith('.md')) {
|
||||
const html = await markdownToHTML(body);
|
||||
|
||||
return { body: html };
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
Polyfill CSS modules in JS:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
plugins: [
|
||||
{
|
||||
resolveMimeType(context) {
|
||||
if (context.path.endsWith('.css')) {
|
||||
return 'js';
|
||||
}
|
||||
},
|
||||
|
||||
async transform(context) {
|
||||
if (context.path.endsWith('.css')) {
|
||||
const stylesheet = `
|
||||
const stylesheet = new CSSStyleSheet();
|
||||
stylesheet.replaceSync(${JSON.stringify(body)});
|
||||
export default stylesheet;
|
||||
`;
|
||||
|
||||
return { body: stylesheet };
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Hook: resolveImport
|
||||
|
||||
The `resolveImport` hook is called for each module import. It can be used to resolve module imports before they reach the browser.
|
||||
|
||||
<details>
|
||||
<summary>Read more</summary>
|
||||
|
||||
es-dev-server already resolves module imports when the `--node-resolve` flag is turned on. You can do the resolving yourself, or overwrite it for some files.
|
||||
|
||||
The hook receives the import string and should return the string to replace it with. This should be a browser-compatible path, not a file path.
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
plugins: [
|
||||
{
|
||||
async resolveImport({ source, context }) {
|
||||
const resolvedImport = fancyResolveLibrary(source);
|
||||
return resolvedImport;
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Hook: serverStart
|
||||
|
||||
The `serverStart` hook is called when the server starts. It is the ideal location to boot up other servers you will proxy to. It receives the server config, which you can use if plugins need access to general information such as the `rootDir` or `appIndex`. It also receives the HTTP server, Koa app, and `chokidar` file watcher instance. These can be used for more advanced plugins. This hook can be async, and it awaited before actually booting the server and opening the browser.
|
||||
|
||||
<details>
|
||||
<summary>Read more</summary>
|
||||
|
||||
Accessing the serverStart parameters:
|
||||
|
||||
```js
|
||||
function myFancyPlugin() {
|
||||
let rootDir;
|
||||
|
||||
return {
|
||||
serverStart({ config, app, server, fileWatcher }) {
|
||||
// take the rootDir to access it later
|
||||
rootDir = config.rootDir;
|
||||
|
||||
// register a koa middleware directly
|
||||
app.use((context, next) => {
|
||||
console.log(context.path);
|
||||
return next();
|
||||
});
|
||||
|
||||
// register a file to be watched
|
||||
fileWatcher.add('/foo.md');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
plugins: [myFancyPlugin()],
|
||||
};
|
||||
```
|
||||
|
||||
Boot up another server for proxying in serverStart:
|
||||
|
||||
```js
|
||||
const proxy = require('koa-proxies');
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
{
|
||||
async serverStart({ app }) {
|
||||
// set up a proxy for certain requests
|
||||
app.use(
|
||||
proxy('/api', {
|
||||
target: 'http://localhost:9001',
|
||||
}),
|
||||
);
|
||||
|
||||
// boot up the other server, because it is awaited es-dev-server will also wait for it
|
||||
await startOtherServer({ port: 9001 });
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Koa Context
|
||||
|
||||
The plugin hooks simply receive the Koa `Context` object. This contains information about the server's request and response. Check the [Koa documentation](https://koajs.com/) to learn more about this.
|
||||
|
||||
To transform specific kinds of files we don't recommend relying on file extensions. Other plugins may be using non-standard file extensions. Instead, you should use the server's MIME type or content-type header. You can easily check this using the `context.response.is()` function. This is used a lot in the examples above.
|
||||
|
||||
Because files can be requested with query parameters and hashes, we recommend using `context.path` for reading the path segment of the URL only. If you do need to access search parameters, we recommend using `context.URL.searchParams.get('my-parameter')`.
|
||||
|
||||
## Order of execution
|
||||
|
||||
The order of execution for the es-dev-server when a file request is received:
|
||||
|
||||
1. User middleware: before "next"
|
||||
2. Serve
|
||||
- Plugins: serve
|
||||
- es-dev-server: static file middleware (if no plugin match)
|
||||
3. Plugins: resolveMimeType
|
||||
4. Plugins: transform
|
||||
5. Resolve module imports
|
||||
- Plugins: resolveModuleImport
|
||||
- es-dev-server: node-resolve (if no plugin resolve)
|
||||
6. es-dev-server: babel + compatibility transforms
|
||||
7. es-dev-server: response cache (caches all JS files served, including plugin transforms)
|
||||
8. User middleware: after "next"
|
||||
|
||||
## Typescript support
|
||||
|
||||
Because es-dev-server doesn't do any bundling, it's easy to integrate it with typescript and doesn't require any extra tooling or plugins. Just run `tsc` on your code, and serve the compiled output with es-dev-server. You can run both `tsc` and es-dev-server in watch mode, changes will be picked up automatically.
|
||||
|
||||
Make sure to configure `tsc` to output real ES modules.
|
||||
|
||||
## Compatibility mode
|
||||
|
||||
Compatibility mode enables bundle-free development using modern browsers features on older browsers. Automatic compatibility mode is enabled by default.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Read more</summary>
|
||||
|
||||
Compatibility mode can be configured using the `--compatibility` flag. The possible options are `auto`, `min`, `max`, and `none`. The default is mode is `auto`.
|
||||
|
||||
**auto**
|
||||
`auto` compatibility looks at the current browser to determine the level of compatibility to enable. On the latest 2 versions of the major browsers, it doesn't do any work. This keeps the server as fast as possible in the general case.
|
||||
|
||||
On older browsers, the server uses the browser's user agent and [@babel/preset-env](https://babeljs.io/docs/en/babel-preset-env) to do a targeted transformation for that specific browser and version. `@babel/preset-env` only works with stage 4 javascript features, they should become an official standard before they can be used.
|
||||
|
||||
If the browser does not support es module scripts, dynamic imports or `import.meta.url` es modules are transformed to [system-js](https://github.com/systemjs/systemjs).
|
||||
|
||||
This works down to at least IE11. Depending on what browser features you are using, it might work with an earlier version too but this is not tested.
|
||||
|
||||
**always**
|
||||
`always` compatibility is the same as `auto`, except that it doesn't skip compiling on the latest 2 versions of the major browsers. This makes it a bit slower on modern browsers but allows you to use new features before they are implemented in the browser.
|
||||
|
||||
**min**
|
||||
`min` compatibility forces the same level of compatibility on all browsers. It makes code compatible with the latest two versions of the major browsers and does not transform es modules.
|
||||
|
||||
**max**
|
||||
`max` compatibility forces the same level of compatibility on all browsers. It compiles everything to es5 and [system-js](https://github.com/systemjs/systemjs).
|
||||
|
||||
**none**
|
||||
`none` disables compatibility mode entirely.
|
||||
|
||||
</details>
|
||||
|
||||
### Polyfills loader
|
||||
|
||||
When compatibility mode is enabled, polyfills are loaded using [polyfills-loader](https://github.com/open-wc/open-wc/tree/master/packages/polyfills-loader).
|
||||
|
||||
<details>
|
||||
<summary>Read more</summary>
|
||||
|
||||
You can customize the polyfill loader configuration from your configuration file. Check the docs for the [polyfills-loader](https://github.com/open-wc/open-wc/tree/master/packages/polyfills-loader) for all possible options.
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
polyfillsLoader: {
|
||||
polyfills: {
|
||||
fetch: false,
|
||||
custom: [
|
||||
{
|
||||
name: 'my-feature-polyfill',
|
||||
path: require.resolve('my-feature-polyfill'),
|
||||
test: "!('myFeature' in window)",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
By default, es-dev-server wraps all scripts and are deferred until polyfills are loaded. Loading order of scripts are preserved, but this can create problems if you rely on a script being executed before HTML is parsed. You can configure `es-dev-server` to exclude certain types of scripts:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
polyfillsLoader: {
|
||||
exclude: {
|
||||
jsModules: true,
|
||||
inlineJsModules: true,
|
||||
jsScripts: true,
|
||||
inlineJsScripts: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Using es-dev-server programmatically
|
||||
|
||||
You can use different components from `es-dev-server` as a library and integrate it with other tools:
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Read more</summary>
|
||||
|
||||
### createConfig
|
||||
|
||||
When using the server from javascript you are going to need a config object to tell the server what options to turn on and off. It's best to use `createConfig` for this as this converts the public API to an internal config structure and sets up default values.
|
||||
|
||||
By default, all options besides static file serving are turned off, so it's easy to configure based on your requirements.
|
||||
|
||||
The config structure is the same as the configuration explained in the [configuration files section](#configuration-files)
|
||||
|
||||
```javascript
|
||||
import { createConfig } from 'es-dev-server';
|
||||
|
||||
const config = createConfig({
|
||||
http2: true,
|
||||
babel: true,
|
||||
open: true,
|
||||
});
|
||||
```
|
||||
|
||||
### createMiddlewares
|
||||
|
||||
`createMiddlewares` creates the dev server's middlewares based on your configuration. You can use this to hook them up to your koa server.
|
||||
|
||||
Returns an array of koa middleware functions.
|
||||
|
||||
```javascript
|
||||
import Koa from 'koa';
|
||||
import { createConfig, createMiddlewares } from 'es-dev-server';
|
||||
|
||||
const config = createConfig({});
|
||||
const middlewares = createMiddlewares(config);
|
||||
|
||||
const app = new Koa();
|
||||
middlewares.forEach(middleware => {
|
||||
app.use(middleware);
|
||||
});
|
||||
```
|
||||
|
||||
### createServer
|
||||
|
||||
`createServer` creates an instance of the dev server including all middlewares, but without starting the server. This is useful if you want to be in control of starting the server yourself.
|
||||
|
||||
Returns the koa app and a node http or http2 server.
|
||||
|
||||
```javascript
|
||||
import Koa from 'koa';
|
||||
import { createConfig, createServer } from 'es-dev-server';
|
||||
|
||||
const config = createConfig({ ... });
|
||||
const { app, server } = createServer(config);
|
||||
server.listen(3000);
|
||||
```
|
||||
|
||||
### watch mode
|
||||
|
||||
`createMiddlewares` and `createServer` requires a chokidar fileWatcher if watch mode is enabled. You need to pass this separately because the watcher nees-dev-server to be killed explicitly when the server closes.
|
||||
|
||||
```javascript
|
||||
import Koa from 'koa';
|
||||
import chokidar from 'chokidar';
|
||||
import { createConfig, createMiddlewares, createServer } from 'es-dev-server';
|
||||
|
||||
const config = createConfig({ ... });
|
||||
const fileWatcher = chokidar.watch([]);
|
||||
|
||||
// if using createMiddlewares
|
||||
createMiddlewares(config, fileWatcher);
|
||||
// if using createServer
|
||||
createServer(config, fileWatcher);
|
||||
|
||||
// close filewatcher when no longer necessary
|
||||
fileWatcher.close();
|
||||
```
|
||||
|
||||
### startServer
|
||||
|
||||
`startServer` asynchronously creates and starts the server, listening on the configured port. It opens the browser if configured and logs a startup message.
|
||||
|
||||
Returns the koa app and a node http or http2 server.
|
||||
|
||||
```javascript
|
||||
import Koa from 'koa';
|
||||
import { createConfig, startServer } from 'es-dev-server';
|
||||
|
||||
async function main() {
|
||||
const config = createConfig({ ... });
|
||||
const { app, server } = await startServer(config, fileWatcher);
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
</details>
|
||||
@@ -1,74 +0,0 @@
|
||||
---
|
||||
permalink: 'developing/ide.html'
|
||||
title: IDE
|
||||
section: guides
|
||||
tags:
|
||||
- guides
|
||||
- guide
|
||||
---
|
||||
|
||||
# IDE
|
||||
|
||||
Your IDE is your primary tool while working with code, we recommend the following tools and plugins to make developing Web Components easier.
|
||||
|
||||
## Visual Studio Code
|
||||
|
||||
We recommend [VSCode](https://code.visualstudio.com/). For setup please visit the instructions on the Visual Studio Code [homepage](https://code.visualstudio.com/).
|
||||
|
||||
### Configuration
|
||||
|
||||
We recommend the following user settings:
|
||||
|
||||
```json
|
||||
{
|
||||
"files.autoSave": "onWindowChange",
|
||||
"editor.tabSize": 2,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"[markdown]": {
|
||||
"files.trimTrailingWhitespace": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**How to set up**:
|
||||
|
||||
1. File > Preferences > Settings
|
||||
1. click on "..." > Open settings.json
|
||||
|
||||

|
||||
|
||||
### Plugins
|
||||
|
||||
We recommend the following plugins:
|
||||
|
||||
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
|
||||
Get ESLint feedback directly in your IDE => more details under [Linting](/linting/)
|
||||
- [lit-plugin](https://marketplace.visualstudio.com/items?itemName=runem.lit-plugin)
|
||||
Syntax highlighting, type checking and code completion for lit-html
|
||||
- [prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) Code formatter.
|
||||
|
||||
## Atom
|
||||
|
||||
An alternative to VSCode is [Atom](https://atom.io/), an IDE created by Github. It provides near-native support for working with web components and has great support for template literals.
|
||||
|
||||
### Recommended plugins
|
||||
|
||||
- [prettier-atom](https://atom.io/packages/prettier-atom) - Template literal highlighting and formatting
|
||||
|
||||
## [Intellij IDEA](https://www.jetbrains.com/idea/) and other Jetbrains variants
|
||||
|
||||
Another possible alternative for development is IntelliJ IDEA. IntelliJ is a Java integrated development environment (IDE) for developing computer software. It is developed by JetBrains (formerly known as IntelliJ), and is available as an Apache 2 Licensed community edition and in a proprietary commercial edition.
|
||||
|
||||
Syntax highlighting from html and css in template literals should be supported out of the box. Generic web components related functionalitites such as Custom Elements support and completion is also available. You can read more about it [here](https://blog.jetbrains.com/phpstorm/2013/10/phpstorm-7-web-toolkit-javascript-templates-web-components-support/).
|
||||
|
||||

|
||||
|
||||
Due to the support available directly in the IDE, the ecosystem for plugins is very limited and we do not recommend any.
|
||||
|
||||
## [Sublime Text 3](https://www.sublimetext.com/3)
|
||||
|
||||
Officially called a text editor Sublime Text features plugins which give it a lot of the possibilities of the IDE's listed above. It is available for Windows, Linux and OSX and can be evaluated for free.
|
||||
|
||||
### Recommended plugins
|
||||
|
||||
- [Lit Element Syntax Highlighting](https://packagecontrol.io/packages/LitElement%20Syntax%20Highlighting) - Syntax highlighting
|
||||
@@ -1 +0,0 @@
|
||||
../../packages/lit-helpers/README.md
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
permalink: 'developing/lit-html.html'
|
||||
title: lit-html
|
||||
section: guides
|
||||
tags:
|
||||
- guides
|
||||
- guide
|
||||
---
|
||||
|
||||
# lit-html
|
||||
|
||||
We recommend [lit-html](https://www.npmjs.com/package/lit-html) with the [lit-element](https://www.npmjs.com/package/lit-element) base class as a general-purpose library for building web components. `lit-html` is feature complete, extremely lightweight and offers a great development experience.
|
||||
|
||||
Under the hood new Web Component APIs such as [Constructable Stylesheets](https://developers.google.com/web/updates/2019/02/constructable-stylesheets) are used when they are available. As browser support improves, you get performance improvements for free.
|
||||
|
||||
## Getting started
|
||||
|
||||
To get started, we recommend using our project scaffolding:
|
||||
|
||||
```bash
|
||||
npm init @open-wc
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Check out the [code examples](/developing/code-examples.html) page for a collection of best practices and design patterns.
|
||||
@@ -1,106 +0,0 @@
|
||||
---
|
||||
permalink: 'developing/types.html'
|
||||
title: Types
|
||||
section: guides
|
||||
tags:
|
||||
- guides
|
||||
- guide
|
||||
---
|
||||
|
||||
# Types
|
||||
|
||||
## Types are good
|
||||
|
||||
> The following information is a quote from [Type Safe JavaScript with JSDoc](https://medium.com/@trukrs/type-safe-javascript-with-jsdoc-7a2a63209b76)
|
||||
|
||||
Types provide valuable information about the nature of code, and help identify typos, enable refactoring, etc. Most editors these days provide some kind of IntelliSense based on type information gleaned from the code. My favorite is Visual Studio Code. With proper type information it can give you symbol defintions on hover, code completion, symbol renaming across files, etc.
|
||||
|
||||
### Benefits of Types
|
||||
|
||||
1. Early detection of type errors
|
||||
2. Better code analysis
|
||||
3. Improved IDE support
|
||||
4. Promotes dependable refactoring
|
||||
5. Improves code readability
|
||||
6. Provides useful IntelliSense while coding
|
||||
7. These benefits are provided by using TypeScript, Flow and even JSDoc comments.
|
||||
|
||||
## Open Web Components Types
|
||||
|
||||
For most of our products we do offer types via JSDocs.
|
||||
In order to utilize types, you will need to set them up for your project.
|
||||
|
||||
### Why types?
|
||||
|
||||
Types can help improve your developer experience by giving you:
|
||||
|
||||
- Awesome intellisense for @open-wc tools/helpers
|
||||
- Ability to jump directly to the source of @open-wc code via F12 (in vs code)
|
||||
|
||||
Here is a small gif showing how it can help.
|
||||
|
||||

|
||||
|
||||
### Setup for JavaScript
|
||||
|
||||
In order to get type linting in a JavaScript project using VS Code all you need is to add a `tsconfig.json`.
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"lib": ["es2017", "dom"],
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"noEmit": true,
|
||||
"strict": false,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": ["**/*.js", "node_modules/@open-wc/**/*.js"],
|
||||
"exclude": ["node_modules/!(@open-wc)"]
|
||||
}
|
||||
```
|
||||
|
||||
### Setup for TypeScript
|
||||
|
||||
If you wish to use our typings in TypeScript you will need to do a little more.
|
||||
|
||||
You will need to add to this to your `tsconfig.json`.
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true
|
||||
},
|
||||
"include": ["node_modules/@open-wc/**/*.js"],
|
||||
"exclude": ["node_modules/!(@open-wc)"]
|
||||
}
|
||||
```
|
||||
|
||||
e.g. we need to include the js files from @open-wc and you can not have it in an exclude.
|
||||
|
||||
Example how a full config might look like
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"lib": ["es2017", "dom"],
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"noEmit": true,
|
||||
"strict": false,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": ["**/*.js", "node_modules/@open-wc/**/*.js"],
|
||||
"exclude": ["node_modules/!(@open-wc)"]
|
||||
}
|
||||
```
|
||||
@@ -1,13 +1,4 @@
|
||||
---
|
||||
permalink: 'about/index.html'
|
||||
title: 'About'
|
||||
section: about
|
||||
tags:
|
||||
- about
|
||||
- section
|
||||
---
|
||||
|
||||
# About Us
|
||||
# About Open Web Components
|
||||
|
||||
## Why `open-wc`
|
||||
|
||||
@@ -15,7 +6,7 @@ We want to provide the community with well-known and experience-tested recommend
|
||||
|
||||
### Our Philosophy
|
||||
|
||||
The goal of Open Web Components is to empower developers with powerful and battle-tested tools for creating and sharing open source web components.
|
||||
The goal of Open Web Components is to empower developers with powerful and battle-tested tools for creating and sharing open-source web components.
|
||||
|
||||
Many web developers have experienced the dreaded "Javascript Fatigue". With our recommendations, we hope you'll enjoy the peace of mind that comes from having a well-known default solution for almost everything. From IDE to CI, `open-wc` has got you covered.
|
||||
|
||||
@@ -31,7 +22,7 @@ We believe that web components provide a standards-based solution to problems li
|
||||
|
||||
The World Wide Web was originally just text documents connected by hyperlinks. As technologies like JavaScript and techniques like <abbr title="Asynchronous JavaScript and XML">AJAX</abbr> and <abbr title="Representational State Transfer">REST</abbr> entered the picture, the web slowly developed into a platform for full-fledged application development.
|
||||
|
||||
At first, JavaScript libraries like jQuery were absolutely required. jQuery gave developers a common set of tools across browsers with extremely different feature sets. Eventually jQuery features became standard browser features. The pattern of libraries innovating and browsers subsequently standardizing continued. As the "[browser wars](https://www.wikiwand.com/en/Browser_wars)" died down, the pace of standardization increased, and many new features came to the web.
|
||||
At first, JavaScript libraries like jQuery were absolutely required. jQuery gave developers a common set of tools across browsers with extremely different feature sets. Eventually, jQuery features became standard browser features. The pattern of libraries innovating and browsers subsequently standardizing continued. As the "[browser wars](https://www.wikiwand.com/en/Browser_wars)" died down, the pace of standardization increased, and many new features came to the web.
|
||||
|
||||
In the last several years, a component-based model for web application development was popularized, and the JavaScript community blossomed with a wide variety of libraries and approaches. Work on standardizing the web’s native component model began at Google in 2012, and after several years of open development, was successfully implemented across all major browsers in 2019.
|
||||
|
||||
@@ -55,6 +46,13 @@ Using the browser's standards-based component model will increase the longevity
|
||||
|
||||
The web components standards are fairly low-level, letting library authors build upon them with innovative APIs. By using such libraries you can build your app in the style you want while still leveraging built-in browser features.
|
||||
|
||||
## Modern Web Family
|
||||
|
||||
Open-wc is part of the Modern Web family, which consists of:
|
||||
|
||||
- modern-web.dev with the `@web` npm namespace
|
||||
- open-wc.org with the `@open-wc` npm namespace
|
||||
|
||||
## Contribute
|
||||
|
||||
Feel free to critique, ask questions and join the conversation, we'd love to hear your opinions and feedback. You can reach us by creating an issue on our [github](https://github.com/open-wc) or on the [#open-wc](hhttps://slack.com/share/IUQNUPWUF/awabyN8iYH4dXX6aGpu16ES9/enQtOTc2Nzc2ODEyOTY3LWM5ZGExNGFiMmM4NDY2YWI2MzYwOGY5ZTNlZjk4OGU4NTFhMGJjNmVhNGI4MzVlNTMwNGRiNGIxNjc4MGJhNDg) channel in the [Polymer slack](https://www.polymer-project.org/slack-invite).
|
||||
Feel free to critique, ask questions, and join the conversation, we'd love to hear your opinions and feedback. You can reach us by creating an issue on our [github](https://github.com/open-wc) or on the [#open-wc](hhttps://slack.com/share/IUQNUPWUF/awabyN8iYH4dXX6aGpu16ES9/enQtOTc2Nzc2ODEyOTY3LWM5ZGExNGFiMmM4NDY2YWI2MzYwOGY5ZTNlZjk4OGU4NTFhMGJjNmVhNGI4MzVlNTMwNGRiNGIxNjc4MGJhNDg) channel in the [Polymer slack](https://www.polymer-project.org/slack-invite).
|
||||
5
docs/discover/slack.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Slack
|
||||
|
||||
You can also find us on the Polymer slack in the [#open-wc](https://slack.com/share/IUQNUPWUF/awabyN8iYH4dXX6aGpu16ES9/enQtOTc2Nzc2ODEyOTY3LWM5ZGExNGFiMmM4NDY2YWI2MzYwOGY5ZTNlZjk4OGU4NTFhMGJjNmVhNGI4MzVlNTMwNGRiNGIxNjc4MGJhNDg) channel.
|
||||
|
||||
You can join the Polymer slack by visiting [https://www.polymer-project.org/slack-invite](https://www.polymer-project.org/slack-invite).
|
||||
3
docs/docs/building/index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Building ||40
|
||||
|
||||
Please see a sub page
|
||||
5
docs/docs/building/overview.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Building >> Overview ||10
|
||||
|
||||
Building is a necessary optimization when shipping apps to production. By using a build you reduce the total size and amount of files transferred to the end-user, and you ensure your code runs on all supported browsers.
|
||||
|
||||
We recommend doing most of the building only in projects which deploy the final result to production, such as apps or websites. This is where you can make the best decisions about supported browsers and optimizations.
|
||||
458
docs/docs/building/rollup.md
Normal file
@@ -0,0 +1,458 @@
|
||||
# Building >> Rollup ||20
|
||||
|
||||
Rollup configuration to help you get started building modern web applications.
|
||||
You write modern javascript using the latest browser features, rollup will optimize your code for production and ensure it runs on all supported browsers.
|
||||
|
||||
## Features
|
||||
|
||||
- Set HTML or JS as input and/or output
|
||||
- Optimized for browsers which support modules
|
||||
- Optional separate build for legacy browsers
|
||||
- Loads polyfills using feature detection
|
||||
- Generates a service worker
|
||||
- Minifies JS
|
||||
- Minifies lit-html templates
|
||||
|
||||
## Automatic setup
|
||||
|
||||
We recommend the open-wc [project generator](https://open-wc.org/init/) for automated setup, for new projects or to upgrade existing projects.
|
||||
|
||||
## Manual setup
|
||||
|
||||
Install the required dependencies:
|
||||
|
||||
<details>
|
||||
<summary>View</summary>
|
||||
|
||||
```bash
|
||||
npm i -D rollup @open-wc/building-rollup rimraf deepmerge es-dev-server
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
Create a `rollup.config.js` file:
|
||||
|
||||
<details>
|
||||
<summary>View</summary>
|
||||
|
||||
```js
|
||||
import merge from 'deepmerge';
|
||||
// use createSpaConfig for bundling a Single Page App
|
||||
import { createSpaConfig } from '@open-wc/building-rollup';
|
||||
|
||||
// use createBasicConfig to do regular JS to JS bundling
|
||||
// import { createBasicConfig } from '@open-wc/building-rollup';
|
||||
|
||||
const baseConfig = createSpaConfig({
|
||||
// use the outputdir option to modify where files are output
|
||||
// outputDir: 'dist',
|
||||
|
||||
// if you need to support older browsers, such as IE11, set the legacyBuild
|
||||
// option to generate an additional build just for this browser
|
||||
// legacyBuild: true,
|
||||
|
||||
// development mode creates a non-minified build for debugging or development
|
||||
developmentMode: process.env.ROLLUP_WATCH === 'true',
|
||||
|
||||
// set to true to inject the service worker registration into your index.html
|
||||
injectServiceWorker: false,
|
||||
});
|
||||
|
||||
export default merge(baseConfig, {
|
||||
// if you use createSpaConfig, you can use your index.html as entrypoint,
|
||||
// any <script type="module"> inside will be bundled by rollup
|
||||
input: './index.html',
|
||||
|
||||
// alternatively, you can use your JS as entrypoint for rollup and
|
||||
// optionally set a HTML template manually
|
||||
// input: './app.js',
|
||||
});
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
Add the following NPM scripts to your `package.json`:
|
||||
|
||||
<details>
|
||||
<summary>View</summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"build": "rimraf dist && rollup -c rollup.config.js",
|
||||
"start:build": "npm run build && es-dev-server --root-dir dist --app-index index.html --compatibility none --open"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### SPA Projects
|
||||
|
||||
If you have a SPA (Single Page App) project you can use `createSpaConfig` to generate your config. This will inject the rollup output into a single index.html file, generate a service worker and load polyfills using feature detection.
|
||||
|
||||
#### HTML as input
|
||||
|
||||
You can set an index.html file as rollup input. Any modules inside will be bundled by rollup and the final output will be injected into the final HTML file.
|
||||
|
||||
<details>
|
||||
<summary>View example</summary>
|
||||
|
||||
```js
|
||||
import merge from 'deepmerge';
|
||||
import { createBasicConfig } from '@open-wc/building-rollup';
|
||||
|
||||
const baseConfig = createBasicConfig();
|
||||
|
||||
export default merge(baseConfig, {
|
||||
input: './index.html',
|
||||
});
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
#### Custom HTML template
|
||||
|
||||
You can also provide the HTML as a template, as a string or read from a file. See the [html plugin docs](https://github.com/open-wc/open-wc/tree/master/packages/rollup-plugin-html#readme) for all possible options.
|
||||
|
||||
<details>
|
||||
<summary>View example</summary>
|
||||
|
||||
```js
|
||||
import merge from 'deepmerge';
|
||||
import { createBasicConfig } from '@open-wc/building-rollup';
|
||||
|
||||
const baseConfig = createBasicConfig({
|
||||
html: {
|
||||
template: /* your template goes here */,
|
||||
},
|
||||
});
|
||||
|
||||
export default merge(baseConfig, {
|
||||
input: './src/app.js',
|
||||
});
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Non-SPA projects
|
||||
|
||||
If you are not building a single page app you can use `createBasicConfig` to set up regular JS to JS bundling.
|
||||
|
||||
From there on it's easy to extend further for different use cases. For example, to bundle and generate multiple HTML files you can take a look at the documentation of [@open-wc/rollup-plugin-html](https://github.com/open-wc/open-wc/tree/master/packages/rollup-plugin-html#readme). See below how to add plugins to the rollup config.
|
||||
|
||||
## Injecting a service worker
|
||||
|
||||
When you use `createSpaConfig` a service worker is generated automatically with Workbox. The service worker registration is not injected into your `index.html` by default to prevent unexpected results. You can turn this on by enabling the `injectServiceWorker` option:
|
||||
|
||||
```js
|
||||
const baseConfig = createSpaConfig({
|
||||
injectServiceWorker: true,
|
||||
});
|
||||
```
|
||||
|
||||
If you overwrite the workbox configuration and the `swDest` property of the workbox config, `injectServiceWorker` will automatically use the value of `swDest` in the service worker registration. Example:
|
||||
|
||||
```js
|
||||
const baseConfig = createSpaConfig({
|
||||
injectServiceWorker: true,
|
||||
workbox: {
|
||||
swDest: './my-sw.js',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Will result in:
|
||||
|
||||
```js
|
||||
navigator.serviceWorker.register('./my-sw.js');
|
||||
```
|
||||
|
||||
## Supporting older browsers
|
||||
|
||||
The default build output works only on browsers that support modules. If you need to support older browsers, such as IE11 or the old Edge, you can set the `legacyBuild` option when you use the create config function.
|
||||
|
||||
This will create a separate rollup build output for legacy browsers and makes sure the correct version is loaded. This has minimal impact on users with modern browsers.
|
||||
|
||||
```js
|
||||
const baseConfig = createSpaConfig({
|
||||
legacyBuild: true,
|
||||
});
|
||||
```
|
||||
|
||||
## Customizations
|
||||
|
||||
Our config generator sets you up with good defaults for most projects. It's easy to extend and customize this config further for your requirements.
|
||||
|
||||
If you find yourself disabling a lot of the default functionality we recommend forking from the default config and taking control yourself. Rollup is relatively easy to configure compared to other build tools, it's better to be in full control if you know what you're doing.
|
||||
|
||||
### Customizing the babel config
|
||||
|
||||
You can define custom babel plugins to be loaded by adding a `.babelrc` or `babel.config.js` to your project. See [babeljs config](https://babeljs.io/docs/en/configuration) for more information.
|
||||
|
||||
For example to add support for class properties:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": ["@babel/plugin-proposal-class-properties"]
|
||||
}
|
||||
```
|
||||
|
||||
### Customizing default plugins
|
||||
|
||||
Our config creators install a number of rollup plugins by default:
|
||||
|
||||
Basic and SPA config plugins:
|
||||
|
||||
- [nodeResolve](https://github.com/rollup/plugins/tree/master/packages/node-resolve#readme)
|
||||
- [babel](https://github.com/rollup/plugins/tree/master/packages/babel#readme)
|
||||
- [terser](https://github.com/TrySound/rollup-plugin-terser#readme)
|
||||
|
||||
SPA config plugins:
|
||||
|
||||
- [html](https://github.com/open-wc/open-wc/tree/master/packages/rollup-plugin-html#readme)
|
||||
- [polyfillsLoader](https://github.com/open-wc/open-wc/tree/master/packages/polyfills-loader#readme)
|
||||
- [workbox](https://www.npmjs.com/package/rollup-plugin-workbox)
|
||||
|
||||
You can customize options for these plugins, or turn them off completely by setting them to false. Check the documentation for the respective plugins to learn which options can be configured.
|
||||
|
||||
<details>
|
||||
<summary>View example</summary>
|
||||
|
||||
Each plugin can be either "true", "false" or an object. If it's an object, this is used as a configuration for the plugin.
|
||||
|
||||
```js
|
||||
const baseConfig = createSpaConfig({
|
||||
nodeResolve: { browser: true, dedupe: ['lit-html'] },
|
||||
babel: true,
|
||||
terser: { exclude: ['node_modules*'] },
|
||||
html: false,
|
||||
polyfillsLoader: false,
|
||||
workbox: false,
|
||||
});
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
Examples:
|
||||
|
||||
### Customize HTML
|
||||
|
||||
[@open-wc/rollup-plugin-html](https://github.com/open-wc/open-wc/tree/master/packages/rollup-plugin-html#readme) powers a lot of what our rollup config does. It has a lot of options available, for example, to transform the HTML output or set a different template.
|
||||
|
||||
We recommend looking into the documentation to get an overview of all available options.
|
||||
|
||||
<details>
|
||||
<summary>View example</summary>
|
||||
|
||||
```js
|
||||
import packageJson from './package.json';
|
||||
|
||||
const baseConfig = createSpaConfig({
|
||||
html: {
|
||||
transform: [
|
||||
// inject lang attribute
|
||||
html => html.replace('<html>', '<html lang="en-GB">'),
|
||||
// inject app version
|
||||
html =>
|
||||
html.replace(
|
||||
'</body>',
|
||||
`<script>window.APP_VERSION = "${packageJson.version}"</script></body>`,
|
||||
),
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Customize polyfills
|
||||
|
||||
[@open-wc/rollup-plugin-polyills-loader](https://github.com/open-wc/open-wc/tree/master/packages/rollup-plugin-polyfills-loader#readme) loads polyfills only when necessary based on feature detection.
|
||||
|
||||
You can prevent certain polyfills from being loaded or add your own polyfills.
|
||||
|
||||
<details>
|
||||
<summary>View example</summary>
|
||||
|
||||
```js
|
||||
const baseConfig = createSpaConfig({
|
||||
polyfillsLoader: {
|
||||
polyfills: {
|
||||
webcomponents: false,
|
||||
intersectionObserver: true,
|
||||
resizeObserver: true,
|
||||
custom: [
|
||||
{
|
||||
name: 'my-feature-polyfill',
|
||||
path: require.resolve('my-feature-polyfill'),
|
||||
test: "!('myFeature' in window)",
|
||||
minify: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Customize built-in babel plugins
|
||||
|
||||
We add some babel plugins by default. These can be overwritten with a different configuration from the config. For example to change the html template minification, or add other modules to be minified:
|
||||
|
||||
<details>
|
||||
<summary>View example</summary>
|
||||
|
||||
```js
|
||||
const baseConfig = createSpaConfig({
|
||||
babel: {
|
||||
plugins: [
|
||||
[
|
||||
require.resolve('babel-plugin-template-html-minifier'),
|
||||
{
|
||||
modules: {
|
||||
'cool-html': ['html'],
|
||||
},
|
||||
htmlMinifier: {
|
||||
removeComments: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Extending the rollup config
|
||||
|
||||
A rollup config is just a plain object. It's easy to extend it using javascript. We recommend using the `deepmerge` library because it is an easy way to merge objects and arrays:
|
||||
|
||||
<details>
|
||||
<summary>View example</summary>
|
||||
|
||||
```javascript
|
||||
import merge from 'deepmerge';
|
||||
import { createSpaConfig } from '@open-wc/building-rollup';
|
||||
|
||||
const baseConfig = createSpaConfig();
|
||||
|
||||
export default merge(baseConfig, {
|
||||
// add your own rollup configuration here
|
||||
input: './index.html',
|
||||
output: {
|
||||
sourcemap: false,
|
||||
},
|
||||
plugins: [
|
||||
// add new plugins
|
||||
myPlugin(),
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
If you have enabled the legacy build option, the `output` option is an array. In that case, you cannot use deepmerge if you need to make changes to the `output` option.
|
||||
|
||||
<details>
|
||||
<summary>View example</summary>
|
||||
|
||||
```javascript
|
||||
import merge from 'deepmerge';
|
||||
import { createSpaConfig } from '@open-wc/building-rollup';
|
||||
|
||||
const baseConfig = createSpaConfig({
|
||||
legacyBuild: true,
|
||||
});
|
||||
|
||||
// set the sourcemap option on both outputs
|
||||
baseConfig.output[0].sourcemap = true;
|
||||
baseConfig.output[1].sourcemap = true;
|
||||
|
||||
export default merge(baseConfig, {
|
||||
input: './index.html',
|
||||
plugins: [
|
||||
// add new plugins
|
||||
myPlugin(),
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
#### Copying assets
|
||||
|
||||
To copy over assets, such as images, css or json files, we recommend using [rollup-plugin-copy](https://www.npmjs.com/package/rollup-plugin-copy)
|
||||
|
||||
<details>
|
||||
<summary>View example</summary>
|
||||
|
||||
```js
|
||||
import merge from 'deepmerge';
|
||||
import { createSpaConfig } from '@open-wc/building-rollup';
|
||||
import copy from 'rollup-plugin-copy';
|
||||
|
||||
const baseConfig = createSpaConfig();
|
||||
|
||||
export default merge(baseConfig, {
|
||||
input: './index.html',
|
||||
plugins: [
|
||||
copy({
|
||||
targets: [{ src: 'assets/**/*', dest: '/dist' }],
|
||||
// set flatten to false to preserve folder structure
|
||||
flatten: false,
|
||||
}),
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
#### Support for CommonJs modules
|
||||
|
||||
Rollup only supports standard es modules (using `import` and `export`). A lot of projects don't use this syntax yet, and instead use the CommonJs module format. This format uses `require` and `module.exports` statements, and is intended for NodeJs.
|
||||
|
||||
To support this in Rollup, you can add the [@rollup/plugin-commonjs](https://github.com/rollup/plugins/tree/master/packages/commonjs) plugin.
|
||||
|
||||
<details>
|
||||
<summary>View example</summary>
|
||||
|
||||
```js
|
||||
import merge from 'deepmerge';
|
||||
import { createSpaConfig } from '@open-wc/building-rollup';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
|
||||
const baseConfig = createSpaConfig();
|
||||
|
||||
export default merge(baseConfig, {
|
||||
input: './index.html',
|
||||
plugins: [commonjs()],
|
||||
});
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
#### Support for Typescript
|
||||
|
||||
<details>
|
||||
|
||||
<summary>View example</summary>
|
||||
|
||||
To support Typescript in rollup you have multiple options. You can run `tsc`, and then run rollup on the generated JS files. This is useful when you are already running `tsc` for use in other tools, such as a dev server. You can also use the [@rollup/plugin-typescript](https://github.com/rollup/plugins/tree/master/packages/typescript) plugin to integrate with rollup more directly. View their documentation for more information.
|
||||
|
||||
```js
|
||||
import merge from 'deepmerge';
|
||||
import { createSpaConfig } from '@open-wc/building-rollup';
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
|
||||
const baseConfig = createSpaConfig();
|
||||
|
||||
export default merge(baseConfig, {
|
||||
input: './index.html',
|
||||
plugins: [typescript()],
|
||||
});
|
||||
```
|
||||
|
||||
</details>
|
||||
3
docs/docs/demoing/index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Demoing ||30
|
||||
|
||||
Please see a sub page
|
||||
19
docs/docs/demoing/storybook-addon-markdown-docs.md
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
title: 'Storybook Addon: Markdown Docs'
|
||||
eleventyNavigation:
|
||||
key: 'Storybook Addon: Markdown Docs'
|
||||
order: 30
|
||||
parent: Tools
|
||||
---
|
||||
|
||||
[//]: # 'AUTO INSERT HEADER PREPUBLISH'
|
||||
|
||||
This extension for [Storybook](https://storybook.js.org/) enables you to use [Markdown JavaScript (mdjs) Format](https://open-wc.org/mdjs/).
|
||||
|
||||
## Installation
|
||||
|
||||
Please check out [@open-wc/demoing-storybook](https://open-wc.org/demoing/) for a fully integrated setup.
|
||||
|
||||
## How it works
|
||||
|
||||
This extension converts the mdjs output directly to JavaScript (in CSF format) which can be displayed by storybook docs.
|
||||
321
docs/docs/demoing/storybook.md
Normal file
@@ -0,0 +1,321 @@
|
||||
# Demoing >> Storybook ||10
|
||||
|
||||
For demoing, documenting and showcasing different states of your Web Component, we recommend using [storybook](https://storybook.js.org/).
|
||||
|
||||
[//]: # 'AUTO INSERT HEADER PREPUBLISH'
|
||||
|
||||
# Features
|
||||
|
||||
- Create API documentation/playground
|
||||
- Use Storybook docs mode to showcase your elements within the normal text flow
|
||||
- Works down to IE11
|
||||
- Prebuilt storybook UI (for a fast startup)
|
||||
- Uses es-dev-server (serve modern code while developing)
|
||||
- Completely separate storybook UI from your code
|
||||
|
||||
## Demo
|
||||
|
||||
```js script
|
||||
import '@d4kmor/launch/inline-notification/inline-notification.js';
|
||||
```
|
||||
|
||||
<inline-notification type="tip">
|
||||
|
||||
<p>Don't take our word for it but look at <a href="https://open-wc.org/demoing-storybook/?path=/docs/demo-card-docs--simple" target="_blank" rel="noopener noreferrer">the documentation of a demo card<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a> and <a href="https://open-wc.org/demoing-storybook/?path=/docs/decorators-withwebcomponentknobs--example-output" target="_blank" rel="noopener noreferrer">the documentation of the knobs decorator<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a>.</p>
|
||||
|
||||
</inline-notification>
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
npm init @open-wc
|
||||
# Upgrade > Demoing
|
||||
```
|
||||
|
||||
### Manual
|
||||
|
||||
- `yarn add @open-wc/demoing-storybook --dev`
|
||||
- Copy at minimum the [.storybook](https://github.com/open-wc/open-wc/tree/master/packages/create/src/generators/demoing-storybook/templates/static/.storybook) folder to `.storybook`
|
||||
- If you want to bring along the examples, you may also copy the `stories` folder.
|
||||
- Be sure you have a [custom-elements.json](#custom-elementsjson) file.
|
||||
- Add the following scripts to your package.json
|
||||
|
||||
```json
|
||||
"scripts": {
|
||||
"storybook": "start-storybook --node-resolve --watch --open",
|
||||
"storybook:build": "build-storybook"
|
||||
},
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
npm run storybook
|
||||
```
|
||||
|
||||
### CLI configuration
|
||||
|
||||
#### Dev server
|
||||
|
||||
The storybook server is based on [es-dev-server](https://open-wc.org/developing/es-dev-server.html) and accepts the same command line args. Check the docs for all available options.
|
||||
|
||||
#### Storybook specific
|
||||
|
||||
| name | type | description |
|
||||
| ---------- | ------ | ------------------------------------------------------------- |
|
||||
| config-dir | string | Where the storybook config files are. Default: `./.storybook` |
|
||||
| output-dir | string | Rollup build output directory. Default: `./static-storybook` |
|
||||
|
||||
### Configuration file
|
||||
|
||||
By default, storybook looks for a config file called `main.js` in your config dir (default `.storybook`). In this file you can configure storybook itself, `es-dev-server` and the `rollup` build configuration.
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
// Globs of all the stories in your project
|
||||
stories: ['../stories/*.stories.{js,mdx}'],
|
||||
|
||||
// Addons to be loaded, note that you need to import
|
||||
// them from storybook-prebuilt
|
||||
addons: [
|
||||
'storybook-prebuilt/addon-actions/register.js',
|
||||
'storybook-prebuilt/addon-knobs/register.js',
|
||||
'storybook-prebuilt/addon-a11y/register.js',
|
||||
'storybook-prebuilt/addon-docs/register.js',
|
||||
],
|
||||
|
||||
// Configuration for es-dev-server (start-storybook only)
|
||||
esDevServer: {
|
||||
nodeResolve: true,
|
||||
open: true,
|
||||
},
|
||||
|
||||
// Rollup build output directory (build-storybook only)
|
||||
outputDir: '../dist',
|
||||
// Configuration for rollup (build-storybook only)
|
||||
rollup: config => {
|
||||
return config;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Create documentation (mdjs)
|
||||
|
||||
Create a `*.stories.md` (for example `card.stories.md`) file within the `stories` folder.
|
||||
|
||||
This uses the [Markdown JavaScript (mdjs) Format](https://open-wc.org/mdjs/) via [storybook-addon-markdown-docs](https://open-wc.org/demoing/storybook-addon-markdown-docs.html).
|
||||
|
||||
````md
|
||||
```js script
|
||||
import '../demo-wc-card.js';
|
||||
|
||||
export default {
|
||||
title: 'Demo Card/Docs (markdown)',
|
||||
parameters: { component: 'demo-wc-card' },
|
||||
};
|
||||
```
|
||||
|
||||
# Demo Web Component Card
|
||||
|
||||
A component meant to display small information with additional data on the back.
|
||||
// [...] use markdown to format your text
|
||||
// the following demo is inline
|
||||
|
||||
```js story
|
||||
export const Simple = () => html` <demo-wc-card>Hello World</demo-wc-card> `;
|
||||
```
|
||||
|
||||
## Variations
|
||||
|
||||
Show demo with a frame and a "show code" button.
|
||||
|
||||
```js preview-story
|
||||
export const Simple = () => html` <demo-wc-card>Hello World</demo-wc-card> `;
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
The api table will show the data of "demo-wc-card" in your `custom-elements.json`.
|
||||
|
||||
<sb-props of="demo-wc-card"></sb-props>
|
||||
|
||||
// [...]
|
||||
````
|
||||
|
||||
### Create documentation (mdx)
|
||||
|
||||
Create a `*.stories.mdx` (for example `card.stories.mdx`) file within the `stories` folder.
|
||||
|
||||
```md
|
||||
import { Story, Preview, Meta, Props } from '@open-wc/demoing-storybook';
|
||||
import { html } from 'lit-html';
|
||||
import '../demo-wc-card.js';
|
||||
|
||||
<Meta title="Card|Docs" />
|
||||
|
||||
# Demo Web Component Card
|
||||
|
||||
A component meant to display small information with additional data on the back.
|
||||
// [...] use markdown to format your text
|
||||
|
||||
<Preview withToolbar>
|
||||
<Story name="Simple" height="220px">
|
||||
{html`
|
||||
<demo-wc-card>Hello World</demo-wc-card>
|
||||
`}
|
||||
</Story>
|
||||
</Preview>
|
||||
|
||||
## API
|
||||
|
||||
The api table will show the data of "demo-wc-card" in your `custom-elements.json`.
|
||||
|
||||
<Props of="demo-wc-card" />
|
||||
|
||||
// [...]
|
||||
```
|
||||
|
||||
### Create stories in CSF (Component story format)
|
||||
|
||||
Create a `*.stories.js` (for example `card-variations.stories.js`) file within the `stories` folder.
|
||||
|
||||
```js
|
||||
export default {
|
||||
title: 'Card|Variations',
|
||||
component: 'demo-wc-card',
|
||||
};
|
||||
|
||||
export const singleComponent = () => html` <demo-wc-card></demo-wc-card> `;
|
||||
```
|
||||
|
||||
For more details see the [official storybook docs](https://storybook.js.org/docs/formats/component-story-format/).
|
||||
|
||||
You can import these templates into any other place if needed.
|
||||
|
||||
For example in tests:
|
||||
|
||||
```js
|
||||
import { expect, fixture } from '@open-wc/testing';
|
||||
import { singleComponent } from '../stories/card-variations.stories.js';
|
||||
|
||||
it('has a header', async () => {
|
||||
const el = await fixture(singleComponent);
|
||||
expect(el.header).to.equal('Your Message');
|
||||
});
|
||||
```
|
||||
|
||||
### Create API playground
|
||||
|
||||
<inline-notification type="tip">
|
||||
|
||||
You can find a more interactive version of this in the [withWebComponentsKnobs docs](/demoing-storybook/?path=/docs/decorators-withwebcomponentknobs--example-output).
|
||||
|
||||
</inline-notification>
|
||||
|
||||
Based on the data in [custom-elements.json](./#custom-elementsjson), we can automatically generate knobs for your stories.
|
||||
|
||||
To enable this feature you will need to add an additional decorator.
|
||||
|
||||
**MDX**
|
||||
|
||||
```md
|
||||
import { withKnobs, withWebComponentsKnobs } from '@open-wc/demoing-storybook';
|
||||
|
||||
<Meta
|
||||
title="WithWebComponentsKnobs|Docs"
|
||||
decorators={[withKnobs, withWebComponentsKnobs]}
|
||||
parameters={{ component: 'demo-wc-card', options: { selectedPanel: 'storybookjs/knobs/panel' } }}
|
||||
/>
|
||||
|
||||
<Story name="Custom Header" height="220px">
|
||||
{html`
|
||||
<demo-wc-card header="Harry Potter">A character that is part of a book series...</demo-wc-card>
|
||||
`}
|
||||
</Story>
|
||||
```
|
||||
|
||||
**CSF**
|
||||
|
||||
```js
|
||||
import { html } from 'lit-html';
|
||||
import { withKnobs, withWebComponentsKnobs } from '@open-wc/demoing-storybook';
|
||||
|
||||
import '../demo-wc-card.js';
|
||||
|
||||
export default {
|
||||
title: 'Card|Playground',
|
||||
component: 'demo-wc-card',
|
||||
decorators: [withKnobs, withWebComponentsKnobs],
|
||||
parameters: { options: { selectedPanel: 'storybookjs/knobs/panel' } },
|
||||
};
|
||||
|
||||
export const singleComponent = () => html` <demo-wc-card></demo-wc-card> `;
|
||||
```
|
||||
|
||||
For additional features like
|
||||
|
||||
- define which components to show knobs for
|
||||
- showing knobs for multiple different components
|
||||
- syncing components states to knobs
|
||||
- Filtering properties and debugging states
|
||||
|
||||
please see the official [documentation of the knobs for web components decorator](/demoing-storybook/?path=/docs/decorators-withwebcomponentknobs--example-output).
|
||||
|
||||
### custom-elements.json
|
||||
|
||||
In order to get documentation for web-components you will need to have a [custom-elements.json](https://github.com/webcomponents/custom-elementsjson) file.
|
||||
You can handwrite it or better generate it. Depending on the web components sugar you are choosing your mileage may vary.
|
||||
Please note that the details of the file are still being discussed so we may adopt to changes in `custom-elements.json` without a breaking release.
|
||||
|
||||
Known analyzers that output `custom-elements.json`:
|
||||
|
||||
- [web-component-analyzer](https://github.com/runem/web-component-analyzer)
|
||||
- Supports LitElement, Polymer, Vanilla, (Stencil)
|
||||
- [stenciljs](https://stenciljs.com/)
|
||||
- Supports Stencil (but does not have all metadata)
|
||||
|
||||
It basically looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 2,
|
||||
"tags": [
|
||||
{
|
||||
"name": "demo-wc-card",
|
||||
"properties": [
|
||||
{
|
||||
"name": "header",
|
||||
"type": "String",
|
||||
"description": "Shown at the top of the card"
|
||||
}
|
||||
],
|
||||
"events": [],
|
||||
"slots": [],
|
||||
"cssProperties": []
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
For a full example see the [./demo/custom-elements.json](./demo/custom-elements.json).
|
||||
|
||||
### Additional middleware config like an api proxy
|
||||
|
||||
As we are using [es-dev-server](https://open-wc.org/developing/es-dev-server.html) under the hood you can use all it's power. You can use the regular command line flags, or provide your own config via `start storybook -c /path/to/config.js`.
|
||||
|
||||
To set up a proxy, you can set up a koa middleware. [Read more about koa here.](https://koajs.com/)
|
||||
|
||||
```javascript
|
||||
const proxy = require('koa-proxies');
|
||||
|
||||
module.exports = {
|
||||
esDevServer: {
|
||||
port: 9000,
|
||||
middlewares: [
|
||||
proxy('/api', {
|
||||
target: 'http://localhost:9001',
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
205
docs/docs/development/dedupe-mixin.md
Normal file
@@ -0,0 +1,205 @@
|
||||
# Development >> Dedupe Mixin ||30
|
||||
|
||||
Automatically Deduplicate JavaScript Class Mixins
|
||||
|
||||
[//]: # 'AUTO INSERT HEADER PREPUBLISH'
|
||||
|
||||
## Features
|
||||
|
||||
- Small
|
||||
- Fast
|
||||
- Typed
|
||||
|
||||
## Usage
|
||||
|
||||
Apply it to each mixin in the chain to make sure they are not applied more than once to the final class.
|
||||
|
||||
```js
|
||||
import { dedupeMixin } from '@open-wc/dedupe-mixin';
|
||||
|
||||
export const MyMixin = dedupeMixin(
|
||||
superclass =>
|
||||
class MyMixin extends superclass {
|
||||
// your mixin code goes here
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
## What is a Mixin?
|
||||
|
||||
> A mixin is an abstract subclass; i.e. a subclass definition that may be applied to different superclasses to create a related family of modified classes.
|
||||
>
|
||||
> - Gilad Bracha and William Cook, [Mixin-based Inheritance](http://www.bracha.org/oopsla90.pdf)
|
||||
|
||||
Let's take for example Logging. Imagine you have 3 Pages
|
||||
|
||||
- Red
|
||||
- Green
|
||||
- Blue
|
||||
|
||||
```
|
||||
+----------+
|
||||
| Page |
|
||||
+----------+
|
||||
| | |
|
||||
+----------+ | +-----------+
|
||||
| | |
|
||||
+---------+ +-----------+ +----------+
|
||||
| PageRed | | PageGreen | | PageBlue |
|
||||
+----+----+ +-----------+ +----------+
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
class Page {}
|
||||
class PageRed extends Page {}
|
||||
class PageGreen extends Page {}
|
||||
class PageBlue extends Page {}
|
||||
```
|
||||
|
||||
Now we want to log whenever someone goes on Page Red.
|
||||
To archive that we extend Page Red and make a Logged Page Red.
|
||||
|
||||
```
|
||||
+----------+
|
||||
| Page |
|
||||
+-+--+--+--+
|
||||
| | |
|
||||
+----------+ | +-----------+
|
||||
| | |
|
||||
+----+----+ +-----+-----+ +-----+----+
|
||||
| PageRed | | PageGreen | | PageBlue |
|
||||
+----+----+ +-----------+ +----------+
|
||||
|
|
||||
+----+----+
|
||||
| Logged |
|
||||
| PageRed |
|
||||
+---------+
|
||||
```
|
||||
|
||||
```js
|
||||
class Page {}
|
||||
class PageRed extends Page {}
|
||||
class PageGreen extends Page {}
|
||||
class PageBlue extends Page {}
|
||||
class LoggedPagRed extends PageRed {}
|
||||
```
|
||||
|
||||
If we want to start logging for PageGreen we have an issue:
|
||||
|
||||
- we can't put the logic in `Page` as Blue should not be logged
|
||||
- we can't reuse the logic in `Logged PageGreen` as we can not extend from 2 source (even if we could it would mean conflicting info in Red and Green)
|
||||
|
||||
What we can do is put it in an "external" place and write it so it can be "mixed in".
|
||||
|
||||
```
|
||||
+----------+ +----------+
|
||||
| Page | | Logging* |
|
||||
+-+--+--+--+ +----------+
|
||||
| | |
|
||||
+----------+ | +-----------+
|
||||
| | |
|
||||
+-----+----+ +-----+-----+ +-----+----+
|
||||
| PageRed | | PageGreen | | PageBlue |
|
||||
| with | | with | +----------+
|
||||
| Logging* | | Logging* |
|
||||
+----------+ +-----------+
|
||||
```
|
||||
|
||||
```js
|
||||
// defining the Mixin
|
||||
export const LoggingMixin = superclass =>
|
||||
class LoggingMixin extends superclass {
|
||||
// logging logic
|
||||
};
|
||||
|
||||
class Page {}
|
||||
// applying a Mixin
|
||||
class PageRed extends LoggingMixin(Page) {}
|
||||
class PageGreen extends LoggingMixin(Page) {}
|
||||
class PageBlue extends Page {}
|
||||
```
|
||||
|
||||
With that approach we can extract logic into a separate code pieces we can use where needed.
|
||||
|
||||
For a more in depth technical explanation please read [Real Mixins with JavaScript Classes](https://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/).
|
||||
|
||||
## Why is Deduping of Mixins Necessary?
|
||||
|
||||
We now want all logging to the Red, Green, and Blue pages.
|
||||
Easy enough - as we can now apply the LoggingMixin on the Page itself.
|
||||
|
||||
```
|
||||
+----------+ +----------+
|
||||
| Page | | Logging* |
|
||||
| with | +----------+
|
||||
| Logging* |
|
||||
+-+--+--+--+
|
||||
| | |
|
||||
+----------+ | +-----------+
|
||||
| | |
|
||||
+-----+----+ +-----+-----+ +-----+----+
|
||||
| PageRed | | PageGreen | | PageBlue |
|
||||
+----------+ | with | +----------+
|
||||
| Logging* |
|
||||
+-----------+
|
||||
```
|
||||
|
||||
However, Team Green were eager to launch, so they already applied `LoggingMixin` to their Page class. When we apply it to the base `Page` class, Mixin is now applied twice 😱
|
||||
Suddenly, the Green page will print each log twice - not what we originally had in mind.
|
||||
|
||||
What we need to do is make sure that each Mixin is attached only once even if we try to apply it multiple times.
|
||||
|
||||
Generally the more generic a mixin is, the higher the chance becomes that is gets applied more than once. As a mixin author you can't control how it is used, and can't always predict it. So as a safety measure it is always recommended to create deduping mixins.
|
||||
|
||||
```js
|
||||
import { dedupeMixin } from '@open-wc/dedupe-mixin';
|
||||
|
||||
export const MyMixin = dedupeMixin(
|
||||
superclass =>
|
||||
class MyMixin extends superclass {
|
||||
// your mixin code goes here
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
You can see exactly this situation in the demo.
|
||||
|
||||
By applying dedupeMixin to the mixin function, before we export it, we can be sure that our mixin class will only take effect once, even if it is mixed in to multiple base classes in the inheritance chain.
|
||||
|
||||
- [no-dedupe](/dedupe-mixin/demo/no-dedupe/) "fails" by logging Green two times
|
||||
- [with-dedupe](/dedupe-mixin/demo/with-dedupe/) "succeeds" by logging Green one time as well
|
||||
|
||||
You can check the source code for both on [github](https://github.com/open-wc/open-wc/tree/master/packages/dedupe-mixin/demo-typed).
|
||||
|
||||
### Nested examples
|
||||
|
||||
You may think that the above example is too simple and can be solved by aligning on when to do changes.
|
||||
However in most real live scenarios the situation is much more complicated 🙈
|
||||
Mixins can be extended and just because you import a class it does not meant that this class has some Mixins pre applied.
|
||||
|
||||
Consider this example:
|
||||
|
||||
```
|
||||
+----------+ +----------+ +----------+
|
||||
| Page | | Logging* | | Feature |
|
||||
| with | +----+-----+ | with |
|
||||
| Logging* | | | Metrics* |
|
||||
+-+--+--+--+ +----+-----+ +----+--+--+
|
||||
| | | | Metrics* | | |
|
||||
+----------+ | +-----------+ +----------+ | +------
|
||||
| | | |
|
||||
+-----+----+ +-----+-----+ +-----+----+ +------+-------+
|
||||
| PageRed | | PageGreen | | PageBlue | | WaterFeature |
|
||||
+----------+ +-----------+ | with | +--------------+
|
||||
| Metrics* |
|
||||
+----------+
|
||||
```
|
||||
|
||||
- Pages generally only need Logging
|
||||
- There is however also more advanced Metrics System which extends Logging
|
||||
- Metrics was separately developed for Features
|
||||
- When we now want to get the same Metrics on Page Blue we get duplicate logging without consciously applying logging even once (we do `class PageBlue extends MetricsMixin(Page) {}`)
|
||||
- Only deduping can help in these scenarios
|
||||
|
||||
_Ascii Graphics made with [AsciiFlow](http://asciiflow.com/)_
|
||||
@@ -1,25 +1,13 @@
|
||||
---
|
||||
permalink: 'init/index.html'
|
||||
title: Create Open Web Components
|
||||
section: guides
|
||||
tags:
|
||||
- guides
|
||||
---
|
||||
|
||||
# Create Open Web Components
|
||||
# Development >> Generator ||15
|
||||
|
||||
Web component project scaffolding.
|
||||
|
||||
[//]: # 'AUTO INSERT HEADER PREPUBLISH'
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
npm init @open-wc
|
||||
```
|
||||
|
||||
<div class="custom-block warning"><p class="custom-block-title">WARNING</p> <p><code>npm init</code> requires node 10 & npm 6 or higher</p></div>
|
||||
|
||||
This will kickstart a menu guiding you through all available actions.
|
||||
|
||||
```
|
||||
@@ -87,7 +75,7 @@ npm init @open-wc
|
||||
<br/>
|
||||
|
||||
- `Testing`<br>
|
||||
This generator adds a complete testing setup with Web Test Runner.
|
||||
This generator adds a complete testing setup with Karma.
|
||||
<br/>
|
||||
|
||||
- `Demoing`<br>
|
||||
3
docs/docs/development/index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Development ||10
|
||||
|
||||
Please see a sub page
|
||||
196
docs/docs/development/lit-helpers.md
Normal file
@@ -0,0 +1,196 @@
|
||||
# Development >> Lit Helpers ||20
|
||||
|
||||
A library with helpers functions for working with [lit-html](https://lit-html.polymer-project.org/) and [lit-element](https://lit-element.polymer-project.org/)
|
||||
|
||||
[//]: # 'AUTO INSERT HEADER PREPUBLISH'
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm i --save @open-wc/lit-helpers
|
||||
```
|
||||
|
||||
## Spread directives
|
||||
|
||||
Spread directives can be used to set an arbitrary collection of properties, attributes or event listeners on an element without knowing all the keys in advance.
|
||||
|
||||
The spread directives work by taking in an object of data to spread. Because of `lit-html` syntax, we need one attribute to apply the directive to. It doesn't actually matter what the name of this attribute is, we recommend sticking to the convention of using `...` as the attribute name.
|
||||
|
||||
### Regular spread
|
||||
|
||||
The regular `spread` directive can be used to set properties, attribute or event listeners. It uses the same syntax as `lit-html` for distinguishing different types of bindings:
|
||||
|
||||
```js
|
||||
import { html, render } from 'lit-html';
|
||||
import { spread } from '@open-wc/lit-helpers';
|
||||
|
||||
render(
|
||||
html`
|
||||
<div
|
||||
...=${spread({
|
||||
'my-attribute': 'foo',
|
||||
'?my-boolean-attribute': true
|
||||
'.myProperty': { foo: 'bar' },
|
||||
'@my-event': () => console.log('my-event fired'),
|
||||
})}
|
||||
></div>
|
||||
`,
|
||||
document.body,
|
||||
);
|
||||
```
|
||||
|
||||
### Property spread
|
||||
|
||||
Because spreading properties is a common use case, you can use the `spreadProps` directive so that you don't need prefix each property with a `.`. This is especially useful when the data comes from external sources.
|
||||
|
||||
```js
|
||||
import { html, render } from 'lit-html';
|
||||
import { spreadProps } from '@open-wc/lit-helpers';
|
||||
|
||||
render(html` <div ...="${spreadProps({ propertyA: 'a', propertyB: 'b' })}"></div> `, document.body);
|
||||
```
|
||||
|
||||
### Attribute spread
|
||||
|
||||
Since `spread` already spreads attribute as the default case, we do not need a separate `spreadAttributes` directive.
|
||||
|
||||
### Re-rendering
|
||||
|
||||
When re-rendering, values are updated if they changed from the previous value. We use the same strict equality check (`!==`) as `lit-html` for this.
|
||||
|
||||
Unlike `lit-html`, when spreading attributes we remove the attribute if it is set to `null` or `undefined`.
|
||||
|
||||
If an entry in the spread data is removed, the property is set to undefined, the attribute is removed or the event listener is deregistered.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
function renderSpread(data) {
|
||||
render(html` <div ...="${spread(data)}"></div> `, document.body);
|
||||
}
|
||||
|
||||
// result: <div foo="bar">
|
||||
renderSpread({ foo: 'bar' });
|
||||
|
||||
// result: <div foo="buz">
|
||||
renderSpread({ foo: 'buz' });
|
||||
|
||||
// result: <div foo="buz" lorem="ipsum">
|
||||
renderSpread({ foo: 'buz', lorem: 'ipsum' });
|
||||
|
||||
// result: <div foo="buz">
|
||||
renderSpread({ foo: 'buz' });
|
||||
|
||||
// result: <div>
|
||||
renderSpread({ foo: 'undefined' });
|
||||
```
|
||||
|
||||
## Live binding
|
||||
|
||||
For efficiency, lit-html does not set properties or attributes if they did not change since the previous render. This can cause problems when the properties are changed outside of lit-html's control. The `live` directive can be used to dirty check the element's live value, and set the property or attribute if it changed.
|
||||
|
||||
A great example for this, is the DOM element's `scrollTop` property which changes without lit-html knowing about it when the user scrolls.
|
||||
|
||||
By using the `live` directive, you can make sure it is always in sync with the value rendered by `lit-html`:
|
||||
|
||||
```js
|
||||
html` <my-element .scrollTop=${live(scrollTop)}></my-element> `;
|
||||
```
|
||||
|
||||
## Privately Settable Read-Only Properties
|
||||
|
||||
`ReadOnlyPropertiesMixin` provides a way for based on `LitElement` (or it's parent class `UpdatingElement`) to define properties by adding `readOnly: true` to their property declaration. Those properties are read-only from the outside, but can be updated internally with the `setReadOnlyProperties` method.
|
||||
|
||||
```js
|
||||
import { ReadOnlyPropertiesMixin } from '@open-wc/lit-helpers';
|
||||
import { LitElement } from 'lit-element';
|
||||
class SettableElement extends ReadOnlyPropertiesMixin(LitElement) {
|
||||
static get properties() {
|
||||
return {
|
||||
timestamp: { type: Number, readOnly: true },
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.timestamp = Date.now();
|
||||
}
|
||||
|
||||
updateTime() {
|
||||
this.setReadOnlyProperties({ timestamp: Date.now() });
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<button @click="${this.updateTime}">Update Time</button>
|
||||
<time>${this.timestamp}</time>
|
||||
`;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The mixin also supports the `@property` decorator.
|
||||
|
||||
```js
|
||||
import { ReadOnlyPropertiesMixin } from '@open-wc/lit-helpers';
|
||||
import { LitElement, property } from 'lit-element';
|
||||
class SettableElement extends ReadOnlyPropertiesMixin(LitElement) {
|
||||
@property({ type: Number, readOnly: true }) timestamp = Date.now();
|
||||
|
||||
updateTime() {
|
||||
this.setReadOnlyProperties({ timestamp: Date.now() });
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<button @click="${this.updateTime}">Update Time</button>
|
||||
<time>${this.timestamp}</time>
|
||||
`;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Known Limitations
|
||||
|
||||
#### Order of Application Matters
|
||||
|
||||
Currently, this mixin only works properly when applied to LitElement (or UpdatingElement) directly. In other words, if you have a component which inherits like the example below, then `ReadOnlyPropertiesMixin` must be applied to `LitElement` only.
|
||||
|
||||
```js
|
||||
// Bad
|
||||
class Lowest extends LitElement {
|
||||
@property({ readOnly: true }) lowestProperty = undefined;
|
||||
}
|
||||
|
||||
class Highest extends ReadOnlyPropertiesMixin(Lowest) {
|
||||
// will not work as expected
|
||||
@property({ readOnly: true }) highestProperty = undefined;
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
// Good
|
||||
class Lowest extends ReadOnlyPropertiesMixin(LitElement) {
|
||||
@property({ readOnly: true }) lowestProperty = undefined;
|
||||
}
|
||||
|
||||
class Highest extends Lowest {
|
||||
@property({ readOnly: true }) highestProperty = undefined;
|
||||
}
|
||||
```
|
||||
|
||||
#### Properties Must be Initialized
|
||||
|
||||
Read only properties must be initialized with some value, even if the value is `undefined`. This is because the mixin allows one free setting, to support class field initialization.
|
||||
|
||||
```js
|
||||
// Bad
|
||||
class Uninitialized extends ReadOnlyPropertiesMixin(LitElement) {
|
||||
@property({ readOnly: true }) uninitialized;
|
||||
}
|
||||
|
||||
// Good
|
||||
class Initialized extends ReadOnlyPropertiesMixin(LitElement) {
|
||||
@property({ readOnly: true }) initialized = undefined;
|
||||
}
|
||||
```
|
||||
323
docs/docs/development/scoped-elements.md
Normal file
@@ -0,0 +1,323 @@
|
||||
# Development >> Scoped Elements ||40
|
||||
|
||||
Scope element tag names avoiding naming collision and allowing to use different versions of the same web component in your code.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm i --save @open-wc/scoped-elements
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
1. Import `ScopedElementsMixin` from `@open-wc/scoped-elements`.
|
||||
|
||||
```js
|
||||
import { ScopedElementsMixin } from '@open-wc/scoped-elements';
|
||||
```
|
||||
|
||||
2. Import the classes of the components you want to use.
|
||||
|
||||
```js
|
||||
import { MyButton } from './MyButton.js';
|
||||
import { MyPanel } from './MyPanel.js';
|
||||
```
|
||||
|
||||
3. Apply `ScopedElementsMixin` and define the tags you want to use for your components.
|
||||
|
||||
```js
|
||||
class MyElement extends ScopedElementsMixin(LitElement) {
|
||||
static get scopedElements() {
|
||||
return {
|
||||
'my-button': MyButton,
|
||||
'my-panel': MyPanel,
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> WARNING: If you are going to use elements that are globally defined you have to declare them in `scopedElements` as well. This is required because we are trying to work as close as possible to the future Scoped Custom Element Registries feature and, by the moment, there is not going ot be inheritance between registries.
|
||||
>
|
||||
> You can declare them like in the following example:
|
||||
>
|
||||
> ```js
|
||||
> static get scopedElements() {
|
||||
> return {
|
||||
> 'old-button': customElements.get('old-button'),
|
||||
> 'my-panel': MyPanel,
|
||||
> };
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> If you try to register the same element globally AND locally with the exact same name AND class instance it will reuse the global tag name and NOT scope it.
|
||||
|
||||
4. Use your components in your html.
|
||||
|
||||
```js
|
||||
render() {
|
||||
return html`
|
||||
<my-panel class="panel">
|
||||
<my-button>${this.text}</my-button>
|
||||
</my-panel>
|
||||
`;
|
||||
}
|
||||
```
|
||||
|
||||
### Complete example
|
||||
|
||||
```js
|
||||
import { css, LitElement } from 'lit-element';
|
||||
import { ScopedElementsMixin } from '@open-wc/scoped-elements';
|
||||
import { MyButton } from './MyButton.js';
|
||||
import { MyPanel } from './MyPanel.js';
|
||||
|
||||
export class MyElement extends ScopedElementsMixin(LitElement) {
|
||||
static get scopedElements() {
|
||||
return {
|
||||
'my-button': MyButton,
|
||||
'my-panel': MyPanel,
|
||||
};
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
.panel {
|
||||
padding: 10px;
|
||||
background-color: grey;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
text: String,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<my-panel class="panel">
|
||||
<my-button>${this.text}</my-button>
|
||||
</my-panel}>
|
||||
`;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Lazy scoped components
|
||||
|
||||
In some situations may happen that you want to use a component in your templates that is not already loaded at the moment of defining the scoped elements map. The `ScopedElementsMixin` provides the `defineScopedElement` method to define scoped elements at any time.
|
||||
|
||||
```js
|
||||
import { LitElement } from 'lit-element';
|
||||
import { ScopedElementsMixin } from '@open-wc/scoped-elements';
|
||||
import { MyPanel } from './MyPanel.js';
|
||||
|
||||
export class MyElement extends ScopedElementsMixin(LitElement) {
|
||||
static get scopedElements() {
|
||||
return {
|
||||
'my-panel': MyPanel,
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
import('./MyButton.js').then(({ MyButton }) => this.defineScopedElement('my-button', MyButton));
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<my-panel class="panel">
|
||||
<my-button>${this.text}</my-button>
|
||||
</my-panel>
|
||||
`;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Obtaining a scoped tag name
|
||||
|
||||
Maybe you want to create a scoped element programmatically and don't know which one is the scoped tag name? No problem, there is a static method called `getScopedTagName` that would help you for that.
|
||||
|
||||
```js
|
||||
import { LitElement } from 'lit-element';
|
||||
import { ScopedElementsMixin } from '@open-wc/scoped-elements';
|
||||
import { MyButton } from './MyButton.js';
|
||||
import { MyPanel } from './MyPanel.js';
|
||||
|
||||
export class MyElement extends ScopedElementsMixin(LitElement) {
|
||||
static get scopedElements() {
|
||||
return {
|
||||
'my-panel': MyPanel,
|
||||
'my-button': MyButton,
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const scopedTagName = this.constructor.getScopedTagName('my-button');
|
||||
|
||||
// do whatever you need with the scopedTagName
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Motivation
|
||||
|
||||
Complex Web Component applications are often developed by several teams across organizations. In that scenario it is common that shared component libraries are used by teams to create a homogeneous look and feel or just to avoid creating the same components multiple times, but as those libraries evolve problems between different versions of the same library may appear, as teams may not be able to evolve and update their code at the same velocity. This causes bottlenecks in software delivery that should be managed by the teams and complex build systems, to try to alleviate the problem.
|
||||
|
||||
[Scoped Custom Element Registries](https://github.com/w3c/webcomponents/issues/716) is a proposal that will solve this problem, but until it is ready, or a polyfill becomes available, we have to _scope_ custom element tag names if we want to use different versions of those custom elements in our code. This package allows you to forget about how custom elements are defined, registered and scopes their tag names if it is necessary, and avoids the name collision problem.
|
||||
|
||||
## Use case and demos
|
||||
|
||||
Consider the following setup
|
||||
|
||||
- **Team Blue** owns **Page A**
|
||||
- **Team Green** owns **Page B**
|
||||
- **Team Black** owns **Feature A & B**
|
||||
|
||||
1. Everything is good and [your app is live](https://open-wc.org/scoped-elements/demo/before-nesting/) [[code](https://github.com/open-wc/open-wc/tree/master/packages/scoped-elements/demo/before-nesting)] with both pages.
|
||||
2. **Team Black** releases a new version (**2.x**) of **Feature B** which unfortunately needs to be breaking in order to support new use-cases.
|
||||
3. **Team Blue** (on **Page A**) does not use any of those new use cases, and they have a tight deadline to meet, so they cannot update right now.
|
||||
4. **Team Green** (on **Page B**) has to deliver an important functionality to your end users, but they need to upgrade to **Feature B 2.x** since it can only be solved with this new version.
|
||||
5. Since **Feature A 1.x & 2.x** are both used in the same app, this will lead to nested dependencies, which then will lead to [catastrophic failure, and errors](https://open-wc.org/scoped-elements/demo/no-scope/) [[code](https://github.com/open-wc/open-wc/tree/master/packages/scoped-elements/demo/no-scope)].
|
||||
|
||||
Two possible solutions come to mind:
|
||||
|
||||
1. Temporarily (!) allow shipping similar source code (most breaking releases are not a total rewrite) and scope them via `@open-wc/scoped-elements`; see the "fixed" example [with-scope](https://open-wc.org/scoped-elements/demo/with-scope/) [[code](https://github.com/open-wc/open-wc/tree/master/packages/scoped-elements/demo/with-scope)] running with nested dependencies.
|
||||
2. Synchronizing updates of shared dependencies - e.g. make sure **Team Blue** & **Team Green** always use the same version when releasing. This can be a viable solution however it comes with a high organizational overhead and is hard to scale up (for 10+ teams)
|
||||
|
||||
#### Technical explanation of scenario
|
||||
|
||||
The simplified app has the following dependencies
|
||||
|
||||
- app
|
||||
- page-a
|
||||
- feature-a 1.x
|
||||
- feature-b 1.x
|
||||
- page-b
|
||||
- feature-a 2.x
|
||||
- feature-b 1.x
|
||||
|
||||
which leads to the following node_modules tree
|
||||
|
||||
```
|
||||
├── node_modules
|
||||
│ ├── feature-a
|
||||
│ ├── feature-b
|
||||
│ ├── page-a
|
||||
│ └── page-b
|
||||
│ └── node_modules
|
||||
│ └── feature-a
|
||||
├── demo-app.js
|
||||
└── index.html
|
||||
```
|
||||
|
||||
To demonstrate, we made three demos:
|
||||
|
||||
1. [before-nesting](https://open-wc.org/scoped-elements/demo/before-nesting/) [[code](https://github.com/open-wc/open-wc/tree/master/packages/scoped-elements/demo/before-nesting)] In this demo, everything works fine as **Page A and B** both are using the same version of **Feature A**
|
||||
|
||||
2. [no-scope](https://open-wc.org/scoped-elements/demo/no-scope/) [[code](https://github.com/open-wc/open-wc/tree/master/packages/scoped-elements/demo/no-scope)] **Feature A** version **1.x** and **2.x** are imported via self registering entry points which leads to the following error message, because the `feature-a` component tries to register multiple times:
|
||||
|
||||
```
|
||||
Uncaught DOMException: Failed to execute 'define' on 'CustomElementRegistry': the name "feature-a" has already been used with this registry
|
||||
at [...]/node_modules/page-b/node_modules/feature-a/feature-a.js:3:16
|
||||
```
|
||||
|
||||
3. [with-scope](https://open-wc.org/scoped-elements/demo/with-scope/) [[code](https://github.com/open-wc/open-wc/tree/master/packages/scoped-elements/demo/with-scope)] This example successfully fixes the problem by using `ScopedElementsMixin` on both **Page A** and **Page B**.
|
||||
|
||||
## How it works
|
||||
|
||||
`ScopedElementsMixin` is mixed into your LitElement and via `static get scopedElements()` you define the tags and classes you wanna use in your elements template.
|
||||
Under the hood it changes your template so `<my-button>${this.text}</my-button>` becomes `<my-button-2748 data-tag-name="my-button">${this.text}</my-button-2748>`.
|
||||
|
||||
Every auto-defined scoped elements gets a random\* 4 digits number suffix. This suffix changes every time to make sure developers are not inclined to use it the generated tag name as a styling hook. Additionally the suffix allows scoped-elements and traditional self-defined elements to coexist, avoiding name collision.
|
||||
|
||||
\* it is actually a global counter that gets initialized with a random starting number on load
|
||||
|
||||
## Limitations
|
||||
|
||||
1. Components imported via npm **SHOULD NOT** be self registering components. If a shared component (installed from npm) does not offer an export to the class alone, without the registration side effect, then this component may not be used. E.g. every component that calls `customElement.define`.
|
||||
|
||||
```js
|
||||
export class MyEl { ... }
|
||||
customElement.define('my-el', MyEl);
|
||||
```
|
||||
|
||||
Or uses the `customElement` typescript decorator
|
||||
|
||||
```ts
|
||||
@customElement('my-el')
|
||||
export class MyEl { ... }
|
||||
```
|
||||
|
||||
Only side effects free class exports may be used
|
||||
|
||||
```js
|
||||
export class MyEl { ... }
|
||||
```
|
||||
|
||||
2. Every component that uses sub components should use `scoped-elements`. Any import to a self registering component can potentially result in a browser exception - completely breaking the whole application.
|
||||
|
||||
3. Imported elements should be fully side effect free (not only element registration)
|
||||
|
||||
4. Currently, only `lit-element` is supported (though other elements/rendering engines could be incorporated in the future).
|
||||
|
||||
5. You cannot use tag selectors in css, but you could use an id, a class name or even a property instead.
|
||||
|
||||
```css
|
||||
🚫 my-panel {
|
||||
width: 300px;
|
||||
}
|
||||
✅ .panel {
|
||||
width: 300px;
|
||||
}
|
||||
```
|
||||
|
||||
6. You cannot use tag names using javascript querySelectors, but you could use an id, a class name or even a property instead.
|
||||
|
||||
```js
|
||||
🚫 this.shadowRoot.querySelector('my-panel');
|
||||
✅ this.shadowRoot.querySelector('.panel');
|
||||
```
|
||||
|
||||
7. Using `scoped-elements` may result in a performance degradation of up to 8%.
|
||||
8. Loading of duplicate/similar source code (most breaking releases are not a total rewrite) should always be a temporary solution.
|
||||
9. Often, temporary solutions tend to become more permanent. Be sure to focus on keeping the lifecycle of nested dependencies short.
|
||||
|
||||
## Performance
|
||||
|
||||
We are using [Tachometer](https://github.com/Polymer/tachometer) to measure the performance penalty of using the scoped elements feature. The chosen test application is a slight variation of the [Polymer Shop Application](https://shop.polymer-project.org).
|
||||
|
||||
This is an example of the results obtained running the performance test.
|
||||
|
||||
```
|
||||
⠋ Auto-sample 560 (timeout in 16m27s)
|
||||
┌─────────────┬───────────────┐
|
||||
│ Version │ <none> │
|
||||
├─────────────┼───────────────┤
|
||||
│ Browser │ chrome │
|
||||
│ │ 80.0.3987.106 │
|
||||
├─────────────┼───────────────┤
|
||||
│ Sample size │ 610 │
|
||||
└─────────────┴───────────────┘
|
||||
┌─────────────────────────────┬────────────┬─────────────────────┬─────────────────┬──────────────────────────┐
|
||||
│ Benchmark │ Bytes │ Avg time │ vs lit-element │ vs scoped-elements-mixin │
|
||||
├─────────────────────────────┼────────────┼─────────────────────┼─────────────────┼──────────────────────────┤
|
||||
│ lit-element │ 281.24 KiB │ 285.72ms - 286.69ms │ │ faster │
|
||||
│ │ │ │ - │ 2% - 2% │
|
||||
│ │ │ │ │ 5.68ms - 7.1ms │
|
||||
├─────────────────────────────┼────────────┼─────────────────────┼─────────────────┼──────────────────────────┤
|
||||
│ scoped-elements-mixin │ 283.21 KiB │ 292.08ms - 293.11ms │ slower │ │
|
||||
│ │ │ │ 2% - 2% │ - │
|
||||
│ │ │ │ 5.68ms - 7.10ms │ │
|
||||
└─────────────────────────────┴────────────┴─────────────────────┴─────────────────┴──────────────────────────┘
|
||||
```
|
||||
|
||||
## Special thanks
|
||||
|
||||
This package was initially inspired by [carehtml](https://github.com/bashmish/carehtml) and we would like to thank [@bashmish](https://github.com/bashmish) for his work on it.
|
||||
3
docs/docs/experimental/index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Experimental ||60
|
||||
|
||||
Please see a sub page
|
||||
@@ -1,14 +1,6 @@
|
||||
---
|
||||
permalink: 'mdjs/index.html'
|
||||
title: Markdown JavaScript (mdjs) Format
|
||||
section: guides
|
||||
tags:
|
||||
- guides
|
||||
---
|
||||
# Experimental >> Markdown JavaScript || 10
|
||||
|
||||
# Markdown JavaScript (mdjs) Format
|
||||
|
||||
Mdjs is a format that allows you to use JavaScript with Markdown, to create interactive demos. It does so by "annotating" JavaScript that should be executed in Markdown.
|
||||
Markdown JavaScript (Mdjs) is a format that allows you to use JavaScript with Markdown, to create interactive demos. It does so by "annotating" JavaScript that should be executed in Markdown.
|
||||
|
||||
To annotate we use a code block with `js script`.
|
||||
|
||||
10
docs/docs/index.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: Documentation
|
||||
eleventyNavigation:
|
||||
key: Docs
|
||||
order: 20
|
||||
---
|
||||
|
||||
Our documentation is hand crafted and optimized to serve as a dictionary to look up details once the need arises.
|
||||
|
||||
For a more guided learning experience please visit our [Guides](../guides/).
|
||||
3
docs/docs/legacy/index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Legacy ||50
|
||||
|
||||
Please see a sub page
|
||||
18
docs/docs/legacy/legacy-projects.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Legacy >> Legacy Projects || 10
|
||||
|
||||
Over the last years we have created different projects and recommendations. During this time certain projects have become deprecated as we moved on to different tools or approaches.
|
||||
|
||||
This doesn't mean that we've completely dropped support for these projects. While we don't feature them on the main website, we still maintain and support these projects. We don't develop any new features or functionalities, but we will continue to support bugfixes and in some cases update along with the dependent tooling.
|
||||
|
||||
The documentation for our legacy projects is maintained in the github readmes:
|
||||
|
||||
### Legacy projects
|
||||
|
||||
- [es-dev-server](https://github.com/open-wc/es-dev-server) is now replaced by [web-dev-server](https://modern-web.dev/docs/dev-server/overview/)
|
||||
- [testing-karma](https://github.com/open-wc/legacy/tree/master/packages/testing-karma) and [karma-esm](https://github.com/open-wc/legacy/tree/master/packages/karma-esm) we now recommend [web-test-runner](https://modern-web.dev/docs/test-runner/overview/)
|
||||
- [testing-karma-bs](https://github.com/open-wc/legacy/tree/master/packages/testing-karma-bs) we now recommend [web-test-runner](https://modern-web.dev/docs/test-runner/overview/) & [@web/test-runner-browserstack](https://modern-web.dev/docs/test-runner/browser-launchers/browserstack/)
|
||||
- [rollup-plugin-index-html](https://github.com/open-wc/legacy/tree/master/packages/rollup-plugin-index-html) we now recommend `@web/rollup-plugin-html`
|
||||
- [webpack-import-meta-loader](https://github.com/open-wc/legacy/tree/master/packages/webpack-import-meta-loader) we now recommend [babel-plugin-bundled-import-meta](https://www.npmjs.com/package/babel-plugin-bundled-import-meta)
|
||||
- [building-webpack](https://github.com/open-wc/legacy/tree/master/packages/building-webpack) we now recommend rollup over webpack
|
||||
- [webpack-index-html-plugin](https://github.com/open-wc/legacy/tree/master/packages/webpack-index-html-plugin) we now recommend rollup over webpack
|
||||
- [storybook-addon-web-components-knobs](https://github.com/open-wc/legacy/tree/master/packages/storybook-addon-web-components-knobs) storybook v6 has a new better knobs system
|
||||
3
docs/docs/linting/eslint-plugin-lit-a11y/index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Linting >> EsLint Plugin Lit A11y || 10
|
||||
|
||||
sse
|
||||
106
docs/docs/linting/eslint-plugin-lit-a11y/overview.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# Linting >> EsLint Plugin Lit A11y >> Overview || -5
|
||||
|
||||
Accessibility linting plugin for lit-html.
|
||||
|
||||
Most of the rules are ported from [eslint-plugin-jsx-a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y), and made to work with [lit-html](https://lit-html.polymer-project.org/) templates and custom elements.
|
||||
|
||||
## Installation
|
||||
|
||||
You'll first need to install [ESLint](http://eslint.org):
|
||||
|
||||
```
|
||||
$ npm i eslint --save-dev
|
||||
```
|
||||
|
||||
Next, install `eslint-plugin-lit-a11y`:
|
||||
|
||||
```
|
||||
$ npm install eslint-plugin-lit-a11y --save-dev
|
||||
```
|
||||
|
||||
**Note:** If you installed ESLint globally (using the `-g` flag) then you must also install `eslint-plugin-lit-a11y` globally.
|
||||
|
||||
## Usage
|
||||
|
||||
Add `lit-a11y` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": ["lit-a11y"]
|
||||
}
|
||||
```
|
||||
|
||||
Then configure the rules you want to use under the rules section.
|
||||
|
||||
```json
|
||||
{
|
||||
"rules": {
|
||||
"lit-a11y/rule-name": 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
You may also extend the recommended configuration like so:
|
||||
|
||||
```json
|
||||
{
|
||||
"extends": ["plugin:lit-a11y/recommended"]
|
||||
}
|
||||
```
|
||||
|
||||
By default, any tagged template literal that starts with `html` is linted. Example:
|
||||
|
||||
```js
|
||||
html`<img />`;
|
||||
```
|
||||
|
||||
It could be the case, however, that you're using multiple rendering libraries in a project, like for example [`htm`](https://github.com/developit/htm), which also uses a `html` tagged template literal, but has a slightly different syntax than lit-html. In this case you can specify the following option, to make sure only lit-html tagged template literals are linted:
|
||||
|
||||
```json
|
||||
{
|
||||
"settings": {
|
||||
"litHtmlSources": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This will cause the plugin to lint _only_ `html` tagged template literals that are imported from either `'lit-html'` or `'lit-element'`.
|
||||
|
||||
If you're importing lit-html from a package that re-exports lit-html, like for example `@apollo-elements/lit-apollo`, you can specify `@apollo-elements/lit-apollo` as a valid litHtmlSource like so:
|
||||
|
||||
```json
|
||||
{
|
||||
"settings": {
|
||||
"litHtmlSources": ["@apollo-elements/lit-apollo"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Supported Rules
|
||||
|
||||
- [lit-a11y/accessible-emoji](./rules/accessible-emoji.md)
|
||||
- [lit-a11y/alt-text](./rules/alt-text.md)
|
||||
- [lit-a11y/anchor-has-content](./rules/anchor-has-content.md)
|
||||
- [lit-a11y/anchor-is-valid](./rules/anchor-is-valid.md)
|
||||
- [lit-a11y/aria-activedescendant-has-tabindex](./rules/aria-activedescendant-has-tabindex.md)
|
||||
- [lit-a11y/aria-attr-valid-value](./rules/aria-attr-valid-value.md)
|
||||
- [lit-a11y/aria-attrs](./rules/aria-attrs.md)
|
||||
- [lit-a11y/aria-role](./rules/aria-role.md)
|
||||
- [lit-a11y/aria-unsupported-elements](./rules/aria-unsupported-elements.md)
|
||||
- [lit-a11y/autocomplete-valid](./rules/autocomplete-valid.md)
|
||||
- [lit-a11y/click-events-have-key-events](./rules/click-events-have-key-events.md)
|
||||
- [lit-a11y/heading-has-content](./rules/heading-has-content.md)
|
||||
- [lit-a11y/iframe-title](./rules/iframe-title.md)
|
||||
- [lit-a11y/img-redundant-alt](./rules/img-redundant-alt.md)
|
||||
- [lit-a11y/mouse-events-have-key-events](./rules/mouse-events-have-key-events.md)
|
||||
- [lit-a11y/no-access-key](./rules/no-access-key.md)
|
||||
- [lit-a11y/no-autofocus](./rules/no-autofocus.md)
|
||||
- [lit-a11y/no-distracting-elements](./rules/no-distracting-elements.md)
|
||||
- [lit-a11y/no-invalid-change-handler](./rules/no-invalid-change-handler.md)
|
||||
- [lit-a11y/no-redundant-role](./rules/no-redundant-role.md)
|
||||
- [lit-a11y/role-has-required-aria-attrs](./rules/role-has-required-aria-attrs.md)
|
||||
- [lit-a11y/role-supports-aria-attr](./rules/role-supports-aria-attr.md)
|
||||
- [lit-a11y/scope](./rules/scope.md)
|
||||
- [lit-a11y/tabindex-no-positive](./rules/tabindex-no-positive.md)
|
||||
@@ -0,0 +1,50 @@
|
||||
# Linting >> EsLint Plugin Lit A11y >> accessible-emoji (DEPRECATED)
|
||||
|
||||
Enforce emojis are wrapped in `<span>` and provide screenreader access.
|
||||
|
||||
> Emoji help us communicate complex ideas very easily. When used in native apps and applications, emoji are reasonably accessible to screen readers, but on the web we need to do a little more to make sure everyone can understand emoji.
|
||||
|
||||
- [Léonie Watson](https://tink.uk/accessible-emoji/)
|
||||
|
||||
While many modern user agents are able to announce emoji to screen reader users, this rule is still useful for apps targetting older user agents.
|
||||
|
||||
## Rule Details
|
||||
|
||||
This rule aims to prevent inaccessible use of emoji in lit-html templates.
|
||||
|
||||
Examples of **incorrect** code for this rule:
|
||||
|
||||
```js
|
||||
html`
|
||||
<span>🐼</span>
|
||||
<i role="img" aria-label="Panda face">🐼</i>
|
||||
`;
|
||||
```
|
||||
|
||||
Examples of **correct** code for this rule:
|
||||
|
||||
```js
|
||||
html` <span role="img" aria-label="Panda face">🐼</span> `;
|
||||
```
|
||||
|
||||
```js
|
||||
html` <span role="img" alt="Panda face">🐼</span> `;
|
||||
```
|
||||
|
||||
```js
|
||||
html`
|
||||
<span role="img" aria-label="Panda face">🐼</span>
|
||||
<span role="img" alt="Panda face">🐼</span>
|
||||
|
||||
<label id="label">Clown</label>
|
||||
<span role="img" aria-labelledby="label">🤡</span>
|
||||
`;
|
||||
```
|
||||
|
||||
## When Not To Use It
|
||||
|
||||
If you exclusively target modern user agents which explicitly support emoji in plain text, or if you do not use emoji in your lit-html templates.
|
||||
|
||||
## Further Reading
|
||||
|
||||
[Accessible emoji by Léonie Watson](https://tink.uk/accessible-emoji/)
|
||||
51
docs/docs/linting/eslint-plugin-lit-a11y/rules/alt-text.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Linting >> EsLint Plugin Lit A11y >> alt-text
|
||||
|
||||
Enforce that all elements that require alternative text have meaningful information to relay back to the end user. This is a critical component of accessibility for screen reader users in order for them to understand the content's purpose on the page. By default, this rule checks for alternative text on `<img>` elements.
|
||||
|
||||
This rule also permits images which are completely removed from the <acronym>AOM (Accessibility Object Model)</acronym>:
|
||||
|
||||
> **Sometimes there is non-text content that really is not meant to be seen or understood by the user.** Transparent images used to move text over on a page; an invisible image that is used to track usage statistics; and a swirl in the corner that conveys no information but just fills up a blank space to create an aesthetic effect are all examples of this. Putting alternative text on such items just distracts people using screen readers from the content on the page. Not marking the content in any way, though, leaves users guessing what the non-text content is and what information they may have missed (even though they have not missed anything in reality). This type of non-text content, therefore, is marked or implemented in a way that assistive technologies (AT) will ignore it and not present anything to the user.
|
||||
|
||||
- [WCAG 1.1.1](https://www.w3.org/WAI/WCAG21/Understanding/non-text-content.html#examples)
|
||||
|
||||
## Rule Details
|
||||
|
||||
Examples of **incorrect** code for this rule:
|
||||
|
||||
```js
|
||||
html`
|
||||
<img src="${src}" />
|
||||
<div role="img"></div>
|
||||
`;
|
||||
```
|
||||
|
||||
Examples of **correct** code for this rule:
|
||||
|
||||
```js
|
||||
html`
|
||||
<img src="${src}" alt="" />
|
||||
|
||||
<img src="${src}" aria-hidden="true" />
|
||||
|
||||
<img src="${src}" alt="foo" />
|
||||
|
||||
<img src="${src}" aria-label="foo" />
|
||||
|
||||
<label id="label">foo</label>
|
||||
<img src="${src}" aria-labelledby="label" />
|
||||
|
||||
<div role="img" aria-label="foo"></div>
|
||||
|
||||
<div role="img" aria-labelledBy="label"></div>
|
||||
|
||||
<div role="img" aria-hidden="true"></div>
|
||||
`;
|
||||
```
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [WCAG 1.1.1](https://www.w3.org/WAI/WCAG21/Understanding/non-text-content.html)
|
||||
- [axe-core, object-alt](https://dequeuniversity.com/rules/axe/3.2/object-alt)
|
||||
- [axe-core, image-alt](https://dequeuniversity.com/rules/axe/3.2/image-alt)
|
||||
- [axe-core, input-image-alt](https://dequeuniversity.com/rules/axe/3.2/input-image-alt)
|
||||
- [axe-core, area-alt](https://dequeuniversity.com/rules/axe/3.2/area-alt)
|
||||
@@ -0,0 +1,31 @@
|
||||
# Linting >> EsLint Plugin Lit A11y >> anchor-has-content
|
||||
|
||||
Enforce that anchors have content and that the content is accessible to screen readers. Accessible means that it is not hidden using the `aria-hidden` attribute. Refer to the references to learn about why this is important.
|
||||
|
||||
## Rule Details
|
||||
|
||||
This rule aims to...
|
||||
|
||||
Examples of **incorrect** code for this rule:
|
||||
|
||||
```js
|
||||
html`
|
||||
<a></a>
|
||||
<a><some-text-bearing-component aria-hidden></some-text-bearing-component></a>
|
||||
`;
|
||||
```
|
||||
|
||||
Examples of **correct** code for this rule:
|
||||
|
||||
```js
|
||||
html`
|
||||
<a>Anchor Content!</a>
|
||||
<a><some-text-bearing-component></some-text-bearing-component></a>
|
||||
`;
|
||||
```
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [WCAG 2.4.4](https://www.w3.org/WAI/WCAG21/Understanding/link-purpose-in-context)
|
||||
- [WCAG 4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value)
|
||||
- [axe-core, link-name](https://dequeuniversity.com/rules/axe/3.2/link-name)
|
||||
@@ -0,0 +1,235 @@
|
||||
# Linting >> EsLint Plugin Lit A11y >> anchor-is-valid
|
||||
|
||||
Performs validity check on anchor hrefs. Warns when anchors are used as buttons.
|
||||
|
||||
The HTML `<a>` element, with a valid `href` attribute, is formally defined as representing a **hyperlink**. That is, a link between one HTML document and another, or between one location inside an HTML document and another location inside the same document.
|
||||
|
||||
In fact, the interactive, underlined `<a>` element has become so synonymous with web navigation that this expectation has become entrenched inside browsers, assistive technologies such as screen readers and in how people generally expect the internet to behave. In short, anchors should navigate.
|
||||
|
||||
The use of JavaScript frameworks and libraries has made it very easy to add or subtract functionality from the standard HTML elements. This has led to _anchors_ often being used in applications based on how they look and function instead of what they represent.
|
||||
|
||||
Whilst it is possible, for example, to turn the `<a>` element into a fully functional `<button>` element with ARIA, the native user agent implementations of HTML elements are to be preferred over custom ARIA solutions.
|
||||
|
||||
## How do I resolve this error?
|
||||
|
||||
### Case: I want to perform an action and need a clickable UI element
|
||||
|
||||
The native user agent implementations of the `<a>` and `<button>` elements not only differ in how they look and how they act when activated, but also in how the user is expected to interact with them. Both are perfectly clickable when using a mouse, but keyboard users expect `<a>` to activate on `enter` only and `<button>` to activate on _both_ `enter` and `space`.
|
||||
|
||||
This is exacerbated by the expectation sighted users have of how _buttons_ and _anchors_ work based on their appearance. Therefore we find that using _anchors_ as _buttons_ can easily create confusion without a relatively complicated ARIA and CSS implementation that only serves to create an element HTML already offers and browsers already implement fully accessibly.
|
||||
|
||||
We are aware that sometimes _anchors_ are used instead of _buttons_ to achieve a specific visual design. When using the `<button>` element this can still be achieved with styling but, due to the meaning many people attach to the standard underlined `<a>` due its appearance, please reconsider this in the design.
|
||||
|
||||
Consider the following:
|
||||
|
||||
```js
|
||||
html`
|
||||
<a href="javascript:void(0)" @click=${foo}>Perform action</a>
|
||||
<a href="#" @click=${foo}>Perform action</a>
|
||||
<a @click=${foo}>Perform action</a>
|
||||
`;
|
||||
```
|
||||
|
||||
All these _anchor_ implementations indicate that the element is only used to execute JavaScript code. All the above should be replaced with:
|
||||
|
||||
```js
|
||||
html` <button @click=${foo}>Perform action</button> `;
|
||||
```
|
||||
|
||||
### Case: I want navigable links
|
||||
|
||||
An `<a>` element without an `href` attribute no longer functions as a hyperlink. That means that it can no longer accept keyboard focus or be clicked on. The documentation for [no-noninteractive-tabindex](no-noninteractive-tabindex.md) explores this further. Preferably use another element (such as `div` or `span`) for display of text.
|
||||
|
||||
To properly function as a hyperlink, the `href` attribute should be present and also contain a valid _URL_. _JavaScript_ strings, empty values or using only **#** are not considered valid `href` values.
|
||||
|
||||
Valid `href` attributes values are:
|
||||
|
||||
```js
|
||||
html`
|
||||
<a href="/some/valid/uri">Navigate to page</a>
|
||||
<a href="/some/valid/uri#top">Navigate to page and location</a>
|
||||
<a href="#top">Navigate to internal page location</a>
|
||||
`;
|
||||
```
|
||||
|
||||
### Case: I need the HTML to be interactive, don't I need to use an `a` tag for that?
|
||||
|
||||
An `<a>` tag is not inherently interactive. Without an href attribute, it really is no different to a `<div>`.
|
||||
|
||||
Let's look at an example that is not accessible by all users:
|
||||
|
||||
```js
|
||||
html` <a class="thing" @mouseenter=${() => (this.showSomething = true)}> ${label} </a> `;
|
||||
```
|
||||
|
||||
If you need to create an interface element that the user can click on, consider using a button:
|
||||
|
||||
```js
|
||||
html`
|
||||
<button class="thing"
|
||||
@click={() => this.showSomething = true}>
|
||||
${label}
|
||||
</button>
|
||||
`;
|
||||
```
|
||||
|
||||
If you want to navigate while providing the user with extra functionality, for example in the `@mouseenter` event, use an anchor with an `href` attribute containing a URL or path as its value.
|
||||
|
||||
```js
|
||||
html`
|
||||
<a class="thing" href=${someValidPath} @mouseenter=${() => (this.showSomething = true)}>
|
||||
${label}
|
||||
</a>
|
||||
`;
|
||||
```
|
||||
|
||||
If you need to create an interface element that the user can mouse over or mouse out of, consider using a div element. In this case, you may need to apply a role of presentation or an interactive role. Interactive ARIA roles include `button`, `link`, `checkbox`, `menuitem`, `menuitemcheckbox`, `menuitemradio`, `option`, `radio`, `searchbox`, `switch` and `textbox`.
|
||||
|
||||
```js
|
||||
html`
|
||||
<div class="thing" role="menuitem" @click=${() => (this.showSomething = true)}>
|
||||
@mouseenter=${() => (this.showSomething = true)}> ${label}
|
||||
</div>
|
||||
`;
|
||||
```
|
||||
|
||||
In the example immediately above an `@click` event handler was added to provide the same experience mouse users enjoy to keyboard-only and touch-screen users. Never fully rely on mouse events alone to expose functionality.
|
||||
|
||||
### Case: I understand the previous cases but still need an element resembling a link that is purely clickable
|
||||
|
||||
We recommend, without reserve, that elements resembling anchors should navigate. This will provide a superior user experience to a larger group of users out there.
|
||||
|
||||
However, we understand that developers are not always in total control of the visual design of web applications. In cases where it is imperative to provide an element resembling an anchor that purely acts as a click target with no navigation as result, we would like to recommend a compromise.
|
||||
|
||||
Again change the element to a `<button>`:
|
||||
|
||||
```js
|
||||
html`
|
||||
<button class="link-button" type="button" @click=${() => (this.showSomething = true)}>
|
||||
Press me, I look like a link
|
||||
</button>
|
||||
`;
|
||||
```
|
||||
|
||||
Then use styling to change its appearance to that of a link:
|
||||
|
||||
```css
|
||||
.link-button {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
display: inline;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.link-button:hover,
|
||||
.link-button:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
```
|
||||
|
||||
This button element can now also be used inline in text.
|
||||
|
||||
Once again we stress that this is an inferior implementation and some users will encounter difficulty to use your website, however, it will allow a larger group of people to interact with your website than the alternative of ignoring the rule's warning.
|
||||
|
||||
## Rule Details
|
||||
|
||||
This rule takes one optional object argument of type object:
|
||||
|
||||
```json
|
||||
{
|
||||
"rules": {
|
||||
"lit-a11y/anchor-is-valid": [
|
||||
"error",
|
||||
{
|
||||
"allowHash": true,
|
||||
"aspects": ["noHref", "invalidHref", "preferButton"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For the `allowHash` option (default `true`), if set to `false`, the empty hash or "scroll-to-top" link will be considered an error.
|
||||
|
||||
Examples of **incorrect** code for this rule with `"allowHash": false`:
|
||||
|
||||
```js
|
||||
html` <a href="#"></a> `;
|
||||
```
|
||||
|
||||
Examples of **correct** code for this rule with `"allowHash": false`:
|
||||
|
||||
```js
|
||||
html` <a href="#top"></a> `;
|
||||
```
|
||||
|
||||
For the `aspects` option, these strings determine which sub-rules are run. This allows omission of certain error types in restrictive environments.
|
||||
|
||||
- `noHref`: Checks whether an anchor contains an `href` attribute.
|
||||
- `invalidHref`: Checks if a given `href` value is valid.
|
||||
- `preferButton`: Checks if anchors have been used as buttons.
|
||||
|
||||
If omitted, all sub-rule aspects will be run by default. This is the recommended configuration for all cases except where the rule becomes unusable due to well founded restrictions.
|
||||
|
||||
The option must contain at least one `aspect`.
|
||||
|
||||
Examples of **incorrect** code for this rule:
|
||||
|
||||
Anchors should be a button:
|
||||
|
||||
```js
|
||||
html`
|
||||
<a @click=${foo}></a>
|
||||
<a href="javascript:void(0)" @click=${foo}></a>
|
||||
<a href=${'javascript:void(0)'} @click=${foo}></a>
|
||||
<a href=${`javascript:void(0)`} @click=${foo}></a>
|
||||
`;
|
||||
```
|
||||
|
||||
Missing `href` attribute:
|
||||
|
||||
```js
|
||||
html`
|
||||
<a></a>
|
||||
<a href="{undefined}"></a>
|
||||
<a href="{null}"></a>
|
||||
`;
|
||||
```
|
||||
|
||||
Invalid `href` attribute:
|
||||
|
||||
```js
|
||||
html`
|
||||
<a href="javascript:void(0)"></a>
|
||||
<a href={"javascript:void(0)"}></a>
|
||||
<a href={`javascript:void(0)`}></a>
|
||||
`;
|
||||
```
|
||||
|
||||
Examples of **correct** code for this rule:
|
||||
|
||||
```js
|
||||
html`
|
||||
<a href="https://github.com"></a>
|
||||
<a href="#"></a>
|
||||
<a href="#section"></a>
|
||||
<a href="foo"></a>
|
||||
<a href="/foo/bar"></a>
|
||||
<a href=${someValidPath}></a>
|
||||
<a href="https://github.com" @click=${foo}></a>
|
||||
<a href="#section" @click=${foo}></a>
|
||||
<a href="foo" @click=${foo}></a>
|
||||
<a href="/foo/bar" @click=${foo}></a>
|
||||
<a href=${someValidPath} @click=${foo}></a>
|
||||
`;
|
||||
```
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [WCAG 2.1.1](https://www.w3.org/WAI/WCAG21/Understanding/keyboard)
|
||||
- [WebAIM - Introduction to Links and Hypertext](http://webaim.org/techniques/hypertext/)
|
||||
- [Links vs. Buttons in Modern Web Applications](https://marcysutton.com/links-vs-buttons-in-modern-web-applications/)
|
||||
- [Using ARIA - Notes on ARIA use in HTML](https://www.w3.org/TR/using-aria/#NOTES)
|
||||
@@ -0,0 +1,35 @@
|
||||
# Linting >> EsLint Plugin Lit A11y >> aria-activedescendant-has-tabindex
|
||||
|
||||
`aria-activedescendant` is used to manage focus within a [composite widget](https://www.w3.org/TR/wai-aria/#composite).
|
||||
The element with the attribute `aria-activedescendant` retains the active document
|
||||
focus; it indicates which of its child elements has secondary focus by assigning
|
||||
the ID of that element to the value of `aria-activedescendant`. This pattern is
|
||||
used to build a widget like a search typeahead select list. The search input box
|
||||
retains document focus so that the user can type in the input. If the down arrow
|
||||
key is pressed and a search suggestion is highlighted, the ID of the suggestion
|
||||
element will be applied as the value of `aria-activedescendant` on the input
|
||||
element.
|
||||
|
||||
Because an element with `aria-activedescendant` must be tabbable, it must either
|
||||
have an inherent `tabIndex` of zero or declare a `tabIndex` of zero with the `tabIndex`
|
||||
attribute.
|
||||
|
||||
## Rule Details
|
||||
|
||||
This rule aims to...
|
||||
|
||||
Examples of **incorrect** code for this rule:
|
||||
|
||||
```js
|
||||
html` <div aria-activedescendant=${someID}></div> `;
|
||||
```
|
||||
|
||||
Examples of **correct** code for this rule:
|
||||
|
||||
```js
|
||||
html` <div aria-activedescendant="foo" tabindex="0"></div> `;
|
||||
```
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [MDN, Using the aria-activedescendant attribute](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-activedescendant_attribute)
|
||||