JS Aggregation Modules with OOP Routing


We discovered an interesting situation in our development process that's worth sharing with the world! So if you're using JavaScript ES Modules, and you're also using any server-side Object Orientated Programming URL routing functionality, then this article is for you!

About JavaScript ES Modules

We're big fans of Open Source culture here at Dousic Media, and many of the developers on our dev teams are active Open Source coders. So that means we're all about the emerging patterns and code paradigms provided by the exciting ES Module functionalities.

However in our case, we're transitioning from a legacy code base to a much more performant and DRY code base. So our use of ES Modules in an existing server-side rendered application... is unconventional yet still very powerful. But there's a major "gotcha" we've found, that luckily has an easy solve! So we wanted to share what we've learned in case anyone else out there runs into the same dilemma.

In a nutshell, the issue we found in our stack, had to do with the concept of using Aggregating modules. You can read about the concept here on MDN, and can also check out Mozilla's awesome examples here in their example repo.

About OOP Routes

On the server-side, the issue that chokes Aggregated JavaScript Modules, is a feature that most OOP back-end frameworks include out-of-the-box... routing options! Routing options allow developers to detach the physically requested URL from the code that's served. This gives devs the flexibility to name project files things that makes sense to the devs, while still keeping the site/app URL that users will see in language that makes sense to the users. Everyone wins!

The Aggregated JavaScript Gotcha

If you had time to check out the ES Module examples, you might have noticed that each level of file inclusion is typically coded as a relative URL. That's because with JavaScript running JS Module code running in a browser, the browser can find files based on where the browser is in your directories.

But in the case of re-routed URL's, all the sudden where the browser thinks it is... isn't really where the browser actually is in relation to your project's directory heirarchy! So that means... sadly any relative paths to your modules won't work correctly if there's a difference between the URL and the file heirarchy organization. This is especially problematic in scenarios where a URL has multiple segments for controllers to read/process.

The Solution

In the end, the solution was simple. However it's also somewhat strict, and for now it means whenever in this routing/modules scenario, the first step is to simply NOT use Aggregated JavaScript Modules.

Instead, you'll want to declare an absolute URL directly to your module file, thus skipping the aggregated module entirely.

To understand the situation and the solution, let's get some pseudo code going!

A Pseudo Example
1. Let's pretend our URL is: https://mysite.com/
2. Consider this Paragraph Module file's path and code from the public root (.) directory of our URL's server: ./js/modules/elements/Paragraph.js
                    
                        class Pargraph
                        {
                            constructor(text) {
                                this.text = text;
                            }

                            generate() {
                                let paragraph = document.createElement('p');
                                let text_node = document.createTextNode(this.text);
                                paragraph.appendChild(text_node);
                                return paragraph;
                            }
                        }
                        
                        export { Pargraph };
                    
                
3. Now consider this Aggregation Module file's path and code:./js/modules/Elements.js
                    
                        export { Pargraph } from './js/modules/elements/Paragraph.js';
                        export { Icon } from './js/modules/elements/Icon.js';
                        ...
                    
                
4.

And now let's use the code on a page located on a URL that's just like this very page:https://mysite.com/blog/my-awesome-blog-article

But keep in mind that this MVC view file is located somewhere like this on the server:./app/views/my-blog-article-view.php

And additionally to the browser, the user is currently located on a root:./index.php file!

                    
                        <?php 
                            
                            $my_paragraph = 'This would really be a paragraph from a database that is defined in a controller for this view, but for sake of simplicity we are just defining it as a string here.';

                        ?>
                        <!doctype html>
                        <html lang="en">

                            <body></body>

                            <script type="module">

                                // This standard call definitely won't work!
                                import { Paragraph } from './js/modules/Elements.js';

                                // And this PHP hack won't work either!
                                import { Paragraph } from '<?php echo url('./js/modules/Elements.js'); ?>';

                                // Neither will this standard bypass of the Aggregated Module file due to URL routing!
                                import { Paragraph } from './js/modules/elements/Paragraph.js');

                                // But this PHP hack does work :) and resolves the file path to the module from https://mysite.com/blog/my-awesome-blog-article
                                import { Paragraph } from '<?php echo url('./js/modules/elements/Paragraph.js'); ?>';

                                // so now we can do JavaScript stuff! Yay!
                                let my_php_db_paragraph = new Paragraph(<?php echo $my_paragraph; ?>);
                                document.body.appendChild(my_php_db_paragraph.generate());

                            </script>
                        
                        </html>

                    
                
TL;DR

In the end, this isn't a common way to work with JavaScript Modules. The secret is to bypass any Aggregating Modules and code absolute paths to your Module scripts. Once they're being imported into JavaScript memory, the local paths take over and will properly find any nested imports used inside a Module JS Class without any route errors.

This unconventional approach is needed in the case of our app, because we are working to get away from a legacy code base and forward to a much more modular JavaScript-focused application that can be run offline. That functionality will help our creators maximize their use of the app as well as help them monetize their content whenever and wherever they find themselves. Our migration process will also allow us to iterate into advanced JavaScript based interfaces for our users, by checking in with our community often to find out what can be better and what is working great (and should be repeated elsewhere!)

Because here at Dousic Media, every detail is important. That's because we're dealing with the hopes and dreams of creators everywhere. And we feel strongly, that their best contributions should always be matched with our best, too!

Happy coding!