Skip to content
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

Generated type declarations #189

Merged
merged 1 commit into from
Oct 18, 2024
Merged

Conversation

theengineear
Copy link
Collaborator

@theengineear theengineear commented Oct 9, 2024

Generate “x-element.d.ts” from JSDoc comments.

Previously, the “x-element.d.ts” file was hand-curated, which was prone
to falling out of date.

Because the TypeScript team manages JSDoc, it’s fairly straightforward
to properly “type” your JS with JSDoc and output declarations which can
be used by folks writing TypeScript — all of that without needing to
actually author TypeScript ourselves.†

Additionally, tools like JSR will build basic documentation for
libraries which author JSDoc comments, so some wins there.

Finally, to ensure best practices for our JSDocs, we enable some
recommended rules from the eslint plugin.

† One goal of “x-element” is to be highly portable, it’s why no
dependencies exist and why the “x-element.js” file can be used
verbatim without a build step.

@@ -1,69 +1,239 @@
export class XElement extends HTMLElement {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Here’s a good example — this should have said export default class XElement extends HTMLElement { (see the default). Easy to miss since we often don’t depend on this module. Generating it from well-considered JSDoc “types” feels like a better way to ensure this stays accurate.

static template(html: Function, engine: {
[key: string]: Function;
}): (properties: object, host: HTMLElement) => any;
static "__#1@#analyzeConstructor"(constructor: any): void;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

… This, is annoying. TypeScript does want to track private fields for [reasons], so as far as I can tell, you cannot remove this stuff.

@@ -3,11 +3,12 @@
"description": "A dead simple starting point for custom elements.",
"version": "1.0.0",
"license": "Apache-2.0",
"repository": "https://github.com/Netflix/x-element",
"repository": "github:Netflix/x-element",
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I went through some of the npm documentation again and tried to further “modernize” this package.json — worth further scrutiny for sure though.

package.json Outdated
@@ -17,19 +18,23 @@
"lint": "eslint --max-warnings=0 .",
"lint-fix": "eslint --fix .",
"test": "node test.js | tap-parser -l",
"build": "tsc",
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Still need to figure out how we want to include this output…

Copy link
Contributor

@Kelketek Kelketek Oct 11, 2024

Choose a reason for hiding this comment

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

@theengineear Do you need to? If you're using JSDoc, TypeScript will import the file as typed.

Nevermind, I thought that worked but I can't get it to work consistently locally. Any reason it couldn't be stored right next to the output file in the NPM package/published directory, and stored that way? That's how most libs do it, as far as I've seen.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Since the entire interface is really a single class — I do think it makes sense to keep this checked into source as a sibling of the file itself. It allows us to scrutinize any changes that could happen there.

That said… that’s just my current opinion there. It’s completely subject to change if it ends up being overly cumbersome!

@@ -0,0 +1 @@
{"root":["./x-element.js"],"version":"5.6.3"}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not sure what this is all about. Can we .gitignore this?

Copy link
Contributor

Choose a reason for hiding this comment

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

@theengineear You could merge the contents into the tsconfig.json. This file is helpful for splitting the project into multiple project references. However, TypeScript also just allows you to define these references in the tsconfig.json if you need them anyway. It just supports allowing you using a separate config file, for which this file name is the default. I don't know why someone would choose to maintain a separate file from ts-config-- that probably makes more sense from the perspective of very large projects.

Anyway, since x-element is intended to be a single file import, I don't imagine you'll need this unless you're planning to add accessory projects to the repo. I'd just delete it for now. The version property doesn't appear to be an officially supported property for tsconfig, and you're already using the files attribute instead of root.

Copy link
Collaborator Author

@theengineear theengineear Oct 16, 2024

Choose a reason for hiding this comment

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

Thanks for the detailed response! I expected to be the one answering my own question there 😅

I’ll ignore the file 👌 Scratch that — Looks like I created it earlier when I must have been playing around with some configuration. Just deleted it 🤘

@theengineear
Copy link
Collaborator Author

Another step I’d like to take is publishing this branch to JSR as throw-away exercise. I’d be curious to see if it significantly improves the experience there.

x-element.js Outdated
@@ -899,110 +975,122 @@ export default class XElement extends HTMLElement {
static #prototypeInterface = new Set(Object.getOwnPropertyNames(XElement.prototype));
}

/** Wrapper to document public interface to the default templating engine. */
class TemplateEngine {
// TODO: If / when we remove `repeat` in favor of `map` — we can probably delete
Copy link
Contributor

Choose a reason for hiding this comment

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

What needs to happen in order for us to remove repeat? It seems like we'd love to do it, but I'm guessing there's a good bit of legacy code. Is there any reason we can't, say, change the version number to 2.0.0 to indicate a breaking change and incentivize people to upgrade by letting them know that type checking is now available?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The only thing we really need to do is have the next release be a 2.x release so that we’re following semantic versioning. It’s not much of a hassle to drag along repeat until we’re actually ready to make that change though 👌

This way, we can continue shipping incremental changes in 1.x without having to manage multiple, major versions or anything.

@theengineear theengineear force-pushed the generated-type-declarations branch from b69eb1f to f9d3bad Compare October 16, 2024 05:24
@@ -12,6 +12,8 @@ jobs:
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm run lint
- run: npm run type
- run: git diff --exit-code
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Rather than add a “build” step. We now have a CI step that will fail if a diff is generated from running npm run type. This is closer to the flow I think we want. I.e., rather than need to run a build to use the types… assert that the types were properly generated before allowing the merge.

// generated-js back to ts… it also seems to be the case that it will help
// IDEs get to the right _js_ when using types. See
// https://www.typescriptlang.org/docs/handbook/declaration-files/dts-from-js.html
"declarationMap": true,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

After re-reviewing this. I think it’s actually most correct to emit a source map.

  1. If you’re using the JS on it’s own — the source map is inert (since our JS is running as-is).
  2. If you’re using the TS declaration file, the source map will hint IDEs to route you back to the original JS.

This was tricky to wrap my head around at first since I typically think about these source mappings as a way to go from the generated JS back to the original TS — but, it seems like you can leverage them to hint IDEs to do the opposite as well.

"/demo",
"/test",
"/etc"
],
"devDependencies": {
"@netflix/eslint-config": "^3.0.0",
"eslint-plugin-jsdoc": "^50.3.1",
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

After using this for a little while — I feel like this is a pretty good solution. Open to other suggestions though. FWIW, I tried using TypeScript to generate errors here and I kept feeling like it wasn’t doing what I wanted it to do — could be that I had it set up wrong though!

@theengineear theengineear force-pushed the generated-type-declarations branch from f9d3bad to 71fa53b Compare October 16, 2024 05:35
*/
/**
* A property value.
* @typedef {object} Property
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I’ll be curious to see how tools like JSR auto-doc this stuff!

@theengineear theengineear force-pushed the generated-type-declarations branch from 71fa53b to 5fc6d6e Compare October 16, 2024 05:40
@theengineear
Copy link
Collaborator Author

^^ Just tested the new CI control. It does indeed fail the build if a diff is detected and it prints the diff.

Screenshot 2024-10-15 at 10 41 38 PM

@theengineear theengineear force-pushed the generated-type-declarations branch from 5fc6d6e to 79f945c Compare October 16, 2024 05:43
@theengineear
Copy link
Collaborator Author

Fiddling around a bit with uploading to JSR — the docs are good in some ways (and rough around the edges in other ways). But, increasingly, it’d be cool to be generating our docs from the actual code itself.

Screenshot 2024-10-15 at 11 01 49 PM

@theengineear theengineear force-pushed the generated-type-declarations branch from 79f945c to 4018e8d Compare October 16, 2024 06:14
* }
* };
* }
* ```js
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

JSDoc also has an @example tag… but this feels more natural to write code blocks like this and it appears to be supported by JSR.

"bump": "./bump.sh"
},
"files": [
"/x-element.js",
"/x-element.d.ts",
"/x-element.d.ts.map",
Copy link
Collaborator

Choose a reason for hiding this comment

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

do we need to generate a source map?

@@ -1,5 +1,12 @@
{
"name": "@netflix/x-element",
"version": "1.0.0",
"exports": "./x-element.js"
"exports": {
Copy link
Collaborator

@klebba klebba Oct 18, 2024

Choose a reason for hiding this comment

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

this seems wrong to not explicitly export the src

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

JSR’s documentation is not great here. I’ve added back the other files, which causes JSR to flag us as “slow” and throw warnings. I think that’s ok for now though.

I’m going to follow along on jsr-io/jsr#494 — hopefully things will get better once our JSDoc-style path forward here is accepted.

klebba
klebba previously approved these changes Oct 18, 2024
Copy link
Collaborator

@klebba klebba left a comment

Choose a reason for hiding this comment

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

awesome!

@theengineear
Copy link
Collaborator Author

FYI, I think JSR is gonna wrongly flag this package as “slow” until jsr-io/jsr#494 is resolved. I think this should all work eventually though 👌

Previously, the “x-element.d.ts” file was hand-curated, which was prone
to falling out of date.

Because the TypeScript team manages JSDoc, it’s fairly straightforward
to properly “type” your JS with JSDoc and output declarations which can
be used by folks writing TypeScript — all of that without needing to
actually author TypeScript ourselves.†

Additionally, tools like JSR will build basic documentation for
libraries which author JSDoc comments, so some wins there.

Finally, to ensure best practices for our JSDocs, we enable some
recommended rules from the eslint plugin.

† One goal of “x-element” is to be _highly_ portable, it’s why no
  dependencies exist and why the “x-element.js” file can be used
  _verbatim_ without a build step.
@theengineear theengineear force-pushed the generated-type-declarations branch from 7605866 to 54c9d2b Compare October 18, 2024 22:23
@theengineear
Copy link
Collaborator Author

Here’s a screen grab of an rc publish to JSR I ran locally (a more zoomed out view). Again, not perfect, but definitely getting there.

Screenshot 2024-10-18 at 3 26 23 PM

@theengineear
Copy link
Collaborator Author

Going to get this merged in — I’ll play around with some rc releases to make sure everything still works as expected 👌

@theengineear theengineear merged commit ad56dc1 into main Oct 18, 2024
1 check passed
@theengineear theengineear deleted the generated-type-declarations branch October 18, 2024 22:34
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.

3 participants