Tips for building a versatile client-server application with separate codebases for the JavaScript components

We are embarking on the process of rebuilding our CMS and leveraging our expertise with VueJS. Despite our familiarity with VueJS, we won't be able to create a full single-page application due to the presence of server-side rendering files (JSP).

The challenge lies in the fact that our CMS does not reside in a unified codebase. Our structure typically looks like this:

CMS essentials (stored in a separate git repository)

  • Module: user management
  • Module: content management
  • and more...

Additional modules

  • Newsletter (located in a separate repository)
  • Forms (found in another repository)
  • and more...

All these components are Maven modules utilized across multiple client projects. We dynamically include dependencies within the Maven 'customer project' (to clarify: this is done in the pom.xml of the project).

Project structure (housed in a distinct repository)

  • Dependency # 1
  • Dependency # 2
  • Client-specific module
  • and more...

Our preference for client-side development points towards utilizing VueJS components, Typescript classes (our proprietary code), and external libraries managed by node/yarn as dependencies.

I have delved into ES6 imports/modules but am faced with a new challenge:

The CMS will entail multiple build bundles, including:

  • generic.js (e.g., containing scripts for modals/alerts) a bundled JS file encompassing TypeScript classes/Vue components, etc.
  • content-management.js (relying on generic.js libraries)
  • and more...

Similarly, the newsletter module will comprise a build bundle named newsletter.js (depending on generic.js libraries).

Moreover, custom project modules will also rely on classes within generic.js.

My ideal scenario involves building the bundles separately, such as:

  • generic.js (built bundle - incorporating all underlying libraries)
  • messages.ts
  • dialogs.ts
  • cms.js
  • newsletter.js
  • custom-client-module.js

In the case of newsletter.js, I aim to utilize classes from generic.js like so:

import {messages} from "????";

messages.alert('Alert text');

I'm currently exploring the best approach to tackling this issue. It may seem complex to me at the moment, but I hope one of you can offer a valuable starting point.

Thank you in advance!

Answer №1

To implement Code splitting with Webpack, you have three options to choose from:

The available approaches for code splitting are as follows:

  • Entry Points: Manually split code using entry configuration.
  • Prevent Duplication: Use the SplitChunksPlugin to dedupe and split chunks.
  • Dynamic Imports: Split code via inline function calls within modules.

If the high level modules (cms.js, newsletter.js, ...) are independent from each other and only rely on generic.js

Directory Structure

src/
├── cms/
│   ├── index.ts
│   ├── ...
├── newsletter/
│   ├── index.ts
│   ├── ...
└── generics/
    ├── index.ts
    ├── messages.ts
    ├── dialogs.ts

src/generics/dialogs.ts

export function open(title: string) {
    console.log('Opening dialog : ', title);
}

src/generics/messages.ts

export function alert(msg: string) {
    console.log('alert : ', msg);
}

src/generics/index.ts

import * as m from './messages';
import * as d from './dialogs';

export let messages = m ;
export let dialogs = d;

src/cms/index.ts

import { dialogs, messages } from '../generics';

messages.alert('CMS started ...');

src/newsletter/index.ts

import { dialogs, messages } from '../generics';

messages.alert('Newsletter started ...');

Webpack Code splitting

1. Entry Points

This is the simplest and most intuitive way to split code. However, it requires manual effort and has some drawbacks.

In your config file, declare your modules manually using the entry object:

// webpack.config.js
const path = require('path');

module.exports = {
    mode: 'development',
    entry: {
        'newsletter': './src/newsletter/index.ts',
        'cms': './src/cms/index.ts',
    },
    module: {
        rules: [
            {
                test: /\.ts?$/,
                use: 'ts-loader',
                exclude: /node_modules/
            }
        ]
    },
    resolve: {
        extensions: [ '.ts', '.js' ]
    },
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist'),
        publicPath: "dist/",
    }
};

With this configuration, two modules will be generated in the dist folder:

               Asset      Size      Chunks             Chunk Names
       cms.bundle.js   4.6 KiB         cms  [emitted]  cms
newsletter.bundle.js  4.64 KiB  newsletter  [emitted]  newsletter

Since the cms and newsletter modules depend on the generics module, it is bundled with them.

2. Prevent Duplication with SplitChunksPlugin

Add the optimization.splitChunks configuration to the webpack.config.js file:

optimization: {
    splitChunks: {
        chunks: 'all',
        minChunks: 1,
        name: 'generics',
        minSize: 10,
    }
}

Now the generics module is in its own chunk (remember to include it in your HTML file), and the build will generate 3 files:

               Asset      Size      Chunks             Chunk Names
       cms.bundle.js  5.85 KiB         cms  [emitted]  cms
  generics.bundle.js  1.61 KiB    generics  [emitted]  generics
newsletter.bundle.js   5.9 KiB  newsletter  [emitted]  newsletter 

3. Dynamic Imports

Update src/cms/index.ts to use dynamic imports:

import(/* webpackChunkName: "generics" */ '../generics').then(generics => {
    console.log('Generics module loaded ...');
    generics.messages.alert('CMS started ...');
});

Update src/newsletter/index.ts to use dynamic imports:

import(/* webpackChunkName: "generics" */ '../generics').then(generics => {
    console.log('Generics module loaded ...');
    generics.messages.alert('Newsletter started ...');
});

You may need to adjust some Typescript compile options and possibly install a polyfill for Promise:

"module": "esnext",
"lib": [
    "es2015"
]   

Now you no longer need to explicitly include the generics module in your HTML file. When the import() is executed, Webpack will fetch the generics module from the server and cache it.

I hope this explanation helps you to kick off your project :)

Edit (response to comment):

If the modules are in separate projects, manage dependencies using npm or yarn. In the newsletter project's package.json, add generics as a dependency.

Use non-relative paths for your import. For example, the dynamic import code will look like this:

import(/* webpackChunkName: "generics" */ 'generics').then(generics => {
    console.log('Generics module loaded ...');
    generics.messages.alert('Newsletter started ...');
});

To bundle all your code, consider adding a main module that has dependencies to all other modules. Then in the index.ts of this main module, import all other modules. This index.ts should be the entry point in the Webpack config.

Remember, bundling all your code into a single file is not recommended for large single page applications, as it can result in a large file size (>8Mb). Webpack's default performance recommendation is around ~250kB per file.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Having trouble retrieving data using a custom URL in Axios with ReactJs

My knowledge of Reactjs is still new and I am currently working on a project using nextjs. I have a component called Trending.js that successfully fetches data from the URL "https://jsonplaceholder.typicode.com/users". However, when I try to change the U ...

Rotating through elements in timed intervals

After exploring various examples of how to show/hide divs with a JavaScript timeout, I am still unable to resolve my specific issue. I currently have six divs that I want to cycle through sequentially every 10 seconds, starting with div #one. Although my ...

Sending information to a personalized mat-grid element using properties

I am currently working on enhancing the functionality of the angular material mat-tree component by building a custom component. The main objective is to create a table with expandable and collapsible rows in a hierarchical structure. I have managed to ach ...

Transformation from a class component to a function component in React JS

I have a class component that I need to convert into a functional component. So, I started by refactoring the constructor. Below is the original class component: class EventCalendar extends React.Component { constructor(props) { super(props) ...

Having multiple upload forms on a single page with PHP, AJAX, and jQuery is not functioning as expected

I'm on the hunt for a jQuery-AJAX image uploader that supports multiple instances of the form on a single page. Do you have any recommendations? Here's my scenario: I have a front-end page where users can update their profiles. Upon loading the ...

"Encountering difficulties while trying to modify the QuillNoSSRWrapper value within a Reactjs

Currently, I am working on a project involving ReactJS and I have opted to use the Next.js framework. As of now, I am focused on implementing the "update module" (blog update) functionality with the editor component called QuillNoSSRWrapper. The issue I ...

The animation in the div element of Vue is not functioning as expected

I attempted to use the following example: <div id="demo"> <button v-on:click="show = !show"> Toggle </button> <transition name="fade"> <p v-show="show">hello</p> </transition> </div> It was ...

Can jQuery script be used within a WordPress page for inline execution?

Can jQuery be executed in the middle of a page (inline)? I attempted to run the following code within a custom WordPress template.... <script type="text/javascript"> jQuery(document).ready( function() { jQuery(".upb_row_bg").css("filter","blur(30 ...

List of Nodes with five links

I'm encountering an issue when trying to reference the final node. The expected output is: Linked List with 5 nodes Node Value: Head Node / Next Node Value: Second Node / Last Node Value: null Node Value: Second Node / Next Node Value: Third N ...

Is it possible to assign functions to each keystroke that does not correspond to a specific keybinding in Angular?

In Angular, there are events tied to keybindings or actions like (focus), (blur), (keydown), and more. You can bind specific keybinds to certain keys as well, such as (keydown.enter), (keydown.alt), etc. Is there a method to trigger an event only when it ...

When attempting to post data using jQuery, an object is encountered that does not have a parameterless constructor

Here is the code snippet I am using with jQuery to post data and register a band: var formData = new FormData(); var file = document.getElementById("CoverPicture").files[0]; formData.append("file", file); var Name = $('#Name').val(); var Genre = ...

Nextjs: Issues with Dropdown functionality when using group and group-focus with TailwindCSS

My goal is to make an array visible once a button is clicked. By default, the array should be invisible, similar to drop-down menus in menu bars. I am utilizing the group and group-focus classes. While the array disappears as expected, it does not reappear ...

Disappear text gradually while scrolling horizontally

There is a need to create a special block that displays fading text on horizontal scroll. However, the problem is that the block is situated on a non-uniform background, making the usual solution of adding a linear gradient on the sides unsuitable. Click ...

Leveraging set() for computed values accurately in Vue

I'm currently facing an issue with setting a computed property that happens to be an array. Within my Vue component, I have the following computed property: posts: { get () { if ( this.type == 'businesses' || this.type == 'busine ...

How can I use XPATH to target a div after choosing a nested element?

I had the task of creating a universal identifier to locate a dropdown menu following a label. There are 10 different forms, each with a unique structure but consistent format, which means I cannot rely on specific IDs or names. Instead, my approach was to ...

Display the mobile keyboard without the presence of an input element

Attempting to display the mobile keyboard on my responsive site, I initially tried placing a hidden input with the placeholder "tap here." However, due to an event firing on this input that reloads the dom, I was unable to show the keyboard. I'm wond ...

Using jQuery to revert back to the original SRC after mouse is not hovering over an element

I've been working on a script to change the src attribute for an icon. The icon I'm loading is a different color and it's meant to notify the user about the link associated with it. Currently, I have the src changing to the second icon on h ...

Encountering a blank or 404 error page in React after running npm build - let's troubleshoot where the issue might be

Upon running npm build(react), my index.html displays a blank page. To address this issue, I initially attempted the following steps: Adding the following line in package.json homepage : "./" or "." or "absolute file PATH" ...

The distance calculation is not functioning properly within the for loop

I am currently working on developing a geolocation test app. After finding some useful code for calculating the distance between two points using latitude and longitude coordinates, I am attempting to create a loop using a for loop to calculate the distan ...

I attempted to create a checkbox cell within a datatable using Vuetify, unfortunately, it is not functioning as intended

Is there a way to create a checkbox within a datatable so that it automatically checks if the user is an admin in the system? I attempted to write some code but it's not functioning correctly. While {{value==1}} does work and accurately determines tr ...