Skip to content

feat: Add basic support for Marko 6 and 5 - #1914

Merged
webpro merged 19 commits into
webpro-nl:mainfrom
caseycarroll:marko-plugin
Jul 31, 2026
Merged

webpro merged 19 commits into
webpro-nl:mainfrom
caseycarroll:marko-plugin

Conversation

@caseycarroll

@caseycarroll caseycarroll commented Jul 29, 2026 •

Copy link
Copy Markdown
Contributor

Adds a marko plugin to knip to analyze dependencies in .marko files.

Marko has an auto tag discovery feature which allows authors to use custom tags without explicitly importing them. To support this, this plugin will look for Marko in the dependencies then look for marko.json files for each project dependency in node_modules. It will then find tag names in the locations specified in marko.json and check that at least one of the tag names is used in the project.

The marko.json parsing is intentionally limited. It only looks at exports and tags-dir because other mechanisms like taglib-imports will be deprecated. If a Marko dependency exposes tags with a deprecated property, the plugin will categorize the dependency as used without searching for usages in the project code.

This plugin also naively covers marko's "concise syntax" since it's not widely used. A follow up PR could add stronger concise syntax support, if the community needs it. https://markojs.com/docs/reference/concise-syntax

Additional Changes

  • Appears the OpenClaw plugin was not accounted for in the docs list, so it's been added to plugins.md as well as marko.

AI Disclosure

I authored the PR description in my own words and reviewed the code manually, but did use a model to implement the plugin and fixtures.

@webpro webpro left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the pull request! Would be cool to add Marko plugin. I do have a note.

@@ -1,5 +1,5 @@
---
title: Plugins (177)
title: Plugins (179)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please undo changes in this file, we can auto-generate separately (and they're always re-generated for website deploy).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! Removed.

/^[ \t]*(export\s+(?:type\s+)?(?:\*(?:\s+as\s+[$\w]+)?|\{[^}]*\})\s+from\s+(["'])([^"'\r\n]+)\2)[ \t]*;?/gm;
const conciseStyleMatcher = /^[ \t]*style(?:\.([\w.-]+))?[^\n{]*\{([\s\S]*?)^[ \t]*\}/gm;
const htmlTagMatcher = /<([a-z][\w.-]*)(?=[\s/|>])/g;
const conciseTagMatcher = /^[ \t]*([a-z][\w.-]*)(?=[ \t/|]|--|$)/gm;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import/export/dynamic-import matchers reimplement what oxc-parser does out of the box. How about this: instead of reconstructing statements with regexes, the compiler could reduce the source to parseable JS (and let the parser do its job):

  1. Strip comments and style blocks (still compiling styles separately as it does now), lines starting with <, and import ... from "<tag>" lines.
  2. Neutralize the Marko-only syntax so a hard error can't land before the imports: server/client/static/$ prefixes, the anonymous class { (give it a name), and the export modifier on local declarations (export interface Input must not surface as an unused export).
  3. Return the synthetic imports (marko, taglib dependencies, compiled styles) followed by the remainder verbatim. Synthetic lines go first: a parse error in markup leftovers truncates the module record from that point on, so the leftovers must come last.

No parsing in the compiler: the pipeline picks up static, dynamic and type imports and re-exports in any shape, quoted attribute values are inert to the tokenizer, and dynamic imports inside the class block get picked up too.

To me this feels sturdier and easier to maintain. Even though Marko syntax might change, using regexes for what oxc-parser does better feels a bit cumbersome.

WDYT?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed this feels cumbersome! I like your suggested approach. Will tinker with that and follow up soon.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, refactored to have a tag dependency pass, a style pass, then a stage where I sanitize the .marko template so parseable JS is fed into oxc-parser.

I've added regex's to account for different marko syntax things like class {}.

@caseycarroll
caseycarroll marked this pull request as ready for review July 30, 2026 19:59

@webpro webpro left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the updates! Almost good to go.

Optional: a fixture with a marko.json that has neither exports nor tags-dir would pin the taglib-imports fallback ("conservatively considered used") behavior.

];

const tagProduction = tagFilePatterns.map(pattern => `**/{${tagDiscoveryDirs.join(',')}}/${pattern}`);
export const production = tagProduction;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export const production = tagProduction;
const production = tagProduction;

It's in the default plugin export obj.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolved

Local tag templates and their conventionally associated component and style files in \`components\`
(Marko 5) or \`tags\` (Marko 6) are conservatively treated as production entries. Knip does not
report unused auto-discovered local tags inside those directories. Modern Marko 5
\`marko-tag.json\` entry points and compiler hooks are also resolved.`;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove docs and note entirely. They look like implementation details, not documenting surprising behavior with answers / workarounds users need to know about.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the docs. I should have cross-validated with other plugins here, my bad!

Comment on lines +101 to +103
const compiler = createCompiler();

export default compiler;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const compiler = createCompiler();
export default compiler;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@webpro
webpro merged commit c6b1516 into webpro-nl:main Jul 31, 2026
@webpro

webpro commented Jul 31, 2026

Copy link
Copy Markdown
Member

πŸš€ This pull request is included in v6.31.0. See Release 6.31.0 for release notes.

Using Knip in a commercial project? Please consider becoming a sponsor.

@webpro

webpro commented Jul 31, 2026

Copy link
Copy Markdown
Member

Thanks again, Casey β€” nice one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants