This repo provides a simple base upon which to build domain/client/server applications for the web.
I started it on Windows 10 in WSL 2 using Node 15.14 and VSCode, so it inherits some of the environment's quirks, like using http://172.22.53.52 instead of localhost. (More on that below.)
It consists of four interrelated node packages.
The Vetur extension seems to expect TypeScript configuration at the project's root. While I could probably have just added a tsconfig.json at that level and called it quits, I opted to define a package as well. In the future, I hope to add some scripts to it.
This package is meant to contain domain logic and type definitions shared by
both the client and server. The source files are simply referenced by relative
file path within client
and server
.
An obvious alternative would be to transpile domain
separately, and then
reference it as a package. This approach offers no clear benefits, but it
has a couple of unappetizing drawbacks:
- The build and development processes would become more complex and brittle
- The consumers might end up referencing lots of source code they don't need
So why is there a Node package here at all? Two reasons:
- We'll add
jest
here later - Webpack (used by
client
) requires aneslint
config within thedomain
folder
For some reason, the current Vue/Webpack setup required a separate eslint
config for these source files. There might be a config option that I could
adjust to get around the issue, but just creating a package and adding
linting was easy enough.
I set this up using Vue CLI with the TypeScript plugin, selecting the preview version of Vue 3.
Already included are a few essentials:
- A
ClientError
type :: the earlier we have error handling patterns, the more likely we are to use them. - A
RemoteResource
type :: this one goes hand-in-hand with errors. It's a structure that's as simple as it is useful, and it's easy to consume. - A
RemoteLock
component :: while we should still consider debouncing triggers for long-running operations, preventing extra button clicks and user input is still a good idea. - A
RemoteResourceDemo
:: shows how it fits together
It seems like Vetur wants client
to be my "project root" in VSCode, but
conceding the domain/client/server structure would defeat the purpose of this
project. When Vetur and VLS aren't available, type inference fails in .vue
files -- another unacceptable conclusion.
There are probably a couple of ways to reconcile these problems. The most
straightforward way was to add another tsconfig.json
at the actual root.
(I've also gotten used to restarting VLS via the Command Pallet, but I've needed to do so much less frequently.)
Creating a typed component in a .vue
file requires a bit of extra diligence.
Specifically, I needed to:
- Provide an explicit type for the return value from
data()
- Specify a return type on
computed
signatures
This problem has occurred only sporadically: sometimes, when I start up an HTTP
with node on WSL, I can't access it at localhost
. Instead, I need to use the
address shown when I run the command below, per
this issue comment:
ip addr | grep -E "\b*inet.*global" | sed -E "s/inet(.*)\/.*/\1/"
No snags here whatsoever, aside from the WSL problem noted above.
I am thoroughly convinced that we should keep logic related to the problem domain free from I/O and side-effects. Doing so will make our applications easier to test, fix, talk about, and extend.
This repo suggests that keeping domain logic in a separate folder (or package) might be a good approach.
See also onion architecture
I've likewise been inspired by type/module techniques for organizing code. The idea is:
- Keep types lightweight -- records, enums, and tuples should be enough
- Prefer static operations when doing things with instances of your types
- Use generics judiciously
Vue provides a few ways to define components with TypeScript. Preferring to
avoid object-oriented idioms, I chose to use defineComponent
.