-
-
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
Add plugins methods to typescript definition #364
Comments
In my opinion it would be the proper approach to place
That could be achieved by altering the jest config to something like "jest": {
"roots": [
"test",
"src/plugin"
],
"testRegex": "(.*?/)?.*test.js$",
"testURL": "http://localhost",
"coverageDirectory": "./coverage/",
"collectCoverage": true,
"collectCoverageFrom": [
"src/**/*"
]
} |
@suspectpart Seems good. |
Should I restructure the plugins that way and make a pull request so we can have the discussion based on the changes? |
We have to consider structure of our release code. Currently: plugin And I'm not sure if code editor could support typescript definition file like this: |
I guess it works by making the compiled
i.e. compiling plugin code into separate folders and copying the corresponding declare namespace dayjs {
interface Dayjs {
isBetween(d1: dayjs.Dayjs, d2: dayjs.Dayjs)
}
} I quickly sketched this and could get Typescript to not complain, but I had no time to create a full working prototype. |
well then this might be a breaking change since it change the file structure && name.
|
Hmm I see. I am not too familiar with those Typescript concerns, maybe someone else has a good suggestion how to keep plugins self-contained without breaking the existing structure? |
Since Angular 6, you can create a npm module. That kind of module contains a file called public_api.ts, that is used to export any file you want. Doing that, you will be able to import any of your library classes, just pointing to your library name (package.json name) |
But is that Angular-specific then? Because we want to have general typings here, not tied npm or anything, right? |
@Noeldeveloper I'm not sure what's the relation between dayjs and Angular. Sure, you can use dayjs in Angular apps, but the dependency is that way: Angular app depends on dayjs, not the other way around. @suspectpart What @Noeldeveloper was referring to, I think, is Angular libraries, which are unrelated here.
// app.ts
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime);
// another-file.ts
/// <reference path="./node_modules/dayjs/plugin/relativeTime.d.ts" />
import dayjs from 'dayjs';
dayjs.fromNow(); // no error Needless to say, this is less than idle.
// app.ts
import dayjs from 'dayjs';
import relativeTime from '@dayjs/relative-time';
dayjs.extend(relativeTime);
// another-file.ts
/// <reference types="@dayjs/relative-time" />
import dayjs from 'dayjs';
dayjs.fromNow(); // no error
// app.ts
No need for anything special here
// another-file.ts
import dayjs from 'dayjs/plugin/relativeTime';
dayjs.fromNow(); // no error; The issue here is that you can't chain things along, since each plugin exposes their own instance of dayjs + the plugin
// dayjs-config.ts
import * as _dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
export const dayjs = _dayjs.extend(relativeTime); // returns dayjs.Dayjs & dayjs.relativeTime.Dayjs type
// another-file.ts
import { dayjs } from './dayjs-config'
dayjs.fromNow() // no error Personally, I think either 4 or 5 are the best ways to handle this, but any of the above would work, and there are probably other ways to handle plugins with types I didn't think of. |
As mentioned on #456, an update for the plugins folder structure might be necessary. @iamkun pointed well that since we'll update the plugins folder structure, we could start working on this issue. I'm not an expert on TS, but as far as I know, the 5th option that @bengry mentioned seems a nice way to go. Any other thoughts? |
Just tested on vscode, if folder structure like this:
and import the plugin same as before, the typescript definition works well as expected. And if this is ok, we might not have to change plugins folder structure to fix this issue. Any thoughts? |
@iamkun I haven't tested this out, but I'd guess that the compiler would always assume the methods of the plugins are there - meaning that invoking: |
@bengry Tested again, you are right. |
@iamkun I think options #3 or #4 are good alternatives. They can be made with no breaking changes (for #3 - |
@iamkun I think they're actually a bit better, since there's no "magic" behind the scenes. Similar to the route that rxjs took - moving from prototype changes to explicit calls: // RxJS < 6
import { Observable } from 'rxjs';
const x$: Observable<number>;
x$.filter(...).map(...)
// RxJS >= 6
import { Observable } from 'rxjs';
import { filter, map } from 'rxjs/operators';
const x$: Observable<number>;
x$.pipe(
filter(...),
map(...),
) Anyway, I don't think there's a way to keep type safety together with prototype changes - since [I think] you don't know at compile-time if the call that modified the prototype has been made or not. Maybe @DanielRosenwasser can offer some help or insights here. What you suggested in #364 (comment) is probably the only route without changing the way plugins work, but at the same time - it makes forks like Day.js Extended pop up, which offer more functionality, for sure - but one that couldn't be added with ample type-safety without making modifications to how plugins work. @prantlf might offer some additional insights here. Ideally this repo would contain "just" a handful of plugins that you can import and use with the original |
@iamkun I agree with @bengry and think option 3 is a really good solution. It is basically making the The type definition for the
|
@t49tran I agreed that option 3 is a nice solution. But it seems a breaking change, user has to modify their current code to make plugins work properly. Besides, what if we want to use multiple plugins at the same time? (Or may be some of the plugin is relay on some other plugin.) |
@iamkun option 3 isn't necessarily a breaking change (though it can be). Consider that import dayjs from 'daysjs';
import AdvancedFormat from 'dayjs/plugin/advancedFormat';
import RelativeTime from 'dayjs/plugin/relativeTime';
const dayJsWithRelativeTimeAndAdvancedFormat = dayjs.extend(AdvancedFormat).extend(RelativeTime); // type is inferred as dayjs.DayJS & AdvancedFormat & RelativeTime;
// or if we change the `extend` to be defined as:
export class DayJS {
...
extend<T extends PluginFunc>(plugin: T): DayJS & T;
extend<T extends PluginFunc, U extends PluginFunc>(plugin1: T, plugin2: U): DayJS & T & U;
extend<T extends PluginFunc, U extends PluginFunc, V extends PluginFunc>(plugin1: T, plugin2: U, plugin3: V): DayJS & T & U & V;
}
you can call it as:
const dayJsWithRelativeTimeAndAdvancedFormat = dayjs.extend(AdvancedFormat, RelativeTime); // type is inferred as dayjs.DayJS & AdvancedFormat & RelativeTime; The latter is similar to how Do note that order is important here, so if |
@bengry I think I know what you mean, and seems a nice choice. However, I don't know much about Typescript, and don't really know how to implement this to test if it works. Can you make a small PR or repo with the modified Cheers. |
@iamkun I'll try to get around to doing something like this this week and either send a PR or make a sample repo with a fork showcasing this, and we can take it from there. Update: See #463 for reference. @iamkun I can also help out in migrating dayjs to be written in TypeScript, if that's something that you'd like to pursue. It makes more sense, in some cases, than keeping a separate type definitions file(s), that's not guaranteed to be in sync, or work like the source code (see the example in the PR in regards to |
I don't know much about this library, but if plugins just add more methods to an existing type, then you probably want a module augmentation. How they're loaded is another story. I don't think I can keep up and help unfortunately, but StackOverflow might be able to give good advice. |
I've been adding my own module augmentations to use plugins for now, but I can't seem to find a way to make a proper one for the |
I updated PR #418, another plugin type defs with module augmentation. I think it is reasonable to stick on It is same as Vue's plugin architecture, and official firebase plugin uses module augmentation to extend type declaration of Vue itself. Looks widely accepted way :) |
@ypresto Not really possible to correctly define typings for plugins such as |
@saboya A format is just a string value so there is nothing to do in TypeScript layer. TypeScript only constrains the type, not the content, of a value, except for enum-like values. |
That's not what I meant. I meant the plugin extends the default constructor adding a new signature, but it's impossible to define it with a module augmentation, so the method signature without the plugin is basically incorrect. |
@saboya Ah, I see. It's a missing signature in index.d.ts .
Line 36 in 35e268a
Also note that current plugin function ( |
Hi all, just released v1.8.8 with plugin definition (module augmentation). |
Everything looks fine. |
Why don't we use static functions taking the dayjs object as first argument, If one still prefer to use plugin style to inject methods over composing functions (e.g. for chainable api), rxjs is a good example with plugin and typescript to take reference. |
@beenotung sounds interesting, some more detail or demo code, please? |
Is there a concluded advised way to use dayjs with plugins in a typesafe way? |
@iamkun Thanks for your reply. I already checked that page, but that still leaves me with a compiler error whenever I want to use a method of the Dayjs interface which is added on the prototype by a plugin. Do you have a working example? |
@JoepKockelkorn A reproduction repo, please? |
@iamkun Hmm seems to work when I set it up in an isolated codesandbox. My setup is a bit different though. I'm using nx from nrwl to build a monorepo and I'm extending dayjs in the /// <reference path="../../../../../node_modules/dayjs/plugin/isoWeek.d.ts" /> Do you need a working example setup with nx to figure out why I have to add this triple-slash directive? |
In a pure typescript project, there's no other config needed. I don't know nx, it's should be the same I think. |
In case anyone wants to make a custom plugin with global typescript support, you can do so like this: import dayjs from 'dayjs'
day.extend((_, c) => {
c.prototype.dangerouslyFormatWithoutLocale = function (date) {
return this.format(date)
}
})
// add custom plugin to dayjs' types
declare module 'dayjs' {
interface Dayjs {
dangerouslyFormatWithoutLocale(date: string): string
}
}
dayjs().dangerouslyFormatWithoutLocale('') // this works with types |
Should plugins's api methods be added in the core
d.ts
file? Or they should have their owns?If they have their own, they should be merge into one when bundling the package?
I'm trying to use this library at another library, which is written with Typescript, and trying to use plugin methods, it's driving me crazy. Any thoughts?
The text was updated successfully, but these errors were encountered: