In this tutorial we will learn how to serve an external JavaScript file to be referenced by a HTML file, using the ESP32. The ESP32 will be running the Arduino core and the HTTP async web server libraries.
Note that this post will be very similar to the previous one where we explained how to serve an external CSS file, to be used in a HTML page, both served by the ESP32. So, the same way it makes sense to separate CSS (appearance) and HTML (content), it also makes sense to separate JavaScript (behavior).
Furthermore, in more complex solutions, the same JavaScript functions may need to be reused across multiple HTML pages, which means that by including the same JavaScript file we avoid code duplication.
Since this tutorial is focused on the end-to-end system, both the JavaScript and HTML codes will be very simple.
Additionally, in order to simplify the process of serving the files, we will leverage the ESP32 SPIFFS file system. So, instead of having to define big hardcoded strings in the Arduino code with the JavaScript and HTML code, we will serve the content from the file system.
In order to upload the JavaScript and HTML files to the ESP32 SPIFFS file system we will use this Arduino IDE plugin. This tutorial explains how to get started with it.
In short, after installing the plugin, we need to go to the Arduino sketch folder where we are developing the code and create a folder named “data“. There, we should place both the HTML and JavaScript files (we will cover their content in the sections below). This is illustrated in figure 1.
Figure 1 – Data folder with the files to upload to the ESP32.
After that, we simply need to go to the Arduino IDE and, under the Tools menu, click the “ESP32 Sketch Data Upload” option. Note that the files will be uploaded to the root of the ESP32 file system and will have the same name they had on the computer from where they were uploaded.
In my case, as shown before in figure 1, the files were named “test.html” and “test.js”. So, in the ESP32, their paths will be “/test.html” and “/test.js“, respectively.
The tests from this tutorial were performed using a DFRobot’s ESP32 module integrated in a ESP32 development board.
Since the objective of this tutorial is to explain how to serve external JavaScript files from the ESP32 and not to focus on JavaScript coding, our script will be really simple. So we will just define a function that will open an alert window.
We will call our function openAlert and in its implementation we will simply call the alert method on the window object, passing as input a string with the content to be displayed. In our case, it will be a “Hello World” message, just for demonstration purposes.
function openAlert() { window.alert("Hello World"); }
Note that this function should be available to be called in a HTML file that includes this JavaScript file.
We will also keep the HTML code very simple, and it will basically consist on a button that will invoke the previously defined JavaScript function when clicked.
So, in order to include the JavaScript file, we need to use a script tag and set its src attribute to point to our JavaScript file. Since the file will be retrieved from the same server that is serving the HTML code, then we should use a relative path, which will be the name of the JavaScript file.
Note that we will include this external JavaScript file inside the head section of the HTML document.
Next, in the body section, we will have a button element that, when clicked, will invoke the openAlert function defined on the JavaScript file. The full HTML code can be seen below.
<!DOCTYPE html> <html> <head> </head> <body> <button onclick="openAlert()">Click me</button> </body> </html>
We will start by importing all the libraries we need to develop our web server application. The first one is the WiFi.h, so we can connect the ESP32 to a WiFi network. The second one is the SPIFFS.h, which is needed in order to interact with the SPIFFS file system. Finally, we need the ESPAsyncWebServer.h library, which exposes all the functionality needed to configure the HTTP web server on the ESP32.
In order to be able to connect the ESP32 to the WiFi network, we will need to provide the credentials of that network, more specifically the network name (SSID) and the network password. We will store them in two global variables, so we can easily change them.
Finally, still as a global variable, we need to declare an object of class AsyncWebServer, since this class exposes the methods needed to setup the HTTP web server on the ESP32. As covered in previous tutorials, the constructor of this class receives as input the number of the port where the ESP32 will be listening to incoming requests.
#include "WiFi.h" #include "SPIFFS.h" #include "ESPAsyncWebServer.h" const char* ssid = "yourNetworkName"; const char* password = "yourNetworkPassword"; AsyncWebServer server(80);
After the library includes and global variables declaration, we will move on to the Setup function. As we usually do, we will start by opening a serial connection, to be able to print some content to the serial port.
Followed by that, we will mount the SPIFFS file system, so we can later access the files to be served to the HTTP clients. Note that we are not going to directly interact with the file system since the HTTP web server framework handles that for us, but we need to make sure to mount the file system before the framework starts using it.
After that, we need to connect the ESP32 to the WiFi network. If you need a more detailed tutorial on how to connect the ESP32 to a WiFi network, please check here. After the connection is established, we print the local IP assigned to the ESP32 on the network, so we can reach it later with a HTTP client.
Serial.begin(115200); if(!SPIFFS.begin()){ Serial.println("An Error has occurred while mounting SPIFFS"); return; } WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi.."); } Serial.println(WiFi.localIP());
Now we need to configure the routes of the server. The first one will be responsible for serving the HTML page and, for simplicity, we will call it “/html”.
Since this is a route that the client is going to use to fetch the HTML content, it makes sense to only listen to HTTP GET requests.
The route handing function implementation will be very simple and it basically consists on returning back to the client the HTML file that we have previously uploaded to the ESP32 file system.
To do it, we just need to call the send method on the AsyncWebServerRequest object pointer that is passed to the handling function by the HTTP web server framework. Since we receive a pointer and not the actual object, we need to use the -> operator to call the mentioned method.
As first input, we will pass the SPIFFS object, so the framework is able to interact with the file system. As second input, we need to pass the full path to the file we want to serve (remember from the introductory section that the file will be on the “/test.html” path on the SPIFFS file system).
Finally, as third argument, we pass a string with the content-type, which will be used by the client to know how to interpret the content returned by the server. In our case, since we are serving a HTML file, the content-type is “text/html“.
server.on("/html", HTTP_GET, [](AsyncWebServerRequest *request){ request->send(SPIFFS, "/test.html", "text/html"); });
Now, we will need an additional route that will be responsible for serving the JavaScript file. The route name should match the name of the file, accordingly to what we have specified in the HTML code. So, we will call it “/test.js“.
Like the previous one, this route will only listen to HTTP GET requests, since the client will only fetch the JavaScript file and should not be able to perform any other action.
In the handling function implementation we will simply return back to the client the JavaScript file, again using the send method of the AsyncWebServerRequest object pointer.
In this case, the full path of the file is “/test.js” and the content-type is “text/javascript“, since we are serving a file that only contains JavaScript code. This is different from serving a HTML file that contains JavaScript functions defined in a script tag and, if that was the case, then we would use “text/html”.
server.on("/test.js", HTTP_GET, [](AsyncWebServerRequest *request){ request->send(SPIFFS, "/test.js", "text/javascript"); });
After configuring all the routes, we need to call the begin method on the server object, so it starts listening to incoming HTTP requests from clients. The final code can be seen below and already includes the call to this method, and also the empty Arduino loop function, since we don’t have any additional logic to implement.
#include "WiFi.h" #include "SPIFFS.h" #include "ESPAsyncWebServer.h" const char* ssid = "yourNetworkName"; const char* password = "yourNetworkPassword"; AsyncWebServer server(80); void setup(){ Serial.begin(115200); if(!SPIFFS.begin()){ Serial.println("An Error has occurred while mounting SPIFFS"); return; } WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi.."); } Serial.println(WiFi.localIP()); server.on("/html", HTTP_GET, [](AsyncWebServerRequest *request){ request->send(SPIFFS, "/test.html", "text/html"); }); server.on("/test.js", HTTP_GET, [](AsyncWebServerRequest *request){ request->send(SPIFFS, "/test.js", "text/javascript"); }); server.begin(); } void loop(){}
To test the code, simply compile it and upload it to your device using the Arduino IDE, with support for the ESP32 Arduino core. Once the procedure finishes, open the Arduino IDE serial monitor and copy the IP address that will be printed when the connection to the WiFi network is established.
Then, open a web browser of your choice and type the following on the address bar, changing #yourIp# by the IP you have just copied.
http://#yourIp#/html
You should get an output similar to figure 2. As can be seen by the developer’s console (I’m using Google Chrome on the example below), after accessing the HTML page, an additional request is made to the server in order to fetch the JavaScript file that is needed. The server then returns the file correctly, as can be seen by the 200 HTTP status code of the response, which means success.
Figure 2 – HTML page served by the ESP32.
After that, simply click the button of the web page. You should get an output like the one shown below at figure 3, which shows the alert box that was defined in the JavaScript file.
Figure 3 – Alert box rendered after clicking the button.