title | layout |
---|---|
Compiler |
detail |
Custom tags need to be transformed to JavaScript before the browser can execute them. The Riot compiler is designed to transform Riot tags into JavaScript modules. A compiled Riot tag will look like this:
export default {
css: `my-tag { color: red; }`, // component css string
template: function() {}, // internal riot template factory function
exports: {}, // component events and lifecycle methods
name: 'my-tag' // component id
}
Each tag file must contain only one tag definition.
The riot+compiler.js
(~90kb Gzip) bundle lets you compile and execute Riot tags directly in your browser.
You can load Riot tags into your browser by setting a type="riot"
attribute on your script tags.
For example:
<!-- mount point -->
<my-tag></my-tag>
<!-- <my-tag/> is specified in an external file -->
<script src="path/to/javascript/my-tag.riot" type="riot"></script>
<!-- include riot.js and the compiler -->
<script src="https://unpkg.com/riot@{{ site.data.globals.version }}/riot+compiler.min.js"></script>
<!-- compile and mount -->
<script>
(async function main() {
await riot.compile()
riot.mount('my-tag')
}())
</script>
Notice that in this case Riot will internally transform all of the export default
expressions to enable better support for browsers that don't support JavaScript modules yet.
Riot can asynchronously compile all of the external tags included via <script>
into the DOM and it will render them via riot.mount
.
You might prefer using data-src
instead of src
on your <script>
tags to prevent the browser from automatically prefetching any Riot script tag in order and avoid loading the same resources twice. Riot will automatically fetch and compile your tags via AJAX.
Your Riot.js components could be also be included directly in your page via <template>
tags. For example:
<!-- somewhere in your page -->
<template id="my-tag">
<my-tag>
<p>{ props.message }</p>
</my-tag>
</template>
The riot+compiler.js
bundle exposes the compileFromString
and inject
methods that combined together, can help you compiling the above component:
const tagString = document.getElementById('my-tag').innerHTML
// get the compiled code
const {code} = riot.compileFromString(tagString)
// create the riot component in runtime
riot.inject(code, 'my-tag', './my-tag.html')
riot.mount('my-tag')
The Compilation phase is asynchronous and it will not block your application rendering. However you should use the browser compilation only for prototyping or for quick experiments.
Pre-compilation has the following benefits:
- Ability to compile tags with your favorite pre-processor.
- Big performance benefit. No need to load and execute the compiler on the browser.
- Sourcemaps support for debugging.
Tools like webpack
and Rollup
are the perfect match to bundle your Riot application tags.
For such tools we provide Riot official loaders to natively import riot components into your source code:
With the Riot loaders your application entry script might look like this:
import { component } from 'riot'
import MyTag from './path/to/tags/my-tag.riot'
component(MyTag)(document.getElementById('root'))
The Riot.js compiler supports typescript syntax out of the box.
If you want to enable the type checking for your component API and expressions you will export its RiotComponent
interface as it follows:
<my-component>
<!-- notice that lang="ts" is optional and eventually needed just for your IDE code highlighting -->
<script lang="ts">
import { RiotComponent } from 'riot'
export interface MyComponent extends RiotComponent<MyComponentProps, MyComponentState> {
/* additional component custom methods and properties */
}
</script>
</my-component>
If you want to enhance your component types you might want to check also the riot.withTypes
method
You might want to check the Riot.js typescript example to setup your project config files.
import {compile} from '@riotjs/compiler'
const { code, map } = compile('<p>{hello}</p>', {
//...options
file: 'my/path/to/my-component.riot',
// transform the `:host` css rules
scopedCss: true,
// expressions delimiters
brackets: ['{', '}'],
// keep HTML comments
comments: false
})
The compile function takes a string and returns an object containing the code
and map
keys.
You can handle the code generated however you like and use it in your build system.
Remember that the Riot compiler outputs JavaScript modules, and you might want to transpile them in your bundle.
You can precompile Riot.js files also via the riot
executable, which can be installed with NPM as follows:
npm install @riotjs/cli -g
Here is how riot
command works:
# compile a file to current folder
riot some.riot
# compile file to target folder
riot some.riot --output some-folder
# compile file to target path
riot some.riot --output some-folder/some.js
# compile all files from source folder to target folder
riot some/folder --output path/to/dist
For more information, type: riot --help
You can watch directories and automatically transform files when they are changed.
# watch for
riot -w src -o dist
You're free to use any file extension for your tags (instead of default .riot
):
riot --extension html
You can use a config file to store and easily configure all of your @riotjs/cli
options and create your custom parsers:
riot --config riot.config src
The riot riot.config.js
file:
export default {
output: 'tags/dist',
// sourcemap type
sourcemap: 'inline',
// files extension
extension: 'foo'
}
If you want to use custom preprocessors in your project you should install @riotjs/cli
as a devDependency
and run it via npm scripts:
{
"scripts": {
"build": "npx riot -c riot.config src"
},
"devDependencies": {
"@riotjs/cli": "^9.0.0",
"@riotjs/compiler": "^9.0.0",
"pug": "^2.0.3"
}
}
That's how your riot.config.js
file might look like in case you want to use pug
as components template engine.
import { registerPreprocessor } from '@riotjs/compiler'
import { render } from 'pug'
// register the pug preprocessor
registerPreprocessor('template', 'pug', (code, options) => {
const { file } = options
return {
code: render(code, {
filename: file,
pretty: true,
doctype: 'html'
})
}
})
export default {
extension: 'pug',
// assign the pug preprocessor to the riot compiler options
riot: {
template: 'pug'
}
}
You can also use the CLI to bundle your entire application.
The app.js
file:
import {component} from 'riot'
import App from './app.riot'
component(App)(document.getElementById('root'))
riot app.js -o dist/app.js
Your dist/app.js
file will contain all the Riot.js components imported in your application and the code to run it.
You can pre-process your components' contents using your favorite programming language.
The @riotjs/compiler
gives the possibility to register your preprocessors:
import { registerPreprocessor } from '@riotjs/compiler'
import pug from 'pug'
import sass from 'sass'
import babel from '@babel/core'
registerPreprocessor('template', 'pug', function(code, { options }) {
const { file } = options
console.log('Preprocess the template', file)
return {
code: pug.render(code),
// no sourcemap here
map: null
}
})
registerPreprocessor('css', 'sass', function(code, { options }) {
const { file } = options
console.log('Compile the sass code in', file)
return {
code: sass.compileString(code, {syntax: 'indented'}).css,
map: null
}
})
registerPreprocessor('javascript', 'babel', function(code, { options }) {
const { file } = options
return babel.transform(code, {
sourceFileName: file
})
})
The Riot.js preprocessors can be only of three types: template
, css
, javascript
(the first argument of the registerPreprocessor
function).
To compile your components with a different template engine you will need to specify the template
option via compiler:
import { compile } from '@riotjs/compiler'
compile(source, {
template: 'pug'
})
For the css
and javascript
preprocessors you can simply enable them directly in your components via type="{preprocessor}"
attribute
<my-component>
<p>{getMessage}</p>
<style type="scss">
import 'color/vars'
p {
color: $primary;
}
</style>
<script type="ts">
export default {
getMessage():string {
return 'hello world'
}
}
</script>
</my-component>
The Riot.js compiler generates sourcempas out of the code provided by the pre-processors. If your preprocessor will not provide any map
output the compiler will not output proper sourcemaps.
import { registerPreprocessor } from '@riotjs/compiler'
import babel from '@babel/core'
registerPreprocessor('javascript', 'babel', function(code, { options }) {
// the babel.transform returns properly an object containing the keys {map, code}
return babel.transform(code, {
sourceMaps: true,
// notice that whitelines should be preserved
retainLines: true,
sourceFileName: options.file,
presets: [[
'@babel/env',
{
targets: {
edge: true
},
loose: true,
modules: false,
useBuiltIns: 'usage'
}
]]
})
})
registerPreprocessor('javascript', 'my-js-preprocessor', function(code, { options }) {
// the Riot.js compiler will not be able to generate sourcemaps
return {
code: myPreprocessor(code),
map: null
}
})
The JavaScript preprocessors should preserve the code whitelines of the original source code. Otherwise the resulting sourcemap will have a broken offset.
Similar to the preprocessor, the compiler output can be modified via registerPostprocessor
:
import { registerPostprocessor } from '@riotjs/compiler'
import buble from 'buble'
// your compiler output will pass from here
registerPostprocessor(function(code, { options }) {
const { file } = options
console.log('your file path is:', file)
// notice that buble.transform returns {code, map}
return buble.transform(code)
})
In this case we make sure that the output code will be converted to es2015 via buble
.
Finally, you can also compile a tag online