Using to besides HTML
You can also apply it to syntaxes besides HTML such as template engines or frameworks if using plugins together.
Installing plugins
Install the parser plugin through npm or Yarn:
- npm
- Yarn
- pnpm
- Bun
npm install -D @markuplint/pug-parser
yarn add --dev @markuplint/pug-parser
pnpm add -D @markuplint/pug-parser
bun add --dev @markuplint/pug-parser
If your code uses tagged template literals containing HTML (e.g., lit-html), install the tagged template literal parser:
- npm
- Yarn
- pnpm
- Bun
npm install -D @markuplint/tagged-template-literal-parser
yarn add --dev @markuplint/tagged-template-literal-parser
pnpm add -D @markuplint/tagged-template-literal-parser
bun add --dev @markuplint/tagged-template-literal-parser
If a syntax has its own specification you should install the spec plugin with the parser plugin:
- npm
- Yarn
- pnpm
- Bun
npm install -D @markuplint/jsx-parser @markuplint/react-spec
yarn add --dev @markuplint/jsx-parser @markuplint/react-spec
pnpm add -D @markuplint/jsx-parser @markuplint/react-spec
bun add --dev @markuplint/jsx-parser @markuplint/react-spec
- npm
- Yarn
- pnpm
- Bun
npm install -D @markuplint/vue-parser @markuplint/vue-spec
yarn add --dev @markuplint/vue-parser @markuplint/vue-spec
pnpm add -D @markuplint/vue-parser @markuplint/vue-spec
bun add --dev @markuplint/vue-parser @markuplint/vue-spec
Supported syntaxes
| Template or syntax | Parser | Spec |
|---|---|---|
| JSX | @markuplint/jsx-parser | @markuplint/react-spec |
| Vue | @markuplint/vue-parser | @markuplint/vue-spec |
| Svelte | @markuplint/svelte-parser | @markuplint/svelte-spec |
| SvelteKit | @markuplint/svelte-parser/kit | - |
| Astro | @markuplint/astro-parser | - |
| Alpine.js | @markuplint/alpine-parser | @markuplint/alpine-spec |
| HTMX | - | @markuplint/htmx-spec |
| Tagged template literals (lit-html etc.) | @markuplint/tagged-template-literal-parser | - |
| Markdown | @markuplint/markdown-parser | - |
| MDX | @markuplint/mdx-parser | @markuplint/react-spec |
| Pug | @markuplint/pug-parser | - |
| PHP | @markuplint/php-parser | - |
| Smarty | @markuplint/smarty-parser | - |
| eRuby | @markuplint/erb-parser | - |
| EJS | @markuplint/ejs-parser | - |
| Mustache or Handlebars | @markuplint/mustache-parser | - |
| Nunjucks | @markuplint/nunjucks-parser | - |
| Liquid | @markuplint/liquid-parser | - |
There is @markuplint/html-parser package but the core package includes it.
You don't need to install and to specify it to the configuration.
It's not able to support syntaxes if one's attribute is complex.
✅ Available code
<div attr="{{ value }}"></div>
<div attr='{{ value }}'></div>
<div attr="{{ value }}-{{ value2 }}-{{ value3 }}"></div>
❌ Unavailable code
If it doesn't nest by quotations.
<div attr={{ value }}></div>
PULL REQUEST WANTED: This problem is recognized by developers and created as an issue #240.
Applying plugins
Specify a plugin to apply to the parser property on the configuration file.
And If it has spec add to the specs property.
Set a regular expression that can identify the target file name to the parser property key.
{
"parser": {
"\\.jsx$": "@markuplint/jsx-parser"
},
"specs": {
"\\.jsx$": "@markuplint/react-spec"
}
}
{
"parser": {
"\\.vue$": "@markuplint/vue-parser"
},
"specs": {
"\\.vue$": "@markuplint/vue-spec"
}
}
{
"parser": {
"\\.ts$": "@markuplint/tagged-template-literal-parser"
}
}
{
"parser": {
"\\.md$": "@markuplint/markdown-parser"
}
}
{
"parser": {
"\\.mdx$": "@markuplint/mdx-parser"
},
"specs": {
"\\.mdx$": "@markuplint/react-spec"
}
}
See explained configuring parser and specs if you want details.
Why need the spec plugins?
For example, the key attribute doesn't exist in native HTML elements. But you often need to specify it when you use React or Vue. So you should specify @markuplint/react-spec or @markuplint/vue-spec.
const Component = ({ list }) => {
return (
<ul>
{list.map(item => (
<li key={item.key}>{item.text}</li>
))}
</ul>
);
};
<template>
<ul>
<li v-for="item in list" :key="item.key">{{ item.text }}</li>
</ul>
</template>
Besides, spec plugins include specific attributes and directives each owned.
Pretenders
In React, Vue, and more, custom components cannot be evaluated as HTML elements. This means markuplint's content model rules — such as permitted-contents — have no way of knowing what a component actually renders. Without this information, a <Button> component that renders a <button> element is treated as an unknown element, and invalid nesting like <a><Button /></a> (interactive content inside interactive content) goes undetected.
<List>{/* No evaluate as native HTML Element */}
<Item />{/* No evaluate as native HTML Element */}
<Item />{/* No evaluate as native HTML Element */}
<Item />{/* No evaluate as native HTML Element */}
</List>
The Pretenders feature resolves that by telling markuplint what each component renders as.
Manual configuration
You can manually specify a selector for each component and the HTML element it renders:
{
"pretenders": [
{
"selector": "List",
"as": "ul"
},
{
"selector": "Item",
"as": "li"
}
]
}
<List>{/* Evaluate as <ul> */}
<Item />{/* Evaluate as <li> */}
<Item />{/* Evaluate as <li> */}
<Item />{/* Evaluate as <li> */}
</List>
This works well for small projects, but manually maintaining the list becomes tedious as your component library grows. That's where dynamic scanning comes in.
See the details of pretenders property on the configuration if you want.
Dynamic scanning
This feature is experimental and may change in future releases.
Instead of manually listing every component, you can let markuplint scan your component source files and discover pretender mappings automatically.
{
"pretenders": {
"scan": [
{
"files": "./src/components/**/*.tsx"
}
]
}
}
This single configuration replaces what might otherwise be dozens of manual pretender entries. When markuplint runs, it analyzes your component files and determines:
- Which HTML element each component renders as its root element
- Whether the component accepts children (slots detection)
- Static attributes on the root element
Supported file types
File extensions determine the scanner automatically:
| Extensions | Scanner | Frameworks |
|---|---|---|
.js, .jsx, .ts, .tsx | JSX scanner | React, Preact, Solid, etc. |
.vue | Template scanner | Vue |
.svelte | Template scanner | Svelte |
.astro | Template scanner | Astro |
You can scan multiple file types at once:
{
"pretenders": {
"scan": [
{
"files": "./src/components/**/*.tsx"
},
{
"files": "./src/components/**/*.vue",
"ignoreComponentNames": ["BaseLayout"]
}
]
}
}
What the scanner detects
Consider the following React component:
const ProfileCard = ({ children }) => {
return <article className="profile">{children}</article>;
};
The scanner automatically discovers that ProfileCard renders as <article> and accepts children. This is equivalent to writing:
{
"selector": "ProfileCard",
"as": {
"element": "article",
"slots": true
}
}
Now markuplint can correctly validate that <ProfileCard> contains only flow content (as <article> does), and that nesting <ProfileCard> inside a <p> would be invalid.
Combining scan with manual definitions
You can use scan alongside manual data definitions. This is useful when the scanner cannot determine the correct mapping for a particular component, or when you want to override the scanned result:
{
"pretenders": {
"scan": [
{
"files": "./src/components/**/*.tsx"
}
],
"data": [
{
"selector": "SpecialComponent",
"as": {
"element": "nav",
"aria": { "name": { "fromAttr": "label" } }
}
}
]
}
}
See pretenders.scan for the full configuration reference.
The as attribute
If a component has the as attribute, it is evaluated as the element specified by this attribute.
<x-ul as="ul"><!-- Evaluate as <ul> -->
<x-li as="li"></x-li><!-- Evaluate as <li> -->
<x-li as="li"></x-li><!-- Evaluate as <li> -->
<x-li as="li"></x-li><!-- Evaluate as <li> -->
</x-ul>
This evaluation also applies to its attributes that are inherited from the component.
<!-- Evaluate as <img src="image.png" alt="image"> -->
<x-img src="image.png" alt="image">