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

Proposal: TypeScript support for gasket servers #277

Open
DullReferenceException opened this issue Apr 21, 2021 · 2 comments
Open

Proposal: TypeScript support for gasket servers #277

DullReferenceException opened this issue Apr 21, 2021 · 2 comments

Comments

@DullReferenceException
Copy link
Contributor

DullReferenceException commented Apr 21, 2021

Problem Statement

TypeScript enthusiasts like me want as much of our code to be written in TypeScript as possible. As a user of @gasket/preset-nextjs, I enjoy the fact that the next.js integration gives me the ability to author some of my code in TypeScript (React components for example), but it's frustrating that any code compatible with SSR (/store.js) or pure gasket artifacts /lifecycles cannot be authored in TypeScript without some hackery or degradation in developer experience (DX).

Technical Challenges

There are many technical challenges to supporting TypeScript source code in gasket applications:

  • Gasket, being a framework engine, needs to remain as tiny as possible and mandate as few technology choices as possible. This includes TypeScript. We therefore would prefer not to have Gasket automatically support TypeScript out of the box.
  • Developers find the presence of compilation artifacts residing alongside their source code to be a nuisance and a potential source of bugs if these artifacts are not up-to-date. Though they may be receptive to having to compile prior to a deployment, they do not wish to have to dodge extraneous file artifacts in their workspace or rely on starting a watching builder to keep things current.
  • However, node-loadable source files are a must for packages like @gasket/plugin-lifecycles, which lists the files in a /lifecycles directory and dynamically loads that code, and @gasket/plugin-redux which needs to discover a redux store factory.

Another tangential thing that developers may desire is the ability to automatically restart gasket servers when code changes. This avoids those confusing moments where you're tearing your hair out wondering why the code is not behaving as written because you forgot to restart.

Current Workarounds

Although TypeScript outside of next.js-built components is not officially supported, some approaches have been employed to support it within some projects, falling into two broad categories. Here are the challenges with these workarounds:

Building TypeScript files before running gasket

Running tsc before invoking the gasket CLI causes generated .js files to be placed alongside .ts files so that they're discoverable by Gasket. This works, but:

  • It is annoying to developers who now have to navigate both .js and .ts files in their source files.
  • For next.js apps, care has to be taken to make sure the TypeScript compilation doesn't conflict with the built-in TS support used when generating webpack artifacts. The ideal compiled output for a browser versus node differs, so you end up wanting different configurations for those two targets.
  • If you'd like your Gasket server to be restarted on source files changes, you have to take care that only code for the server side does this since next.js already has hot module reloading for client-side artifacts, and you wouldn't want changes to React component .tsx files to provoke server restarts.

Registering a module loader for TypeScript files

Another workaround involves registering a module loader for .ts files, using techniques like @babel/register or ts-node/register early in the source code of a gasket app, such as in gasket.config.js. This has some challenges:

  • It requires heavy toolchains like babel or typescript to be deployed alongside an app's dependencies since they are now used at runtime instead of just build time.
  • Although it addresses issues with importing source code, every package, that dynamically loads source code, such as @gasket/plugin-lifecycle, needs to know to look for other file extensions like .ts.
  • Source changes to server code not provoking restarts is also not addressed.

Proposed Solution

  • Add a gasket config property like extensions that is an array of which file extensions gasket plugins should look for when dynamically loading source files (or can we read this from Module?). This should have a reasonable default for typical node development.
  • Update plugins that do code discovery, like @gasket/plugin-lifecycle, to use this extensions config value.
  • In @gasket/plugin-start or a new @gasket/plugin-clean plugin:
    • Introduce a new clean command to gasket
    • Inject a clean package.json script that invokes gasket clean
  • Add clean hooks for any plugin that generates build artifacts, like @godaddy/gasket-plugin-intl
  • Create a @gasket/plugin-nodemon package which:
    • Hooks the create lifecycle and generates:
      • A devDependency on nodemon
      • A nodemon.json with useful defaults
      • A modified local package.json script which invokes gasket local via nodemon
    • Emits a nodemonConfig lifecycle event during create to enable plugins to modify the generated config
  • Create a @gasket/plugin-typescript package, which:
    • Hooks the configure lifecycle, injecting .ts (and maybe .tsx?) as an extension.
    • Hooks the create lifecycle after @gasket/plugin-nodemon and generates:
      • Appropriate devDependencies, like:
        • typescript
        • ts-node
        • @types/foo dependencies for any packages with known external type definitions. These will be read by invoking a new tsTypeDefinitionPackages lifecycle, enabling any plugin to register their own type definitions.
      • Two TypeScript config files, one for client code (tsconfig.json so it's picked up by the next.js babel compiler), one for server code
      • Appropriate .gitignore modifications
      • A modified local package.json script where ts-node is used to execute gasket or nodemon --exec ts-node if it's already wrapped by nodemon.
    • Hooks the nodemonConfig lifecycle and adds watching of .ts files, except those that are likely to be client-only
    • Hooks the build lifecycle, invoking tsc with the server config file if the gasket command isn't local
    • Hooks the clean lifecycle, deleting generated .js files
@rclayton-godaddy
Copy link

rclayton-godaddy commented Jul 17, 2021

Can we add a "before everything" init method where we can compile all *.ts files into js before Gasket reads the config file? This would let us to write everything in TypeScript including the Gasket config.

@mmogasbe-godaddy
Copy link

This would be awesome

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

No branches or pull requests

3 participants