Code Monkey home page Code Monkey logo

Comments (7)

stephancasas avatar stephancasas commented on May 28, 2024

Hi, there and thank you for being, as far as I know, the first developer to test Snowblade! In all of my testing, I've been bringing-in Alpine via CDN by including a <script src="..."> in the <head> of index.html. As far as organization, there's a couple of methods with which I've been playing in this regard.

Event-based Logic

If you're using Alpine's magic properties, $event and $dispatch to send data between your application's different sections, you can include a <script> tag or script component in the <head> of that component's primary entry point.

For example, at this moment, I'm working on an app that provides an interface to "Reports," "Clients," and "Settings." Each of these items is their own component, revealed using Alpine's x-if property and wrapped with a template tag by Snowblade's $$wrap attribute.

In the project directory, they're organized like this:

├── components
│   ├── clients
│   │   ├── clients.html
│   │   ├── clientthumbnail.html
│   │   ├── scripts.html
│   │   └── sidebar.html
│   ├── reports
│   │   ├── reports.html
│   │   ├── reportviewer.html
│   │   ├── scripts.html
│   │   └── sidebar.html
│   └── settings
│       ├── colorscheme.html
│       ├── scripts.html
│       ├── settings.html
│       └── templates.html
└── shared
    ├── buttons
    │   ├── basicbutton.html
    │   └── iconbutton.html
    └── inputs
        ├── basicinput.html
        ├── labeledinput.html
        ├── onebuttoninput.html
        ├── search.html
        └── twobuttoninput.html

You may notice each section contains an instance of scripts.html. This is where I'm presently keeping the Alpine logic and any additional helpers. Because Snowblade fulfills explicit imports before implied imports, I can write my component definition for each section like this:

<meta snowblade name="Clients">
<link snowblade href="./scripts.html">

<head>
  <Scripts />
</head>
<body>
  <!-- ... markup for clients section goes here ... -->
</body>

At runtime, Snowblade will render and then set-aside anything in the <head> of each component. Right before delivering its final output, these scripts are carried to the <head> of index.html, so they're made available to your components before your body is loaded in the browser.

It makes for a very heavy head section, but... who cares? Once it's in the browser, it doesn't really matter. If you'd prefer to keep the scripts in-line throughout the document, you can instead apply your <Scripts /> expression at the end of your component, and completely omit the <head> and <body> tags.

State-based Logic

If you're using @ryangjchandler's Spruce state-management library to operate a stateful frontend, you can extract your Alpine scripts into completely separate js files that you then import like regular <script src="..."> tags. This method is quite nice, because I can leverage the capabilities of TypeScript, import Alpine as a dependency, and package it with my app's logic using Rollup. I'm currently playing around with this and trying to find what approach I like best, but I still haven't quite found the workflow I want to use permanently.


As I've started to use Snowblade in production, I've learned many things about what I want out of it and how I want my projects' structures to evolve moving forward. There's been no 100% right answer for me, so as you get a chance to experiment with it, please feel free to share your insights!

Also, a pretty cool new feature and several bugfixes are getting pushed today, so please keep an eye out for that. 🥳

from presto.

go4cas avatar go4cas commented on May 28, 2024

Thanks for the detailed reply, @stephancasas!

Yeah, I've done a proof-of-concept with alpine component logic. Also using Rollup to build and package. So, the release of Snowblade came at the right time.

I'm going to play around a bit more, and I'm sure I'll bugging you soon with more questions.

Keep up the great work!!

PS: Have you ever thought about client side routing in alpine apps? I'm trying with that idea at the moment.

from presto.

stephancasas avatar stephancasas commented on May 28, 2024

Outstanding. I'd definitely be interested in seeing what structure you wind up adopting. Every time I think I've landed on a decision, I think of another consideration.

Please feel free to keep the questions coming. Having pushed the latest feature, attribute coalescence control, I'm going to put a demo video together and share it with the Alpine community on Discord. With any luck, Snowblade will get some clicks and some additional test users.

Thank you for the kind words!


I'm definitely interested in client-side routing, and had that goal in-mind at the start of this project. As I start to work with Snowblade a bit more in production, this is something with which I'd like to experiment heavily. Any considerations you've had in this regard are of interest!

from presto.

go4cas avatar go4cas commented on May 28, 2024

Thanks again, @stephancasas! So, I finally got some time to work on this again.

This is the draft model I came up with:

My components are organised like this:

├── components
│   ├── my-component1
│   │   ├── index.html
│   │   ├── index.js
│   │   └── store.js
│   ├── my-component2
│   │   ├── index.html
│   │   ├── index.js
│   │   └── store.js
│   └── my-component3 
│   │   ├── index.html
│   │   ├── index.js
│   │   └── store.js
└── shared
    ├── button
    │   ├── index.html
    │   ├── index.js
    │   └── store.js

As you can see, for each component I could have the following:

  • index.html: the template of the component
  • index.js: the logic of the component
  • store.js: the local state of the component

My app.js looks like this:

import { registerComponents, registerStores } from './utils.js'
import * as components from 'glob:./components/**/index.js'
import * as stores from 'glob:./components/**/store.js'

;(async () => {
  await registerComponents(components)
  await registerStores(stores)
  await import('alpinejs')
})()

I have some helper methods in utils.js:

import Spruce from '@ryangjchandler/spruce'

export const buildComponent = (data, methods = {}, init = () => {}) => {
  return () => { return { init, ...data, ...methods } }
}

export const registerComponents = components => {
  Object.values(components).forEach(component => Object.assign(window, component))
}

export const registerStores = stores => {
  for (let store in stores) {
    Spruce.store(Object.keys(stores[store])[0], stores[store][Object.keys(stores[store])[0]])
  }
}

So, during build time, the packaging will recursively work through all components, and import the logic and component state objects, and presto will sort out the markup.

This allows me to have n-number of nested components in my layout, and I can decided to have any permutation of:

  • A simple component that just renders, without logic and state
  • A component with a template and some logic
  • A complex component with lots of logic and state

I would really appreciate your feedback on this, and if you could point out any potential issues my approach may have.

Something in the back of my mind is how to effectively package load these ... at the moment all templates and all logic and all state objects will be packaged. What impact would this have on bigger apps, with 100's of components? Is there a way to lazy load?

from presto.

stephancasas avatar stephancasas commented on May 28, 2024

I really like this idea! I've got a somewhat similar experimental setup going which uses the same forEach() method to work through bringing in component properties and methods. At the time of writing it, I thought I was crazy, so I'm glad I'm not the only one who had the same idea, haha.

My original concept was a class-based method where each component was its own class and App was the root class. At runtime, the constructor for App received an API key (or session token, etc.) as an argument, and then each component/module was initialized.

The constructor for each component received instance as an argument, which is to be the single instance of the root App class. As Alpine can only handle (to my knowledge) objects with a null prototype or the prototype Object, when each component was done initializing, I used Object.keys() and Object.getOwnPropertyNames(Object.getPrototypeOf()) to cast each class-based component to an object with null prototype.

After that process was completed, each instance of this was reassigned to a Spruce store, and reactivity worked at least up to the two levels of nesting that I tested. Without question, this was a whole lot of extra work, but I've gotten far too comfortable with being able to use TypeScript to consider abandoning all of the benefits that using interface and class offer.


As of yet, I'm not totally sure on how I'd fractionalize the loading of this. With regards to Presto, lazy loading is something I wanted to have working on day one, but getting things working in a Node environment alone proved to be far more challenging than I'd initially expected.

In my mind, I'd like to be able to either bring in Presto via CDN or have it rolled-up as a dependency. Once it's loaded in the browser, it would listen for DOMNodeInserted to see if injected markup matched a component to which it had access. If a component name matches, it would render and yield the required markup as described by the app logic.

Right now, Presto uses cheerio to handle DOM traversal and markup injection, so making the jump to jQuery or another selector library for the browser shouldn't be too far of a stretch. However, I'd really like to get away from using either because a good 30% of my time developing was in writing different escape sequences and reordering execution to combat the formatting and logic that cheerio applies. It's almost like Presto should be using an XML library, which may eventually be what happens.


Regarding performance of larger apps, I don't presently have an answer. Presto essentially yields "one giant DOM," with Alpine running the show behind the scenes. I'm currently running one of my client's apps in production in this way, and performance hasn't been an issue thus far.

This kind of development sort of turns the current standard model upside-down in that the HTML becomes the larger payload instead of the bundled app logic. I've reverse-engineered plenty of Angular apps where their logic bundles were gigantic, but the entry point markup was only the body tag with a <script> initializing the logic, and thus the eventual markup contained within.

I may be wrong, but there may actually be performance benefits to supplying the markup up-front in that the browser doesn't have to first load the app bundle, and then also render the templates contained within it.


I've got a demo app up on GitHub now which takes a loose approach to what I described above, but I'd really like to see how things can be improved in the ways we've discussed here. Your organization structure is really good in that components are nested in directories that provide for relevant context, and thus messing around with very-specific component file names isn't necessary. In the next couple of weeks, I've got a new project on my desk where I'll be using Presto to do 100% of the component management. As I get to working on this, I'm going to try implementing what you've mentioned as well as some of the things I've tried on my own, too. Throughout that process, I'll share what I find works well!


Thank you for your feedback and patience. The last week has been busy with trying to get the documentation in-shape for a good preview and then for a YouTube demo video. For sure, please keep me updated with any progress you're making!

from presto.

go4cas avatar go4cas commented on May 28, 2024

@stephancasas, here's the first draft of my planned boilerplate: https://github.com/go4cas/wasp-template. Some feedback would be great, please.

from presto.

stephancasas avatar stephancasas commented on May 28, 2024

This is superb!

Your approach to isolating component markup; logic; and store, then zipping it all up with the helper functions and rolling-up with Vite is excellent. Being completely transparent, I hadn't even thought that far ahead myself!

In the next week, I've got a report due for a client where I think I can leverage your model as the scaffolding. As I work through building it, I'll definitely share any of my findings.

from presto.

Related Issues (4)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.