-
Notifications
You must be signed in to change notification settings - Fork 1k
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
chore: Deprecate Structure, introduce Skeleton #7039
chore: Deprecate Structure, introduce Skeleton #7039
Conversation
This reverts commit 326cd22.
|
Excellent to have this data to guide decisions! I plan on going through structure and documenting all it's current diagnostic checks. I will implement them all within skeleton to ensure users still get all the same checks. I have some ideas floating around about error/warning enumeration and how that could couple with docs around these errors/warnings which are checked for. Will probably need to get everyone's thoughts on that idea though. It would be interesting if the check command could become a go to tool for developers. Perhaps running it in the background during development. Asking what the command output is in the issue template to gather more debug data etc. I can imagine it filling a niche left by deprecating the editor extension. PR status update
|
Sounds great! For this PR,
^^ yes, exactly!! |
The check command tests which structure currently provides are, to the best of my knowledge, listed below.
Some additional tests have been added because they seemed natural at the time:
All the warnings and errors are enumerated in the Note: the TOML check (15) is added but the whole skeleton package relies on Note: I didn't include the prerender info check (9) because it was hardcoded to never been thrown as an error. Note: I think in an invalid route syntax will throw an error with the check command itself because of the way Note: I didn't include the ENV var warning checks yet. I wanted to confirm we still want these checks. I think this would mean reading ALL project files and checking all the env var uses? |
Anyone any idea why this exists? I noticed it being flagged up as a parser error in skeleton because it's looking for a name for each export.
|
…or additional tests
@Josh-Walker-GM I just fixed merge conflicts for you—let me know if this was undesired and I'll revert. You can see the changes here: 539abb8. |
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.
This is awesome @Josh-Walker-GM, if only for the sheer amount of code. We're going to have to have a lot of back and forths, and run a lot of experiments before it's even close to merging, but I think this has the potential to solve a lot of problems for us. I'm going to need to go through it at least a couple more times (I only made it through a few files), but I've got a lot of comments:
- there's lots of enums. the team (and TS community at large) is divided on them. just keep that in mind
- we should think even bigger here and try remove this package's dependency on
@redwoodjs/internal
entirely. probably by subsuming the functionality it's importing - it's definitely worth seeing if we can play more into composition over inheritance, but I need to get a complete grasp of the code first. don't make any radical changes yet, unless you feel strongly
hasWarnings(): boolean { | ||
return this.warnings.length > 0 | ||
} |
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.
When I started learning TS I was told to let TS infer the return type as much as possible. Not sure if things have changed, but unless you've heard otherwise let's remove the return type for functions like these:
hasWarnings(): boolean { | |
return this.warnings.length > 0 | |
} | |
hasWarnings() { | |
return this.warnings.length > 0 | |
} |
printWarnings(): void { | ||
if (this.warnings.length > 0) { | ||
const titleLine = `${chalk.bgYellow('[Warn]')}\t${this.name} ${chalk.dim( | ||
this.filepath | ||
)}` | ||
const warningLines = this.warnings.map((warning) => { | ||
return ` (W${warning.code}) ${warning.message}\n` | ||
}) | ||
console.log(titleLine.concat('\n', ...warningLines).trimEnd()) | ||
} | ||
} |
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 reuse hasWarnings
here? I also prefer less nesting by returning early, but that's just my preference:
printWarnings(): void { | |
if (this.warnings.length > 0) { | |
const titleLine = `${chalk.bgYellow('[Warn]')}\t${this.name} ${chalk.dim( | |
this.filepath | |
)}` | |
const warningLines = this.warnings.map((warning) => { | |
return ` (W${warning.code}) ${warning.message}\n` | |
}) | |
console.log(titleLine.concat('\n', ...warningLines).trimEnd()) | |
} | |
} | |
printWarnings() { | |
if (!this.hasWarnings()) { | |
return | |
} | |
const titleLine = `${chalk.bgYellow('[Warn]')}\t${this.name} ${chalk.dim( | |
this.filepath | |
)}` | |
const warningLines = this.warnings.map((warning) => { | |
return ` (W${warning.code}) ${warning.message}\n` | |
}) | |
console.log(titleLine.concat('\n', ...warningLines).trimEnd()) | |
} |
printErrors(): void { | ||
if (this.errors.length > 0) { | ||
const titleLine = `${chalk.bgRed('[Error]')}\t${this.name} ${chalk.dim( | ||
this.filepath | ||
)}` | ||
const errorLines = this.errors.map((error) => { | ||
return ` (E${error.code}) ${error.message}\n` | ||
}) | ||
console.log(titleLine.concat('\n', ...errorLines).trimEnd()) | ||
} | ||
} |
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.
printErrors(): void { | |
if (this.errors.length > 0) { | |
const titleLine = `${chalk.bgRed('[Error]')}\t${this.name} ${chalk.dim( | |
this.filepath | |
)}` | |
const errorLines = this.errors.map((error) => { | |
return ` (E${error.code}) ${error.message}\n` | |
}) | |
console.log(titleLine.concat('\n', ...errorLines).trimEnd()) | |
} | |
} | |
printErrors() { | |
if (!this.hasErrors()) { | |
return | |
} | |
const titleLine = `${chalk.bgRed('[Error]')}\t${this.name} ${chalk.dim( | |
this.filepath | |
)}` | |
const errorLines = this.errors.map((error) => { | |
return ` (E${error.code}) ${error.message}\n` | |
}) | |
console.log(titleLine.concat('\n', ...errorLines).trimEnd()) | |
} |
warnings: RedwoodWarning[] = [] | ||
errors: RedwoodError[] = [] | ||
|
||
public readonly name: string |
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.
The handbook says public
is redundant https://www.typescriptlang.org/docs/handbook/2/classes.html#public:
public readonly name: string | |
readonly name: string |
constructor(public readonly filepath: string, name?: string) { | ||
this.name = name ?? path.parse(this.filepath).name // default to the file name if not given a specific name | ||
} |
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.
You're gonna have to walk me through this one. I'm mainly confused why (or how even) filepath
is accessed off this
in the constructor's body when it's passed as an arg?
export function extractCells( | ||
project: RedwoodProject | undefined = undefined | ||
): RedwoodCell[] { |
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.
You can simplify this a lot:
export function extractCells( | |
project: RedwoodProject | undefined = undefined | |
): RedwoodCell[] { | |
export function extractCells(project?: RedwoodProject): RedwoodCell[] { |
getTOMLs(forceExtract = false): RedwoodTOML[] { | ||
if (forceExtract || this.tomls === undefined) { | ||
this.tomls = extractTOMLs(this) | ||
} | ||
return this.tomls | ||
} |
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.
You may be able to save on duplication here (and in the other get*
methods) by using deocrators. Though you probably want to wait for TS v5 since it has a different (more accurate to modern JS) decorator implementation https://devblogs.microsoft.com/typescript/announcing-typescript-5-0-beta/#decorators
} | ||
|
||
hasWarnings(cascade = false): boolean { | ||
let warningsFound = this.warnings.length > 0 |
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.
May as well use the class you're extending from:
let warningsFound = this.warnings.length > 0 | |
let warningsFound = super.hasWarnings() |
} | ||
|
||
hasErrors(cascade = false): boolean { | ||
let errorsFound = this.errors.length > 0 |
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.
let errorsFound = this.errors.length > 0 | |
let errorsFound = super.hasErrors() |
private cells?: RedwoodCell[] | undefined | ||
private directives?: RedwoodDirective[] | undefined | ||
private functions?: RedwoodFunction[] | undefined | ||
private layouts?: RedwoodLayout[] | undefined | ||
private pages?: RedwoodPage[] | undefined | ||
private routers?: RedwoodRouter[] | undefined | ||
private sdls?: RedwoodSDL[] | undefined | ||
private services?: RedwoodService[] | undefined | ||
private sides?: RedwoodSide[] | undefined | ||
private tomls?: RedwoodTOML[] | undefined |
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.
It's probably worth using JS's private field syntax (#cell
, and when accessing, this.#cell
) unless you have a good reason otherwise since it isn't one to one with TS's private
keyword: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields
Also, the ?
and undefined
union look redundant to me; I'd just go with ?
private cells?: RedwoodCell[] | undefined | |
private directives?: RedwoodDirective[] | undefined | |
private functions?: RedwoodFunction[] | undefined | |
private layouts?: RedwoodLayout[] | undefined | |
private pages?: RedwoodPage[] | undefined | |
private routers?: RedwoodRouter[] | undefined | |
private sdls?: RedwoodSDL[] | undefined | |
private services?: RedwoodService[] | undefined | |
private sides?: RedwoodSide[] | undefined | |
private tomls?: RedwoodTOML[] | undefined | |
#cells?: RedwoodCell[] | |
#directives?: RedwoodDirective[] | |
#functions?: RedwoodFunction[] | |
#layouts?: RedwoodLayout[] | |
#pages?: RedwoodPage[] | |
#routers?: RedwoodRouter[] | |
#sdls?: RedwoodSDL[] | |
#services?: RedwoodService[] | |
#sides?: RedwoodSide[] | |
#tomls?: RedwoodTOML[] |
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'm sorry I'm late to the party here, but why not use TS's class syntax when in TS land?
private
gets you: Nicer syntax for defining, nicer syntax for accessing, faster performance (meh, most likely negligible)
#
gets you: Real privacy (can't do project['cells']
)
So, yeah, technically/theoretically #
is better. But practically I prefer private
Thanks @jtoar a few comments:
I have seen that TS enums aren't the enums of my childhood and that they don't behave well under certain conditions. There's 4 enums in use. Error and warning enumeration - I'd like these enumerated but don't have to use TS enums for this if there are better approaches. The other two are side and is the project js or ts which on reflection can probably be easily changed to a union of strings. Edit: I updated the side and js/ts enum to string unions.
This is what #6919 was hoping to do? I was thinking that the paths package was going to be the lighter weight package which contains all the paths/config utilities. Then it would be the case we rely on that paths package not internal. It also means that we'd not be duplicating the path/config logic between paths and skeleton and that you wouldn't have to haul skeleton about if you wanted path/config functionality. |
Hey @Josh-Walker-GM - Tobbe and I had a chance to go through this together, and here's what we came up with. Either of us will be happy to pair with you to get these changes in!
Hopefully this all makes sense, otherwise we can link up soon and get this in! |
WIP: Initial implementation, likely to change and develop significantly
Problem
The structure package was created with the Redood VSCode extension in mind and so is written in a style optimised for that use case. That use case no longer exists and the remaining introspection functionality which remains in use within the framework would benefit from an updated package which would be more maintainable and have a more convenient api.
Solution
Remove the structure package and implement a replacement package which provides the introspection ability, here it's named skeleton.
Description
The skeleton package implements a series of classes which model the individual components of a Redwood project; cells, routes etc. These components can be extracted from a project either individually or as part of a larger project tree. The properties of the components are determined by analysing the AST from the project files.
Functions are provided to extract the individual components. Taking cells as an example you may extract one cell from a file using
extractCell
or extract all the cells within a project usingextractCells
. This is true for the other components such as pages, routers etc. It is also possible to generate a full project usingRedwoodProject.getProject
. The goal is to provide flexibility.Caching is introduced to prevent the need to repeatedly extract, generate and process the AST from the individual js/ts project files.
Changes
Outstanding
Whilst there is a large amount of possible outstanding additions the sensible decision is to prioritise only cells and routes as these are the components which currently require the full AST introspection.
Pings
@Tobbe, @dac09