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: introduce meta element APIs v2 #883

Merged
merged 13 commits into from
Oct 10, 2024

Conversation

davidlj95
Copy link
Owner

@davidlj95 davidlj95 commented Oct 5, 2024

Issue or need

Current APIs to set <meta> elements for the page aren't very convincing. Reasons:

  • A service is used. A lightweight token would be preferred. Also the service has just one method so even more reason for that See perf: injection tokens' factories don't get tree-shaken #891
  • An unneeded abstraction is introduced: NgxMetaMetaDefinition. So requires an extra cognitive step before using the service.
  • That abstraction introduces two utility functions that perpetuate that one:
    • makeKeyValMetaDefinition
    • makeComposedKeyValMetaDefinition
  • In there an explanation is made about why a <meta> can be modeled as a key / value map.
  • Too many explanations and abstractions. I just want to set some <meta> elements, heck.
  • NgxMetaDefinition abstraction feels wrong. Better not to have a wrong abstraction. Why feels wrong?
    • withContent(content: string) method assumes that all <meta>s will have one string content value. However, for the theme-color standard meta for instance, the meta accepts both a media optional attribute and a mandatory content attribute. Right now, to set that media attribute, a trick is used: generate one definition on demand for each different media. But doesn't sound good / defeats the purpose of the definition abstraction. Which was a factory of MetaDefinitions
    • Code/service actually managing <meta> elements (creating, updating, removing them) doesn't seem to need that. It only needs:
      • The attributeSelector in case it needs to remove <meta> elements from the page
      • The MetaDefinition to create/update the element. Or a lack of one to actually remove the existing <meta> elements on the page.
    • However, that abstraction is forced there. And the abstraction comes with the limitation of only being able to provide a dynamic string content value. So doesn't support more complex cases like the one mentioned above about theme-color

Finally, currently there's no API to set multiple <meta> elements with same name.
That's something some metadata require though. As seen in #881.

Proposed changes

New APIs here we go. The main requirements were to fix the spots mentioned above. Taking into account:

  • Attribute selector to remove <meta> elements identified by a name or property attribute shouldn't be needed to be typed by users. i.e: name="description".
  • Multiple <meta> elements must be allowed to be managed to solve test: standard theme color metadata setting two metas #881
  • Friendly for developers to use
  • No unnecessary abstractions
  • Avoid using objects where possible. As objects properties can't be shortened nor tree-shaken.
  • Use lightweight injection tokens

Eventually the APIs you see here were designed after several iterations:

const ngxMetaElementsService = inject(NgxMetaElementsService)

// 1. Single elements
// 1.1. Set a single metadata element
//    Specifically `<meta name="description" content="foobar">`
ngxMetaElementsService.set(
  withNameAttribute('description'), 
  withContentAttribute('foobar')
) // set `<meta name="description" content="foobar">`

// 1.2. Remove any metadata elements with a given name
//    Specifically any `<meta name="description">` element found
ngxMetaElementsService.set(
  withNameAttribute('description'), 
  withContentAttribute(undefined) 
   // 👆 or just `undefined`
   // but using `undefined` with the call will help when setting values
) 

// Many elements
// 2.1. Set multiple metadata elements
//    Specifically:
//    `<meta name="theme-color" content="darkblue" media="(prefers-color-scheme: 'dark')">`
//    `<meta name="theme-color" content="lightblue">`
ngxMetaElementsService.set(
  withNameAttribute('theme-color'), 
  [
    withContentAttribute('darkblue', { media: "(prefers-color-scheme: dark)" }),
    withContentAttribute('lightblue')
  ]
) 

// 2.2. Remove any metadata elements with a given name
//    Specifically any `<meta name="theme-color">` element found
ngxMetaElementsService.set(
  withNameAttribute('theme-color'), 
  []
)

The API to manage 1 <meta> element is separated from the API to manage many <meta>s at once. This is done for tree-shaking purposes. If a user just includes metadata managers that set a single <meta>, no need for the code to manage many <meta>s.

One may wonder: but why not only provide the code to manage many <meta>s and be the case of just one element array a specific case of it? Well because not sure about performance implications of removing and inserting so many nodes vs updating them instead. And if we do it this way, it's quite easy to at some later point call the many elements handler from the single element handler if there are no performance implications.

Eventually, due to finding in #891 it's better to use a service. Then putting both APIs to manage one element and many elements in the same service was a better decision to avoid injecting the Angular code regarding dependency injection twice (see #112). Finally, instead of two APIs, one for 1 element and one for many elements, just 1 API to set one or many elements is provided. First to save some bytes now that we're providing a service that will come as a whole and can't provide two single-purpose APIs. Second, to provide a consistent behavior. Let's say many meta elements with same name are externally (outside of the library) added to the DOM. But then, library uses the single element setter API to update those elements. In that scenario, just first one found would be updated. Which feels weird. An API that does the same in every case is more robust.

APIs are in @alpha state until they're used by all built-in meta modules and prove to functional.

Discovered #891 during implementation of this

To review

  • Avoid Meta and use document APIs directly?

After giving it a try, the refactor to do that is quite simple. However, the user may be already using Meta APIs. So reusing that code would result in smaller bundle size. If not using it, though, there's a win in bundle size terms. Given Meta APIs are provided as a service. Just 2 of them are used. But there are 7. And some code that isn't used anyway. So there would be a win in there. Well, we can care about that later.

Allowing the user to choose which implementation to use would be ideal. But maybe that's going too far? To do so, the service could be turned into an abstract class / injection token. And provided a default implementation in provideNgxMetaCore, but with the option to provide another. Though we'd loose the tree-shaking in there if unused. It's weird that this service is unused though. As setting those <meta> elements is a key piece of functionality. Also, would be difficult to tree shake the default one if providing another. As wouldn't be possible to remove the default's usage. Hmmm...

Also the DOCUMENT injection token is another thing to couple to. Though we're coupling to it in other places. Like when setting the canonical URL link[rel] element or modifying the html[lang].

Will open a PR to see the bundle size changes. If it's not much, using DOCUMENT APIs directly decouples from using Meta Angular API. Which is an interesting trade-off. So that the code survives deprecation of Meta API.

  • Export custom type instead of MetaDefinition? Make it compatible with it (i.e.: the httpEquiv property)?

Makes sense in order to decouple from it. As seen in previous point. Also we make explicit that no renaming of name attributes will happen (httpEquiv -> http-equiv). Migration from a MetaDefinition is quite straight-forward. It's also compatible with MetaDefinition except from that mapped property. Also, we can add new attributes as they appear. Like media for theme-color standard metadata.

API is in @alpha but if doing so for NgxMetaElementsService.set, it disappears from docs. So using @beta in there.

Quick reminders

  • 🤝 I will follow Code of Conduct
  • No existing pull request already does almost same changes
  • 👁️ Contributing docs are something I've taken a look at
  • 📝 Commit messages convention has been followed
  • 💬 TSDoc comments have been added or updated indicating API visibility if API surface has changed.
  • 🧪 Tests have been added if needed. For instance, if adding new features or fixing a bug. Or removed if removing features.
  • ⚙️ API Report has been updated if API surface is altered.

Copy link
Owner Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

Join @davidlj95 and the rest of your teammates on Graphite Graphite

Copy link

codecov bot commented Oct 5, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 99.47%. Comparing base (0e91f78) to head (9739690).
Report is 3 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #883      +/-   ##
==========================================
+ Coverage   99.45%   99.47%   +0.01%     
==========================================
  Files          79       82       +3     
  Lines         368      379      +11     
  Branches       68       70       +2     
==========================================
+ Hits          366      377      +11     
  Partials        2        2              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link

github-actions bot commented Oct 5, 2024

📦 Bundle size (Angular v16)

Git ref: 97396909182fd671df18d5883bde573b17e11823

Module file Size Base size Difference
ngx-meta-core.mjs 3442 bytes (3.4KiB) Not available Not available
ngx-meta-json-ld.mjs 370 bytes (370B) Not available Not available
ngx-meta-open-graph.mjs 1265 bytes (1.3KiB) Not available Not available
ngx-meta-routing.mjs 572 bytes (572B) Not available Not available
ngx-meta-standard.mjs 1085 bytes (1.1KiB) Not available Not available
ngx-meta-twitter-card.mjs 645 bytes (645B) Not available Not available
Total 7379 bytes (7.3KiB) Not available Not available

Base size data is not available yet. Try again when the CI/CD has finished running on main branch

Copy link

github-actions bot commented Oct 5, 2024

📦 Bundle size (Angular v15)

Git ref: 97396909182fd671df18d5883bde573b17e11823

Module file Size Base size Difference
ngx-meta-core.mjs 3402 bytes (3.4KiB) Not available Not available
ngx-meta-json-ld.mjs 355 bytes (355B) Not available Not available
ngx-meta-open-graph.mjs 1235 bytes (1.3KiB) Not available Not available
ngx-meta-routing.mjs 557 bytes (557B) Not available Not available
ngx-meta-standard.mjs 1070 bytes (1.1KiB) Not available Not available
ngx-meta-twitter-card.mjs 630 bytes (630B) Not available Not available
Total 7249 bytes (7.1KiB) Not available Not available

Base size data is not available yet. Try again when the CI/CD has finished running on main branch

Copy link

github-actions bot commented Oct 5, 2024

📦 Bundle size (Angular v17)

Git ref: 97396909182fd671df18d5883bde573b17e11823

Module file Size Base size Difference
ngx-meta-core.mjs 3026 bytes (3.0KiB) Not available Not available
ngx-meta-json-ld.mjs 226 bytes (226B) Not available Not available
ngx-meta-open-graph.mjs 986 bytes (986B) Not available Not available
ngx-meta-routing.mjs 394 bytes (394B) Not available Not available
ngx-meta-standard.mjs 988 bytes (988B) Not available Not available
ngx-meta-twitter-card.mjs 514 bytes (514B) Not available Not available
Total 6134 bytes (6.0KiB) Not available Not available

Base size data is not available yet. Try again when the CI/CD has finished running on main branch

Copy link

github-actions bot commented Oct 5, 2024

📦 Bundle size (Angular v18)

Git ref: 97396909182fd671df18d5883bde573b17e11823

Module file Size Base size Difference
ngx-meta-core.mjs 3026 bytes (3.0KiB) Not available Not available
ngx-meta-json-ld.mjs 226 bytes (226B) Not available Not available
ngx-meta-open-graph.mjs 986 bytes (986B) Not available Not available
ngx-meta-routing.mjs 394 bytes (394B) Not available Not available
ngx-meta-standard.mjs 988 bytes (988B) Not available Not available
ngx-meta-twitter-card.mjs 514 bytes (514B) Not available Not available
Total 6134 bytes (6.0KiB) Not available Not available

Base size data is not available yet. Try again when the CI/CD has finished running on main branch

@davidlj95 davidlj95 force-pushed the stacked/feat-introduce-new-meta-element-apis branch 3 times, most recently from 6709d12 to a506951 Compare October 7, 2024 10:52
@davidlj95 davidlj95 force-pushed the stacked/feat-introduce-new-meta-element-apis branch 2 times, most recently from 8db094d to 243b641 Compare October 9, 2024 22:29
@davidlj95 davidlj95 force-pushed the stacked/feat-introduce-new-meta-element-apis branch from 243b641 to cb8c242 Compare October 10, 2024 12:27
@davidlj95 davidlj95 marked this pull request as ready for review October 10, 2024 15:27
@davidlj95 davidlj95 enabled auto-merge (squash) October 10, 2024 17:25
@davidlj95 davidlj95 merged commit 52a3c34 into main Oct 10, 2024
34 checks passed
@davidlj95 davidlj95 deleted the stacked/feat-introduce-new-meta-element-apis branch October 10, 2024 17:29
Copy link

🎉 This PR is included in version 1.0.0-beta.17 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant