How I Build Web Apps As A Freelancer
timdaub.github.io
timdaub.github.io
I came from backend development, writing mostly in C++ and Python for most of my career. I've always found modern JavaScript frameworks so difficult to reason about because it feels like I'm always working through nine layers of abstraction.
Last year, I read Julia Evans's article "A little bit of plain Javascript can do a lot," and it inspired me to try to push regular JS as far as it would go in my next project. It's now 8 months later, I have thousands of users, and I'm still using plain JavaScript.[1] No babel/webpack or anything. I do use Flask to aggregate source files together, but I purposely avoid any cleverness there. Overall, I find the code much easier to debug than any of my other web apps.
One nice advantage of using only vanilla JS is that it's easier to find contributors. If you're a React project, then only React developers can contribute. But if you're a vanilla JS project, you're not bound to developers of any particular religion. Vue, React, Angular, Svelte developers can all work in a vanilla JS codebase.
[0] https://jvns.ca/blog/2020/06/19/a-little-bit-of-plain-javasc...
I am sure that some people really need these massive frameworks with build chains and universal rendering. I am just not one of them. I use JS for small logic here and there, mostly form validation and a bit of dynamic content. Most web software I know fits this description. I don't know why, but web developers are obsessed with making their jobs seem more complex than it really is. It's like everyone is scared that if someone realizes they just used HTML, JS, CSS, and a small set of scripts that people would find out they weren't real programmers.
Glad you like the post! Your blog inspired me to get better at writing and to take blogging more seriously. So I guess I want to say thanks! Keep up the amazing work!
No SPA, no babel, no tsconfig with endless configuration options.
On enterprise projects where I am just yet another cog, I let the FE team play with whatever stacks they feel like.
But I should mention that I ignore support for legacy browsers. I assume users have a version of Chrome, Firefox, Edge, or Safari from the last year or two.
I did a lot of JavaScript ten years ago. I guess browser compatibility has improved since then.
It's possible that users with old browsers have tried to use my app, found that it didn't work in their legacy browser, and then just moved on without telling me.
It's also worth mentioning that my app is for server administration, so my users are more technically-savvy than average and tend to be the kind of people who see the need to use the latest browser versions.
Being so against using any sort of transpilation you avoid even TypeScript and JSX? Even though Parcel gives you these things completely for free, out of the box?
I do not think this approach would scale to a team in any good way.
Also with regards to using something like unpkg URLs in built code, whilst I a not completely against using urls for dependencies (ala deno), isn't there the huge risk that without pulling dependencies in at build time, those URLs will break or become overloaded or compromised?
> Also with regards to using something like unpkg URLs in built code, whilst I a not completely against using urls for dependencies (ala deno), isn't there the huge risk that without pulling dependencies in at build time, those URLs will break or become overloaded or compromised?
In my post, I said I use unpkg for prototypes, so I don't think it's a problem. But in any case, unpkg let's you pin a version:
unpkg.com/:package@:version/:file
That is not to say that complexity is never necessary. Sometimes we are trying to solve inherently complex problems. But there is a disturbing amount of accidental complexity in modern web development. To make things worse, because web development is such a huge part of the software industry now and how so many people have come into that industry, a whole generation of developers is growing up believing that the accidental complexity is necessary or even desirable.
I actually really like typescript but I am unaware of being able to use it without very obnoxious things like webpack being involved.
Adapting existing projects is harder though.
I mean, he literally says he's a solo/freelancer. Should be pretty clear:
> I'm sure they won't work for everyone as all our contexts differ. But working solo as a freelancer, I've found that these principles contribute to me being content about what I'm doing. Hence, I was eager to share them.
Honestly, if you're doing solo/freelance work, whoever comes along next is likely to toss out whatever you've done anyway, so you may as well optimize for whatever stack works best for you.
This is a fair point
If you stick an await in the code it breaks it because it transpiles it to a missing library. You have to disable Babel or import a regenerator/polyfill or whatever they need nowadays… but first, you have to figure out what’s wrong.
Not only is it free OOTB, but iirc you can't turn it off[1]. So OP might be using it unknowingly. 0 config means just that, for better and worse.
[1] - https://github.com/parcel-bundler/parcel/issues/1207#issueco...
One advantage is that there are fewer technologies another developer arriving needs to know to even get started working on the code.
Although there are alternatives (Flow, for example), to me it seems like the community has picked TS as the way forward, so in my opinion TS is a very low risk technology with real benefits, so I wouldn't hesitate adding it to a project's tech stack.
It's here to stay and a big chunk of JavaScript developers enjoy using TypeScript.
As you can already infer from the headline of that post, I tried writing a statement that is about how Typescript is weakening the JavaScript ecosystem.
Really, I hope that in a couple of years browsers would just allow for WebIDL integration and that is it.
e.g. for VS Code, stick a jsconfig.json in your project's root:
{
"compilerOptions": {
"checkJs": true,
"target": "es2020"
}
}
If you want TypeScript to know about the types in your project, or help it with stuff it can't figure out on its own you can add them using JSDoc comments:https://www.typescriptlang.org/docs/handbook/jsdoc-supported...
The best thing is if TypeScript is being silly about your working code (its type definitions for the DOM can be particularly painful to work with), you can just ignore it :)
I think it is the need to be maximally productive in the shortest amount of time. Basically, any time you spend fixing someone else's problem is time you aren't getting closer to the clients goals. It is not that the time isn't for the greater good, but the hours I have allocated to this problem are fairly tight to start with and I don't enjoy the "We are over budget. Here is why" talks.
I have reached the point where I have started using npm ls as a proxy for how much pain migrating a legacy app will end up being.
Yes, that is an important reason how I arrived at this way of working!
I've realized the more time not spent on producing output in the shortest amount of time, I'm eating away at my work life balance
I started doing this a couple of years ago - defaulting to writing open source code unless I had a really good reason not to - and my productivity has gone through the roof as a direct result.
The way I think about this is that ideally as a software developer I should only ever have to solve any problem once. If I solve it by writing some open source code, and do it well, I'll never have to solve it again in the future.
I compare that to my previous decade+ of engineering where I would frequently find myself solving a problem that I'd already solved a couple of years earlier, but in closed-source code that I couldn't easily reuse.
Exactly! Imagine we software developer craftspeople would all come with an "open source utility belt" when someone hired us <3
1. Using some sort of typed JavaScript via Typescript or Flow makes for much more resilient and stable code. Particularly if you're developing re-usable APIs. You're doing the right thing with automated tests but missing an opportunity by avoiding a type system. We had a much fewer runtime issues once switching to a type system.
2. The above sort of necessitates a transpiler.
3. With respect to using your own code, most freelancers (I assume) are delivering code to third party teams to later maintain. Using common, shared libraries is a benefit for later developers, so this rule should be tempered with later benefits.
Re: third party code: I'm not against third-party libs. But what I tend to prefer is having control over my code. This makes me faster in building and maintaining. I'm a solo freelancer. I can't afford to wait three days for a PR upstream to be merged.
Also, I like to create my own little dependency. That way I have a way to create separate and loosely coupled APIs (shareable) and control.
New codebases for us use Typescript, extending existing work we use Flow that can be backported in easier.
Too many developers start their baby projects thinking they'll be building it alongside hundreds of fellow developers and spend weeks prematurely configuring their build tooling, boilerplate, tech stack. How it'll be supported in twenty platforms, etc. You're only giving yourself more headaches for later on.
I agree with your point on using your own work. People reach for the nicest looking library but there are so many standardized web apis capable of doing what they need, with only very little modification to make it work the way they expect it to.
Getting out the big guns in solo freelance is like going to the hardware store and buying the most complex and expensive drill possible. Only to arrive home full of sweat to drill a 5mm hole.
(FYI: I love expensive drills, but I don't own one)
I mainly use Svelte now which unfortunately requires a compilation step but the benefits are so great it's worth it.
There's such a great disconnect between what front end devs need and what the W3C/TC39 keep adding.
> (...)
> import { h, Component, render } from 'https://unpkg.com/preact?module'
Will break on breaking changes in upstream, as no version is specified? Or am I missing something?
Ed: not to mention when unpkg goes away...
import { h, Component, render } from 'https://unpkg.com/preact@10.5.10?module'
Web apps and JavaScript apps are not the same thing.
I’d rather tree shake a lodash function than roll my own. Not because I don’t know how, but merely because that code is better than mine. Period.
I strongly agree with avoiding overcomplicated processes and tools. When you start spending noticeable amounts of time trying to figure out your build process or how to manage your hosting infrastructure, to pick two repeat offenders, something is wrong. The overwhelming majority of web apps simply aren’t that complicated, and building and deploying them shouldn’t be complicated either.
Like the author, I am a big fan of tools like Parcel that have one job, do it well, and mostly “just work”. Virtual hat tip to Caddy as a web server for similar reasons. I think some of the more recent build tools like Snowpack and esbuild are worth watching too. All of these tools share the common theme that they attempt to make routine jobs simple, automatic and fast, so developers can get on with developing useful software.
In some ways, I take almost the opposite view when it comes to writing code. I tend to start almost everything from a clean slate, and usually I end up using a relatively small set of libraries, carefully chosen for each project, for productivity. I rarely use a heavyweight framework, as I think the benefits they offer are much more limited than their popularity would suggest and the potential long-term costs they come with are often underestimated. I almost never use scaffolding tools, for similar reasons.
I suspect this apparent dichotomy is a bit like what you build and what you outsource in a startup. There is the core idea, which is where you’re creating value, and you want to be as flexible and productive as possible in that area. For everything else that is necessary but not the unique part of what you’re doing, you want to be involved as little as possible. Outsourcing that stuff is good, but outsourcing 80% of something and still having to do the other 20% but on someone else’s terms is often the worst position of all.
1) This is an entirely self-centered approach. "These are the tools that I like, and I code the way I'm productive, with the specific knowledge I've acquired, tailored to how I best work." That's a lot of "I" in there.
The idea of programming this way really bothers me because as someone who's hired freelancers, the best interests of your clients are nowhere in your calculus. Now, if you gave them the choice between lower cost code and code that anyone else can work on, it's your client's decision. But you are making that decision for them. If your clients are non-tech people who don't understand the ramifications of each choice, then you are doing them a disservice.
Nowhere in your article do you mention documentation or making your source easily understood by anyone else, so I rest my case.
It looks like if you are ever hit by a train, your client is screwed, and you've not thought about that.
2) I know that we, as engineers, love to reinvent the wheel, but when you start implementing stuff readily available you introduce bugs and security weaknesses. Maybe you trust yourself not to do that, but do your clients know you're not using industry standard code?
Anyway, your life, your choices. I'm not sure you've been on the client side inheriting code written like this that no new programmer can understand right away. Whoever inherits your code is going to have a lot of head scratching to do, and the only saving grace is that they will have your tests to validate any changes, and they will hope that your test coverage is appropriate.
But from the point of view of your own interests, everything you wrote is very beneficial.
1) The pain points usually come when you're configuring things from scratch. If you're always doing solo greenfield projects, you can just carry around a boilerplate project and you should be able to largely avoid messing with configuration after the first time
2) "I optimize for performance and quality" Well, doing a JS build can help a lot with asset size and therefore performance, especially compared to doing a transpile at runtime which is what it sounds like this person is doing instead
Zero worries about npm, node, babel, whatever is the tool of the day.
Working today, just like 15 years ago.
On the TypeScript thing, if you're already using Parcel it can do all the TypeScript stuff for you with nothing extra required!
I also don't like to setup webpack but by doing that you learn a lot about how things work.
This is for example why I started to write my own component framework in vue. Just to learn the underling stuff. It is also open sourced ;) https://github.com/lampewebdev/lcs-components-yt
Gonna check to see if he has a template or boilerplate that he uses for new projects.
Something that is unknown to a wide range of today's programmers.
"that guy" is here and reading your mean comment. Anyways, I don't think my code is that bad.
We now have 1 strongly-typed domain model that is used directly throughout the entire application. If you want to talk about productivity, cutting out entire layers of unnecessary mapping is key to getting leverage. Our custom javascript interop amounts to approximately 150 lines of code. Needless to say, we don't worry about javascript too much. Any interaction requiring js is quickly abstracted away under some razor component, never to be worried about again.