-
-
Notifications
You must be signed in to change notification settings - Fork 17
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
Refactor into TypeScript (built via tsdx
for now)
#8
Conversation
- because that makes a bit more sense / is more intuitive
- move source files into src/ - whitelist these in package.json - rename persist.js to index.js to follow standards
- will need to export more from here with TS, like an interface, so won't be able to `import * as` in the future - also give it a default export
- for now so that pack and logs are ignored, but also node_modules, etc in the future
- add scripts and devDeps for tsdx - will add typings, module, files, etc when code is converted to TS - add tsdx .gitignore stuff - add partial tsdx tsconfig - react / jsx support is unnecessary - a bunch of redudant things removed that were already covered in strict mode - removed strict mode for now -- will gradually add that in (deps): add tsdx and TS to devDeps (no other peerDeps/devDeps for now) - no tests, husky, prettier, etc yet (though I lint with standard globally)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some typings and tsconfig
questions that I'm not sure of as a TS newbie and not knowing how to test that the types are accurate. Also potentially more that could be changed in tsconfig
but idk.
The IAsyncStorage
rename should probably be done to avoid future confusion. Otherwise looks fine, the total diff isn't that big
"target": "es5", | ||
"module": "esnext", | ||
"lib": ["dom", "esnext"], | ||
"importHelpers": true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't add tslib
to my devDeps
but I do use ...rest
and ...spread
in some places... not sure if this can be removed or if I should add tslib
or if this is fine 🤷♂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's an _extends
helper added to the compiled code as well as code around rest/spread. I also checked and saw that tslib
was in my node_modules
already and it's actually a dependency of tsdx
, so maybe that part of the default package.json
it creates is outdated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ehhh confused again because per the tslib
docs, the output code should import tslib
if this is true.... why isn't it??
Ah so to handle differences in Union Types, TS uses type guards for similar to effect to pattern-matching. Though it looks like these will have runtime performance impact as they require a runtime check (might be possible to hide some redundant ones in prod builds, though they might be necessary for safety) |
- add a few interfaces for the types - add a few function signatures - no .js extension import, and no .ts extension either, TS only supports extensionless imports apparently(??) - no errors or warnings in current (non-strict) config - need to specify some `any`s a bit more however (deps): add mobx + MST to devDeps so that the types work
- typings now output for TS devs, who overlap a lot with MobX devs! - ESM build now hidden behind `module`, while CJS dev and prod builds are under `main` - CJS appears to still be necessary per #3, but not necessarily for browser support, for _test_ support it's also necessary - unless one is transpiling node_modules or using the `esm` package, Node's support for ESM is still behind a flag / not in LTS - also it may not support `module` field per docs? might need .mjs?
- not many changes almost surprisingly - guess because I already resolved most implicit any warnings before
- IStateTreeNode is the type passed to onSnapshot and applySnapshot - snapshot from onSnapshot _should_ always have string keys - Storage.getItem should always return a string or object - add a typeguard here to properly ensure behavior similar to pattern-matching - my first typeguard woo! - be more general than just checking `jsonify` as we can't have a string snapshot anyway (though checking `jsonify` might be more efficient than `isString`)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other than the tslib
stuff, this looks good to go to me now. Can always fix typings later if needed and can probably do most typings changes in patch releases too, so should be low pressure.
I also manually checked the compiled output to make sure it's equivalent so no immediate need for regression testing (though tests would still be good to have ofc #4)
- add a `nodeNoop` option that is `true` by default - generally folks do not want to hydrate their store during SSR, so no-op by default and require explicit opt-in to Node hydration - see #8 for discussion - also throw an Error if localStorage isn't supported and no alternative storage provider has been given (docs): add `nodeNoop` option and section on Node and SSR usage to give more details and note possible gotchas
woops ^that commit reference is a mistake, referenced the wrong issue by number |
@rafamel since it sounded like you were using TS in your project, would you like to review this? Would be good to have a second pair of eyes :) |
Great job!! Everything looks good. I also gave it a run within a ts project project and there are no typing issues popping up either. The only thing is, it looks like you're supporting synchronous functions for storage via |
Sorry about that, I assumed a couple things and only took a sideways read at the code. I initially thought that |
Awesome, thanks for the quick check and test inside a TS project @rafamel! Per my initial comment/description, there's a decent bit of work to type
Should be made more specific at some point but the work required to handle |
Sounds good, though just as further info, this would work w/ localforage: interface IStorage {
clear(): Promise<void>;
getItem(key: string): Promise<string>;
removeItem(key: string): Promise<void>;
setItem(key: string, value: string): Promise<void | string>;
} As callbacks are optional, interface IStorage {
clear(): Promise<void> | void;
getItem(key: string): Promise<string | void | null> | string | void | null;
removeItem(key: string): Promise<void> | void;
setItem(key: string, value: string): Promise<void | string> | void | string;
} Then we'd only need to make if (typeof window.localStorage !== 'undefined' && (!storage || storage === window.localStorage)) {
storage = AsyncLocalStorage
} We'd have: if (!storage && typeof window.localStorage === 'undefined') {
throw Error(`...error`);
}
const asyncStorage = makeAsyncStorage(storage || window.localStorage); The only thing left would be objects instead of string + jsonify. For this I'd suggest making |
Thanks for the suggestions @rafamel! 😄
For reference, how did you test this to make sure? Just seeing if the intersection compiles? Because I'd like to add a test for that eventually and this being effectively my first usage of TS, still got some things to learn.
Yep that adds a decent bit of complexity as I was thinking. I thought about having some AsyncStorage class that would have the same effect as your In any case, I'm going to merge this as is for now, and will figure out the storage interfaces later. Kinda tired of working on it for now 😅 Now I'm still wondering if it's best practice to have the types as a minor version bump or a patch bump... It adds new exported goodies like types and CJS (minor) but has not made any actual changes to the API surface, which remains the same (patch) - so could go both ways imo. Probably doesn't matter much anyway 🤷♂ |
Regarding tests, they can be done by writing a normal ts file using your exported functions and running tsconfig.test.json: {
"$schema": "http://json.schemastore.org/tsconfig",
"extends": "./tsconfig.json",
"include": ["src/**/*", "test/**/*"],
"compilerOptions": {
"baseUrl": "./",
"noEmit": true
}
} Then just run: As for minor vs. patch release, I'd personally do minor as I'd count this as a feat, though not like it'd matter too much being still on 0.x :P Hope it helps! |
Sorry, by tests I meant specifically how did you make sure " |
Yes, that's how. Run |
This was the part I was curious about, thanks! I was thinking there might be some other way to handle checking whether a type conforms to another in TS |
This PR was released in v0.1.0 |
resolves #3 (adds CJS builds via
tsdx
since it now requires a build process) and resolves #7 (written in TS and has typings output)Still need to test this out and still don't have tests yet 😕 . The typings need to be tested for accuracy (is there a good way of testing this? typed tests?) and the output for equivalence with the previous non-TS version (i.e. a regression test). Could create a
@types
tag on NPM for folks to test it out in their apps (and perhaps to act as an alpha tag for types changes in the future?).Some further improvements to the types could potentially be made, but not without a decent amount more work or testing:
store
can be ofIStateTreeNode
from MST (same type asonSnapshot
's snapshot arg) I'm fairly sure. Or perhaps some other MST type. Or maybe evenobject
would be more specific and still accurateIAsyncStorage
can be made more generic (c.f. https://github.com/localForage/localForage/blob/master/typings/localforage.d.ts or perhaps even more generic?) and then exported and used as thestorage
type (or one of its types, see below) and re-exported outside of the package as its typings for others to use.storage
needs crazier typing or no default (require importing the adaptor like withredux-persist
) becauseIAsyncStorage | Storage
doesn't work. It needs to supportwindow.localStorage
as an argument, otherwise onlyIAsyncStorage
type. And all will behave likeIAsyncStorage
after the if check. Pretty sure there's some TS metaprogramming like that which MST does to make this possible, but as a complete TS newbie, I have no clue and am not sure where to look in MST source code.