https://github.com/michaelolof/vue-literal-compiler
A Vue Compiler that allows you compile your string literals to render functions at build time and write components in SFC paradigm
https://github.com/michaelolof/vue-literal-compiler
single-file-component template-literals type-safety typescript vue vue-components vue-loader vue-template-compiler vue-template-loader
Last synced: 4 months ago
JSON representation
A Vue Compiler that allows you compile your string literals to render functions at build time and write components in SFC paradigm
- Host: GitHub
- URL: https://github.com/michaelolof/vue-literal-compiler
- Owner: michaelolof
- Created: 2019-01-03T16:41:56.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2023-01-07T02:42:49.000Z (over 3 years ago)
- Last Synced: 2025-10-04T00:56:41.594Z (9 months ago)
- Topics: single-file-component, template-literals, type-safety, typescript, vue, vue-components, vue-loader, vue-template-compiler, vue-template-loader
- Language: TypeScript
- Homepage:
- Size: 1.05 MB
- Stars: 155
- Watchers: 2
- Forks: 4
- Open Issues: 13
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Vue Literal Compiler
A simple stand in replacement for the default vue-template-compiler that allows you write Vue Templates, Scoped CSS, Custom Blocks and Scripts in SFC format all in your JavaScript/TypeScript files.
## Preview

## Motivation
* Maintain the SFC paradigm. Write all your Templates, Styles and Scripts in one file (JavaScript/TypeScript)
* Compiles your literal templates into render functions at compile time rather than at runtime.
* Provide Support for CSS in JavaScript/TypeScript.
* Provide Support for Scoped CSS
* Provide support for SASS, SCSS, LESS etc. using the lang attribute.
* Provide Support for Custom Blocks in JavaScript/TypeScript.
* Provide Support for lintable typesafe templates using Functional Templates
## Change Log
- ### v 1.2.6
- Added Support for Type Safe `v-for` bindings.
- ### v 1.2.5
- Opening and Closing Style Tags `` are now optional. (They will be global by default)
- Added Support for Functional Templates.
## See Working Examples
* [Vue Literal Compiler Sample](https://github.com/michaelolof/vue-literal-compiler-sample)
## Get Started
- ### [Installation](#installation)
- ### [API](#api)
- ### [Painless Migration / Testing](#painless-migration-testing)
- ### [Why `.lit.*` files?](#why-lit-files)
- ### [Type Safe Bindings vs Type Safe Templates.](#type-bindings-vs-type-templates)
## Installation
1. Install Vue Literal Compiler
```
npm install --save-dev vue-literal-compiler
```
2. Include it in your webpack.config.js vue-loader options.
```js
{
test: /\.lit\.ts$/,
loader: 'vue-loader',
options: {
compiler: require("vue-literal-compiler"),
}
},
```
3. Add an additional Vue Loader Configuration. This is to appease VueLoaderPlugin and prevent it from squawking. See issue https://github.com/vuejs/vue-loader/issues/1238
```js
{
test: /\.vue$/,
loader: "vue-loader"
},
{
test: /\.lit\.ts$/,
loader: 'vue-loader',
options: {
compiler: require("vue-literal-compiler"),
loaders: {
'scss': 'vue-style-loader!css-loader!sass-loader',
'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax',
}
}
},
```
4. Finally since we're using plain TypeScript / JavaScript files rather than .vue files, ts-loader or babel will attempt to parse our files. We have to stop this.\
So in ts-loader's config we do this.
```js
{
test: /\.tsx?/,
loader: 'ts-loader',
exclude: [ /node_modules/, /\.lit.ts$/ ],
options: {
appendTsSuffixTo: [/\.vue$/],
}
},
```
## API
WIth `.vue` files we're used to putting our html, css (sass, less etc) and scripts (javascript or typescript) all in one file. It gave us the ability to reason about our components as a single unit in a single location.\
This is also very possible with Vue Literal Compiler.
### Templates
So with a `.vue` file we can define our templates like this:
```html
{{ greeting }}
```
The same template in a plain javascript/typescript file will look like this.
```ts
/** @VueLiteralCompiler Template */
const template = `
{{ greeting }}
`;
```
Notice you don't need the template tag to define a template.\
If we wanted to support multiple languages just like in `.vue` files, we simply add the template tag back with a lang atrribute.
```ts
/** @VueLiteralCompiler Template */
const template = `
div
p {{ greeting }} World!
other-component
`;
```
### Fat Arrow Templates
A Fat arrow template is one which accepts one parameter (the instance of your vue component) and returns a string (the template literal)\
To use a fat arrow template simply change the template variable from a string to a function that takes one parameter and returns a string.\
**NOTE - You must use a fat arrow. Regular functions will not work**
```ts
/** @VueLiteralCompiler Template */
const template = app => `
${ app.greeting }
`;
```
Notice you can easily switch between using string literals variable encapsulation ` app.greeting
` and your regular vue template style ``\
It just works.\
To support intellisense and code refractoring support in TypeScript, just type hint app
```ts
const template = (app:App) => `
${ app.greeting }
`;
```
At this point `app.greeting` and `app.sayHi()` is now type safe.\
\
To support syntax highlighting for VSCode simply install an extension like vscode-lit-html and tag your string literals as defined by the installed extension.
### Styles
Styles are defined in `.vue` files using a style tag
```html
.p {
font-size: 2em;
text-align: center;
}
```
To do the same using a Vue Literal Compiler:
```ts
/** @VueLiteralCompiler Styles */
const styles = `
.p {
font-size: 2em;
text-align: center;
}
`;
```
To Support scoped styles, preprocessor support or multiple styles.
```ts
/** @VueLiteralCompiler Styles */
const styles = `
.p {
font-size: 2em;
text-align: center;
& span {
font-weight: bold;
}
}
<style>
body {
background-color: gray;
}
`;
```
The opening and closing style tags can also be optional
```ts
/** @VueLiteralCompiler Styles */
const styles = `
.p {
font-size: 2em;
text-align: center;
}
`;
```
Optional Style Tags are global by default.
### Custom Blocks
A custom block in a `.vue` file would look this:
```html
Hello World
Just make sure things work.
This is how we rock
```
The same with Vue Literal Compiler will look like this.
```ts
/** @VueLiteralCompiler Custom Block */
const customBlock = `
Hello World
Just make sure things work.
`;
/** @VueLiteralCompiler Custom Block */
const anotherCustomBlock = `
This is how we rock
`;
```
## Painless Migration / Testing
One of the immediate advantages of Vue Literal Compiler is the painless interoperability with existing `.vue` code base. You can use Vue Literal Compiler (`.lit.*`) side by side with your existing `.vue` **today**. To do that simply modify your webpack.config.js
```js
{
test: /\.tsx?/,
loader: 'ts-loader',
exclude: [ /node_modules/, /\.lit.ts$/ ],
options: {
appendTsSuffixTo: [/\.vue$/],
}
},
{
test: /\.vue$/,
loader: 'vue-loader'
options: {
loaders: {
// Since sass-loader (weirdly) has SCSS as its default parse mode, we map
// the "scss" and "sass" values for the lang attribute to the right configs here.
// other preprocessors should work out of the box, no loader config like this necessary.
'scss': 'vue-style-loader!css-loader!sass-loader',
'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax',
}
}
},
{
test: /\.lit\.ts$/,
loader: 'vue-loader',
options: {
compiler: require("vue-literal-compiler"),
loaders: {
// Since sass-loader (weirdly) has SCSS as its default parse mode, we map
// the "scss" and "sass" values for the lang attribute to the right configs here.
// other preprocessors should work out of the box, no loader config like this necessary.
'scss': 'vue-style-loader!css-loader!sass-loader',
'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax',
}
}
},
```
At this point migrating your code base from `.vue` to `.lit.ts` or `.lit.js` is just a matter of copying and pasting your templates, styles and scripts respectively.
## Why `.lit.*` files?
While Vue Literal Compiler doesn't discriminate against file extension types (Whatever test case you put in your webpack config is what the compiler will use.) There are some reasons you might want to use .lit.* files over .vue.* files though.
### 1. Standards
First Vue Literal Compiler doesn't care what file type you use. But standards are important.
### 2. Interoperability with exisiting `.vue` files
Using `.vue.ts` or `.vue.js` with an existing code base of `.vue` files can cause unexpected errors due to name clashes. This is due to the way `vue-loader` and `ts-loader` work internally. To avoid this when working with an existing codebase of `.vue` files, just use a different file type aside from `.vue.ts` or `.vue.js`.
## Type Safe Bindings vs Type Safe Templates
With Fat Arrow Templates and Variable Encapsulations **your bindings are type safe**. This is a different approach from DSLs like JSX/TSX which promises typesafe templates.
With type safe bindings we can maintain the regular vue syntax we are used to (i.e `v-show` or `v-if` instead of `teniary`, `v-for` instead of `map(...)`, `@click` etc.) and still get complete type safety.
**Basic Data Binding:**
Normal Vue syntax:
```
Message: {{ msg }}
```
Type Safe Alternative:
```
Message: ${ app.msg }
```
\
**Attribute Data Bindings**
Normal Vue Syntax:
```
Button
```
Type Safe Alternative:
```
Button
```
\
**Using JavaScript Expressions**
Normal Vue Syntax:
```html
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
...
...
```
Type Safe Alternative:
```ts
${ app.number + 1 }
${ app.ok ? 'YES' : 'NO' }
${ app.message.split('').reverse().join('') }
...
...
```
\
**Class And Style Bindings**
Normal Vue Syntax:
```html
```
Type Safe Alternative:
```ts
```
\
**List Rendering**
List Rendering are sort of an edge case when it comes to type safety due to the way they are declared. (`v-for`s in Vue are not exactly JavaScript compliant.)
A typical Vue List would look like this:
```html
-
{{ item.message }}
```
Following our normal convention, the Fat Arrow alternative should typically look like this:
```ts
-
${ app.item.message }
```
Unfortunately this would throw a compile time error in TypeScript saying `app.item` is not found.
To get around this error we need to do two things.
First Create a Template Addon Interface
```ts
interface TemplateAddOn {
_item: typeof App.prototype.items[0]
}
```
Then in the Fat Arrow Template:
```ts
const template = (app:App & TemplateAddOn) => `
-
${ app._item.message }
```
Now `app._item` is now recognized.\
The underscore in `app._item` is just there to indicate to you that it is an addon and not part of your original component. (Feel free to use whatever you want.)\
The cast to any however is necessary if `app._item` is not of type `number`, `string` or `symbol`.\
**Only `` casts are supported by the compiler.**
Other examples of type safe lists:
```ts
-
${ app.parentMessage } - ${ app._index } - ${ app._item.message }
${ app._index }. ${ app._key }: ${ app._value }
```