-
Notifications
You must be signed in to change notification settings - Fork 24
Description
Motivation
I wrote some very simple code to run in a browser and print a full year of פרשה names:
import { HebrewCalendar } from '@hebcal/core'
const events = HebrewCalendar.calendar({
year: new Date().getFullYear() - 1,
numYears: 2,
sedrot: true,
noHolidays: true,
})
console.log(
events.map((event) => ({
datetime: event.date.greg(),
label: event.render('he-x-NoNikud'),
}))
)When compiled for production using https://vitejs.dev/ with default settings, this produced 150 kB of optimized JS!
dist/index-Cca6IFY6.js 150.13 kB │ gzip: 53.69 kB
Since all functions are contained in the HebrewCalendar class and the Event hierarchy, Tree Shaking cannot see which APIs are actually used.
Suggestion
It ought to be possible to build a new modular API in which application code only imports features that are actually used. This should allow tree shaking to entirely drop unused features (eg, emojis, zmanim calculations, unused locales, etc).
This would be a new API that requires explicit imports to enable more calculations and methods. The existing API would remain as a wrapper to avoid breaking existing code. Applications that want to benefit from tree shaking would need to migrate to the new API to gain the benefits.
Details
Making code tree-shakable involves a number of changes (these changes can be made independently for incremental improvements, but each incremental change would require clients to refactor further to get more benefit):
Note: All of these suggestions are rough ideas; I have not reviewed the code carefully enough to be sure that each of these make sense and would actually allow non-trivial amounts of code to be tree-shaken
- Replace the static methods on
HebrewCalendarwith separateexports to drop unused methods.- This is the simplest improvement, and should be able to drop files like
tachanun.tsandhallel.tseasily.
- This is the simplest improvement, and should be able to drop files like
- Replace locale name strings with separately-exported locale objects containing their translations.
- This way, if a project only uses one locale, other locales will be dropped.
- Unless you expect projects to use both
heandhe-x-nonikud, it would also make sense to movehe-x-nonikudto a separate file generated at build time, so that projects that only use it can drop nikud entirely.
- Replace the
flagsenum with an array ofimportable objects that contain theirEventsubclasses and the relevant parts ofgetHolidaysForYear_()- This way, projects that only use certain flags can drop all code for other events.
- Fully implementing this would involve a major refactor of
getHolidaysForYear_(); we should try to estimate how much code this could actually save first.
- Similarly to the previous bullet, replace the various boolean options in
HebrewCalendar.calendar()with an array ofimported functions or plugins to specify which events you want returned.- This would let projects drop all of the Zmanim code (if they don't pass
candleLightingoromer), which should be a very big win. - This can also improve end-user type safety; you can declare the function as returning an array of
Eventsubclasses based on the plugins you pass. - This can be implemented in stages; declare all of the plugins first (so that consumers can
importonly what they need), and you can then slowly split apart the underlying code to be more modular. This allows you to make incremental improvements under the hood without forcing application code to change further. - Similarly, you could eventually split apart
getHolidaysForYear_()based on plugins as well for further savings.
- This would let projects drop all of the Zmanim code (if they don't pass
- Move any expensive members of
Eventand subclasses (eg,getEmoji(); I don't know if there are any other costly members) to a top-levelexported function that takes theeventas a parameter instead.- To be less disruptive, you could declare an
exported function that addsgetEmoji()toEvent.prototype, so that it is still available as a member function, but only if this isimported. - This can even maintain full type safety in TypeScript; example (TypeScript will only see this file, and therefore the
prototypedeclaration, if imported). - I haven't tried it, but you can probably make this even safer in TypeScript by declaring the
importable installer function asasserts Event is {prototype: {getEmoji(): string}}
- To be less disruptive, you could declare an
Let me know if you need help understanding, designing, prototyping, or implementing this.