I’ve recently started a new personal project and I’m using Deno. I’m really happy with it as my main complaint about typescript currently is the transpile process slows down development when you’re used to all the bells and whistles of modern JavaScript. Things like hot reloading take 3x as long.
I started a project with a Deno backend and Svelte front end last week and it was painful trying to share types between the projects. Deno not allowing imports without file extensions '.ts' and the Typescript tsserver not allowing imports with a '.ts' extension is really annoying, and neither group seems to want to budge. I ended up having to create a shared types folder and a script that copies the files to each project stripping out file extensions for non-Deno projects.
Does anyone else have a better solution shared Typescript code?
> I ended up having to create a shared types folder and a script that copies the files to each project stripping out file extensions for non-Deno projects.
Still a hack, but perhaps symlinks could be a little simpler?
Whats the purpose of sharing types? Define entities on each and leave behavior as a domain consideration. Clients should be disposable and decoupled. If you want to render html on the server that's fine but svelte isn't the best choice for that.
Server sends data as JSON to the client. Client parses JSON. Both now have the exact same data shape. Shared TS types let you use the same representation for the same data no matter where.
At the expense of coupling which is not a trivial concern. Regardless, the browser is the only platform capable of using those types (even so the types aren't shared but generated). You don't use those types in Swift or Java or Kotlin. You don't use those types in a python client library.
In my experience this coupling cannot be avoided. At a bare minimum the client has to know the communication protocol of the server. This means you typically need to define this protocol in both the server and client. If you could define it once and have both the client and server pull from the single source, it's a win.
Why does adding types result in any more coupling than what exists already?
If your client and server both treat field "temperature" as a string for protocol version 1, and then the client upgrades to protocol version 2 and temperature is now a float, the server has to be modified anyway, type system or no type system, because otherwise it'll break.
If anything, the type system helps to expose the fact that the client and server now disagree about the type of a field, which is helpful.
> Why does adding types result in any more coupling than what exists already?
Because the client and server now share a code base. The client requires the server to run.
> If your client and server both treat field "temperature" as a string for protocol version 1, and then the client upgrades to protocol version 2 and temperature is now a float, the server has to be modified anyway, type system or no type system, because otherwise it'll break.
If the data interchange format changes then yes both the client and server need to change. But if either the client or the server wants to coerce temperature to a different type they are free to do so. As long as the conform to the interchange format the internal representation of the data is irrelevant to third-parties.
In a world where types are shared, updates to the server necessitate updates to the client even if the client doesn't want to change (and vice-versa).
> If anything, the type system helps to expose the fact that the client and server now disagree about the type of a field, which is helpful.
The coupling should receive the credit not the type system. Regardless this undoubtedly true. Two isolated pieces of code don't know what the other is doing. You need to test and you need to write design docs.
> Because the client and server now share a code base.
This isn't true. Types aren't "a code base", they're an interface - without which it is impossible for a client and server to talk with each other anyway.
> The client requires the server to run.
Not relevant. Nothing about a typed interface prevents the client from running without having a running server - it won't be very useful without something to exchange data with, but you can still run it, unless you've coded otherwise.
> But if either the client or the server wants to coerce temperature to a different type they are free to do so.
No, type coercions are bad engineering. They're brittle and only work in a very tiny number of situations - nothing approaching the general case.
> As long as the conform to the interchange format the internal representation of the data is irrelevant to third-parties. Type coercions at the interface level are a hack that is not acceptable for anything beyond hobbyist work.
An interchange format is a set of types. Types are not internal representation - they're a mathematical concept used in type-checking. Types happen to be used in the process of generating an internal representation of data, but that's not what they are. You're falsely equating the two, and your point is invalid without that equivalence.
> In a world where types are shared, updates to the server necessitate updates to the client even if the client doesn't want to change (and vice-versa).
This is false. If the server is changed, the client only needs to change if the interface between the two also changes. If the server makes a change to its internal data storage format, then that has nothing to do with the client, and so the interface stays the same, and so the shared type declarations/schema stay the same.
If the interface changes, then you already have to update both client and server, so the tiny overhead of maintaining an explicit type schema dwarfed by the amount of effort you spent changing the rest of the code anyway, and more than made up for by the compile-time detection of warnings when you change the interface code for the server, compile, and then get an error that the client no longer compiles.
> The coupling should receive the credit not the type system.
A type system will detect type errors at compile-time. A tightly-coupled system with type coercion and no types will detect errors at run-time, if at all. It's obviously better to detect errors at compile-time. Therefore, even if coupling is correlated with the usefulness of a type system, type systems are still obviously beneficial to include.
Regardless, it doesn't make any sense to talk about "credit" in this case. I think that you misunderstand the purpose (and utility) of type systems, but I'm not sure what to suggest to you so that you can understand.
> I think that you misunderstand the purpose (and utility) of type systems, but I'm not sure what to suggest to you so that you can understand.
I enjoy writing code in typed languages. I am in full agreement that they have purpose and utility.
> No, type coercions are bad engineering.
The opposite is true. Your job as a software engineer is to coerce types. I don't mean calling `float64(unsafeString)`. That's certainly type coercion but, as you said, its brittle and probably not the best design.
Consider a database. It stores its data on disk as something (ignore implementation). It loads that data into memory where it is coerced to a type (probably in the C-language). Its filtered, grouped, whatever else. Serialized to something and sent over the wire. Your Python server receives that something and coerces it to a Python type. That Python type is serialized to something and sent over the wire. Your Javascript front-end loads the data and represents it as a Javascript type.
Nothing about this process is bad or ugly or brittle. It would be infinitely worse to maintain the something type across all these different boundaries. Imagine pickling types whenever we want to save something to disk or share it over the network. Imagine being forced to write application code in the same language as your database!
> Type coercions at the interface level are a hack that is not acceptable for anything beyond hobbyist work.
Representing a value in the database as an enum and as its string name in the interchange format is not hobbyist work. I'm certain if you have worked professionally you've used enums and not marshaled their underlying value.
> If the server is changed, the client only needs to change if the interface between the two also changes.
You are advocating for type coercion. The server and client store internal representations and must marshal an interface type to communicate. That's a form of type coercion (I should say "casting" since its explicit but I've mistakenly used coercion so far so we'll roll with it).
---
I think you're too tunnel visioned on the types argument. Types are great. I love them. But coupling your database to your server to your client just to share types between the three is obviously not a good idea. There are greater concerns at play. Extend the argument all the way. Separate your client from your server.
> I enjoy writing code in typed languages. I am in full agreement that they have purpose and utility.
My apologies, that was a bit uncharitable of me.
And, you're right, I didn't quite understand the meaning of "coercion" - I misinterpreted it to mean "implicit coercion". So, my intention was to say that "implicit type coercion is bad engineering" - you're correct in that in general, it's not bad, and necessary.
However, you still don't understand my point, which is that adding types don't introduce any more coupling than what actually had to exist in order to make the program run in the first place.
Let's ignore implicit type coercions, because they are bad engineering, and not relevant to the following point. In a language without implicit type coercions, programs must be well-typed to run correctly. In dynamic languages, even though the code might not contain type annotations, the above principle must still hold. If you pass a string to a function expecting an integer, then your code will break, and all that dynamic typing does is push the error from compile-time to whenever in run-time that you hit that code path.
This is equally true for a client-server interface. If your client and server do not agree on their interface, which includes types (alongside packet header, and field length and order), they will break. If you do not explicitly state what those types are in some formal spec, the types still have to line up in the program! The types are still there, even if you don't explicitly state or check them.
> It would be infinitely worse to maintain the something type across all these different boundaries.
This isn't true; you already maintain types between the boundaries formed between functions in a single program, there are far more of those than inter-program boundaries, and after you set up the tooling for checking network communication schemas, it's only a little more work than maintaining "normal" types between functions.
> Imagine pickling types whenever we want to save something to disk or share it over the network.
I don't understand - "pickling types" is redundant because the pickle format already includes types automatically. Unless you mean building a schema for the pickle and machine-checking that the loading code and the saving code match? In which case, if you're in an engineering context, I absolutely advocate for this, because a mismatch means a runtime error at best, and data loss or corruption at worst. The argument for doing so in a client-server setup is even stronger, however, because data saving and loading code are colocated with each other (making errors easier to manually spot) far more often than sending and receiving code in a client-server codebase.
> Imagine being forced to write application code in the same language as your database!
There's absolutely nothing about type schemas that forces you to use a single language - most schemas are language-independent, like JSON Schema[1] (which is a schema (including types) for JSON data that is in JSON, so you already have a parser for it, and you write it once and then check it everywhere) and ASN-1.
> coupling your database to your server to your client just to share types between the three is obviously not a good idea
The point is that there's no more coupling than what exists in the first place. Programs must be well-typed to run correctly (if there's implicit type coercion, which is bad practice, the program must still be well-typed - you just now automatically insert type-level logic for the coercions) - that means the types are there, even if you don't write them, so the coupling is also there, even if you don't want it.
If your database sends a string to your server when it was expecting an integer, then either your p...
If we reset for a moment. My point is that merging an SPA with an API server with the explicit and sole purpose of defining a type once and using it in two different domains is not a meaningful use of programming time. In my perfect world, an identical definition of the type would be defined in each domain.
You're welcome to disagree but I'm having a hard time following the conversation.
When did I ever say that you would need to "merge" your SPA with an API server? I didn't, because you don't, because, like I said above...
My point is "there's no more coupling [from using an explicit shared type schema] than what exists in the first place." for which I provided a logical argument in the comment you replied to.
This was in response to your earlier statement "At the expense of coupling which is not a trivial concern."
Do you have any additional arguments for why adding a type schema introduces additional coupling that were not covered above?
I use a JSON specification to manage interchange. I repeatedly reference the necessity of data interchange formats (e.g. a well-typed JSON schema). Interchange formats mean applications couple to a specification. Not to a code base. Which I'm sure you're well aware since apparently we've been in complete agreement the entire time.
Under the architecture I describe, no, clients and servers do not couple to one another. The client and server couple to a data interchange format.
> contracts define types
Yes, the interchange format defines the types for the interchange. But a string can be represented as an enumeration or a float or an integer or a boolean. The type of the data is dependent on context.
> REST focuses almost exclusively on types
In a sense yes, but it also focuses on decoupling clients and servers. The types are really more of an implementation detail. The type of interchange can be XML, HTML, JSON, bytes, text, etc. The fields in a JSON document can all be strings for the purposes of interchange but may be parsed to integers and floats for the purposes of rendering the data to the user. The server may parse those fields differently.
Its not relevant to the client or the server how the other chooses to interpret the data. All that matters is that the interchange format is maintained.
This sounds just like the headaches I had about a year ago using ESM for isomorphic code, with node. The only thing that ever worked was, of course, Babel.
Another question, how are people actually using Typescript with Svelte? It seems like you can't use $some.prop = avalue, it can't be type checked, so wtf is the point of using Typescript with Svelte?
Damn I was hoping Svelte had proper Typescript support given that they claim that Svelte officially supports TypeScript. Is it just like Vue - it "supports" it, but there's still important stuff that's not type checked at compile time?
Well, yes, to my understanding. If you add lang="ts" to the script block, you can write normal TypeScript code for the component logic, and it will also check props that you are passing to child components in the template. It's like renaming the file from ".jsx" to ".tsx". It might not be perfect, but I haven't encountered any problems with it so far.
It might interest you that Wikipeida chose Vue to modernise its frontend in the future [1]. Different frameworks offer different benefits and different downsides.
React is not the end-all, nor will it ever be, as long as there are interesting new projects coming out, and neither is Vue or Svelte. My website uses React, but personally I would rather use Vue or Svelte in the future.
Yeah we chose Vue at work too, but it was only because we didn't have enough experience of either.
> Wikipeida chose Vue
Yeah their reasons look very much like dubious attempts to justify the choice someone has already made.
1. You can use it without a build step.
Sure, but only if you're not using Typescript, which would be mad. A build step is a mild inconvenience. Not something that should cause you to choose a worse solution (though they do also use PHP so they're probably used to bad tech).
2. Vue.js may enable us to rely on fewer libraries as dependencies.
Ha! When I added Vue and its required CLI stuff to our package.json it pulled in around 1000 dependencies. React has like 4.
3. The official Vue libraries are evolving in a stable and predictable fashion.
You can still use React classes if you want to. They're not deprecated. It's unlikely they ever will be.
4. Vue.js development is not led by a single corporation whose goals may diverge from those of the WMF.
I'm not sure about "$some.prop = avalue", but props are declared as exports of your component [1], and they can be given types. Perhaps you're refering to "$$props"? The only time I've hacked into the component variables is to check whether slot is set.
Having used Typescript with Svelte, I think that Typescript is still potentially useful, just not directly in `.svelte` files. There's nothing wrong with keeping all your business logic in separate `.ts` files and then importing them into your Svelte components, because those components can and often are primarily view logic, and the utility of having everything type-checked down to the template level is... not particularly significant IMO. If your Typescript modules are still type-checked and pass tests, then I find trying to make a Svelte project Typescript all the way down to be kind of paranoid.
To each their own, but I think life's too short to be adding types to your view logic and your templates. It adds so much complexity for so little gain. Maybe it'll get better at some point where it will change my mind, in particular when using something like Deno.
I get the appeal of TS, but why the fuck would any project tie themselves to the language in this way? Angular 2+ did it, I didn't realize Deno did as well. Very bizarre decision unless I'm missing something.
What do you mean? You can use TypeScript or JavaScript, so .ts or .js extensions.
I think what you are missing is that, if you use TypeScript in Deno, the extension is .ts. What's confusing is the way Node.js+TypeScript does it, which means using TypeScript with .js extensions.
Ohh I see. I should probably delete my comment then. I thought the OP meant that Deno enforces the use of Typescript like Angular does. That makes a lot more sense, thanks for the explanation.
The way TypeScript/Node does it is correct. The thing you're importing is JS with a .js extension. If you publish to npm you're publishing the JS files, not TS files. If you write .d.ts/.js pairs instead of .ts - which should be identical to importers, there's no .ts file to import.
Considering that Ryan Dahl started both Node.js (where imports do not include file extensions) and Deno (where he added them back after deciding it was a bad decision to leave them off) I'm not sure how you've come to the conclusion that TypeScript/Node does importing correctly. Additionally, the ecmascript import syntax https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe... specifically mentions that your tooling may or may not need file extensions but the examples in the spec (and indeed in browsers at large) require a full or relative path including the extension. If anything, I'd bet that file extensions make it into Node and tsc in the next few years.
I don't see how it is correct. If you publish to npm, of course you do it the way Node does it, but that's not very interesting. If you publish a module for consumption by Deno, you're publishing TS files, not JS files, and there is no need for .d.ts files.
Just like the sibling comment stated. Angular does not force you to use Typescript, you can use JavaScript. Angular 2+ even used to work with Dart but they dropped it.
This also makes using TypeScript libraries that weren’t authored specifically for Deno nontrivial.
One solution is to use an import map on the Deno side to map your extensionless imports to the corresponding files, but this only works when you control the Deno command line arguments (so not for libraries you plan to publish for others to use).
It’s unfortunate because, other than this huge problem, Deno is the best TypeScript runtime I’ve found.
I may be nitpicking here, but Deno isn't a typescript runtime, is it? In the usual case, does it not transpile typescript to JavaScript and run the code from there?
Another good workaround is to use something like JSPM, which bundles up the NPM library with all its dependencies into an ES module, ready to be consumed (and even loaded remotely!) by Deno
Deno has _very_ custom module resolution rules. The first thing you notice is the required `.ts` extensions and browser-esque URLs. Oh, and also the import maps proposal (which is its own can of worms). Then you dig a little deeper and learn that deno also supposedly supports all `node` resolution rules sometimes via compat flags. Then you learn that it pulls types from magic `/// <reference types` comments, which sounds like what TS normally does, until you realize deno uses it to _override_ the types for JS files, and not pull in new files, which isn't something TS does at all. Oh, and `@deno-types` comments for overriding individual imports, besides.
One of the first lines in the deno docs is:
> Deno has no "magical" module resolution.
Which, as an implementor of module system rules, seems incredibly far from the truth of the matter (where deno has the most complex resolution rules of any runtime currently in use). I think maybe there were simple goals at the start, but that came crashing against reality and xkcd 927 executed in full force.
I would argue it isn't very custom. Using fully qualified URLs predates Node.js and CommonJS. It was also one of the big debates when we all were doing AMD. Node.js has realised that _mistake_ of eliding extensions and is headed that direction as well.
We have been adding a Node.js compatibility mode, but I am not sure that qualifies as "_very_ custom module resolution".
Both the triple-slash reference and @deno-types were solutions to problems to not have opaque type resolution with TypeScript. They do not impact the runtime resolution, only the type check resolution. Instead of opaquely searching for these by reading the `package.json` and probing the file system like `tsc`, Deno is explicit.
I would be glad to work with you to explore how we can figure out how to resolve your implementor concerns. The door has been open for a long time, and I have mentioned it in passing to Daniel a couple times. Let me know if you would like to talk.
this is web dev mindset, in all other languages the module name is just a name, it's not supposed to tell you how to retrieve the module, that's the job of the module system. Otherwise it's the service locator anti pattern. I always find it redundant that syntacically, the module name in javascript has to be a string (as in quoted) for whatever reason. In python and php it's just tokens and namespace separator.
You could generate an import map for Deno from the directory contents, and then your NodeJS-style imports would work there too. No file copying in that way.
I had attempted to use Deno not long ago, but I learned that (apparently, I might be wrong) you can only get proper intellisense/error checking support if you're using a VS Code extension. I write my code in Vim/Neovim almost exclusively, and the import declarations really screwed up my TypeScript plugin; it didn't know how to handle the URLs and so I had no intellisense/error checking.
I gave up after searching around for a few minutes and learning that for now I'd need to use VS Code to write Deno code, if I want those features.
It does suck that it can’t use standard typescript tooling because of a couple of small, key, breaking-differences (mainly the import path style, to my understanding). I don’t think they could have done it any differently, and with enough traction the ecosystem support is a solvable problem, but it’s still a shame.
I agree, though I do think that Deno's import paths are more correct than Node.js or tsc. I'm not exactly sure sure why official Typescript can't support both by now.
About 3 months ago I got my feet wet and fiddled around with it for a bit. Eventually I used it to deploy(using deno deploy) a small script which would forward calls to an unofficial API of some App. It turned out chrome would not let me set a Cookie header (which the API required) due to some security policy in my axios calls so my script would proxy the request and inject it when calling the API.
I was pretty amazed by how easy the whole process was. I can see myself using it more often for side projects and even recommending it at work (If I get the enterprise java crowd to try something new).
In my experience, enterprise Java crowd never spills its managed beans. In my whole career, I couldn't even get any of them admit that JSF may not be the best front end technology in the world. It's a lost cause.
I have to second this. I worked on a java app which was painful to scale, due to JSF and JPA. I have distanced myself from the project as much as I can.
Now, I see another new app for a different purpose being started and I hoped to push for a modern way of building it, but no they just won't give-up using java and JSF.
Nothing wrong with java, it's just java and jsf crowd is hard to be converted to something else.
It's interesting to see the intersection between Deno TypeScript code and native Rust libraries, e.g the native HTTP library [1], however it would be interesting to see a performance evaluation between this and a regular express app.
I am very interesting in the performance implications here as their _has_ to be some performance tradeoffs given you have to serialize / deserialize across the bindings. I get that Rust is fast, but at what point does the trade-off no longer make sense?
On many of our internal benchmarks, Deno's std/http server outperforms Node's http (so express/koa too). There's still some room for improvement on our end and we would like to put together rigorous benchmarks before sharing that broadly.
I don't really get the point of Deno anymore. First they were all about their sandbox/security system, but since you couldn't specify that per module it turned out basically useless.
Then they had "url" imports, but now they're adding npm compatibility. At that point people will just keep publishing on npm, so I don't see why I should go to the trouble of switching to Deno.
If you are concerned about security in one of your imported dependencies (or in user code) you can run that code in a sandboxed worker (See: https://deno.land/manual@v1.18.0/runtime/workers#specifying-...). Beyond this it is still very useful to limit your runtime for software to only access specific directories, network addresses, etc. So the sandbox/security is definitely not useless.
Running TypeScript directly is a great part, but there’s a bigger, little-discussed aspect: Deno has a built-in, prescribed, standardized,
- Testing framework
- Linter
- Bundler
- Formatter
It’s like having Cargo or Go’s CLI for JavaScript/TypeScript. It eliminates so much fiddling and breakage, gets everyone in the ecosystem on the same page, etc. Even for a solo project it’s been a dream to use, compared with the hodgepodge of a Node project.
At least for me one of my favorite things is how integrated it is, after having used Rust for a while, having the compiler, linter, lightweight testing and formatting all in one bundle is an amazing time saver and a major boon for me. No more fiddling with ever growing configurations for ESLint, or having to install Jest/Ava/uvu/tap for testing and their ESLint rules and then Prettier which may also require ESLint... I realize not everyone cares enough to configure it, but the approach of Deno (and Rust, and Go, and so on) is such a weight of my shoulders when starting a project. Just open Neovim, create a TypeScript file, write some tests, all without having to do any kind of configuration or installation of packages.
For me, one of the largest benefits is the Web API compatibility.
As a frontend dev by day, trying to write services/scripts in Node is constantly frustrating. Simple things like base64 encoding or making http requests have me heading off to the documentation and trying to remember "the Node way".
The other large part for me is the lack of npm. The npm ecosystem is one of my largest sources of daily frustration. I look forward to ESM everywhere.
Yeah, that's kinda sorry state of Deno right now. We have more than pure JS/TS ecosystem that NPM used to thrive on.
The backend market is already leaned toward Go. It's the best language for microservices. We also have WASM entering the scene on the frontend side, which allows mixing other languages, especially Rust. Hell, I heard some people ported a whole C++-based UI engine to make it cross-platform. We're already entering the next generation.
I love the current version of Deno for smaller hobby projects. But using it in a full blown production env with actual paying customers depending on it is a whole different story.
Deno is a lot simpler and more cohesive than node. But the simplicity is what currently stops me from attempting any big projects with it. Yes, having no package.json files is great - but on the other hand it hinders me from configuring my project in one file.
I also dont get the point of Deno. when i was looking into it seemed to me that was presented as a modern NPM alternative. Yet, it is completely unusable for any kind of frontend work. Which is wierd, since javascript is a frontend language. You cannot use it to write anything that uses DOM API. Not sure if that is supposed to change. Would be nice if it was clearer that it is a solution for back-end only.
I’m really strongly rooting for Deno. I’m even implementing my current passion-project on it (https://github.com/brundonsmith/bagel). But I have a couple of significant (but solvable!) criticisms, if anybody on the team is here.
1) The documentation for the standard APIs is extremely wanting. The main, and weirdest, problem is that it seems almost impossible for search engines to index (or at least DDG). I can search for very very specific things, and still get kicked to the home page and have to manually navigate to the part I’m looking for (and the navigation is also pretty difficult; I often can’t find the thing at all and have to rely completely on editor hover-overs for documentation). It can also be confusing- some things like certain standard APIs appear to have multiple conflicting reference-manuals? And it can be hard to know which one is current or correct. Even getting past all the obfuscation, the docs are just ok. They often leave out deeper details (this last part is less of a problem and more understandable, this being a young project).
2) The VSCode extension still needs work. It’s usable, and better than nothing, and it’s better than it used to be, but (ordered from most to least significant):
- When I open certain files - usually bundles or something; maybe files outside of the project directory? - the language server crashes over and over and over, each time popping the terminal back up to show the output and distracting from what I’m trying to look at. This doesn’t happen with my normal, in-project Deno files at least.
- It doesn’t have auto-import as you’re typing like normal TypeScript does; you can auto-import by clicking the lightbulb, so I assume it’s a relatively short hop to plug that in. Makes a significant difference in flow.
- It chokes on absolute import-paths in Windows. Thinks they’re malformed remote URLs.
- It has caching issues sometimes. I’ll change some type in one file and other files won’t get the updated type until I close and re-open them, or sometimes the whole editor. This is uncommon so it’s not a huge deal.
I list these criticisms because I want to see Deno succeed. Switching from using TypeScript on Node (with the build steps/configuration, ad-hoc testing and linting, and Node’s pathological legacy APIs) to Deno was a huge quality of life improvement, even with all these problems. Brought to its full potential, I think Deno could be a revolution.
Yeah, I've seen worst API documentation, but their docs site is needlessly weird. Documentation is something that should be solved by now. No project needs whatever custom thing Deno uses. I don't get it.
Leaving out details, for the most part, is the right approach for now, I think. Only Node/JavaScript veterans should probably be using Deno for the time being. But I definitely hope Deno can eventually take over and make Node obsolete.
> 2) The VSCode extension still needs work. It’s usable, and better than nothing, and it’s better than it used to be, but (ordered from most to least significant)
At this point, I've kind of given up on VS Code language/runtime extensions in general. Virtually none of them work with the slightest variation in toolchain configuration, whether it's C++ or Typescript or even ESLint. You're lucky if they don't slow the heck down over time. Import paths are the worst! Somehow that's something I inevitably run into, again, no matter the language.
I kinda want to blow all this stuff up and start over. Maybe Deno is a part of that equation. haha
I've recently used Deno to implement a couple of bots for a small discord channel, namely webhook handlers for Discord Slash Commands and Twitch's EventSub. It's honestly been amazing. Next to no configuration and deployments are dead simple. Just set up some env vars and push to github to deploy.
Deno seems ideal for the now common use case of serverless JavaScript, erm, servers. It's hard to imagine the project superseding the prior art in other domains, however. Deno is a little too weird.
If you like Deno because of TypeScript, you're gunna love this: Imagine the jump from JavaScript to TypeScript, only taken to a whole new level! It's free, open source, cross-platform, really fast, has an enormous ecosystem, and is great for games and native apps! It's called C#.NET.
I had a weird experience on Libera.chat #node - I asked if anyone knew of an official #deno channel and ljharb's [1] reply was (something like) "deno is repeating all of the mistakes that JS has made in the browser" and the sentiment was that "deno sucks." I found it to be a strange comment.
Anyone have more info on this concept of Isolates-as-a-service? (or know of an open source platform that lets you host it, kind of your own lambda but with isolates?) I'd love to use something like that locally (in our environment) to execute user-generated code in a sandbox away from our code so that we can allow customers to write custom functions.
Ryan seems to be a brilliant engineer that takes (only) two attempts to get something right. The cost of such remarkably rapid learning is that he often gets it completely backwards the first time.
Inventing Node before Deno. Creating a compatibility mode in Deno to run Node scripts instead of a tool to port Node scripts to Deno. They even maintain a tool to convert Deno scripts to Node. You really can’t get it more backwards!
I sometimes look at his GitHub issues and PRs and wrong ideas are not rare. Thankfully he assembled a good team that double checks him and catches most of those wrong ideas. But they can only catch so many, and it’s likely more difficult for the executive decisions of the Deno company since money is directly involved like creating a Node compat mode.
102 comments
[ 2.9 ms ] story [ 170 ms ] threadDoes anyone else have a better solution shared Typescript code?
https://github.com/microsoft/TypeScript/issues/42813
Still a hack, but perhaps symlinks could be a little simpler?
Server sends data as JSON to the client. Client parses JSON. Both now have the exact same data shape. Shared TS types let you use the same representation for the same data no matter where.
If your client and server both treat field "temperature" as a string for protocol version 1, and then the client upgrades to protocol version 2 and temperature is now a float, the server has to be modified anyway, type system or no type system, because otherwise it'll break.
If anything, the type system helps to expose the fact that the client and server now disagree about the type of a field, which is helpful.
Because the client and server now share a code base. The client requires the server to run.
> If your client and server both treat field "temperature" as a string for protocol version 1, and then the client upgrades to protocol version 2 and temperature is now a float, the server has to be modified anyway, type system or no type system, because otherwise it'll break.
If the data interchange format changes then yes both the client and server need to change. But if either the client or the server wants to coerce temperature to a different type they are free to do so. As long as the conform to the interchange format the internal representation of the data is irrelevant to third-parties.
In a world where types are shared, updates to the server necessitate updates to the client even if the client doesn't want to change (and vice-versa).
> If anything, the type system helps to expose the fact that the client and server now disagree about the type of a field, which is helpful.
The coupling should receive the credit not the type system. Regardless this undoubtedly true. Two isolated pieces of code don't know what the other is doing. You need to test and you need to write design docs.
This isn't true. Types aren't "a code base", they're an interface - without which it is impossible for a client and server to talk with each other anyway.
> The client requires the server to run.
Not relevant. Nothing about a typed interface prevents the client from running without having a running server - it won't be very useful without something to exchange data with, but you can still run it, unless you've coded otherwise.
> But if either the client or the server wants to coerce temperature to a different type they are free to do so.
No, type coercions are bad engineering. They're brittle and only work in a very tiny number of situations - nothing approaching the general case.
> As long as the conform to the interchange format the internal representation of the data is irrelevant to third-parties. Type coercions at the interface level are a hack that is not acceptable for anything beyond hobbyist work.
An interchange format is a set of types. Types are not internal representation - they're a mathematical concept used in type-checking. Types happen to be used in the process of generating an internal representation of data, but that's not what they are. You're falsely equating the two, and your point is invalid without that equivalence.
> In a world where types are shared, updates to the server necessitate updates to the client even if the client doesn't want to change (and vice-versa).
This is false. If the server is changed, the client only needs to change if the interface between the two also changes. If the server makes a change to its internal data storage format, then that has nothing to do with the client, and so the interface stays the same, and so the shared type declarations/schema stay the same.
If the interface changes, then you already have to update both client and server, so the tiny overhead of maintaining an explicit type schema dwarfed by the amount of effort you spent changing the rest of the code anyway, and more than made up for by the compile-time detection of warnings when you change the interface code for the server, compile, and then get an error that the client no longer compiles.
> The coupling should receive the credit not the type system.
A type system will detect type errors at compile-time. A tightly-coupled system with type coercion and no types will detect errors at run-time, if at all. It's obviously better to detect errors at compile-time. Therefore, even if coupling is correlated with the usefulness of a type system, type systems are still obviously beneficial to include.
Regardless, it doesn't make any sense to talk about "credit" in this case. I think that you misunderstand the purpose (and utility) of type systems, but I'm not sure what to suggest to you so that you can understand.
I enjoy writing code in typed languages. I am in full agreement that they have purpose and utility.
> No, type coercions are bad engineering.
The opposite is true. Your job as a software engineer is to coerce types. I don't mean calling `float64(unsafeString)`. That's certainly type coercion but, as you said, its brittle and probably not the best design.
Consider a database. It stores its data on disk as something (ignore implementation). It loads that data into memory where it is coerced to a type (probably in the C-language). Its filtered, grouped, whatever else. Serialized to something and sent over the wire. Your Python server receives that something and coerces it to a Python type. That Python type is serialized to something and sent over the wire. Your Javascript front-end loads the data and represents it as a Javascript type.
Nothing about this process is bad or ugly or brittle. It would be infinitely worse to maintain the something type across all these different boundaries. Imagine pickling types whenever we want to save something to disk or share it over the network. Imagine being forced to write application code in the same language as your database!
> Type coercions at the interface level are a hack that is not acceptable for anything beyond hobbyist work.
Representing a value in the database as an enum and as its string name in the interchange format is not hobbyist work. I'm certain if you have worked professionally you've used enums and not marshaled their underlying value.
> If the server is changed, the client only needs to change if the interface between the two also changes.
You are advocating for type coercion. The server and client store internal representations and must marshal an interface type to communicate. That's a form of type coercion (I should say "casting" since its explicit but I've mistakenly used coercion so far so we'll roll with it).
---
I think you're too tunnel visioned on the types argument. Types are great. I love them. But coupling your database to your server to your client just to share types between the three is obviously not a good idea. There are greater concerns at play. Extend the argument all the way. Separate your client from your server.
> I enjoy writing code in typed languages. I am in full agreement that they have purpose and utility.
My apologies, that was a bit uncharitable of me.
And, you're right, I didn't quite understand the meaning of "coercion" - I misinterpreted it to mean "implicit coercion". So, my intention was to say that "implicit type coercion is bad engineering" - you're correct in that in general, it's not bad, and necessary.
However, you still don't understand my point, which is that adding types don't introduce any more coupling than what actually had to exist in order to make the program run in the first place.
Let's ignore implicit type coercions, because they are bad engineering, and not relevant to the following point. In a language without implicit type coercions, programs must be well-typed to run correctly. In dynamic languages, even though the code might not contain type annotations, the above principle must still hold. If you pass a string to a function expecting an integer, then your code will break, and all that dynamic typing does is push the error from compile-time to whenever in run-time that you hit that code path.
This is equally true for a client-server interface. If your client and server do not agree on their interface, which includes types (alongside packet header, and field length and order), they will break. If you do not explicitly state what those types are in some formal spec, the types still have to line up in the program! The types are still there, even if you don't explicitly state or check them.
> It would be infinitely worse to maintain the something type across all these different boundaries.
This isn't true; you already maintain types between the boundaries formed between functions in a single program, there are far more of those than inter-program boundaries, and after you set up the tooling for checking network communication schemas, it's only a little more work than maintaining "normal" types between functions.
> Imagine pickling types whenever we want to save something to disk or share it over the network.
I don't understand - "pickling types" is redundant because the pickle format already includes types automatically. Unless you mean building a schema for the pickle and machine-checking that the loading code and the saving code match? In which case, if you're in an engineering context, I absolutely advocate for this, because a mismatch means a runtime error at best, and data loss or corruption at worst. The argument for doing so in a client-server setup is even stronger, however, because data saving and loading code are colocated with each other (making errors easier to manually spot) far more often than sending and receiving code in a client-server codebase.
> Imagine being forced to write application code in the same language as your database!
There's absolutely nothing about type schemas that forces you to use a single language - most schemas are language-independent, like JSON Schema[1] (which is a schema (including types) for JSON data that is in JSON, so you already have a parser for it, and you write it once and then check it everywhere) and ASN-1.
> coupling your database to your server to your client just to share types between the three is obviously not a good idea
The point is that there's no more coupling than what exists in the first place. Programs must be well-typed to run correctly (if there's implicit type coercion, which is bad practice, the program must still be well-typed - you just now automatically insert type-level logic for the coercions) - that means the types are there, even if you don't write them, so the coupling is also there, even if you don't want it.
If your database sends a string to your server when it was expecting an integer, then either your p...
You're welcome to disagree but I'm having a hard time following the conversation.
My point is "there's no more coupling [from using an explicit shared type schema] than what exists in the first place." for which I provided a logical argument in the comment you replied to.
This was in response to your earlier statement "At the expense of coupling which is not a trivial concern."
Do you have any additional arguments for why adding a type schema introduces additional coupling that were not covered above?
That and a Node/Deno backend. Which is what the OP is talking about.
No, not really. Clients are loosely coupled, but they must comply with a contract.
And guess what: contracts define types, and REST focuses almost exclusively on types.
Under the architecture I describe, no, clients and servers do not couple to one another. The client and server couple to a data interchange format.
> contracts define types
Yes, the interchange format defines the types for the interchange. But a string can be represented as an enumeration or a float or an integer or a boolean. The type of the data is dependent on context.
> REST focuses almost exclusively on types
In a sense yes, but it also focuses on decoupling clients and servers. The types are really more of an implementation detail. The type of interchange can be XML, HTML, JSON, bytes, text, etc. The fields in a JSON document can all be strings for the purposes of interchange but may be parsed to integers and floats for the purposes of rendering the data to the user. The server may parse those fields differently.
Its not relevant to the client or the server how the other chooses to interpret the data. All that matters is that the interchange format is maintained.
Does it type check templates yet?
Webstorm now properly supports template type checking too https://blog.jetbrains.com/webstorm/2021/11/webstorm-2021-3/...
Vue 3 looks a lot better than Vue 2 but honestly it's just playing catch up to React. I don't know why you'd choose it.
React is not the end-all, nor will it ever be, as long as there are interesting new projects coming out, and neither is Vue or Svelte. My website uses React, but personally I would rather use Vue or Svelte in the future.
[1]: https://phabricator.wikimedia.org/T241180
> Wikipeida chose Vue
Yeah their reasons look very much like dubious attempts to justify the choice someone has already made.
1. You can use it without a build step.
Sure, but only if you're not using Typescript, which would be mad. A build step is a mild inconvenience. Not something that should cause you to choose a worse solution (though they do also use PHP so they're probably used to bad tech).
2. Vue.js may enable us to rely on fewer libraries as dependencies.
Ha! When I added Vue and its required CLI stuff to our package.json it pulled in around 1000 dependencies. React has like 4.
3. The official Vue libraries are evolving in a stable and predictable fashion.
You can still use React classes if you want to. They're not deprecated. It's unlikely they ever will be.
4. Vue.js development is not led by a single corporation whose goals may diverge from those of the WMF.
I mean... come on.
[1]: https://svelte.dev/tutorial/declaring-props
To each their own, but I think life's too short to be adding types to your view logic and your templates. It adds so much complexity for so little gain. Maybe it'll get better at some point where it will change my mind, in particular when using something like Deno.
I think what you are missing is that, if you use TypeScript in Deno, the extension is .ts. What's confusing is the way Node.js+TypeScript does it, which means using TypeScript with .js extensions.
"They" dropped Angular altogether: https://blog.angular.io/finding-a-path-forward-with-angularj...
One solution is to use an import map on the Deno side to map your extensionless imports to the corresponding files, but this only works when you control the Deno command line arguments (so not for libraries you plan to publish for others to use).
It’s unfortunate because, other than this huge problem, Deno is the best TypeScript runtime I’ve found.
I may be nitpicking here, but Deno isn't a typescript runtime, is it? In the usual case, does it not transpile typescript to JavaScript and run the code from there?
Is Node really a JavaScript runtime? V8 converts the JS to an AST. Would that make Node an `AST runtime`?
Why yes, you can run directly Typescript code in Node (well, at least just as direct as Deno, as it transpiles TS to JS) with the likes of ts-node.
https://www.npmjs.com/package/ts-node
One of the first lines in the deno docs is:
> Deno has no "magical" module resolution.
Which, as an implementor of module system rules, seems incredibly far from the truth of the matter (where deno has the most complex resolution rules of any runtime currently in use). I think maybe there were simple goals at the start, but that came crashing against reality and xkcd 927 executed in full force.
We have been adding a Node.js compatibility mode, but I am not sure that qualifies as "_very_ custom module resolution".
Both the triple-slash reference and @deno-types were solutions to problems to not have opaque type resolution with TypeScript. They do not impact the runtime resolution, only the type check resolution. Instead of opaquely searching for these by reading the `package.json` and probing the file system like `tsc`, Deno is explicit.
I would be glad to work with you to explore how we can figure out how to resolve your implementor concerns. The door has been open for a long time, and I have mentioned it in passing to Daniel a couple times. Let me know if you would like to talk.
I gave up after searching around for a few minutes and learning that for now I'd need to use VS Code to write Deno code, if I want those features.
Now, I see another new app for a different purpose being started and I hoped to push for a modern way of building it, but no they just won't give-up using java and JSF.
Nothing wrong with java, it's just java and jsf crowd is hard to be converted to something else.
Then they had "url" imports, but now they're adding npm compatibility. At that point people will just keep publishing on npm, so I don't see why I should go to the trouble of switching to Deno.
- Testing framework
- Linter
- Bundler
- Formatter
It’s like having Cargo or Go’s CLI for JavaScript/TypeScript. It eliminates so much fiddling and breakage, gets everyone in the ecosystem on the same page, etc. Even for a solo project it’s been a dream to use, compared with the hodgepodge of a Node project.
As a frontend dev by day, trying to write services/scripts in Node is constantly frustrating. Simple things like base64 encoding or making http requests have me heading off to the documentation and trying to remember "the Node way".
The other large part for me is the lack of npm. The npm ecosystem is one of my largest sources of daily frustration. I look forward to ESM everywhere.
https://github.com/aws/aws-sdk-js-v3/issues/1289
The backend market is already leaned toward Go. It's the best language for microservices. We also have WASM entering the scene on the frontend side, which allows mixing other languages, especially Rust. Hell, I heard some people ported a whole C++-based UI engine to make it cross-platform. We're already entering the next generation.
1) The documentation for the standard APIs is extremely wanting. The main, and weirdest, problem is that it seems almost impossible for search engines to index (or at least DDG). I can search for very very specific things, and still get kicked to the home page and have to manually navigate to the part I’m looking for (and the navigation is also pretty difficult; I often can’t find the thing at all and have to rely completely on editor hover-overs for documentation). It can also be confusing- some things like certain standard APIs appear to have multiple conflicting reference-manuals? And it can be hard to know which one is current or correct. Even getting past all the obfuscation, the docs are just ok. They often leave out deeper details (this last part is less of a problem and more understandable, this being a young project).
2) The VSCode extension still needs work. It’s usable, and better than nothing, and it’s better than it used to be, but (ordered from most to least significant):
- When I open certain files - usually bundles or something; maybe files outside of the project directory? - the language server crashes over and over and over, each time popping the terminal back up to show the output and distracting from what I’m trying to look at. This doesn’t happen with my normal, in-project Deno files at least.
- It doesn’t have auto-import as you’re typing like normal TypeScript does; you can auto-import by clicking the lightbulb, so I assume it’s a relatively short hop to plug that in. Makes a significant difference in flow.
- It chokes on absolute import-paths in Windows. Thinks they’re malformed remote URLs.
- It has caching issues sometimes. I’ll change some type in one file and other files won’t get the updated type until I close and re-open them, or sometimes the whole editor. This is uncommon so it’s not a huge deal.
I list these criticisms because I want to see Deno succeed. Switching from using TypeScript on Node (with the build steps/configuration, ad-hoc testing and linting, and Node’s pathological legacy APIs) to Deno was a huge quality of life improvement, even with all these problems. Brought to its full potential, I think Deno could be a revolution.
Leaving out details, for the most part, is the right approach for now, I think. Only Node/JavaScript veterans should probably be using Deno for the time being. But I definitely hope Deno can eventually take over and make Node obsolete.
> 2) The VSCode extension still needs work. It’s usable, and better than nothing, and it’s better than it used to be, but (ordered from most to least significant)
At this point, I've kind of given up on VS Code language/runtime extensions in general. Virtually none of them work with the slightest variation in toolchain configuration, whether it's C++ or Typescript or even ESLint. You're lucky if they don't slow the heck down over time. Import paths are the worst! Somehow that's something I inevitably run into, again, no matter the language.
I kinda want to blow all this stuff up and start over. Maybe Deno is a part of that equation. haha
[1] https://github.com/ljharb
Inventing Node before Deno. Creating a compatibility mode in Deno to run Node scripts instead of a tool to port Node scripts to Deno. They even maintain a tool to convert Deno scripts to Node. You really can’t get it more backwards!
I sometimes look at his GitHub issues and PRs and wrong ideas are not rare. Thankfully he assembled a good team that double checks him and catches most of those wrong ideas. But they can only catch so many, and it’s likely more difficult for the executive decisions of the Deno company since money is directly involved like creating a Node compat mode.