K
Kysely•8mo ago
oof2win2

building kysely makes the package size very large

hi. i'd be more than happy to figure this out because i like kysely a lot more than drizzle. the thing was that the package size was so much larger with drizzle. i made a repro here. when running bun build --minify --outfile, i get kysely to be 340.40kB whilst drizzle is 55.35kB, meanwhile the functional code besides the database is identical
40 Replies
oof2win2
oof2win2OP•8mo ago
the demo is essentially just a post and comments api with very basic functionality
Igal
Igal•8mo ago
Hey 👋 Which bun version?
oof2win2
oof2win2OP•8mo ago
1.0.25 same thing happens on 1.1.2 though
Igal
Igal•8mo ago
I see stuff in the output file that should'nt be there. e.g. PostgresDriver implementation, while SQLiteDialect is in use
oof2win2
oof2win2OP•8mo ago
the same issue occurs when using esbuild, so it's not just bun. i tried adding a sideEffects: false field to kysely's package json, but it still seems to import everything for some reason Figuring out some dependencies, the CJS was included because of kysely-d1 requiring it. reduced the import of kysely by ~200kB by changing that to esm and changing it to use type imports only in some cases
koskimas
koskimas•8mo ago
There's a separate package.json file in esm and cjs folders inside dist. What happens if you add sideEffects: false there?
Igal (mobile)
Igal (mobile)•8mo ago
Also, #props and bundlers don't live together happily shrunk the bundle by like 20kb by changing #props to _props Also immediately invoked freeze(...) calls. we might want to hint that they're pure it still bothers me, that all dialects are bundled even if you only imported one
koskimas
koskimas•8mo ago
We could move to separate imports, but how much would that really save? Vast majority of the code is in the core that always gets imported. The whole thing is like a hairball. If you pull the Kysely class, pretty much everything comes with it.
Igal (mobile)
Igal (mobile)•8mo ago
I think it gets bundled due to side effects, and other stuff
koskimas
koskimas•8mo ago
It'd be cleaner to have separate imports for sure, but I don't think the bundle size is affected that much The hairball is due to Kysely class actually depending on pretty much everything. There's no way to tree-shake most of it Well if the tree shaker is smart enough then it's definitely possible, but if it can't tree-shake the current "import everything" index file, then it can't do it even if we split that to separate imports right?
due to side effects
What side effects? We don't have many of those
Igal (mobile)
Igal (mobile)•8mo ago
we can add pure hints on immediately invoked functions
oof2win2
oof2win2OP•8mo ago
yeah i think thats the main issue
koskimas
koskimas•8mo ago
No it's not Just try to manually remove the other dialects to see how much it affects
oof2win2
oof2win2OP•8mo ago
okay ill fiddle with it but no matter what i dunno why dialects would take up 100kB but not sure, might be mistaken
koskimas
koskimas•8mo ago
You can see all the code the dialects have in the repo. It's a couple of tiny classes per dialect. https://github.com/kysely-org/kysely/tree/master/src/dialect All code in kysely (not minified) 1.6MB the whole dialects folder 148KB All dialects combined only explains 10% of Kysely's size.
oof2win2
oof2win2OP•8mo ago
hmm whats so big? is it the various query types?
koskimas
koskimas•8mo ago
A lot of documentation is one thing
oof2win2
oof2win2OP•8mo ago
like alter table etc
koskimas
koskimas•8mo ago
The whole schema module is 132KB
oof2win2
oof2win2OP•8mo ago
damn
koskimas
koskimas•8mo ago
The types are also super complex and take up some space. But the most wasteful thing is probably the operation nodes lemme check
oof2win2
oof2win2OP•8mo ago
i mean complex types get eradicated in any bundling process
Unknown User
Unknown User•8mo ago
Message Not Public
Sign In & Join Server To View
oof2win2
oof2win2OP•8mo ago
since it then becomes only js
koskimas
koskimas•8mo ago
Yep, 456k just the operation nodes
oof2win2
oof2win2OP•8mo ago
🤯
koskimas
koskimas•8mo ago
i mean complex types get eradicated in any bundling process
That's true. And most of the operation node files is just types
oof2win2
oof2win2OP•8mo ago
hmm ill check it all out when im back seems that the operation nodes files aren't just types, looks much more like an AST declaration to me (might be confused here) i'd guess its still only for TS to have valid typings though, which is not something should be necessary when actually running kysely
Igal (mobile)
Igal (mobile)•8mo ago
tsup is for libraries, it excludes node_modules.
Unknown User
Unknown User•8mo ago
Message Not Public
Sign In & Join Server To View
oof2win2
oof2win2OP•8mo ago
oh that would probably optimize the hell out of this
Igal (mobile)
Igal (mobile)•8mo ago
maybe a typescript transformer?
oof2win2
oof2win2OP•8mo ago
cant you already compile queries with kysely i mean yeah adds a build tool but it would work main issue i think of is conditional queries, need to have separate compiled queries for each branch wym? i was thinking pretty much along the lines of the existing query compilation. essentially you would have a query as below within your code
// within some function or whatever
const person = await db
.selectFrom('person')
.select('first_name')
.where('id', '=', id)
.executeTakeFirst()
console.log(person.first_name)
// within some function or whatever
const person = await db
.selectFrom('person')
.select('first_name')
.where('id', '=', id)
.executeTakeFirst()
console.log(person.first_name)
then using some TS magic, the build step would infer that db is an instance of Kysely, compile the query (as is in the docs), and then some essentially "basic" instance of Kysely could be used to execute the compiled query, so essentially after it would look like this
// same function, just after build step
const person_query = { sql: 'select * from person where id = $1', parameters: [1], query: { ... } }
const person = await db
.executeQuery(person_query)
console.log(person.first_name)
// same function, just after build step
const person_query = { sql: 'select * from person where id = $1', parameters: [1], query: { ... } }
const person = await db
.executeQuery(person_query)
console.log(person.first_name)
are we talking about the same thing? because if we are then that would actually be insane to have
koskimas
koskimas•8mo ago
You should really profile things before you build a plugin like that. You'll save the CPU time of building the query, which is nanoseconds. Microseconds at best The query builder is a lot faster than running JSON.stringify
oof2win2
oof2win2OP•8mo ago
not the time of building the query, but you don't need to import any of the operation nodes since those aren't relevant at that point - assuming my previous assumption about them being only for TS type inference was correct
koskimas
koskimas•8mo ago
No the nodes are used internally to represent the query. They are the very thing that makes kysely work internally
oof2win2
oof2win2OP•8mo ago
ah my bad thought a compiled query could be executed simply from the sql and parameters
koskimas
koskimas•8mo ago
And how would you transform this query:
let query = db
.selectFrom('person')
.select('first_name')
.where('id', '=', id)

if (foo) {
query = query.where('first_name', '=', foo)
}
let query = db
.selectFrom('person')
.select('first_name')
.where('id', '=', id)

if (foo) {
query = query.where('first_name', '=', foo)
}
oof2win2
oof2win2OP•8mo ago
yeah, that relates to my previous point here you would need to have two queries, execute the basic one if !foo and the second one (first name check) if foo since they expand factorially for every if statement it would not be fun to debug with say 5 statements, since thats 5! = 120 queries
Unknown User
Unknown User•8mo ago
Message Not Public
Sign In & Join Server To View
Want results from more Discord servers?
Add your server