DEV Community

Jacob Schatz
Jacob Schatz

Posted on • Updated on

Big Giant Vue Apps

I recently took a huge risk and left Netlify to pursue an opportunity of a lifetime to start a startup. Subs is currently in development. You can read all about it on our Launchrock, and sign up for updates.

Subs makes password management seamless by removing the need to constantly enter a master password. I am joining this startup with someone I deeply respect, and I'm already heads-down working hard on this exciting new project. This isn't my first time writing a new product from idea to production, but it's already introducing me to new and interesting challenges.

For this project, the architecture is going to be a bit different from what I've done in the past. Typically, I've used what I know well: a web app that evolves over time into something bigger. Instead of offering a ton of decent features to a few platforms, the plan is to offer a single, well-built, performant major feature to a multitude of platforms — web, mobile, desktop, and browser extensions.

Architecture and Tooling

The codebase for this startup will be large, using one mono-repo to house eight individual projects covering all of our targeted platforms. We'll also have an additional "common" project to house all of the shared components, services, and stores.

This might seem like a lot in one repo, but there are many benefits to working this way.

I'm putting the building blocks for cross-platform infrastructure in place now, so that future development is as easy as it can be. One thing I've learned: the prototype usually becomes the final code, so do it right the first time. Putting in the building blocks from the beginning allows me to give the major features the attention they need.

The majority of the front end will be driven by Vue and NativeScript. I've spoken about the benefits of Vue in the past. For this project, using the Vue ecosystem to drive the web application, desktop application, and browser extensions affords a level of reusability across the mono-repo and "common" repo, so code won't need to be written twice.

Nativescript with NativeScript-Vue will be used for the iOS and Android applications. While they won't benefit directly from the same shared components, it may let us share a Vuex store across all application instances to handle state, as well as sharing the services that call the API.

Battle-tested libraries are necessary for encryption-related tasks. So, for extra security, I wrote a command-line interface in Python using execa for process execution. This buys us flexibility in the future. If we want to rewrite the CLI in another language like Rust or Go for greater performance, it would be a drop-in replacement.

Project Structure

Diagram of Subs' project structure

The diagram might be confusing so let me explain. The project features at least four Vue CLI generated applications: a web application, a desktop application, a browser extension, and the common library.

The common library does not have a build step. It is simply imported into projects. When a project using the common modules is built, the common library gets baked in. I do have to run yarn link initially and link it manually, but it's in my README for the future. This way, any changes I make to the common library immediately benefit from hot module replacement, and I can see the changes immediately.

I can use my shared components in the web app like so:

code screenshot

What's cool about this (besides the fact that I'm using Sublime Text) is that my components library is empty minus an entry point.

I am importing every single component at once which does not take advantage of tree shaking as much as it would in other apps. If that was a concern, I'd restructure my exports in my common library. In my case, my app is very simple, so I can get away with this.

Each app within packages has its own managed node_modules. And I have to run yarn on each of the eight projects. And I have to run yarn link subscommon everywhere I want to use my common library.

Is this a boring solution? It is a marginally longer setup that isn't a big deal to me. I like my setups to be explicit but not tedious. I like less magic and more understanding. The total time to set up the project is about ten minutes. Could it have been three minutes? Maybe.

Sharing State and Services

I also share the Vuex store and my services. Since Vuex is the only part of my app that uses the services, they simply get imported on their own.

When Vuex initializes, it performs a recursive injection of the store into all the child components, so you need to make sure it is using the right Vue instance. Here's my shared Vuex store:

import Vuex from "vuex";
import menu from "./menu";
import user from "./user";

export default function store(Vue) {
  Vue.use(Vuex);
  const store = new Vuex.Store({
    modules: {
      menu,
      user,
      ... more stuff
    }
  });
  return store;
}
Enter fullscreen mode Exit fullscreen mode

I don't import Vue above because I pass Vue into my web app and desktop app like so:

import Vue from "vue";
import App from "./App.vue";
import { store } from "subscommon";

Vue.config.productionTip = false;

new Vue({
  store: store(Vue),
  render: h => h(App)
}).$mount("#app");
Enter fullscreen mode Exit fullscreen mode

Now Vuex can latch onto the app properly and populate any child components with $store.

The Sum of All Parts

This project structure serves its purpose by allowing me to iterate on the primary application quickly, across various target platforms.

How quick? I was able to initialize Vue CLI Electron Builder, import my store, and have an exact running copy of my desktop app in five minutes, with no code changes.

I was able to get my Chrome extension running almost as quickly too, but admittedly it took me a few hours to understand how Chrome extensions work, so some time was spent reading the docs. Using the Vue CLI Plugin Browser Extension got me up and running very quickly, promising minimal code changes between all extensions. I'll believe it when I see it, but I haven't gotten there yet.

Meanwhile, my desktop app is so damn small and fully functional. Here's the file structure:

Screenshot of App.vue file

I kept HelloWorld as the entry point to all my apps because it's the way Vue CLI 3 scaffolds it and it makes sense to me. I'm sure that, at some point, some developers will tell me it's not kosher and to change it. Until then, I'm keeping it.

We hope to get a working proof-of-concept into your hands very soon so we can hear what you think. If you are interested in what I am building and want to know when my beta launches, then sign up on Launchrock and I'll keep you updated.

Top comments (14)

Collapse
 
akryum profile image
Guillaume Chau

Yarn workspaces could save you the common linking step. :)

Collapse
 
akryum profile image
Guillaume Chau

What you need to add to the root package.json:

  "workspaces": [
    "packages/*"
  ],

Then remove all node_modules folders in the mono-repo, remove the package-lock.json file and then run yarn in the root directory.

Example with yarn workspaces and lerna

Collapse
 
jakecodes profile image
Jacob Schatz

Thanks a lot! I'll give this a try. This is exactly what I am trying to do.

Collapse
 
karmablackshaw profile image
KarmaBlackshaw

is there anything like this for NPM ?

Collapse
 
olavoasantos profile image
Olavo Amorim Santos

Came to write exactly this. Workspaces with Lerna can really help organize and manage the monorepo.

Collapse
 
seniorwebdeveloperlife profile image
seniorWebDeveloperLife

Not to rain on your parade but what does Subs offer that Google password management does not? I have already sold my soul to Google and it fills in passwords already connected to my google account in every browser that I use without using a fourth party. What is your use case? What would I gain by switching and how much will it cost? Good luck on your startup (no sarcasm intended)!

Collapse
 
sonicoder profile image
Gábor Soós

What is the benefit of a planned to be gigantic monorepo vs using packages?

Collapse
 
jakecodes profile image
Jacob Schatz

It's a Coke and Pepsi argument, both have their pluses and minuses. For me I can add in GitLab CI instructions to build for all the different platforms and make sure everything is good before releasing all at once.

Also so far I've really enjoyed doing one commit that represents a larger body of work. For example, I needed to make a Chrome extension and that doesn't just involve the extension package, it involves, the client package and the api package. So it makes sense to me to have centralized commits that represent the whole idea. It's easier for me to track things.

And each of these repos is technically a npm package. I am just not publishing them.

Also running git push origin master once just saves time rather than doing it 8 times. And having one PR/MR is easier to deal with for me. It's a larger PR/MR which is usually bad but in this case it doesn't cause conflicts because it's many separate packages.

Also by keeping the yarn link inside the repo the setup is easier. One clone and a link, and you are good to go. It means I can develop the common library at the same time as the client for example.

Collapse
 
v6 profile image
🦄N B🛡

// , I'm glad to see you want your git commits to tell a cohesive story. It's kind of what the Sand of Sky blog recommends:

sandofsky.com/workflow/git-workflow/

Collapse
 
v6 profile image
🦄N B🛡 • Edited

// , I recommend being careful not to optimize too closely. One of the best ways to avoid optimization is the ultimate flexibility: Keeping the option on the table to throw out the code, or well abstracted parts of it, and rewrite that code or product from scratch.

It's a little known "dark" pattern of Software Design, from the shadows of Agile, called "Sacrificial Architecture."

exponential growth isnt kind to architectural decisions

It came to mind when I read this in your post:

One thing I've learned: the prototype usually becomes the final code, so do it right the first time.

Should the prototype usually become the final code?

Essentially it means accepting now that in a few years time you'll (hopefully) need to throw away what you're currently building.

I've already run into a couple of projects that took the microservice path without really needing to — seriously slowing down their feature pipeline as a result. So a monolith is often a good sacrificial architecture, with microservices introduced later to gradually pull it apart.

martinfowler.com/bliki/Sacrificial...

Collapse
 
emlautarom1 profile image
Martín Emanuel

Great content! Thanks for sharing your workflow. I'm about to start a project with Vue targeting web, desktop (Electron) and mobile (NativeScript) so this is comes really in handy.

Collapse
 
madza profile image
Madza

Secure password management tools are in high demand, tho nowadays it's very challenging venture to go into by actually building them :) Best of luck with Google OAuth and AES256, if you can bring unique features compared to Lastpass, Dashline, Bitwarden, etc and reduce the security risks, all roads are open.

Collapse
 
padakipavan profile image
padaki-pavan

Looks pretty neat. Have you looked into flutter yet? Seems like it can further reduce your codebase.

Collapse
 
hawkeye64 profile image
Jeff Galbraith

Instead of yarn link which I used to use a LOT, look into yarn workspaces. I have converted to this and it is much more seamless in automatically linking between shared/common packages.