This is a tutorial for people wanting to share javascript code between the sever and the front end.
This is useful in cases where your site loads server side but then performs similar actions client side.
One way to do this using the Node/Express model/view/controller layout is to create your own local NPM modules, and that is what this tutorial teaches you.
The main steps are:
Create a folder in the project that will be your new npm module. Call it something like “sharedFunctions”
Navigate to the folder then run npm init to start a new package.json
Name the package as you like
add "private: true, to the package.json to keep from accidentally uploading your package
Create an index.js in the new module file and define functions there like
In your client side (or server side) javascript create an index.js file that will import the functions from your module. I.E.
const clientsideJS=require("package-name"); //The name you gave your npm package
myExampleOutput = ClientsideJS.example(2) //2 = parameter to pass to the function in the npm module
//Test the code
console.log(myExampleOutput(2));
After that run npm install ../path/to/npm_package/ to install the package you just made! Not this is different from the usual npm install
You can then install a bundler like parcel to bundle your module into a package that will be included client side
Parcel is nice because it doesn’t need any configuration. You can create an index file and import JS files, TypeScript files, SCSS files, or many other languages, and it will build the output for you as compressed, browser-friendly JavaScript. If you don’t need anything more complicated than that, it’s great.
Here’s an example that uses SCSS (compiles to CSS) and TypeScript (compiles to JS), and then inlines the CSS and JS code in the compressed HTML output.
The left sidebar shows the file structure.
The middle pane has a TypeScript file (top), an SCSS file (middle), and an HTML file (bottom). The HTML file directly imports the .ts and .scss file without any more information. (Webpack would require a lot more configuration and setup.)
The right pane shows the build output. The CSS and JS have been inlined and everything is minified. (Parcel still added about 1.2 KB of weight to the JS.)
Hmm I like parcel pretty well, but I have a few more questions.
Is there a way I can have the bundle file saved as something other than index.js? How is cache busting going to work on the clientside?
Right now I am still figuring out function import and exports.
So I have a shared-functions folder that contains functions I want to share between front end and back end.
When I “export” this function to the client side JS bundle I do it by defining the function as we went over yesterday. So in the index.js file of the shared-functions folder I have
exports.addFood=function(numberIn)
{
var numOut=numberIn + 1;
return numOut;
}
Then in the index.js of my clientSideJS folder I have
const tools=require("../shared-functions");
This lets me import those functions from the index.js and I can call them using: tools.addFood
Now the question is, how can I have functions in other files in the shared-functions folder?
I tried making a new file called addFood.js and putting the function there.
Something like this:
But parcel breaks at this point. Trying different variations doesn’t work. So I am wondering what is the way to have multiple files in the shared-functions folder so that parcel can find and bundle them?
I just woke up, and this is untested, but you might be able to generate a random string when the server boots and then append it to the script’s URL. You could try to do it with a Handlebars helper like this (unless someone else here has a better idea).
EDIT: actually, what I originally posted might generate a new date on every page load, so it wouldn’t be ideal. I moved it out of the helper function, so I think this will only generate the time stamp once when the server starts, but it should be double-checked to make sure it isn’t changing on every page load.
// Create a new timestamp in hex format that would be generated
// every time you start the server. Example: "1723dc22d5b"
const currentTime = Date.now().toString(16);
// I don't remember what syntax you're using to define helpers, but something
// like this might work.
hbs.registerHelper('cacheBuster', function() {
return currentTime;
});
The program is just a random example. The app.js file could be any file that uses the tools package. Inside the tools directory are various modules (files) that perform various types of functions. tools/conversions.js has functions for conversions, and tools/data.js has some fixed data related to the tools. The tools/index.js file imports the other files in that directory and exports them in a single object.
You can then do:
// import all the functionality from the directory
const tools = require("./tools");
// convert Fahrenheit to Celsius
tools.f2c(100);
// find out what scale is used in Spain
tools.countries["Spain"];
I left some notes in the code. Let me know if you want me to walk through it in more detail.
By the way, there is some ES6 in those files I posted in my last comment, so some of that syntax might not work in older browsers if you don’t transpile it. Parcel will transpile it automatically, so you don’t have to worry about that kind of syntax if using Parcel.
So I just want to note, one stumbling block for me was not realizing when you do module.exports you are either exporting a function or an object. When you include the {} it becomes an object. So my functions worked after removing those.
Also, I did not know you could console.log a function.
Learned both these things thanks to Carlos at the meetup today!
but if your file has multiple functions, you can export them like this:
// filename: greetings.js
function hello(name) {
return `hello ${name}`;
}
function goodbye(name) {
return `goodbye ${name}`;
}
// Esperanto version
function saluton(nomo) {
return `saluton ${nomo}`;
}
module.exports = {
hello,
goodbye,
saluton,
};
and import them like this:
// import file greetings.js
const greetings = require("./greetings");
// use a prefix, since it's an object with functions on it
greetings.hello("Alice");
greetings.goodbye("Bob");
greetings.saluton("mondo");
or just import one or more of them individually like this in order to use them without the prefix:
// The names on the left should match keys on the exported object
// from the file greetings.js. This imports just 2 of the 3 functions.
const { hello, goodbye } = require("./greetings");
// refer to the functions without prefixes now, because they are just
// functions that were extracted (destructured) from the object
hello("Alice");
goodbye("Bob");
If you export it as an object you can add more functions later. If you don’t want to use the prefix when calling the function, you can import them with the curly braces to destructure the function:
Destructuring picks items out of objects and arrays. A simpler example that you can run in the browser is:
// this could be any object
var food = {
foodName: "carrots",
vitamins: "a lot",
taste: "good",
};
// this extracts two key-value pairs from the object
var { foodName, taste } = food;
console.log("foodName", foodName); //=> "carrots"
console.log("taste", taste); //=> "good"
I have another question related to sharing code between server and client.
Basically when the page first renders, it is server side. But at that time, I also want to define global state that can be used and modified later by the client.
Right now, I just have handlebars render that with the following code:
<script>
var globalJSON = {{{JSON_FROM_SERVER}}};
</script>
This is working for now, but I am worried about it breaking after modification and tree shaking. What is the best way to pass this data to the client?
My hands are hurting a bit today so it’s hard to type at the moment but I could look on a call if you want. If they are feeling better later or tomorrow I can type a reply then.