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

feat: Support providing custom TypeScript transformers #5602

Open
3 tasks done
maxpatiiuk opened this issue Mar 28, 2024 · 3 comments
Open
3 tasks done

feat: Support providing custom TypeScript transformers #5602

maxpatiiuk opened this issue Mar 28, 2024 · 3 comments
Labels
Feature: Want this? Upvote it! This PR or Issue may be a great consideration for a future idea.

Comments

@maxpatiiuk
Copy link

maxpatiiuk commented Mar 28, 2024

Prerequisites

Describe the Feature Request

We need an ability to provide a custom TypeScript transformer plugin to Stencil to modify the code BEFORE Stencil extracts data from the AST.

const transformers: ts.CustomTransformers = {
before: [
convertDecoratorsToStatic(config, buildCtx.diagnostics, typeChecker, program),
performAutomaticKeyInsertion,
updateStencilCoreImports(transformOpts.coreImportPath),
],
after: [convertStaticToMeta(config, compilerCtx, buildCtx, typeChecker, null, transformOpts)],
afterDeclarations: [],
};

Describe the Use Case

Stencil allows to define a rollup plugin that would transform the output code.

However, current implementation is very limited because rollup plugins are called after Stencil pulled the data from the AST for each component and transformed the code using TypeScript plugins.

This means:

  • Rollup plugin can not modify the JS Doc documentation as it is extracted by Stencil at an earlier step.
  • Rollup plugin is not able to add any @Watch/@State/@Prop/@Method/@Listen either, as the component meta is extracted by Stencil at an earlier step, before the Rollup plugin is run.
  • Rollup plugin is not able to add new lifecycle hooks either, because the BUILD object (that is used by Stencil for dead code elimination in minified production builds) is computed at an earlier step.
  • The source map returned by Rollup plugin's transform seems to be ignored too, leading to worse debug experience.
  • When Stencil runs unit tests, it does not run Rollup, so Rollup plugins don't get run. Instead, we are forced to duplicate the Rollup plugin logic in a Jest transformer (we create a Jest transformer that transforms code before calling Stencil's default Jest transformer). Having a TypeScript transformer would make this much simpler as TypeScript is being used by Stencil both in unit tests and dev/prod builds

Describe Preferred Solution

Ability to provide a custom TypeScript transformer in the stencil.config.ts file that would be called before Stencil extracts data from the AST and begins it's transformation.

Describe Alternatives

Expose buildCtx and compilerCtx objects to Rollup plugins so that they could modify the Stencil's compiler meta or internal file system before Stencil outputs the built files.

A partial solution may also be to expose the cmpMeta to the component at run-time to let it dynamically define watchers and to let it get a list of all the props/states available on the component (to integrate each prop with an external reactivity library)

Without this, our "hacky" solution may be to use Stencil's Core Compiler API rather than the CLI directly (we would have to create our own CLI) to provide a custom file system object that would modify the source files before Stencil extracts component meta from the AST

Related Code

No response

Additional Information

Requested by: Esri

Related issues:

@ionitron-bot ionitron-bot bot added the triage label Mar 28, 2024
@alicewriteswrongs
Copy link
Member

Hey @maxpatiiuk, thanks for writing up the feature request!

I'm just curious, what is it that you're trying to do via your rollup plugin that you're unable to do? Just to get a sense of what the use-case for this is for your team.

@maxpatiiuk
Copy link
Author

maxpatiiuk commented Mar 28, 2024

Sure. We have several use cases that would be much easier to resolve with a TypeScript transformer (until Stencil supports all of this natively):

  • We have many common Props and some common Methods between components - adding these Props at compile time would improve consistency and DX for our devs
  • Some props are not present in every component, but when present, they should have a consistent JS DOC. Adding JS Doc for these at compile time would be great
    • Current solution is to write custom JS Doc extraction logic (using modified tsdoc) rather than Stencil's doc output
  • We have a lot of common logic and boilerplate between components (loading an external data store, setting up two way watchers to sync with it, loading translation strings, resolving several common promises before component load, etc...). Not being able to use extends in our components adds a lot of boilerplate to each component. Our solution at the moment was something like feat: add support for reactive controllers #3162 (we added Lit-compatible controllers to Stencil, that can hook into the component lifecycle)
  • We are also doing some "hacky" things at the moment to:
    • add dynamic @Watchers support (adding watchers on the fly at runtime) to watch changes for props that are common between many components
      • dynamic watchers are also used to provide a nice API for users of our components to watch prop changes (rather than using events or mutation observer, as those are move verbose or have performance issues)
    • add getters/setters support for Prop/State to do validation and casting before a prop is set, and to keep the Stencil prop in sync with an external store.
    • we are also experimenting with completely replacing the place where Stencil stores the prop/state values (the internal Map) to have Stencil write directly to our external store, removing the need for watchers or getters/setters that do proxying. It works at the moment, even with hot-reloading in development, but may break on any changes to Stencil's source code, so we are not comfortable using this in production

In general, most things are motivated by:

  • We want to use an external store (more like viewModel) that will contain the component's props/states and then keep Stencil Component in sync with that store. Having an external ViewModel is a core requirement for us to reduce our exposure to Stencil/underlying rendering framework in case of breaking changes/the need to migrate to a different framework.
    • ViewModel contains all non-rendering logic of the component. Then, the component itself stays minimal - just renders some elements given current state and calls methods on the viewModel in response to user's actions
  • We want to be able to write maintainable components with little boilerplate code. The fact that Stencil does not support extending components, and doesn't support componsition (mean business logic composition, like react hooks/vue composables/lit controllers, not component composition) makes writing production-ready maintainable code much harder.

@alicewriteswrongs
Copy link
Member

Thanks for providing that breakdown! I know that code re-use and extensibility / composition is a pain point in Stencil at present and it is something that we want to address for sure.

I'm going to circulate what you shared among the team and we'll noodle on it a bit!

@rwaskiewicz rwaskiewicz added Feature: Want this? Upvote it! This PR or Issue may be a great consideration for a future idea. and removed triage labels Apr 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature: Want this? Upvote it! This PR or Issue may be a great consideration for a future idea.
Projects
None yet
Development

No branches or pull requests

3 participants