├── .gitattributes
├── .gitignore
├── .prettierrc
├── babel.config.js
├── example
├── poi.config.js
└── index.js
├── .editorconfig
├── src
├── mustUseDomProp.js
└── index.js
├── circle.yml
├── test
└── index.test.js
├── LICENSE
├── package.json
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.log
3 | .DS_Store
4 | /dist
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "singleQuote": true
4 | }
5 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['poi/babel', 'power-assert']
3 | }
4 |
--------------------------------------------------------------------------------
/example/poi.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | exports.entry = 'example/index'
4 |
5 | exports.configureWebpack = {
6 | resolve: {
7 | alias: {
8 | 'vue-html$': path.join(__dirname, '../src')
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/src/mustUseDomProp.js:
--------------------------------------------------------------------------------
1 | const acceptValue = ['input', 'textarea', 'option', 'select']
2 |
3 | export default (tag, type, attr) => {
4 | return (
5 | (attr === 'value' && acceptValue.includes(tag) && type !== 'button') ||
6 | (attr === 'selected' && tag === 'option') ||
7 | (attr === 'checked' && tag === 'input') ||
8 | (attr === 'muted' && tag === 'video')
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | working_directory: ~/repo
5 | docker:
6 | - image: circleci/node:latest-browsers
7 | branches:
8 | ignore:
9 | - gh-pages # list of branches to ignore
10 | - /release\/.*/ # or ignore regexes
11 | steps:
12 | - checkout
13 | - restore_cache:
14 | key: dependency-cache-{{ checksum "yarn.lock" }}
15 | - run:
16 | name: install dependences
17 | command: yarn
18 | - save_cache:
19 | key: dependency-cache-{{ checksum "yarn.lock" }}
20 | paths:
21 | - ./node_modules
22 | - run:
23 | name: test
24 | command: yarn test
25 |
--------------------------------------------------------------------------------
/example/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import HTML from 'vue-html' // eslint-disable-line import/no-unresolved
3 |
4 | Vue.use(HTML)
5 |
6 | const Todos = {
7 | props: ['todos'],
8 | render(html) {
9 | return html`
10 |
11 | ${
12 | this.todos.map((todo, index) => {
13 | return html`
14 | ${todo}
15 | `
16 | })
17 | }
18 |
19 | `
20 | }
21 | }
22 |
23 | new Vue({
24 | el: '#app',
25 | data: {
26 | todos: ['Conquer the world', 'Rewrite Peco'],
27 | todo: ''
28 | },
29 | methods: {
30 | add() {
31 | this.todos.push(this.todo)
32 | this.todo = ''
33 | }
34 | },
35 | render(html) {
36 | return html`
37 |
38 | {
42 | this.todo = e.target.value
43 | }
44 | }
45 | />
46 | Add
47 |
48 | <${Todos} todos=${this.todos} />
49 |
50 | `
51 | }
52 | })
53 |
--------------------------------------------------------------------------------
/test/index.test.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert'
2 | import Vue from 'vue'
3 | import HTML from '../src'
4 |
5 | Vue.use(HTML)
6 |
7 | describe('main', () => {
8 | it('works', () => {
9 | const vm = new Vue({
10 | render(html) {
11 | return html`
12 | hello
13 | `
14 | }
15 | }).$mount()
16 | assert(vm.$el.textContent === 'hello')
17 | })
18 |
19 | it('transform vue-specific attributes', () => {
20 | const vm = new Vue({
21 | data: { count: 0 },
22 | methods: {
23 | handleClick() {
24 | this.count++
25 | }
26 | },
27 | render(html) {
28 | return html`
29 |
30 |
31 |
32 | `
33 | }
34 | }).$mount()
35 | vm.$el.dispatchEvent(new Event('click'))
36 | vm._watcher.run()
37 | assert(vm.count === 1)
38 |
39 | const hi = vm.$el.querySelector('.hi')
40 | assert(hi.textContent === 'hi')
41 |
42 | const foo = vm.$el.querySelector('#foo')
43 | assert(foo.textContent === 'hi')
44 | })
45 | })
46 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) EGOIST <0x142857@gmail.com> (github.com/egoist)
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-html",
3 | "version": "1.0.0",
4 | "description": "Use tagged template string in Vue.js render function",
5 | "license": "MIT",
6 | "repository": "egoist/vue-html",
7 | "author": {
8 | "name": "EGOIST",
9 | "email": "0x142857@gmail.com",
10 | "url": "http://github.com/egoist"
11 | },
12 | "scripts": {
13 | "test": "npm run lint && npm run test:unit",
14 | "test:unit": "poi puppet --test --plugin @poi/puppet --framework mocha",
15 | "lint": "xo",
16 | "build": "bili --format umd,cjs,es,es-min,umd-min --js buble --module-name HTML --inline --name html",
17 | "example": "poi -so --config example/poi.config.js",
18 | "prepublishOnly": "npm run build"
19 | },
20 | "files": [
21 | "dist"
22 | ],
23 | "xo": {
24 | "extends": [
25 | "rem",
26 | "plugin:prettier/recommended"
27 | ],
28 | "envs": [
29 | "browser",
30 | "mocha"
31 | ],
32 | "rules": {
33 | "unicorn/filename-case": "off",
34 | "no-new": "off"
35 | }
36 | },
37 | "main": "dist/html.js",
38 | "module": "dist/html.es.js",
39 | "keywords": [
40 | "vue",
41 | "htm",
42 | "html"
43 | ],
44 | "devDependencies": {
45 | "@poi/plugin-puppet": "^0.1.3",
46 | "babel-preset-power-assert": "^3.0.0",
47 | "bili": "^3.4.2",
48 | "eslint-config-prettier": "^3.3.0",
49 | "eslint-config-rem": "^4.0.0",
50 | "eslint-plugin-prettier": "^3.0.0",
51 | "htm": "^2.0.0",
52 | "poi": "^12.2.4",
53 | "power-assert": "^1.6.1",
54 | "prettier": "^1.15.3",
55 | "vue": "^2.5.21",
56 | "xo": "^0.23.0"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import htm from 'htm'
2 | import mustUseDomProp from './mustUseDomProp'
3 |
4 | export default Vue => {
5 | Vue.mixin({
6 | beforeCreate() {
7 | const createElement = this.$createElement.bind(this)
8 | const h = (tag, attrs, ...children) => {
9 | return createElement(tag, attrs && getVNodeData(tag, attrs), children)
10 | }
11 | this.$$createElement = createElement
12 | this.$createElement = htm.bind(h)
13 | }
14 | })
15 | }
16 |
17 | function getVNodeData(tag, attrs) {
18 | const data = {}
19 |
20 | const basics = ['slot', 'key', 'ref', 'refInFor', 'class', 'style']
21 |
22 | for (const key of Object.keys(attrs)) {
23 | if (key.substring(0, 2) === 'on') {
24 | // OnClick => on: {click}
25 | data.on = data.on || {}
26 | const newKey = lowerCaseFirstLetter(key.substring(2))
27 | data.on[newKey] = attrs[key]
28 | } else if (key.substring(0, 8) === 'nativeOn') {
29 | // NativeOnClick => nativeOn: {click}
30 | data.nativeOn = data.nativeOn || {}
31 | const newKey = lowerCaseFirstLetter(key.substring(8))
32 | data.nativeOn[newKey] = attrs[key]
33 | } else if (key.substring(0, 8) === 'domProps') {
34 | // DomPropsInnerHTML => domProps: {innerHTML}
35 | data.domProps = data.domProps || {}
36 | const newKey = lowerCaseFirstLetter(key.substring(8))
37 | data.domProps[newKey] = attrs[key]
38 | } else if (key.substring(0, 2) === 'v-') {
39 | data.directives = data.directives || []
40 | const name = key.substring(2)
41 | data.directives.push({
42 | name,
43 | value: attrs[key]
44 | })
45 | } else if (mustUseDomProp(tag, attrs.type, key)) {
46 | data.domProps = data.domProps || {}
47 | data.domProps[key] = attrs[key]
48 | } else if (basics.indexOf(key) > -1) {
49 | data[key] = attrs[key]
50 | } else {
51 | // All others props => {attrs: props}
52 | data.attrs = data.attrs || {}
53 | data.attrs[key] = attrs[key]
54 | }
55 | }
56 |
57 | return data
58 | }
59 |
60 | function lowerCaseFirstLetter(string) {
61 | return string.charAt(0).toLowerCase() + string.substring(1)
62 | }
63 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-html
2 |
3 | [](https://npmjs.com/package/vue-html) [](https://npmjs.com/package/vue-html) [](https://circleci.com/gh/egoist/vue-html)
4 |
5 | > Use tagged template string in Vue.js render function
6 |
7 | ## Why is this useful?
8 |
9 | If you want to use Vue without a bundler / transpiler, this library will (reasonably) make your app smaller:
10 |
11 | - Vue (runtime + template compiler): 32kB gzipped
12 | - Vue (runtime + vue-html): 23kB gzipped
13 |
14 | **What's the downside?** No handy sugars like `v-model` support.
15 |
16 | ## Install
17 |
18 | ```bash
19 | $ npm install --save vue-html
20 | ```
21 |
22 | CDN versions:
23 |
24 | - `UMD`: https://unpkg.com/vue-html/dist/html.js (exposed as `window.HTML`)
25 | - `ESM`: https://unpkg.com/vue-html/dist/html.es.js
26 |
27 | ## Usage
28 |
29 | [](https://codesandbox.io/s/50qxwm44mx)
30 |
31 | ```js
32 | import Vue from 'vue'
33 | import HTML from 'vue-html'
34 |
35 | Vue.use(HTML)
36 |
37 | const Todos = {
38 | props: ['todos'],
39 | render(html) {
40 | return html`
41 |
42 | ${
43 | this.todos.map((todo, index) => {
44 | return html`
45 | ${todo}
46 | `
47 | })
48 | }
49 |
50 | `
51 | }
52 | }
53 |
54 | new Vue({
55 | el: '#app',
56 | data: {
57 | todos: ['Conquer the world', 'Rewrite Peco'],
58 | todo: ''
59 | },
60 | methods: {
61 | add() {
62 | this.todos.push(this.todo)
63 | this.todo = ''
64 | }
65 | },
66 | render(html) {
67 | return html`
68 |
69 | (this.todo = e.target.value)}
72 | />
73 | Add
74 |
75 | <${Todos} todos=${this.todos} />
76 |
77 | `
78 | }
79 | })
80 | ```
81 |
82 | The usage is very similar to Vue JSX except that the `html` function is powered by [HTM (Hyperscript Tagged Markup)](https://github.com/developit/htm).
83 |
84 | ### Using Components
85 |
86 | ```js
87 | const App = {
88 | render(html) {
89 | return html`
90 |
91 | <${Todos} />
92 | <${Todos}> or with children />
93 |
94 | `
95 | }
96 | }
97 | ```
98 |
99 | You can also use the traditional way of using local / global components:
100 |
101 | ```js
102 | const App = {
103 | render(html) {
104 | return html`
105 |
106 | `
107 | },
108 | components: {
109 | Todos
110 | }
111 | }
112 | ```
113 |
114 | ## Contributing
115 |
116 | 1. Fork it!
117 | 2. Create your feature branch: `git checkout -b my-new-feature`
118 | 3. Commit your changes: `git commit -am 'Add some feature'`
119 | 4. Push to the branch: `git push origin my-new-feature`
120 | 5. Submit a pull request :D
121 |
122 | ## License
123 |
124 | [MIT](https://egoist.mit-license.org/) © [EGOIST](https://github.com/egoist)
125 |
--------------------------------------------------------------------------------