If you read the official Deno manual, you'll notice that the author advices you to contain all dependecies into one file.
Similarly, if you're creating a library, the de-facto standard is to export all modules in a single mod.ts
file.
Somehow, this convention is suppose to make the development experiences better.
But instead, let me tell you why this convention is doing the opposite, which is making the development experience worse by increasing compile time (around 200% based on my simple benchmark).
If you want to look at benchmarks, check it out at this repository.
Imagine we have a deps.ts
:
export {A} from 'https://deno.land/x/a/mod.ts'
export {B} from 'https://deno.land/x/b/mod.ts'
And a main.ts
file:
import {A} from './deps.ts'
By running deno run main.ts
, Deno will actually also resolve dependencies of B
and typecheck thems even if main.ts
don't really need them, imagine B
also imports from its own deps.ts
, and so on and so forth.
Ultimately, most of the compile time is wasted on compiling unused imports.
Moreover, if you try to bundle it by doing deno bundle main.ts
, you'll notice that the bundle output also contain B
and its dependencies because Deno do not support tree shaking.
So how can this problem be solved? Easy, just use direct URL imports! By doing so, you will have improved compile time and improved bundle size.
// main.ts
import {A} from 'https://deno.land/x/a/mod.ts'
These are all the problems we have when we are using npm
.
You can find Ryan Dhal, the creator of Deno talking about this problem here.
Do you remember that when you tries to import a Lodash function into your code and suddenly the bundle size got so huge?
So how the Lodash community solves this? They do it by publishing a package for each function.
Therefore if we want to avoid:
- unnecessary long compile time
- unnecessary huge bundle size
- Deno packages collapsing into a black hole.
The whole Deno community needs to stop placing all dependencies/export into one deps.ts
/mod.ts
file.
If we continue to use deps.ts
and mod.ts
, we will hinder the development experience of Deno developers by wasting their precious time waiting to compile unused dependencies.
Here's what I propose for managing dependancies, so instead of putting every dependencies in single deps.ts
like this
// deps.ts
export {A} from 'https://deno.land/x/a/mod.ts'
export {B} from 'https://deno.land/x/b/mod.ts'
Export each of them as separate files under the deps
folder.
// deps/a.ts
export {A} from 'https://deno.land/x/a/mod.ts'
// deps/b.ts
export {B} from 'https://deno.land/x/b/mod.ts'
Ok, without
mod.ts
how do we allow library users to discover API easily?
Simple, instead of placing all export into a singlemod.ts
, split them into separate files under themod
folder.
For example, instead of having:
// mod.ts
export {A} from './a.ts'
export {B} from './b.ts'
Do:
// mod/a.ts
export {A} from '../src/a.ts'
// mod/b.ts
export {B} from '../src/b.ts'
Last but not least, if the whole Deno community ditch this deps.ts/mod.ts practice, Deno by itself might not even need to implement tree-shaking and skip-unused-import features (which is tremendously difficult to implement).
It's like how social distancing prevented the spread of Corona virus instead of relying on vaccine.
Please if you think this article make sense, and for the greater good of Deno's future, please share it with your fellow Deno developers.
Top comments (16)
The removal of
deps.ts
andmod.ts
works well with a module alias file. I use module aliases for my dependencies and for folders in my projects (e.g. if I had autils
folder with a logger in it I would configure an alias to use@utils/logger.ts
)I mean, if you have a dependency, I think there’s a pretty high chance you’re gonna use it somewhere, so it’ll be compiled anyway.
So are you saying if I import A from X, I'm going to use B, C, D, E, F, G, H anyway? If that is true, why would the lodash community wanted modularize lodash packages?
No, I’m saying if you have B, C, D, E, F, G and H in there you’re gonna use them anyway.
I don't disagree with that, but you have to know that this problem applies to every level of dependencies, so even if you're gonna use everything module, you cannot make sure that the author of the library that you're importing are going to utilize every functions that imported from the
mod.ts
of another library.Eventually this creates a snowball effect, little by little, all those unused imports will take up a significant portion if not most of the compile time.
I’m talking about modules, not functions. You are right that you can’t be sure that a dependency will use every function of all its dependency, but in most cases it won’t matter, as all the functions will be compiled no matter how many you use.
At first I wasn't convinced
But the idea of separating the dependencies while keeping the possibility to update them without doing it for each file is well thought-out
This pattern should be proposed to more people, in the end it doesn't bring any disadvantages.
EDIT: this is very time consuming, not recommended.
Anyone else wondering what happened between the end of this comment and the edit. What did this person see.
Hi @oganexon , thanks for the reply. But I have a question for you, isn't longer compile time time-consuming too?
I meant to divide each dep in it's own file.
My module only does one thing and the cli has its own dep in the file so I already divided the deps
So overall I support your idea to provide a
mod
folder but not adeps
folderTrue, I might have taken that way too far lol
Sometimes it might make sense. If your project is a bunch of functions that may or may not be used, this totally makes sense. But if you're project is something like Oak, then a fair amount of it will be wanted, and maybe other parts will be added on, and those dependencies can be isolated if needed.
Or if your project is an enterprise app, like mine, then you most likely want most, if not all, of the dependencies.
Compile time isn't a big deal (to me). It's a one time thing for each new launch. And such a decision imposes architecture which may or may not be what I'm looking for.
The compromise I've made in my public Deno libraries is to allow each major feature to be independently loaded, but also have a mod.ts that loads them all if someone so chooses. I tend to choose it.
It's might be true for backend, but for frontend development, a fast feedback loop is very crucial, especially most of frontend developer's time is spent on styling and layout rather than logic.
You mention some compile time benchmarks you ran. I'd be interested if you'd add some examples to your article to highlight your point.
Will the example provided in this repository helps? github.com/wongjiahau/deno-mod-ben...
Yup! Thanks