Breaking it Down
Consider the following basic example of a carousel (thanks Slick JS). It’s driven by some dynamic data source, because after all, this is really the point of using a JS framework in the first place. Get the code at https://github.com/snellcode/js-anti-framework/, and see a demo at https://snellcode.github.io/js-anti-framework/.
What we have here are the bare bones of a framework. We have an html view (index.html, including a <script> template), we have an ES6 controller class (src/components/users-carousel/users-carousel.es6), there are SCSS styles (src/components/users-carousel/users-carousel.scss), and the code is organized by components. Using components as a top level structure is a great way to organize your code, so that each component will target it’s specific goal, and contain all the relevant code in one place. They can be as simple or complex as you need. For example, if you find one of your components is more complex you can add more files and/or folders into that component, including whatever you might need. This is in line with the emerging standard of Web Components, which encourages re-usable elements that bundle all their required code into a stand-alone package.
Let’s take a closer look at our component controller code (src/components/users-carousel/users-carousel.es6). We can see in the main.es6 file (src/main.es6) that we get a new instance of the controller for each instance of .users-carousel rendered in our html. So when you refer to this, you are just referring to that one instance. This will allow you to have several of the same components on one page. Looking at the constructor, we can see it initializes this.users as an empty list, defines our template html, and finally runs this.update(). There are a few extra properties included to deal with filtering the users by group (allUsers, groups, activeGroup).
The Basic Anti-Framework
In this basic ‘anti-framework’, we have to do everything ourselves. There is no automatic data binding like in real frameworks. Instead, we simply call this.update() any time our state/model changes. When we update, we teardown any JS events, and fully re-render the view with the updated data. This one-way data flow is typical of React Flux/Redux, and many other frameworks are adopting it as well. The idea is that when you change your state (in this case this.users, or this.activeGroup), you then completely re-render the view that is consuming that data. This may seem counter intuitive and inefficient, however as your app becomes more complex, this approach really does scale up in a manageable way. The cost of re-rending is actually smaller than you may think, especially in modern devices and browsers.
Clearly there are a lot of improvements we could make, as this example is very basic. In a real world usage, we would have several templates, one for each portion of the view. We would have a larger tree of state data, and our update logic would become more complex. We can manage this complexity by using Flux/Redux style reducers to enforce transactional state changes in a centralized way. At a certain point, we may consider using a real framework if it does become too complex. But then again, if we target our components specifically and narrowly for their purpose, we can end up with a bunch of simple components that work together to make the whole.
Requires node and gulp. If you don’t have them, here’s a good way to get them (Linux).
When you have node and gulp, install the project using npm install. After that, use gulp build to build dist files. If you are working on the files, use just gulp, and this will watch for file changes and will run build when you save changes.
You will also need to serve the files to the browser. An easy way to do this is running php -S localhost:8000 inside the project folder (in a different terminal than gulp). Then you can direct your browser to http://localhost:8000/ to see the demo.