WebC Syntax
Syntax highlighting, completions and Go to Definition for component tags in WebC files.
The best thing about a .webc file is that it's pretty much HTML with extra :nail_care::sparkles:. This allows us to piggyback off existing VS Code HTML handling via text.html.derivative, adding "just enough" magic for .webc files to look and work good.
This extension would not exist without the prior art by mvsde and dwkns. I have been using both for many years. Thank you!
AI disclaimer
- :robot: Extension code and tests were mostly written by Claude Code.
- :man: Extension docs and code comments were written or edited by a human.
Features
Front matter
The following (case-insensitive) front matter formats are identified and parsed correctly by default:
Custom formats added via setFrontMatterParsingOptions (hello TOML) will be identified as generic front matter and parsed as plaintext.
The TextMate scope fences are punctuation.definition.frontmatter.{begin,end}.webc, and the language string after the dashes is scoped by keyword.other.frontmatter-language.webc.
Expression attributes
| Attribute |
Value |
Parsed as |
Scope |
:* bindings (such as :name, :@prop) |
required |
JavaScript |
entity.other.attribute-name.dynamic.webc |
@attributes |
optional |
JavaScript |
entity.other.attribute-name.prop.webc |
@html |
required |
JavaScript |
entity.other.attribute-name.prop.webc |
@raw |
required |
JavaScript |
entity.other.attribute-name.prop.webc |
@text |
required |
JavaScript |
entity.other.attribute-name.prop.webc |
webc:bucket |
required |
string |
entity.other.attribute-name.directive.webc |
webc:else |
none |
— |
entity.other.attribute-name.directive.webc |
webc:elseif |
required |
JavaScript |
entity.other.attribute-name.directive.webc |
webc:for |
required |
JavaScript |
entity.other.attribute-name.directive.webc |
webc:if |
required |
JavaScript |
entity.other.attribute-name.directive.webc |
webc:ignore |
none |
— |
entity.other.attribute-name.directive.webc |
webc:import |
required |
string |
entity.other.attribute-name.directive.webc |
webc:is |
required |
string |
entity.other.attribute-name.directive.webc |
webc:keep |
none |
— |
entity.other.attribute-name.directive.webc |
webc:nokeep |
none |
— |
entity.other.attribute-name.directive.webc |
webc:raw |
none |
— |
entity.other.attribute-name.directive.webc |
webc:root |
required |
string |
entity.other.attribute-name.directive.webc |
webc:scoped |
optional |
string |
entity.other.attribute-name.directive.webc |
webc:setup |
none |
— |
entity.other.attribute-name.directive.webc |
webc:type |
required |
string |
entity.other.attribute-name.directive.webc |
11ty:type |
required |
string |
entity.other.attribute-name.html |
A webc:for value such as item of items or (key, value) in obj is
tokenised as a JavaScript expression; of and in get the same keyword
scope they have in a for statement, and the names either side are variables.
Examples of JavaScript parsing
item.author is understood as an object and a property.
({ color: theme.accent }) is understood as an object literal with a key and a property access.
> or < are understood as operators, not tag closures.
JavaScript templates
<template webc:type="js"> (and superseded webc:type="render") content will be treated as JavaScript, including when a formatter has split the start tag across several lines.
Autocomplete and attribute hover
webc:, @ and 11ty:type attributes will show available options when typing. A description and link to the docs will be shown both in the completion list and on hover.
Given the static nature of the extension, dynamic :* bindings are not included.
Go to component source
Quick linking to a .webc file for a component (i.e. <my-card> -> **/_components/**/my-card.webc) is supported via:
- Cmd/Ctrl + click
- Go to Definition (F12)
- Peek Definition (Alt/Option + F12)
Also works on the string values of webc:is and webc:import. A closing
tag stands for its opening tag, so </div> after <div webc:is="my-card">
opens my-card too.
An element with webc:import follows the import listed:
<my-card webc:import="./cards/my-card.webc"></my-card> -> cards/my-card.webc (path is relative from the importer)
<syntax-highlight webc:import="npm:@11ty/eleventy-plugin-syntaxhighlight"></syntax-highlight> -> node_modules/@11ty/eleventy-plugin-syntaxhighlight/syntax-highlight.webc
The extension only looks for components in **/_components/**/*.webc by default.
If you have components living elsewhere, you'll need to define the paths manually via config. Setting custom paths will override/replace the default **/_components/**/*.webc. All paths are relative to your workspace folder.
{your project}/.vscode/settings.json
{
"webc.components": [
"src/_components/**/*.webc",
"src/_layouts/partials/*.webc",
"npm:@11ty/is-land/*.webc",
],
}
Settings
The extension defaults are as follows. Both will be overridden by any user or workspace level config.
"files.associations": { "*.webc": "html" }
"webc.components": ["**/_components/**/*.webc"]
Local development and testing
npm ci
npm run test:grammars
npm test
npm run package
Local install
code --install-extension webc-syntax.vsix
Regenerate screenshots
npm run screenshots
Publish
This is my first VS Code extension, so I'm going with manual publishing. CI/CD can come later. I also tried getting a token so I can publish via CLI and was pushed into a whole Azure trial funnel and wow, no thank you.
Bump version in package.json and update CHANGELOG.md.
Regenerate screenshots if needed.
Run the tests and build the package with commands above.
Install the .vsix locally and open test/manual/hover-attributes.webc to sense check the attribute hovers and completions.
Open Marketplace Publisher admin.
Drag and drop the built webc-syntax.vsix file.
Commit the version bump and changelog, tag it, and push:
git commit -am "v$(node -p "require('./package.json').version")"
git tag "v$(node -p "require('./package.json').version")"
git push && git push --tags