DEV Community

Cover image for Vanilla 🍦SPA Parcel Starter
Manav Misra
Manav Misra

Posted on • Updated on

Vanilla 🍦SPA Parcel Starter

TLDR

Go to the repo

Use This Template

clone: Template repo

cd into the cloned directory

npm i

npm start to fire up Parcel localhost server.

Write your code 👩🏾‍💻 🦄

What's It All About?

Read the README

Summarily, if you are a newer dev, it can be distracting to set up tooling and linting and other 'dev tools 🧰' to have a reductive workflow.

This template is for writing a Vanilla SPA application (good to do as a prerequisite to React, Vue, etc.) using "parcel-bundler".

So, you can dive right in and have a set of 'battle-tested' tooling helping you learn and develop and be more reductive overall. 🤓

As you progress, you adjust the each and everything as you see fit. It grows/adapts with you. 🚀

It's helped hundreds of my previous 👨🏾‍🎓 in bootcamps 🏕️ and college 🏫 courses.

VS Code Extensions & Settings

When you 🔥 this up in VS Code, you will be prompted in the bottom right to install some extensions. These are listed in the '.vscode' directory 📁.

To take full advantage dl and install 🪁.

Linting/Prettier for CSS & JS

It's using extends: ["airbnb-base", "prettier"]. ⚠️ "airbnb" linting is very strict! 😖 I strongly encourage you to stick with it and research the linting 'mistakes' b4 just turning them off.

I've already adjusted a few for you:

rules: {
    "array-callback-return": "warn",
    "import/extensions": "warn",
    "import/prefer-default-export": "warn",
    "no-plusplus": ["error", { allowForLoopAfterthoughts: true }],
    "no-unused-expressions": ["error", { allowShortCircuit: true }],
    "no-unused-vars": "warn",
  },
Enter fullscreen mode Exit fullscreen mode

Prettier

There is some very basic "prettier" config stuff in other files, but we just use the default stuff for that one.

"husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.js": "eslint --cache --fix",
    "*.css": "stylelint --fix",
    "*.{js,css,md}": "prettier --write"
  }
Enter fullscreen mode Exit fullscreen mode

We have also activated 'pre-commit 🪝s.' This prevents committing of code that doesn't pass the linter. It also 'auto-formats' the code for consistent repo code.

CSS

Stylelint also is riding along. CSS mistakes can be easy to make, and it can get sloppy. This will guide along the way and prevent some common CSS specificity issues.

Project Entry-Point 🎉

The entry point is: 'index.html'. À la React, we have a root that will wrap our functional components, which will come from 'index.js.'

 <!-- TODO: 'render' content here -->
    <div id="root"></div>
    <script src="./index.js"></script>
Enter fullscreen mode Exit fullscreen mode

And, in 'index.js,'

/**
 * TODO: Import some components
 * U might also need to import 'api' to get your initial data?
 */
import testComponent from "./components";

const root = document.getElementById("root");

const render = () => {
  root.innerHTML = testComponent();
};

// ⚠️ Don't 4get to actually render! 😆
render();

// TODO: Use api to 'fetch' 'initial data?' Maybe 'api.index()'?
Enter fullscreen mode Exit fullscreen mode

Modularity

Each of the other 'module' directories contain 'index.js' files and you can build in other files as needed from there. I've already included some comments and/or starter code in each one. We take a look at each briefly.

'api/index.js'

/**
 * TODO: All interactions with external data should be managed from here.
 * You probably need to 'import' stuff from 'db' if using a database.
 * Then you can create methods such as 'index,' 'create(payload),' etc.
 */
Enter fullscreen mode Exit fullscreen mode

As an example, this might turn into something like shown below. Specifically, this would apply very closely if you were using Firebase's Firestore for data management - that's not really important for what we are doing here.
The point is to maintain a separation of concerns/services. It's going to 'api's job to manage interacting with external sources.

import db from "./db";

export default {
  async index() {
    const querySnapshot = await db.collection("students").get();

    return querySnapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }));
  },

  // More generically, the PARAMETER might be called 'payload'
  async create(student) {
    db.collection("students").add(student);
  },

  // TODO: Add addl. 'api methods' 👇🏾 if needed
};
Enter fullscreen mode Exit fullscreen mode

'db'

This 🏠s your connection to an external database service. It is subsequently used by 'api' - in fact, this directory could be 🏠ed inside of 'api'...and in fact I am going to change that!

Anyway, 'db/config.js' is for any configuration details that you might need to connect to your database. If these are supposed to be 'secret,' you should look into using a '.env,' or, even better, keep that on server-side.

'db/client.js' is for establishing and export whatever database clients are created using your configuration details.

Finally, as always...as always...'db/index.js' is there to simply export out whatever 'api' might need to use.

'components'

This is what initially renders, and is where most of your work will probably be done. As your app grows, you may even need a 'pages' directory 📁 and a 'layouts' directory 💦.

In this one, we see a simple example of a functional component: export { default as Main } from "./Main";. It's nothing but a function that returns some HTML markup, in this case.

Again, this is really where you may end up with a lot of work 💦 depending on your app's needs. This directory could end up looking 👀 something like: Components Directory Structure Example

'lib'

This is where we might keep various 'utility functions' that our components might need to import to do their respective jobs.

'store'

This is to enforce a 'single source of truth' concerning the state of our data at any given time in our app. It's a 'data store' or 'data library.' I don't really like the name 'store,' but that's what the dev community says it is. As Petey Pablo says, "Who am I?"

There are many strategies for doing this, but I've included the following starter 🌱 code to do with as you see fit:

export default {
  data: [],
  setData(newData) {
    // 'concat' is 'non-mutating'
    this.data = this.data.concat(newData);
  },
};
Enter fullscreen mode Exit fullscreen mode

Summarily, any part of your app that either wants to 'get' or 'set' data should do this via the 'store' - a single source of truth. This is the foundation upon which Redux/Vuex/Flux, etc. are based on.

There's a Lot! 💦

Well, I hope this helps more than it hurts. There's a lot to consider...but just get started with this and grow into it. You don't have to use each part of this! Really, just rendering a few functional components is fine.

If you do want to see a somewhat simple example of making something with this, here's a project repo of something we worked on in one of my college class.


Reach out if you need a bit more guidance. I have other template repos too for other stuff.


Update: For a more in-depth discussion, join my GitHub discussion.

Top comments (0)