├── .browserslistrc ├── .commitlintrc ├── .editorconfig ├── .eslintrc ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .husky ├── .gitignore ├── commit-msg └── pre-commit ├── .lintstagedrc ├── .npmignore ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── benchmarks ├── generate-tachometer-config.js ├── index.html └── template-tachometer.json ├── docs └── browserstack-logo.svg ├── package.json ├── src ├── transform.js ├── transform.spec.js ├── wrap.d.ts ├── wrap.js └── wrap.spec.js ├── wallaby.js ├── web-test-runner.bs.config.mjs ├── web-test-runner.config.mjs └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | last 2 Chrome major versions 2 | last 2 Firefox major versions 3 | last 2 Safari major versions 4 | last 2 Edge major versions 5 | last 2 ChromeAndroid major versions 6 | last 2 iOS major versions 7 | Firefox ESR 8 | Edge 18 9 | -------------------------------------------------------------------------------- /.commitlintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.js] 12 | block_comment_start = /* 13 | block_comment = * 14 | block_comment_end = */ 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb-base", "prettier"], 3 | "parser": "babel-eslint", 4 | "env": { 5 | "browser": true 6 | }, 7 | "rules": { 8 | "import/no-extraneous-dependencies": [ 9 | "error", 10 | { 11 | "devDependencies": ["src/**/*.spec.js", "*.config.mjs"] 12 | } 13 | ], 14 | "import/extensions": 0 15 | }, 16 | "overrides": [ 17 | { 18 | "files": ["index.js"], 19 | "rules": { 20 | "import/prefer-default-export": 0 21 | } 22 | }, 23 | { 24 | "files": ["src/**/*.spec.js"], 25 | "env": { 26 | "mocha": true 27 | }, 28 | "rules": { 29 | "max-classes-per-file": 0, 30 | "no-unused-expressions": 0, 31 | "import/prefer-default-export": 0 32 | } 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | ci: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v1 15 | with: 16 | node-version: 14.x 17 | - run: yarn install --frozen-lockfile 18 | - run: yarn lint 19 | - run: yarn test:bs 20 | env: 21 | BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} 22 | BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | benchmarks/tachometer.json 2 | coverage/ 3 | node_modules/ 4 | .vscode/ 5 | results.json 6 | *.log 7 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*": [ 3 | "eclint fix" 4 | ], 5 | "*.js": [ 6 | "eslint --fix", 7 | "prettier --write" 8 | ], 9 | "*.md": [ 10 | "prettier --write" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /.github/ 2 | /.husky/ 3 | /.vscode/ 4 | /benchmarks/ 5 | /coverage/ 6 | *.spec.js 7 | .browserslistrc 8 | .commitlintrc 9 | .editorconfig 10 | .eslintrc 11 | .lintstagedrc 12 | .prettierignore 13 | .prettierrc 14 | results.json 15 | wallaby.js 16 | web-test-runner.bs.config.mjs 17 | web-test-runner.config.mjs 18 | *.log 19 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /coverage/ 2 | CHANGELOG.md 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "singleQuote": true, 4 | "trailingComma": "all" 5 | } 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | 6 | ## [0.1.5](https://github.com/bashmish/carehtml/compare/v0.1.4...v0.1.5) (2022-04-23) 7 | 8 | 9 | ### Bug Fixes 10 | 11 | * comply with Custom Elements name constraints ([2881679](https://github.com/bashmish/carehtml/commit/2881679)) 12 | 13 | 14 | 15 | 16 | ## [0.1.4](https://github.com/bashmish/carehtml/compare/v0.1.3...v0.1.4) (2022-04-21) 17 | 18 | 19 | ### Features 20 | 21 | * support Lit 2.x ([c5f40b3](https://github.com/bashmish/carehtml/commit/c5f40b3)) 22 | 23 | 24 | ### Bug Fixes 25 | 26 | * define types in package.json correctly to be auto loaded ([dd96c9a](https://github.com/bashmish/carehtml/commit/dd96c9a)) 27 | 28 | 29 | 30 | 31 | ## [0.1.3](https://github.com/bashmish/carehtml/compare/v0.1.2...v0.1.3) (2021-04-01) 32 | 33 | 34 | ### Features 35 | 36 | * cache template to avoid rerendering when strings reference changes ([067b39d](https://github.com/bashmish/carehtml/commit/067b39d)) 37 | 38 | 39 | 40 | 41 | ## [0.1.2](https://github.com/bashmish/carehtml/compare/v0.1.1...v0.1.2) (2021-03-20) 42 | 43 | 44 | ### Features 45 | 46 | * add TypeScript definitions (#22) ([a788861](https://github.com/bashmish/carehtml/commit/a788861)) 47 | 48 | 49 | ### Bug Fixes 50 | 51 | * fix(transform): do not throw on nullish value (#19) ([d5b3706](https://github.com/bashmish/carehtml/commit/d5b3706)) 52 | 53 | 54 | 55 | 56 | ## [0.1.1](https://github.com/bashmish/carehtml/compare/v0.1.0...v0.1.1) (2019-08-22) 57 | 58 | 59 | ### Bug Fixes 60 | 61 | * generate name correctly in Safari if constructor has no name ([f9af239](https://github.com/bashmish/carehtml/commit/f9af239)) 62 | 63 | 64 | 65 | 66 | # 0.1.0 (2018-12-22) 67 | 68 | 69 | ### Features 70 | 71 | * auto register Custom Element classes with different names and in different circumstances ([8aad0bb](https://github.com/bashmish/carehtml/commit/8aad0bb)) 72 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Mikhail Bashkirov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # carehtml (Custom Auto Registered Elements HTML) 2 | 3 | Templates with automatic registration of Custom Elements. 4 | 5 | ```js 6 | const template = html`<${CustomElement}>`; 7 | ``` 8 | 9 | Inspired by [JSX](https://reactjs.org/docs/introducing-jsx.html) in general and [htm](https://www.npmjs.com/package/htm) in particular. 10 | 11 | ## Motivation 12 | 13 | There are 2 main reasons Web Components need something like this: 14 | 15 | 1. Lack of scoping when registering Custom Elements which creates issues in tests and makes it impossible to have 2 different components with the same name. 16 | 2. Inability to have 2 different versions of the same Custom Element when refactoring from an old to a new version, especially when having nested node modules. 17 | 18 | ## Usage with Lit 19 | 20 | You need to wrap the Lit `html` tag: 21 | 22 | ```js 23 | import { LitElement, html as litHtml } from 'lit'; 24 | import takeCareOf from 'carehtml'; 25 | 26 | const html = takeCareOf(litHtml); 27 | 28 | class MySearchBar extends LitElement { 29 | render() { 30 | return html` 31 | <${MyInput} name="query"> 32 | <${MyButton}> 33 | <${MyIcon} icon="search"> 34 | Search 35 | 36 | `; 37 | } 38 | } 39 | ``` 40 | 41 | > Wrapping is extra work which might seem unnecessary in the user code, but that allows to decouple `carehtml` from `lit`, primarily in terms of npm dependencies. 42 | > This allows to use `carehtml` with any version of `lit` and develop `carehtml` with its independent release cycle. 43 | 44 | ## Usage with Other Templating Libraries Based on Tagged Templates 45 | 46 | In fact it can work with any other templating library which relies on [tagged templates](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_templates). 47 | For example with `htm` allowing to mix Custom Element classes with Preact components (and any other components supported by `htm` including React ones). 48 | 49 | This is an example taken from the `htm` docs with one change: instead of simple ``; 60 | 61 | class CustomHello extends HTMLElement { 62 | constructor() { 63 | super(); 64 | this.attachShadow({ mode: 'open' }); 65 | this.shadowRoot.innerHTML = this.render(); 66 | } 67 | 68 | static get observedAttributes() { 69 | return ['name']; 70 | } 71 | 72 | attributeChangedCallback() { 73 | this.shadowRoot.innerHTML = this.render(); 74 | } 75 | 76 | render() { 77 | return `

Hello, ${this.nameValue}!

`; 78 | } 79 | 80 | get nameValue() { 81 | return this.getAttribute('name') || 'unknown'; 82 | } 83 | } 84 | 85 | class PreactApp extends PreactComponent { 86 | // eslint-disable-next-line class-methods-use-this 87 | render() { 88 | const clickHandler = () => { 89 | document.querySelector('#hello').setAttribute('name', 'world'); 90 | }; 91 | return html` 92 |
93 | <${PreactButton} onClick="${clickHandler}">Say hello to the world! 94 | <${CustomHello} id="hello"> 95 |
96 | `; 97 | } 98 | } 99 | 100 | const fixture = document.createElement('div'); 101 | document.body.appendChild(fixture); 102 | 103 | render(html`<${PreactApp} />`, fixture); 104 | 105 | expect(fixture.children.length).to.equal(1); 106 | 107 | const appElement = fixture.children[0]; 108 | expect(appElement.tagName).to.equal('DIV'); 109 | expect(appElement.className).to.equal('app'); 110 | expect(appElement.children.length).to.equal(2); 111 | 112 | const buttonElement = appElement.children[0]; 113 | expect(buttonElement.tagName).to.equal('BUTTON'); 114 | expect(buttonElement.innerText).to.equal('Say hello to the world!'); 115 | 116 | const helloElement = appElement.children[1]; 117 | expect(helloElement.tagName).to.equal('CUSTOM-HELLO'); 118 | expect(helloElement.id).to.equal('hello'); 119 | expect(helloElement.shadowRoot.children.length).to.equal(1); 120 | 121 | expect(helloElement.shadowRoot.children[0].tagName).to.equal('P'); 122 | expect(helloElement.shadowRoot.children[0].innerText).to.equal('Hello, unknown!'); 123 | 124 | buttonElement.click(); 125 | expect(helloElement.shadowRoot.children[0].innerText).to.equal('Hello, world!'); 126 | 127 | document.body.removeChild(fixture); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /wallaby.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-extraneous-dependencies 2 | const wallabyWebpack = require('wallaby-webpack'); 3 | 4 | const wallabyPostprocessor = wallabyWebpack(); 5 | 6 | module.exports = { 7 | files: [{ pattern: 'src/**/*.js', load: false }, '!src/**/*.spec.js'], 8 | tests: [{ pattern: 'src/**/*.spec.js', load: false }], 9 | testFramework: 'mocha', 10 | env: { 11 | kind: 'chrome', 12 | }, 13 | postprocessor: wallabyPostprocessor, 14 | setup: () => { 15 | window.__moduleBundler.loadTests(); // eslint-disable-line no-underscore-dangle 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /web-test-runner.bs.config.mjs: -------------------------------------------------------------------------------- 1 | import { legacyPlugin } from '@web/dev-server-legacy'; 2 | import { browserstackLauncher } from '@web/test-runner-browserstack'; 3 | import browserslist from 'browserslist'; 4 | import browserslistBrowserstack from 'browserslist-browserstack'; 5 | import config from './web-test-runner.config.mjs'; 6 | 7 | const getCapabilities = browserslistBrowserstack.default; 8 | 9 | function filterUniqueCapabilities(capabilities) { 10 | return capabilities.filter((c) => ( 11 | c === capabilities.find((c2) => ( 12 | c2.browser === c.browser && c2.browser_version === c.browser_version 13 | )) 14 | )); 15 | } 16 | 17 | const allCapabilities = await getCapabilities({ 18 | username: process.env.BROWSERSTACK_USERNAME, 19 | accessKey: process.env.BROWSERSTACK_ACCESS_KEY, 20 | browserslist: { queries: browserslist() }, 21 | formatForSelenium: true, 22 | }); 23 | 24 | const uniqueCapabilities = filterUniqueCapabilities(allCapabilities); 25 | 26 | const browsers = uniqueCapabilities.map(capability => { 27 | return browserstackLauncher({ 28 | capabilities: { 29 | 'browserstack.user': process.env.BROWSERSTACK_USERNAME, 30 | 'browserstack.key': process.env.BROWSERSTACK_ACCESS_KEY, 31 | project: 'carehtml', 32 | name: 'specs', 33 | build: `carehtml local run at ${Date.now()}`, 34 | ...capability, 35 | } 36 | }); 37 | }); 38 | 39 | export default { 40 | ...config, 41 | concurrentBrowsers: 5, 42 | browsers, 43 | plugins: [ 44 | legacyPlugin(), 45 | ], 46 | }; 47 | -------------------------------------------------------------------------------- /web-test-runner.config.mjs: -------------------------------------------------------------------------------- 1 | import { playwrightLauncher } from '@web/test-runner-playwright'; 2 | 3 | export default { 4 | files: 'src/**/*.spec.js', 5 | coverage: true, 6 | nodeResolve: true, 7 | browsers: [ 8 | playwrightLauncher({ product: 'chromium' }), 9 | playwrightLauncher({ product: 'firefox' }), 10 | playwrightLauncher({ product: 'webkit' }), 11 | ], 12 | }; 13 | --------------------------------------------------------------------------------