├── .github └── workflows │ ├── latest.yml │ └── release.yml ├── .gitignore ├── .npmignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── dev ├── context.json ├── index.html ├── sample.html └── sample2.html ├── examples ├── cdn │ ├── basic.html │ └── context.html └── npm │ ├── basic.html │ └── context.html ├── index.js ├── index.min.js ├── package.json └── src └── wc-template.js /.github/workflows/latest.yml: -------------------------------------------------------------------------------- 1 | name: Latest 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | verify: 7 | name: Verify 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v1 12 | - name: Setup 13 | uses: actions/setup-node@v1 14 | with: 15 | node-version: 14 16 | - name: Cache 17 | uses: actions/cache@v1 18 | with: 19 | path: node_modules 20 | key: ${{ runner.OS }}-npm-cache-${{ hashFiles('**/package.json') }} 21 | restore-keys: | 22 | ${{ runner.OS }}-npm-cache- 23 | - name: Install 24 | run: npm i 25 | - name: Test 26 | run: npm run test --if-present 27 | - name: Lint 28 | run: npm run lint --if-present 29 | - name: Types 30 | run: npm run types --if-present 31 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | check: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@master 14 | - name: Setup 15 | uses: actions/setup-node@v1 16 | with: 17 | node-version: 14 18 | - name: Cache 19 | uses: actions/cache@v1 20 | with: 21 | path: node_modules 22 | key: ${{ runner.OS }}-npm-cache-${{ hashFiles('**/package.json') }} 23 | restore-keys: | 24 | ${{ runner.OS }}-npm-cache- 25 | - name: Verify 26 | run: | 27 | npm i 28 | npm run preversion 29 | npm: 30 | runs-on: ubuntu-latest 31 | needs: check 32 | steps: 33 | - name: Checkout 34 | uses: actions/checkout@master 35 | - name: Publish 36 | run: | 37 | echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" > ~/.npmrc 38 | npm publish --access public 39 | gh: 40 | runs-on: ubuntu-latest 41 | needs: check 42 | steps: 43 | - name: Checkout 44 | uses: actions/checkout@master 45 | - name: Publish 46 | run: | 47 | echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> ~/.npmrc 48 | ORG="$(echo '${{ github.repository }}' | cut -d'/' -f1)" 49 | echo "registry=https://npm.pkg.github.com/$ORG" > .npmrc 50 | npm publish 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package/ 3 | *.tgz 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github/ 2 | .vscode/ 3 | dev/ 4 | *.tgz 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "chrome", 6 | "request": "launch", 7 | "name": "Chrome", 8 | "url": "http://localhost:5500/dev", 9 | "webRoot": "${workspaceFolder}", 10 | "pathMapping": { 11 | "/dev/": "${workspaceFolder}/dev/" 12 | } 13 | }, 14 | { 15 | "type": "browser-preview", 16 | "request": "launch", 17 | "name": "Browser Preview", 18 | "url": "http://localhost:5500/dev/", 19 | "webRoot": "${workspaceFolder}", 20 | "pathMapping": { 21 | "/dev/": "${workspaceFolder}/dev/" 22 | } 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 VanillaWC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

<wc-template> Tagged Template Literals in HTML

2 | 3 |
4 | GitHub Releases 5 | NPM Releases 6 | Bundlephobia 7 | Latest Status 8 | Release Status 9 | 10 | Discord 11 | Published on WebComponents.org 12 |
13 | 14 | ## Installation 15 | 16 | *Installation* 17 | ```sh 18 | npm i @vanillawc/wc-template 19 | ``` 20 | 21 | *Import from NPM* 22 | ```html 23 | 24 | ``` 25 | 26 | *Import from CDN* 27 | ```html 28 | 29 | ``` 30 | 31 | ## Demo 32 | 33 | Try it on [WebComponents.dev](https://webcomponents.dev/edit/BggRSZ5D4tmsyce94mpB?sv=1&pm=1) 34 | 35 | ## Usage 36 | 37 | **Attributes** 38 | 39 | - `src` - load an external source file 40 | - `context` - load the tags from an external endpoint 41 | 42 | ### Basic Usage 43 | 44 | ```html 45 | 46 | ``` 47 | 48 | ## Context 49 | 50 | If the template is a tagged template literal, context can be provided via an external endpoint. 51 | 52 | ```html 53 | 54 | ``` 55 | 56 | ## Contributing 57 | 58 | See [CONTRIBUTING.md](https://github.com/vanillawc/vanillawc/blob/main/CONTRIBUTING.md) 59 | -------------------------------------------------------------------------------- /dev/context.json: -------------------------------------------------------------------------------- 1 | { 2 | "names": [ 3 | "John", 4 | "Jean", 5 | "Red", 6 | "Jerome" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /dev/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /dev/sample.html: -------------------------------------------------------------------------------- 1 |

This is a sample template

2 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptates culpa sint expedita magni, quasi eos illo dolor, optio, nisi placeat sunt inventore laboriosam iusto recusandae iste asperiores? Quibusdam, iusto cum?

3 | -------------------------------------------------------------------------------- /dev/sample2.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/cdn/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/cdn/context.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/npm/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/npm/context.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // node_modules/@vanillaes/interpolate/index.js 2 | function interpolate(template, tags = {}) { 3 | const keys = Object.keys(tags); 4 | const values = Object.values(tags); 5 | try { 6 | return new Function(...keys, `return \`${template}\`;`)(...values); 7 | } catch (e) { 8 | throw new TemplateException(template, tags, e); 9 | } 10 | } 11 | var TemplateException = class extends Error { 12 | constructor(template, tags, message) { 13 | super(); 14 | this.name = "TemplateError"; 15 | let msg = "\n------------------\n"; 16 | msg += `Template: \`${template}\``; 17 | msg += "\n------------------\n"; 18 | msg += `Tags: ${JSON.stringify(tags, null, 2)}`; 19 | msg += "\n------------------\n"; 20 | msg += message; 21 | this.message = msg; 22 | } 23 | }; 24 | 25 | // src/wc-template.js 26 | var WCTemplate = class extends HTMLElement { 27 | static get observedAttributes() { 28 | return ["src", "context"]; 29 | } 30 | attributeChangedCallback(name, oldValue, newValue) { 31 | if (!this.__initialized) { 32 | return; 33 | } 34 | if (oldValue !== newValue) { 35 | this[name] = newValue; 36 | } 37 | } 38 | get src() { 39 | return this.getAttribute("src"); 40 | } 41 | set src(value) { 42 | this.setAttribute("src", value); 43 | this.setSrc(); 44 | this.render(); 45 | } 46 | get context() { 47 | return this.getAttribute("context"); 48 | } 49 | set context(value) { 50 | this.setAttribute("context", value); 51 | this.setContext(); 52 | this.render(); 53 | } 54 | constructor() { 55 | super(); 56 | this.__initialized = false; 57 | this.__template = ""; 58 | this.__context = {}; 59 | } 60 | async connectedCallback() { 61 | if (this.hasAttribute("src")) { 62 | await this.setSrc(); 63 | } 64 | if (this.hasAttribute("context")) { 65 | await this.setContext(); 66 | } 67 | this.render(); 68 | this.__initialized = true; 69 | } 70 | async setSrc() { 71 | const path = this.getAttribute("src"); 72 | this.__template = await this.fetchSrc(path); 73 | } 74 | async fetchSrc(src) { 75 | const response = await fetch(src); 76 | if (response.status !== 200) 77 | throw Error(`ERR ${response.status}: ${response.statusText}`); 78 | return response.text(); 79 | } 80 | async setContext() { 81 | const path = this.getAttribute("context"); 82 | this.__context = await this.fetchContext(path); 83 | } 84 | async fetchContext(src) { 85 | const response = await fetch(src); 86 | if (response.status !== 200) 87 | throw Error(`ERR ${response.status}: ${response.statusText}`); 88 | return response.json(); 89 | } 90 | render() { 91 | this.innerHTML = interpolate(this.__template, this.__context); 92 | } 93 | }; 94 | customElements.define("wc-template", WCTemplate); 95 | export { 96 | WCTemplate 97 | }; 98 | -------------------------------------------------------------------------------- /index.min.js: -------------------------------------------------------------------------------- 1 | function c(i,t={}){let e=Object.keys(t),r=Object.values(t);try{return new Function(...e,`return \`${i}\`;`)(...r)}catch(s){throw new n(i,t,s)}}var n=class extends Error{constructor(t,e,r){super();this.name="TemplateError";let s=` 2 | ------------------ 3 | `;s+=`Template: \`${t}\``,s+=` 4 | ------------------ 5 | `,s+=`Tags: ${JSON.stringify(e,null,2)}`,s+=` 6 | ------------------ 7 | `,s+=r,this.message=s}};var a=class extends HTMLElement{static get observedAttributes(){return["src","context"]}attributeChangedCallback(t,e,r){!this.__initialized||e!==r&&(this[t]=r)}get src(){return this.getAttribute("src")}set src(t){this.setAttribute("src",t),this.setSrc(),this.render()}get context(){return this.getAttribute("context")}set context(t){this.setAttribute("context",t),this.setContext(),this.render()}constructor(){super();this.__initialized=!1,this.__template="",this.__context={}}async connectedCallback(){this.hasAttribute("src")&&await this.setSrc(),this.hasAttribute("context")&&await this.setContext(),this.render(),this.__initialized=!0}async setSrc(){let t=this.getAttribute("src");this.__template=await this.fetchSrc(t)}async fetchSrc(t){let e=await fetch(t);if(e.status!==200)throw Error(`ERR ${e.status}: ${e.statusText}`);return e.text()}async setContext(){let t=this.getAttribute("context");this.__context=await this.fetchContext(t)}async fetchContext(t){let e=await fetch(t);if(e.status!==200)throw Error(`ERR ${e.status}: ${e.statusText}`);return e.json()}render(){this.innerHTML=c(this.__template,this.__context)}};customElements.define("wc-template",a);export{a as WCTemplate}; 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vanillawc/wc-template", 3 | "version": "1.0.25", 4 | "license": "MIT", 5 | "author": "Evan Plaice (https://evanplaice.com/)", 6 | "description": "Template HTML with tagged template literals", 7 | "keywords": [ 8 | "web-components", 9 | "vanilla", 10 | "tagged", 11 | "template", 12 | "literal" 13 | ], 14 | "repository": "https://github.com/vanillawc/wc-template/", 15 | "main": "index.js", 16 | "scripts": { 17 | "start": "npx live-server --no-browser --port=5500 --open=dev", 18 | "lint": "esmtk lint", 19 | "build": "npm run build:esm && npm run build:min", 20 | "build:esm": "esmtk bundle src/wc-template.js index.js", 21 | "build:min": "esmtk minify src/wc-template.js index.min.js", 22 | "package": "npx rimraf package && npm pack | tail -n 1 | xargs tar -xf", 23 | "preversion": "npm run lint", 24 | "postversion": "git push --follow-tags" 25 | }, 26 | "devDependencies": { 27 | "@vanillaes/interpolate": "^2.0.4", 28 | "esmtk": "^0.5.6" 29 | }, 30 | "standard": { 31 | "ignore": [ 32 | "index.js" 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/wc-template.js: -------------------------------------------------------------------------------- 1 | /* eslint no-undef: 0 */ 2 | import { interpolate } from '../node_modules/@vanillaes/interpolate/index.js' 3 | 4 | export class WCTemplate extends HTMLElement { 5 | static get observedAttributes () { 6 | return ['src', 'context'] 7 | } 8 | 9 | attributeChangedCallback (name, oldValue, newValue) { 10 | if (!this.__initialized) { return } 11 | if (oldValue !== newValue) { 12 | this[name] = newValue 13 | } 14 | } 15 | 16 | get src () { return this.getAttribute('src') } 17 | set src (value) { 18 | this.setAttribute('src', value) 19 | this.setSrc() 20 | this.render() 21 | } 22 | 23 | get context () { return this.getAttribute('context') } 24 | set context (value) { 25 | this.setAttribute('context', value) 26 | this.setContext() 27 | this.render() 28 | } 29 | 30 | constructor () { 31 | super() 32 | this.__initialized = false 33 | this.__template = '' 34 | this.__context = {} 35 | } 36 | 37 | async connectedCallback () { 38 | if (this.hasAttribute('src')) { 39 | await this.setSrc() 40 | } 41 | 42 | if (this.hasAttribute('context')) { 43 | await this.setContext() 44 | } 45 | 46 | this.render() 47 | this.__initialized = true 48 | } 49 | 50 | async setSrc () { 51 | const path = this.getAttribute('src') 52 | this.__template = await this.fetchSrc(path) 53 | } 54 | 55 | async fetchSrc (src) { 56 | const response = await fetch(src) 57 | if (response.status !== 200) throw Error(`ERR ${response.status}: ${response.statusText}`) 58 | return response.text() 59 | } 60 | 61 | async setContext () { 62 | const path = this.getAttribute('context') 63 | this.__context = await this.fetchContext(path) 64 | } 65 | 66 | async fetchContext (src) { 67 | const response = await fetch(src) 68 | if (response.status !== 200) throw Error(`ERR ${response.status}: ${response.statusText}`) 69 | return response.json() 70 | } 71 | 72 | render () { 73 | this.innerHTML = interpolate(this.__template, this.__context) 74 | } 75 | } 76 | 77 | customElements.define('wc-template', WCTemplate) 78 | --------------------------------------------------------------------------------