548 karma · joined September 22, 2016
Any engineer who thinks that its React that is causing the slow renders and replacing it with X is deluded. Yes, there are ways to make slow React web apps. But there are also ways to make fast React web apps. It just requires effort and a little bit of foresight.
The tsc transpilation to lower ES versions is actually really useful when using not-so-recent Node versions. Not to mention this severely restricts TypeScript syntax to "just types" which isn't too bad but it means you now have to worry about yet another thing.
Then there's the ESM & CJS mess. You almost always want to export in both formats which will change a lot of the syntax, so this quickly falls apart for anything serious.
Just use esbuild if you want speed.
After around 2 years, we are back on HN with a massive update for Notesnook. But before that, here's a short introduction:
Notesnook is an end-to-end encrypted, 100% open source [0] note taking app that prioritizes user privacy.
v3 brings a lot of really radical changes, such as:
- Migration to SQLite
- At rest encryption
- Note linking
- Nested notebooks
- App lock on all platforms
You can read more about all these here: https://blog.notesnook.com/introducing-notesnook-v3/
Our goal with Notesnook has always been to make privacy simpler to adopt for normal users who don't have time to think about privacy. We do that by making privacy that "default" everywhere in the app. We recently crossed 100K registered users with over 4 million notes created on our platform.
If anyone has any questions I'd be happy to answer.
After v3, our primary focus will be on self hosting and getting an audit done.
There are encrypted backups. Actually, backups are encrypted by default.
Notesnook also works offline by default so there's no need for a special mode. It's also, obviously, local-first since everything gets encrypted on your device. We don't have a lot of options there.
What "might be better" in the editor?
Oh and to clear a few confusions:
1. Notesnook is 100% open source. That includes the server, client apps, and everything else. It's not partially open source.
2. Zero knowledge does not mean Zero Knowledge Proof but Zero Knowledge as in we, the company and people behind Notesnook, have no knowledge regarding what you have in your notes. I see that this might be more accurately called "no knowledge".
Just a few weeks back a user came to us after failing to migrate GBs of their data off of Evernote. This, of course, included attachments. They had successfully imported & synced 80K items, but when they tried to login on their iPhone, the sync was really, really slow. They had to wait 5 hours just to get the count up to 20K items. And that's when the app crashed resetting the whole sync progress to 0.
In short, we had not considered someone syncing 80K items. To be clear, 80K is not a lot of items even for a local-first sync system, but you do have to optimize for it. The solution consisted of extensively utilizing batching & parallelization on both the backend & the users' device.
The result? Now their 80K items sync within 2 minutes.
Consumer devices are notorious for their reliability problems. Compared to a full blown server that you have 100% control over with almost insane amounts of RAM & CPU power & a lot of guarantees.
Running a migrations on a server is far, far different to running it on every users' device. The sheer scale of it is different.
> Using 4 GB per user on your backend works?
That was a comment on the average RAM on a consumer device - not the total RAM required per user.
But anyone looking to go local-first or build a local-first solution should have a clear idea of what problems can arise. As I said in the original comment: it's not all gardens and roses.
1. What to do with stale user data? What happens if a user doesn't open the app for a year? How do you handle migrations?
2. What about data corruption? What happens if the user has a network interruption during a sync? How do you handle partial states?
3. What happens when you have merge conflicts during a sync? CRDT structures are not even close to enough for this.
4. What happens when the user has millions of items? How do you handle sync and storage for that? How do you handle backups? How do you handle exports?
One would imagine that having all your data locally would make things fast and easy, but oh boy! Not everyone has a high end machine. Mobiles are really bad with memory. iOS and Android have insane level of restrictions on how much memory an app can consume, and for good reason because most consumer mobile phones have 4-6 gbs of RAM.
All these problems do not exist in a non-local-first situation (but other problems do). Things are actually simpler in a server-first environment because all the heavy lifting is done by you instead of the user.
I know you can probably get more performance out of Rust or Go but I don't think rq/s benchmarks really hold up in reality. If you are running a startup, you'd probably see 1 to 2 rq/s unless you become viral. .NET Core can easily handle that and more.
I think whenever we, as programmers, try to pin down a certain principle, it bites us. Hard. DRY was cool as an observation but when it got turned into a law we saw the spaghetti code.
Duplication, on the other hand, is detested almost as much as the goto statement. Let me tell you, it's not that bad. Duplicate code makes everything more flexible. It helps you to NOT bend over backwards in order to change a line of code. It allows you to NOT touch anyone else's code.
So many good things. Of course, I agree with the author's summary of the bad things that can happen with duplicated code. But there's a litmus test for that:
If you have to make changes in multiple blocks of duplicated code in order to change the behavior of something, there's a problem. DRY out the code so you only have to touch 1 place.
If, however, 2 blocks of code LOOK similar but aren't actually the same, and changing one block doesn't make the other block outdated and stale, you are good to go.
Judge and decide. It's just 2 approaches that when taken to an extreme can cause a lot of pain, but if used with common sense, nothing is simpler.
Any serious note taker eventually realizes that the way their brain works doesn't really fit into any PARA, ARPA or any other standarized system.
Note taking is a messy business. It's not supposed to be organized because you take notes to become more organized, not to organize more notes.
Just take your notes.
Organization will automatically happen. Humans are very bad at doing things randomly. There's always an order, a sequence, a method to the way we do things. Who's to say your method is better than mine?
The most an AI should do is find relevant notes on a topic I search. That's it. I don't need its robotic voice summarizing my grotesque ideas in a painless, unpassionate way. The last thing I need is more of my ideas seeping into more of my computers in hopes of somehow making me smarter.
I talked to them about this and they said they'd work on it etc. etc. Not sure if anything has changed in that regard or not.
But I get it. If X tool saves time, why not pay for it? I wouldn't call that frugal though because you are still spending where you can save.
The good news, though, is that it's really simple to mix and match Tiptap and Remirror since both of them use Prosemirror internally.
We have had quite a long and rough ride in search of a stable rich text editor. We began with Quill.js then migrated to TinyMCE and then finally settled on Prosemirror. Unfortunately, contenteditable is still absolutely horrible on web browsers, especially mobile ones.
Tiptap is a good choice if you are looking for a framework agnostic and thin abstraction over Prosemirror. However, if you are primarily working with React you should go with Remirror[1]. Tiptap's APIs are heavily inspired by Remirror (almost a duplicate in some places). Remirror takes the edge on the maturity and stability of the API and extensions. The sheer number of utilities offered by them to simplify Prosemirror's APIs is astounding. And trust me, you will need a lot of those utilities eventually. Prosemirror is not an easy API - really, really well designed but not easy.
In the end, though, its Prosemirror that's doing all the heavy lifting. And no matter how many abstractions you put on it, you will have to get really, really close in with Prosemirror's internals. Tiptap or Remirror do not make that any easier or harder aside from the initial bootstrapping.
---
Edit: almost forgot this but the only real selling point of Tiptap is their extensive documentation.
$ hyperfine -w1 "NODE_ENV=production node ./fdir.mjs" "./bin/bfs /
home/thecodrr/ -false"
Benchmark 1: NODE_ENV=production node ./fdir.mjs
Time (mean ± σ): 965.5 ms ± 53.0 ms [User: 703.0 ms, System: 1220.5 ms]
Range (min … max): 858.4 ms … 1041.3 ms 10 runs
Benchmark 2: ./bin/bfs /home/thecodrr/ -false
Time (mean ± σ): 1.530 s ± 0.127 s [User: 0.341 s, System: 2.282 s]
Range (min … max): 1.401 s … 1.808 s 10 runs
Summary
'NODE_ENV=production node ./fdir.mjs' ran
1.58 ± 0.16 times faster than './bin/bfs /home/thecodrr/ -false'
$ cat fdir.mjs
#!/usr/bin/env node
import { fdir } from "fdir";
console.log(await new fdir().onlyCounts().crawl("/home/thecodrr").withPromise());
For some reason, reducing the UV_THREADPOOL_SIZE to 2 gives the best result on my machine (I have heard the opposite in case of macOS): $ hyperfine -w1 "UV_THREADPOOL_SIZE=2 NODE_ENV=production node ./fdir.mjs" "NODE_ENV=production node ./fdir.mjs" "./bin/bfs /home/thecodrr/ -false"
Benchmark 1: UV_THREADPOOL_SIZE=2 NODE_ENV=production node ./fdir.mjs
Time (mean ± σ): 355.8 ms ± 16.1 ms [User: 479.4 ms, System: 356.0 ms]
Range (min … max): 328.3 ms … 387.5 ms 10 runs
Benchmark 2: NODE_ENV=production node ./fdir.mjs
Time (mean ± σ): 935.4 ms ± 52.7 ms [User: 695.8 ms, System: 1176.5 ms]
Range (min … max): 850.6 ms … 1031.9 ms 10 runs
Benchmark 3: ./bin/bfs /home/thecodrr/ -false
Time (mean ± σ): 1.534 s ± 0.104 s [User: 0.353 s, System: 2.307 s]
Range (min … max): 1.428 s … 1.773 s 10 runs
Summary
'UV_THREADPOOL_SIZE=2 NODE_ENV=production node ./fdir.mjs' ran
2.63 ± 0.19 times faster than 'NODE_ENV=production node ./fdir.mjs'
4.31 ± 0.35 times faster than './bin/bfs /home/thecodrr/ -false'
Another factor to take into account is that I ran all this on a WSL instance which may or may not affect the performance. However, since both programs are running on WSL, the results should be accurate.console.log(await new fdir().onlyCounts().crawl("/home/tavianator/code/android").withPromise());
Disclaimer: I am the developer of fdir.