-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
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
Added a plugin system. #92
Conversation
The plugin system allows users to extend dayjs with additional plugins, or change existing functionality. The plugins work by calling the extend method: dayjs.extend({ n: "methodName", m: function(arguments) { // } }) When overwriting a function, the original function can be referenced from inside the plugin using: this.getOld(pluginName) For example: dayjs.extend({ n: "format", m: function(formatStr) { let oldFormat = this.getOld("format") let oldResults = oldFormat(formatStr) ... }) This commit also introduces a plugin which extends the format options to include: -Q: Quarter of the year -k/kk: 1-24 hour time -ddd: Date of the month with ordinal (1st-31st) -S: Milliseconds
Codecov Report
@@ Coverage Diff @@
## @next #92 +/- ##
====================================
Coverage 100% 100%
====================================
Files 3 3
Lines 206 183 -23
Branches 39 28 -11
====================================
- Hits 206 183 -23
Continue to review full report at Codecov.
|
@Frondor had suggested a system more like Vue js, where the prototype is passed into the plugin function and the plugin is able to alter it however it would like. It would look something like:
User's
Both that approach and the current one should work equally well, it seems to be a matter of preference and how much control plugin authors should be given over the default DayJS object. |
@schiem I agree giving plugin a full access to the Dayjs class. That makes it a real plugin 😋 |
Plus, I thinks we might better use |
Just one thing, you may want to define whether you want to extend In Vue you can't do I know its a total different approach, but just take a look, and tell later: https://codepen.io/Frondor/pen/MGopZx?editors=0010 // The plugin
const lastNamer = {
install (Person, opts = {}) {
const formatter = s => {
return typeof opts.formatter === 'function' ? opts.formatter(s) : s
}
Person.addHook(function (p) {
p.lastName = formatter(p.cfg.lastName)
})
Person.prototype.getName = function () {
console.log('I am', `${this.firstName} ${this.lastName}`)
}
}
}
// Plugin installation with options
Person.extend(lastNamer, {
formatter: ln => ln.toUpperCase()
})
// Library usage
const person = new Person({
firstName: 'Jon', // supported by default
lastName: 'Snow' // supported after plugin
})
console.log(person)
person.getName() // new custom method |
If I can weigh in with my two cents: From the README:
I believe it would be great to implement plugins in a way which extends this concept of immutability. By this I mean, instead of mutating const extended_dayjs = dayjs.extend(plugin); This would leave the original Edit: My opinion here also applies to any localisation dayjs lets users set which is in conflict with existing work in #90. |
Have to agree with harry. Also, take in mind that after #90, you will be able to do import { Dayjs } from 'dayjs'
class MyDayjs extends Dayjs {
constructor (cfg) {
super(cfg)
}
}
@harrysarson the localization approach is fully immutable besides of the myDayjs.format('dddd') // "Monday"
myDayjs
.add(1, 'day')
.setLocale(eng) // set the same locales just for this example sake
.format('dddd') // "Tuesday"
myDayjs.format('dddd') // "Monday" |
@Frondor The person example you gave is very close to what's being done here, but the extend method is placed on the dayjs "factory" that has access to the Dayjs class, and that's used to extend the prototype. The main difference is that the install is handled internally, so the user just needs to pass in the plugin name and method to be run. Personally, I like removing as much of the boiler plate for the plugin author as possible, but I think that's a matter of preference. I also really like the idea of hooks, I'll give that a look. @harrysarson I agree that it would be good to maintain immutability, but I can only think of two ways to do it with a plugin system:
Which leaves us with:
As a side note, I dislike the idea of exposing the Dayjs class to the end user. It seems to me that destroys a lot of the immutability--with the current set up, the end user can't (reasonably) access the underlying Dayjs class, so the library is completely immutable. Exposing the underlying class opens it up for any user to alter the internals. |
Again just my personal views here:
I think a
I think this would be a sensible approach. |
I agree that immutability matters. But consider a real project example like this: // assumed we are running a Spanish site
// in root.js or index.js init dayjs settings
import dayjs from 'dayjs'
import { Locale_ES } from 'dayjs/locales' // Spanish locale constant
import { Plugin_TimeAgo } from 'dayjs/plugins' // extend dayjs#fromNow
dayjs.extend([Locale_ES, Plugin_TimeAgo])// set locale && plugin
// for the rest of the code in this project
dayjs().fromNow() // -> new API 1 year ago...
dayjs().set(...).format() // format in Spanish Although, it changes the original We might cloud have a second argument for // same as above
const extendedDayjs = dayjs.extend([Locale_ES, Plugin_TimeAgo], true) // no change to original dayjs but return a new one
dayjs().format() // default English
extendedDayjs().format() // Spanish Plus, in my approach, I've treated locales as a kind of extension, so did plugins. So that we might could minimize the new added APIs and concepts. |
@iamkun That makes sense to me. Any thoughts on whether it makes more sense to have the Dayjs core handle installing the plugins vs. having the plugins install themselves using an install method? Having the core install them would reduce the complexity for the plugin author, but it would also reduce the amount of flexibility the plugin author has. |
@schiem I've made a simplified version base on @Frondor's code above. //-----core.js-------
import LocaleDefault from 'LocaleDefault' // english
class Dayjs {
getLocale() {
return LocaleDefault // override this to return new locale
}
format(string, anotherLocale) {
return anotherLocale ? anotherLocale.weekName || this.getLocale().weekName // use this.getLocale() to get correct locale
}
}
const dayjs = config => new Dayjs(config)
dayjs.extend = (cb, isNew = false) => { // isNew -> immutable or not
if (isNew) {
class newDayjs extends Dayjs {
}
cb(newDayjs)
return config => new newDayjs(config)
} else {
cb(Dayjs)
}
}
export default dayjs
//-----plugin.js-----
const PluginWeek = (vm) => {
vm.prototype.week = function (proto, dayjsClass) {
return this.$W
}
}
const LocaleSpanish = (vm) => {
const LocalSpanishObject = { weekName: 'spanish week' }
vm.prototype.getLocale = function () {
return LocalSpanishObject
}
}
//-----usuage.js----
// set global
dayjs.extend(PluginWeek)
dayjs.extend(LocaleSpanish)
dayjs().week() // new API
dayjs().format() // format with spanish locale
const extendedDayjs = dayjs.extend(PluginWeek, true) // get new class instance for immutability
|
…ugin authors. Plugins now take the Dayjs (or extended subclass) as a parameter and are responsible for installing themselves. The extend function is chainable, for example: dayjs.extend(plugin1).extend(plugin2) The extend function also now takes a second boolean parameter, which will return a new version of dayjs. Example: var extended = dayjs.extend(plugin1, true) extended().plugin1() dayjs().plugin1() //Error
I've incorporated those changes to the plugin system. I'll leave the locale system to @Frondor. You can still use the plugin system via
But you can now pass in true to return a new factory. It's now also chainable, so you can do:
Or
Plugins are now passed in the class object that they're modifying, so they can modify them however they need to, like so:
|
@schiem Looks good.
|
I've switched the
The reason for that change is that For point 2, I think that since we're passing in the Dayjs prototype for plugins to modify, it makes sense for them to handle the install themselves (since there might be plugins, like locales, that might modify a variable instead of a method). And if they're handling the install themselves, then I think it might make sense for them to keep track of the original function themselves. This would also reduce conflicts when installing multiple plugins that operate on the same method. For instance, say there were two different format plugins that added slightly different functionality. If the core was responsible for keeping track of the original
If the plugins are responsible for saving the original method, then they should be storing whichever method is currently on the plugin. For example:
It also gives the plugin authors the opportunity to not call the original if they need to completely change the core functionality. I think that not enforcing a specific install method gives the plugin authors a lot more freedom to extend in the way they need to. |
I'm gonna to merge this pr fist. And put locale && plugin 's code altogether. Then we might could discuss the new release in one pr. |
🎉 This PR is included in version 1.6.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
The plugin system allows users to extend dayjs with additional plugins,
or change existing functionality.
The plugins work by calling the extend method:
When overwriting a function, the original function can be referenced from
inside the plugin using:
this.getOld(pluginName)
For example:
This commit also introduces a plugin which extends the format options to include:
-Q: Quarter of the year
-k/kk: 1-24 hour time
-ddd: Date of the month with ordinal (1st-31st)
-S: Milliseconds