-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Automatically create JS versions of our TS code in the docs #2638
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very cool! Seems like an awesome thing to me, much less duplication! And we can still do it manually if we really need to (we can right? Would we then use those Tabs again?).
I will let others check the details of the PR and approve it, to make sure it works fully with how we do things currently (e.g. if you toggle to JS, does it remmeber the toggle? Mostly checking if we lost anything with not using our Tab system anymore). Aha but I see now that we actually do still use Tabs, they are produced by the plugin -> ok that sounds good then!
One th ing that would certainly be valuable is more documentation on this -> we should mention in docs README.md that we are using this mechanism because it is a bit magic, so having a central place with some discoverability where we describe it would be valuable.
Also, these remark/ files, we should provide some top level header comments in them, let's say one at the top of each file, to explain a bit the motivation and what are they here for, as they are a bit unexpected.
Also I didn't review the code in them, I will let the others do that.
Awesome all together!
Yes! I wanted to get this out to get some comments on the approach but I wanted to do this, I'll get on it |
No worries, I assumed so! |
Can I get a review? 🙏🏼 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love this idea. It will make our lives much easier.
Testing
I tried with some more complex examples:
wasp/web/docs/advanced/web-sockets.md
Line 69 in 24920c0
<Tabs groupId="js-ts"> |
- here the tricky part are the TS specific comments (which stay in the JS version), we'd probably need to rewrite those code blocks not to use those comments and move that info somewhere outside of the code block
Another example:
wasp/web/docs/data-model/entities.md
Line 48 in 9977e27
<Tabs groupId="js-ts"> |
We show Prisma code with the language switch for completeness sake - it toggles between different variants of the text below. So I guess, here we'll just use the tabs directly, you addressed it in the README, that's great 👍
In this example:
<Tabs groupId="js-ts"> |
The JS version still has the import statement (without anything imported):
For me, the next step would be - try to convert all the code blocks to see where the current approach fails and try to address those cases. For some of the cases we'll need to modify the code (e.g. to exclude Typescript specific comments), that's okay for me.
web/README.md
Outdated
``` | ||
~~~ | ||
|
||
And it will automatically generate a JS and TS version with a selector to switch between them. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd the resulting code with Tabs
and everything so the reader can understand what happens under the hood more clearly.
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
...
</TabItem>
</Tabs>
it syntactically valid. | ||
|
||
~~~md | ||
```ts title="src/apis.ts" auto-js with-hole |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd also show the code here so it's clear what will be the result (the reader doesn't have to guess). Also, I'd maybe show the example without auto-js
and then mention below, "you can combine these two tags, of course".
This file defines a plugin for the unified library that processes code blocks | ||
in Markdown documents. It looks for code blocks with a specific meta flag | ||
(`auto-js`) and replaces them with a pair of code blocks: one for JavaScript | ||
and one for TypeScript, as well as a tabbed interface to switch between them. | ||
This way we can author code examples in TypeScript and have them automatically | ||
converted to JavaScript for the documentation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An example code conversion would be amazing here, it will let the reader know right away what this plugin does and you can eliminate much of the prose.
web/src/remark/auto-js-code.js
Outdated
const { format } = require('prettier') | ||
const path = require('path') | ||
|
||
const ENABLED_META_FLAG = 'auto-js' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const ENABLED_META_FLAG = 'auto-js' | |
const META_FLAG_NAME = 'auto-js' |
The const is capturing the meta flag name, it's not communicating the state (enabled
).
web/src/remark/auto-js-code.js
Outdated
const tsMeta = newMeta | ||
const tsLang = node.lang | ||
const tsCode = await format(node.value, { parser: 'babel-ts' }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which prettier config are we using here? I think it would make a lot of sense to use the .prettierrc
that also in the web/
folder to keep the code blocks consistent with the code blocks that were written by hand.
web/src/remark/code-with-hole.js
Outdated
const { visitParents } = require('unist-util-visit-parents') | ||
const { default: escapeStringRegexp } = require('escape-string-regexp') | ||
|
||
const ENABLED_META_FLAG = 'with-hole' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const ENABLED_META_FLAG = 'with-hole' | |
const META_FLAG_NAME = 'with-hole' |
web/src/remark/code-with-hole.js
Outdated
const wrapInWordBoundaries = (/** @type {string} */ reStr) => { | ||
return String.raw`\b${reStr}\b` | ||
} | ||
|
||
const enabledMetaRegexp = new RegExp( | ||
wrapInWordBoundaries(escapeStringRegexp(ENABLED_META_FLAG)) | ||
) | ||
|
||
const holeIdentifierRegexp = new RegExp( | ||
wrapInWordBoundaries(escapeStringRegexp(HOLE_IDENTIFIER)) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe just use new RegExp
directly since we know the regexes up front.
web/src/remark/code-with-hole.js
Outdated
if (!node.lang || !SUPPORTED_LANGS.has(node.lang)) | ||
throw new Error(`Unsupported language: ${node.lang}`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd wrap the code block with curly braces.
@@ -0,0 +1,51 @@ | |||
// @ts-check |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think - should we name it with-ellipsis
vs. with-hole
to be more specific? Granted, it's hard to write with-ellipsis
than with-hole
that a downside definitely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case I used the name hole
because it is a common concept in ASTs where a part of the program is not done or not valid yet. I don't especially favor the name ellipsis since it is harder to spell and no more specific.
I think it's a valid use of team time since it will speed us the docs authoring process. It took me a couple of minutes per code block to do it, so it might be a few hours max IMHO. What I looking to achieve: find the the edge cases for which the plugin might not produce 1:1 JS code. While this is still hot, while you have the context, I think it'll be good to figure out and write down the process/limits of the plugin. Let's do it like this: invest one hour to convert as many examples as possible and try going for as many unique examples as possible 🙂 |
Turning to draft until #2658 is merged |
Description
Part of #2455
This PR allows to just write TS code directly, and automatically transform it and appear with the JS/TS selector.
It does so by adding two plugins to Docusaurus' MDX handling:
autoJSCode
will find any code block with theauto-js
meta, transform it withts-blank-space
, and remove the extra blank space withprettier
.Example transformation
Input:
Equivalent output (actual output is done in AST):
Why use `ts-blank-space`?
Basically, it's the transformer that does the fewest modifications to the input code, which I think is important to keep the overall visual structure of the code in the examples. It also helps to not introduce TS-only constructs like
enum
s that, when automatically converted, introduce distracting noise.I also feel validated in its approach since it uses the TypeScript compiler directly to do its job, and not a custom parser that might get outdated. As well, the blank-space-replacing method is used by SWC and Node.js in their TypeScript support.
codeWithHole
is made to deal with some of our examples, that just omit some syntactically needed parts of the code with...
. In order for the TypeScript transformation to work correctly, this plugin allows us to use an identifier namedhole
that will be replaced by...
, while still being syntactically correct.Example transformation
Input:
Equivalent output (actual output is done in AST):
With these two plugins, we can transform our examples to be less complex to author while keeping the option to read our docs in JS or TS. If there are any examples that can't be automatically converted, or that shouldn't be converted, we still retain the option to do it manually as we've been doing until now.
I converted two examples so you can see the output, please check them out.