Skip to content

Commit 99eed54

Browse files
WIP
1 parent 9c0aa28 commit 99eed54

File tree

178 files changed

+253
-3458
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

178 files changed

+253
-3458
lines changed

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ clean:
2020

2121
test:
2222
@ make build
23-
@ $(ISTANBUL) cover $(MOCHA) -- test/runner.js
23+
@ $(ISTANBUL) cover $(MOCHA) -- test/index.js
2424

2525
lint:
2626
@ $(ESLINT) src test

README.md

+32-21
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,28 @@ For example `my-tag.tag`:
1919
{ message }
2020
</p>
2121

22-
<div if={ error }>
23-
<p>{ error }</p>
24-
</div>
22+
{#if error}
23+
<div>
24+
<p>{ error }</p>
25+
</div>
26+
{#else}
27+
no errors
28+
{/if}
2529

2630
<ul>
27-
<li each={ item in items }>{ item.value }</li>
31+
{#each item in items}
32+
<li>{ item.value }</li>
33+
{/each}
2834
</ul>
2935
</template>
3036

3137
<script>
3238
export default {
39+
error: 'this is an error',
40+
message: 'nice message',
41+
items: [{ value: 'one'}, { value: 'two'}]
3342
updateMessage(message) {
34-
this.update({ message: 'goodbye', error: false })
43+
this.update({ message, error: false })
3544
}
3645
}
3746
</script>
@@ -47,13 +56,11 @@ Will become:
4756

4857
```js
4958
riot.define('my-tag', {
50-
store: {
51-
message: 'click me',
52-
error: 'i am an error',
53-
items: [{ value: 'foo'}, { value: 'bar' }]
54-
},
55-
updateMessage() {
56-
this.update({ message: 'goodbye', error: false })
59+
error: 'this is an error',
60+
message: 'nice message',
61+
items: [{ value: 'one'}, { value: 'two'}],
62+
updateMessage(message) {
63+
this.update({ message, error: false })
5764
}
5865
get css() {
5966
return `
@@ -62,19 +69,21 @@ riot.define('my-tag', {
6269
}
6370
`
6471
},
65-
render(h, store) {
72+
render(h) {
6673
return h`
6774
<p onclick="${ this.updateMessage.bind(this, 'goodbye') }">
68-
${ store.message }
75+
${ this.message }
6976
</p>${
70-
if (store.error) {
71-
`<div>
72-
<p>${ store.error }</p>
73-
</div>`
74-
}
77+
this.error ? `
78+
<div>
79+
<p>${ this.error }</p>
80+
</div>
81+
` : `no errors`
7582
}<ul>${
76-
store.items.map((item) => {
77-
return `<li>${ item.value }</li>`
83+
Array.from(this.items).map((item) => {
84+
return (`
85+
<li>${ item.value }</li>
86+
`)
7887
})
7988
}</ul>
8089
`;
@@ -97,6 +106,8 @@ Check for example [this demo](https://jsfiddle.net/gianlucaguarini/ed31q3qk/) to
97106
- The source code will be simplified a lot avoiding to maintain different forks for the browser/node versions
98107
- It will generate always sourcemaps
99108
- It will be simpler to validate the riot tags syntax due to a cleaner and unambigous structure
109+
- No need for `<virtual>` tags and other weird hacks
110+
- Allow the rendering of raw markup
100111

101112
[travis-image]: https://img.shields.io/travis/riot/compiler.svg?style=flat-square
102113
[travis-url]: https://travis-ci.org/riot/compiler

package.json

+5-3
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@
2424
"compiler"
2525
],
2626
"devDependencies": {
27+
"chai": "^3.5.0",
2728
"coveralls": "^2.12.0",
2829
"eslint": "^3.17.1",
29-
"esprima": "^3.1.3",
30-
"expect.js": "^0.3.1",
3130
"istanbul": "^0.4.5",
3231
"mocha": "^3.2.0",
33-
"rollup": "^0.41.5"
32+
"rollup": "^0.41.5",
33+
"rollup-plugin-commonjs": "^8.0.2",
34+
"rollup-plugin-node-resolve": "^2.0.0",
35+
"shelljs": "^0.7.7"
3436
},
3537
"author": "Riot maintainers team + smart people from all over the world",
3638
"contributors": [

rollup.config.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1+
import commonjs from 'rollup-plugin-commonjs'
2+
import nodeResolve from 'rollup-plugin-node-resolve'
3+
14
export default {
25
moduleName: 'compiler',
3-
banner: '/* Riot Compiler WIP, @license MIT */'
6+
banner: '/* Riot Compiler WIP, @license MIT */',
7+
plugins: [
8+
nodeResolve(),
9+
commonjs({
10+
include: 'node_modules/**',
11+
ignoreGlobal: true
12+
})
13+
]
414
}

src/index.js

+3-4
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,9 @@ import { parse } from './parser'
99
* @returns { Object } output.code - the generated javascript code
1010
* @returns { Object } output.map - source map
1111
*/
12-
function compile(name, source, options) {
13-
if (!name) throw `Please specify the name of the tag you want to compile`
14-
const output = parse(source, options)
15-
return output
12+
function compile(name, source, options = {}) {
13+
if (!name) throw 'Please specify the name of the tag you want to compile'
14+
return parse(name, source, options)
1615
}
1716

1817
export default {

src/parser.js

+54-33
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { SourceMapGenerator } from 'source-map'
2-
import esprima from 'esprima'
2+
import { parse as parseTemplate } from './parsers/template'
3+
import { parse as parseJs } from './parsers/js'
4+
import { parse as parseCss } from './parsers/css'
5+
import { offset } from './util'
36

4-
// Riot tags fragments
7+
// Riot tags fragments regex
58
const SCRIPT = /<script(\s+[^>]*)?>\n?([\S\s]*?)<\/script\s*>/gi
69
const TEMPLATE = /<template(\s+[^>]*)?>\n?([\S\s]*?)<\/template\s*>/gi
710
const STYLE = /<style(\s+[^>]*)?>\n?([\S\s]*?)<\/style\s*>/gi
@@ -21,8 +24,8 @@ function sourcemap(source, code, generated) {
2124
* @returns { String } output.type - the "type" attribute used on the current fragment
2225
* @returns { String } output.code - the code contained in this fragment
2326
*/
24-
function getFragment(source, regExp) {
25-
const match = regExp.exec(source)
27+
function fragment(source, regExp) {
28+
const match = source ? regExp.exec(source) : null
2629
return match ? {
2730
// get the type="whathever" attribute
2831
type: match[1] ? TYPE_ATTR.exec(match[1])[2] : null,
@@ -31,30 +34,38 @@ function getFragment(source, regExp) {
3134
}
3235

3336
/**
34-
* Generate the output code together with the sourcemap
35-
* @param { String } source - source code of the tag we will need to compile
36-
* @param { Object } options - user options
37-
* @returns { Promise } output
38-
* @returns { String } output.code
39-
* @returns { String } output.map
40-
* @returns { String } output.fragments
37+
* Generate the js output from the parsing result
38+
* @param { String } name - the component name
39+
* @param { String } head - additional javascript that could be added to the component
40+
* @param { String } template - the result of the template parsing
41+
* @param { String } exports - all the methods exported by the component
42+
* @param { String } css - component specific syles
43+
* @returns { String } object that must be provided to the riot.define method
4144
*/
42-
function generate(source, options) {
43-
new Promise((resolve, reject) => {
44-
const fragments = {
45-
css: getFragment(STYLE),
46-
js: getFragment(SCRIPT),
47-
template: getFragment(TEMPLATE)
48-
}
49-
50-
const map = sourcemap(options.src, source, options.dist)
51-
52-
resolve({
53-
fragments,
54-
code: '', // TODO: generate the output code
55-
map
45+
export function generate(name, head, exports, template, css) {
46+
return `
47+
${ head }
48+
riot.define(${ name }, {
49+
${ exports ? `
50+
${ exports }
51+
` : ''
52+
}
53+
${
54+
css ? `
55+
get css() {
56+
return \`${ css }\`
57+
}
58+
` : ''
59+
}
60+
${
61+
template ? `
62+
render(h) {
63+
return\`${ template }\`
64+
}
65+
` : ''
66+
}
5667
})
57-
})
68+
`
5869
}
5970

6071
/**
@@ -66,12 +77,22 @@ function generate(source, options) {
6677
* @returns { String } output.fragments
6778
*/
6879
export function parse(name, source, options) {
69-
return generate(source, options)
70-
.then(({code, map, fragments}) => {
71-
return {
72-
code: `riot.define(${ name }, ${ code })`,
73-
fragments,
74-
map
75-
}
80+
return new Promise((resolve, reject) => {
81+
const fragments = {
82+
css: fragment(source, STYLE),
83+
js: fragment(source, SCRIPT),
84+
template: fragment(source, TEMPLATE)
85+
}
86+
87+
const map = options.src && options.dist ? sourcemap(options.src, source, options.dist) : ''
88+
const js = parseJs(fragments.js, offset(source, '<script'))
89+
const template = parseTemplate(fragments.template, offset(source, '<template'))
90+
const css = parseCss(fragments.css, offset(source, '<style'))
91+
92+
resolve({
93+
fragments,
94+
code: generate(name, js.head, js.exports, template.code, css.code),
95+
map
7696
})
97+
})
7798
}

src/parsers/css.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function parse(source, offset) {
2+
3+
}

src/parsers/flow-expressions.js

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// match "item in items" or "item,i in items"
2+
export const EACH_EXPR = /((\w+)|(?:(\w+),(?:\s+)?(\w+))?)\s+in\s+([\w]+)/i
3+
4+
export const each = {
5+
start(str, value) {
6+
const values = value.match(EACH_EXPR)
7+
const item = values[2] || values[3]
8+
const target = values[5]
9+
const iterator = values[4]
10+
11+
return `${ target }.map(${ iterator ? `(${item}, ${iterator})` : `(${item})`} => { return (`
12+
},
13+
end() {
14+
return '))}'
15+
}
16+
}
17+
18+
export const conditional = {
19+
start(str, value) {
20+
21+
},
22+
end(str, value) {
23+
24+
}
25+
}

src/parsers/js.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function parse(source, offset) {
2+
3+
}

src/parsers/template.js

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { each, conditional } from './flow-expressions'
2+
/**
3+
* HTML_TAGS matches opening and self-closing tags, not the content.
4+
* Used by {@link module:compiler~_compileHTML|_compileHTML} after hidding
5+
* the expressions.
6+
*
7+
* 2016-01-18: exclude `'\s'` from attr capture to avoid unnecessary call to
8+
* {@link module:compiler~parseAttribs|parseAttribs}
9+
* @const {RegExp}
10+
*/
11+
export const HTML_TAGS = /<(-?[A-Za-z][-\w\xA0-\xFF]*)(?:\s+([^"'\/>]*(?:(?:"[^"]*"|'[^']*'|\/[^>])[^'"\/>]*)*)|\s*)(\/?)>/g
12+
/**
13+
* HTML5 void elements that cannot be auto-closed.
14+
* @const {RegExp}
15+
* @see {@link http://www.w3.org/TR/html-markup/syntax.html#syntax-elements}
16+
* @see {@link http://www.w3.org/TR/html5/syntax.html#void-elements}
17+
*/
18+
export const VOID_TAGS = /^(?:input|img|br|wbr|hr|area|base|col|embed|keygen|link|meta|param|source|track)$/
19+
// match all the "{" and "\{" and "${" to replace eventually with "${"
20+
export const TMPL_EXPR = /(({|[\\|$]{))(?!#)/
21+
// match all the {#whathever } expressions
22+
export const START_FLOW_EXPR = /{#(\w+(?:\sif)?)(?:\s)?(.+)?}/
23+
// match all the {/whathever } expressions
24+
export const END_FLOW_EXPR = /{\/(?:\s+)?(\w+)(?:\s+)?}/
25+
26+
27+
export function replaceFlow(expr, name, value, position) {
28+
switch (name) {
29+
case 'each':
30+
return each[position](expr, value)
31+
case 'if':
32+
return conditional[position](expr, value)
33+
default:
34+
return expr
35+
}
36+
}
37+
38+
export function normalizeMarkup(name, attr, ends) {
39+
// force all tag names to lowercase
40+
name = name.toLowerCase()
41+
// close self-closing tag, except if this is a html5 void tag
42+
ends = ends && !VOID_TAGS.test(name) ? `></${name}` : ''
43+
return `<${name}${ends}>`
44+
}
45+
46+
47+
export function parse(html, offset) {
48+
const lines = html.split('\n')
49+
const map = []
50+
51+
lines.map((line, row) => {
52+
map.push({
53+
original: { line: row + offset, column: 0 },
54+
generated: { line: row + 1, column: 0 }
55+
})
56+
57+
line
58+
.replace(TMPL_EXPR, expr => expr.substring(0, 1) === '{' ? '${' : expr)
59+
.replace(START_FLOW_EXPR, (_, expr, name, value) => {
60+
lines[row -1] = lines[row -1].trim()
61+
lines[row -1] += '${'
62+
return replaceFlow(expr, name, value, 'start')
63+
})
64+
.replace(END_FLOW_EXPR, (_, expr, name, value) => {
65+
lines[row +1] = lines[row -1].trim()
66+
lines[row +1] = '}' + lines[row +1]
67+
return replaceFlow(expr, name, value, 'end')
68+
})
69+
.replace(HTML_TAGS, (_, name, attr, ends) => normalizeMarkup(name, attr, ends))
70+
})
71+
72+
}

src/util.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Get the rows offset of a target string from a source text file
3+
* @param { String } source - source string to test
4+
* @param { String } target - string we want to detect
5+
* @returns { Number } offset in rows of the target string
6+
*/
7+
export function offset(source, target) {
8+
return source.substring(0, source.indexOf(target) ).split('\n').length
9+
}

test/expected/simple-template.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
riot.define('simple-template', {
2+
render(r) {
3+
r`
4+
<p>${ this.message }</p>
5+
`
6+
}
7+
})

0 commit comments

Comments
 (0)