CodeVetter · Repo Unpack · Extension points
Chalarangelo/30-seconds-of-code
The repo is designed for extension mostly at the content layer: drop a markdown file into content/snippets/{lang}/s/, a YAML into content/collections or content/languages, and the glob-driven pipeline picks it up automatically. Code-level extension follows consistent registries and naming conventions: new models register via Model.prepare + models.js, new page types via Page.register + `${Model}Page`, new serializers via Serializer.prepare + `${name}Serializer`, and new markdown features via the remark/HAST plugin list in plugins/index.js. Search, ranking, and presentation are tuned through data files and settings rather than code.
- New snippet: add {name}.md under content/snippets/{lang}/s/ with front-matter (title, shortTitle, language, tags, cover, excerpt, listed, dateModified, optional tocEnabled/journeyId); the content/snippets/**/s/*.md glob picks it up with zero code changes.
- New collection: add a YAML under content/collections with slug, title, description, and either explicit snippetIds or languageMatcher/tagMatcher; optional parent/topLevel/listed/allowUnlisted control hierarchy and visibility; hub.yaml featuredListings controls homepage featuring order.
- New language: add {name}.yaml under content/languages with short/long/name/references/additionalReferences — it is globbed automatically and its references map feeds linkInlineCode autolinking and merged additionalReferences.
- New model: extend ContentModel (or Model), add a static block calling Model.prepare(this, [indexes]) and ContentModel.addContentModel(this), define scopes as static methods taking a records param, and register it in models.js so the JSON reviver can instantiate it.
- New page type: create a Page subclass with a static { Page.register(this) } block, implement params and props getters, optionally additionalSchemaData — PageSerializer picks it up automatically; the class MUST be named `${Model}Page` for Page.from resolution.
- New serializer: extend Serializer, declare fields via Serializer.prepare(this, [key | [key, fn], ...]), and expose it through serializers.js — ContentModel.serializeAs resolves `${name}Serializer` by name.
- New markdown transform: add a plugin under plugins/ast (pre-remarkRehype, operates on mdast) or plugins/hast (post-remarkRehype, operates on hast), export it from plugins/index.js, and .use() it in MarkdownParser.setupProcessors — ordering constraints are documented inline (e.g. linkInlineCode must precede safeguardExternalLinks).
- Highlighter swap/extension: ShikiHighlighter and PrismHighlighter are interchangeable via the 'shiki'|'prism' parameter selected by the --fast-highlight flag; Shiki languages are driven by content/grammars.yaml and the theme by codeHighlighters/themes/cosmos.json; custom Shiki transformers live in codeHighlighters/transformers.
- Search tuning is config-driven: stop words live in src/lib/search/settings.js, token filtering rules in server.js tokenFilter, fuzzy weight is a parameter to search(), and recommendation scoring constants are in settings.js recommendations block.
- Presentation is tuned in settings.js: cardsPerPage/collectionCardsPerPage, home card counts, tag display-name map, cover sizes/suffixes/srcsets, sublink labels, and collection id constants (mainCollectionId, updateLogsCollectionId, updateLogTag).
- New generated artifact: write a class with a static generate() writing into settings.paths.out.* and call it from bin/prepare's full/dev command lists — Redirects, Feed, Sitemap, TimestampDump, SearchIndex, AstroContent all follow this pattern.
- New client behavior: add a plain JS file under src/astro/scripts and reference it from an .astro component via <script src='../scripts/x.js'>; for reusable content widgets, drop scripts/styles under content/components and let loadWebComponents inject them into rendered HTML.