DEV Community

Cover image for How to structure your Alpine.js Code into modules
goldnead
goldnead

Posted on • Originally published at devblog.adriangoldner.com

How to structure your Alpine.js Code into modules

I really love AlpineJS. It just got the right balance between ease of use & must-have JavaScript features. I like to think of it as a jQuery-alternative plus two-way bindings without the heavy load of a framework like Vue or React.

However, I still use a bundler (Rollup most of the time) to organize my code into modules. And since AlpineJS resides globally in the window scope (one drawback of its simplicity) you can't bundle it up into single components as easily as in Vue, for example.

And because I like to organize my code into little chunks I'll show you the pattern I use to write my AlpineJS-Components:

Create the Main Entry-File

I use to call my main entry JavaScript-File main.js or site.js and it looks something like this:

// Import the all-mighty AlpineJS
import "alpinejs";

// Component to bootstrap our site
import App from "./components/App";

// import any components you might want to use now:
import { initNavigation } from "./components/Navigation";
import { initExampleComponent } from "./components/ExampleComponent";

// Start the app!
App(() => {
  initNavigation();
  initExampleComponent();
});
Enter fullscreen mode Exit fullscreen mode

As you can see after importing alpine I import a main component called App that is responsible for bootstrap and start all components. In my components, I only export one init-function that gets called in the App-Component's callback.

Create the App-Component

The App-Component looks like the following:

// components/App.js
export const App = fn => {
  if (document.readyState != "loading") {
    fn();
  } else {
    document.addEventListener("DOMContentLoaded", fn);
  }
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Yeah, it's just as simple as it gets.

The App-Component takes only a callback function fn as an argument which will then be called if the DOM is ready to handle our JavaScript code.

Our first AlpineJS Component

Then you can create your individual components like so:

// components/ExampleComponent.js
/**
 * Initialize our component here!
 */
export const initExampleComponent = () => {
  // First, check if this component has to be initialized
  if (hasExampleComponent()) {
    // then, fire it up!
    start();
  }
};

/**
 * Checks if page has autocomplete component
 * @return {Boolean}
 */
const hasExampleComponent = () => {
  return document.getElementsByClassName("example-component").length > 0;
};

// Start our component
const start = () => {
    // initialize your alpine component here into the window object
    window.example = () => {
      return {
        isOpen: false,
        // ... and so forth
      };
    };
};
Enter fullscreen mode Exit fullscreen mode

I like this approach a lot because it is pretty transparent and you only "pollute" the main window scope if the given component exists on the site. That might be unnecessary with, for example, a navigation component because you might want to render it on every page but I used this pattern many times for small components that were used only on a few pages. It just keeps my code tidy.

Do you like this pattern? Is it something you do already when using AlpineJS?

Oh, and hi there! 👋🏻 My name is Adrian and this is my very first post at dev.to 🎉

Top comments (1)

Collapse
 
skttl profile image
Søren Kottal

Don't know if getElementsByClassName is better or faster, but you could also detect the component by doing

const hasExampleComponent = () => {
  return document.querySelectorAll("[x-data='example']").length > 0;
};
Enter fullscreen mode Exit fullscreen mode

That way, you don't have to rely on some arbitrary class name.