https://github.com/gfxfundamentals/lesson-builder
shared code for generating articles
https://github.com/gfxfundamentals/lesson-builder
Last synced: 3 months ago
JSON representation
shared code for generating articles
- Host: GitHub
- URL: https://github.com/gfxfundamentals/lesson-builder
- Owner: gfxfundamentals
- Created: 2019-06-30T04:02:47.000Z (almost 6 years ago)
- Default Branch: master
- Last Pushed: 2025-02-21T04:53:29.000Z (4 months ago)
- Last Synced: 2025-04-06T22:51:13.877Z (3 months ago)
- Language: JavaScript
- Size: 14.5 MB
- Stars: 5
- Watchers: 2
- Forks: 7
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# GFXFundamentals Lesson Builder
This is the lesson builder (static site generator) used on
[WebGLFundamentals](https://webglfundamentals.org),
[WebGL2Fundamentals](https://webgl2fundamentals.org), and
[ThreeJSFundamentals](https://threejsfundamentals.org).The code is a mess as it started small and got hacked over time.
Still, I got tired of propagating changes between projects so
I managed to get it separated out.I'm not sure it has any features over something like Jekyll except
1. it's written in JavaScript.
I'm not saying Ruby is bad. Only that
I really only want to have to maintain one dev environment.I particularly like that npm defaults to all dependencies being local.
No knowledge needed. No special incantations. It just works.
My, very limited experience with
Ruby is that Ruby expects many things to be globally installed
and you have to use other tools to help you make things local.2. I think, but I'm probably wrong, that it handles HTML better.
My markdown files have lots of embedded HTML. For whatever
reason most of time I try this in other systems they get confused
and end up seeing markdown inside portions of HTML. I probably
just don't know how to use them correctly.3. It handles multiple languages
I'm sure other systems do that too but it's not a common request
so I'm guessing that's often less supported.Anyway, I'm not trying to promote this as a solution to anything.
It's just what I have hacked together over the years to make
the sites listed above.# Docs
These are in no way complete. They're most notes to myself.
## .md files
.md files have front matter like headers at the front. Basically a keyword
followed by a colon. and a space. A blank line ends the front matter
and starts the content* `Title`: The title for the page
* `TOC`: (optional) The title for the table of contents if different
* `Description`: A description currently only used for meta data like for facebook/twitter
* `Link`: (optional) A link to some other page
The link's sole purpose is to put an external link in the table of contents.
It also lets the title of that link be localized (the link can be localized too)
as insome-article.md
```
Title: How to use Canvas
Link: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API
```ja/some-article.md
```
Title: Canvasの使い方
Link: https://developer.mozilla.org/ja/docs/Web/API/Canvas_API
```English lessons sit in .md files in a folder called `lessons` below a folder named whatever, usually the topic of the site. Example `webgl/lessons/some-lesson.md`. Translations have .md files
of the same name one subfolder deeper, ideally using the language
code. Example `webgl/lessons/zh_cn/some-lesson.md`.## required files
```
contributors.md
toc.hanson
build/templates/index.template
build/templates/lessons.template
build/templates/missing.template
build/templates/example.template
build/templates/diagram.template
/lessons/index.md
/lessons/toc.html
/lessons/langinfo.hanson
/lessons/at-least-one-lesson.md
```## langinfo.hanson
Each language folder as well as the English folder have a `langinfo.hanson` file which is similar to JSON except comments
and trailing commas are allowed.The contents is
```js
{
// The language (will show up in the language selection menu)
language: 'English',// only needed if different from the folder name
langCode: 'en',// Phrase that appears under examples
defaultExampleCaption: "click here to open in a separate window",// Title that appears on each page
title: 'WebGL Fundamentals',// Basic description that appears on each page
description: 'Learn WebGL from the ground up. No magic',// Link to the language root.
link: 'https://webglfundamentals.org/webgl/lessons/ja', // replace `ja` with country code// html that appears after the article and before the comments
commentSectionHeader: 'Questions? Ask on stackoverflow.\nIssue/Bug? Create an issue on github.',// markdown that appears for untranslated articles
missing: "Sorry this article has not been translated yet. [Translations Welcome](https://github.com/gfxfundamentals/webgl-fundamentals)! 😄\n\n[Here's the original English article for now]({{{origLink}}}).",// various translations
translations: {
badTranslation: 'Sorry, the translation of this area is out of date. Please consider helping to fix it.',
updateNeeded: 'The translation of this article is out of date. Please consider helping to fix it.',
},// the phrase "Table of Contents"
toc: "Table of Contents",// translation of categories for table of contents
categoryMapping: {
'fundamentals': "Fundamentals",
'image-processing': "Image Processing",
'matrices': "2D translation, rotation, scale, matrix math",
'3d': "3D",
'lighting': "Lighting",
'organization': "Structure and Organization",
'geometry': "Geometry",
'textures': "Textures",
'rendertargets': "Rendering To A Texture",
'2d': "2D",
'text': "Text",
'misc': "Misc",
'reference': "Reference",
},}
```## toc.hanson
This is the table of contents. It looks like this
```js
{
"topic1": [
"some-article.md",
"some-other-article.md",
],
"topic2": [
"another-article.md",
"yet-another-article.md",
],
"topic3": [
"sub-topic1": [
"foo-article.md",
"bar-article.md",
],
]
}
```The actual HTML generated is a nested `
- ` list where `topic1` is translated via the `langInfo.json` and the titles come from
the articles/lessons.
## toc.html
This is a language specific template the table of contents.
allowing you to add links that are language specific. Example:
```html
{{{tocHtml}}}
```
Yes: this should be renamed to `toc.template` 😅
## index.md
This is the language specific content of index.html for each
language that gets inserted as `content` into the index.template.
Example:
```md
Title: WebGL Fundamentals
WebGL from the ground up. No magic.
These are a set of articles that teach WebGL from basic principles.
They are NOT old rehashed out of date OpenGL articles like many others on the net.
They are entirely new, discarding the old out of date ideas and bringing you
to a full understanding of what WebGL really is and how it really works.
{{{include "webgl/lessons/toc.html"}}}
```
## Building
```js
const buildStuff = require('@gfxfundamentals/lesson-builder');
async function main() {}
const buildSettings = {
outDir: 'out', // where to generate the site
baseUrl: 'https://webglfundamentals.org', // the site
rootFolder: 'webgl', // folder where `lessons` folder is
lessonGrep: 'webgl*.md', // grep to find .md files for lessons
siteName: 'WebGLFundamentals',
siteThumbnail: 'webglfundamentals.jpg', // in rootFolder/lessons/resources
templatePath: 'build/templates', // where the templates are
owner: 'gfxfundamentals', // owner of git repo
repo: 'webgl-fundamentals', // name of git repo
thumbnailOptions: {
thumbnailBackground: 'webglfundamentals.jpg',
text: [
{
font: '100px lesson-font',
verticalSpacing: 100,
offset: [100, 120],
textAlign: 'left',
shadowOffset: [15, 15],
strokeWidth: 15,
textWrapWidth: 1000,
},
],
},
};
await buildStuff(buildSettings);
}
```
You can build specific lessons by passing in an array of filenames
in the settings
```js
{
...
filenames: [
'some-article.md',
'another-article.md',
];
}
```
This is mostly to facilitate a continuous build. If you monitor
for lesson article changing then you can pass the list of changed
files to build just those files.
Note: the builder only builds `index.html`, the lesson html files, `sitemap.xml`,
`link-check.html`, `contributors.html`, `contributors.js`, and `atom.xml`.
It does not copy any other files. (examples, images, etc...). Use other build tools for that.
`contributors.js` is [the list of contributors from github](https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#list-repository-contributors).
By default this is bogus data of 2 entires. To get real data
at build time set the environment variable `LESSON_BUILDER_ENV` to `production` in which case a request
will be made to github. I would have preferred to use git but (a) git has no info about links to people.
github at least as link I can insert. (b) git has no avatar links - I suppose here I could use gravatar
but for now I opted to use github.
`contributors.html` is build from `contributors.md` and inserted into the `index.template`.
It just assumes `contributors.md` is a manually edited file.
## .templates
The build system looks for [handlebars](https://handlebarsjs.com) template files in `build\templates`.
The main templates it expects are `index.template` which it uses for the index.html file for each language
and `lesson.template` which it use for each lesson.
Available to the templates are the following variables.
* `settings` is the `buildSettings` passed into the builder (see above)
* `langInfo` is the contents of the langinfo.hanson file above.
* `langInfo.baseDirname`: The basename of the language folder.
There is no good reason for this to be different than the `langCode` but it can be.
* `langInfo.home`: The path to the home eg `topic/lessons/ja`
* `langInfo.carousel`: Some JSON for Schema.org schema.
Example usage
```html
{
"@context":"https://schema.org",
"@type":"ItemList",
"itemListElement":
{{{langInfo.carousel}}}
}
```
* `originalLangInfo` is the contents of the **english** langinfo.hanson file
This is useful in a template with the `stringify` helper as you can do
stuff like
```html
```
and have it insert either the language specific translation or
fallback to the english.
* `content`: The .md file converted to HTML
* `langs`: An array of info for all available languages
Each entry includes
* `language`: The text from the langinfo.language
* `url` (the URL to the corresponding page for this language)
* `relUrl` (the relative URL for this language from this page)
effective if this is "" then the page being generated is
this language (not really)
Here is an example handlebars template generating a language `` dropdown using `langs`
```html
{{#each langs}}
{{language}}
{{/each}}
```
* `title`: The title of the lesson from the .md front matter
* `description`: The description of the less from the .md from matter
* `src_file_name`: the .md file
* `dst_file_name`: the .html file being written
* `toc`: ???
* `tocHTML`: the language specific generated table of contents
* `url`: The url of the page
* `relUrl`: The relative url of the page
* `screenshot`: url for a screenshot for meta tags
* `screenshotSize`: the size of the screenshot
* `templateOptions`: same as `langInfo` above. Just cruft.
## handlebar helpers
* `include`: Includes another template
```
{{{include "some/other/file"}}}
* `ifexists`: Checks if a file exists
```
{{#ifexists filename="foo/{{bar}}/test.txt" bar={{langInfo.langCode}}}
{{else}}
{{/ifexists}}
```
* `example`: Inserts an example using `example.template`
```
{{{example url="../some-example.html}}}
```
Optionally takes a `caption`, `width`, `height`, `startPane`
```
{{{example url="../example.html" caption="adjust slider" width="500" height="400"}}}
```
Passed to `example.template` are the `width`, `height`, `caption`, `examplePath` (so links are absolute),
`encodedUrl` in case there is a query string
`url`, `cacheid` for cache busting, `params` (which is whatever
the `startPane` parameter was)
* `diagram`: Inserts an diagram using `diagram.template`
Optionally takes a `caption`, `width`, `height`, and a `className`
Passed to `diagram.template` are the `width`, `height`, `caption`, `examplePath` (so links are absolute),
`url`, `className`
* `image`: Inserts an image using `image.template`
Optionally takes a `caption`, and a `className`
* `selected`: Inserts the word `selected`. See `langs`
* `stringify`: Insert the `JSON.stringify(value)` of the named value.
example `const foo = {{{stringify names="langInfo.toc,originalLangInfo.toc"}}}`
will insert `langInfo.toc` if it exists else `originalLangInfo.toc` (the English)
* `warning`: Inserts the warning template with a translated message.
* `escapehtml`
Inside markdown you can insert code using triple backticks
but sometimes you're embedding code in HTML in which case markdown is not processed.
To handle that you can use `escapehtml`. Example:
```html
{{#escapehtml}}
foo
{{/escapehtml}}
```
which will produce
```html
<div>
<div>foo</div>
</div>
```
NOTE: double brackets are required, triple brackets will fail!
## markdown issues / notes
### HTML blocks (vs inline) requires no blank lines
**BAD !!**
```
hello
world
```
###
Good
```
hello
world
```
### Inline html infuriatingly has its content parsed as markdown
```
text a*b*c text
```
produces
```
text abc text
```
### space at end of line means break
```
abc
def
```
produces `
abc
def
## Debugging
You can set the following environment variables to help find
issues.
* `ARTICLE_FILTER`: only articles with this substring in the filename will be built
* `ARTICLE_VERBOSE`: Set to 1, Prints more info (not much though)
* `ARTICLE_FIX`: Set to 1, Tries to fix URLS
I don't totally remember what this is for but it loads
an lesson .md file, tries to fix some URLs, then writes
that .md file back out. So, be sure your originals are
checked into git.
* `SHOW_CONTENT`: Set to 1, shows various transformations
## Testing
I started adding some tests to hopefully make it easy to um, test.
Before this I had to use one of the other repos using this repo.
One test generates files in the `out` folder and then diffs them
to the `test/expected` folder. If it prints a difference and you
want to update the expectations you can use
```bash
UPDATE_EXPECTED=true npm test
```