TypeScript and Webflow: A Winning Combination for Smarter Development Workflows
If you’ve always been writing your code in Webflow or CodeSandbox, then you’ll need to make sure you have a few things before getting started.
- Code Editor - The code editor is where we will write our code on our local machine. I use Visual Studio Code but there are lots of other options like Sublime Text or VIM.
Before getting started, you can verify you have npm installed by typing npm -v.
Let’s assume that we have a basic p5.js sketch already in Webflow or CodeSandbox that we want to move from Webflow’s code editor to our local development environment where we will use Typescript.
Here's the block of code we will be working with.
If you aren’t too familiar with p5.js, here’s a rundown of what’s going on in this code.
- First things first, we need to set up our canvas. We're going to create a square canvas with a width (w) and height (h) of 600 pixels. We're also defining a variable size that will be used later to determine the size of our circles.
- The setup function is a special p5.js function that runs once at the beginning of our program. Inside this function, we use the createCanvas function to create our canvas with the specified width and height.
- The draw function is another special p5.js function that runs continuously, creating an animation loop. Inside this function, we start by setting the background color to a dark gray using the background function. The argument 40 represents the grayscale value (0 is black, 255 is white).
- We also use noStroke to remove the outline from our shapes. And here's a neat trick: we use translate to move the origin of our coordinate system to the center of the canvas. This way, when we draw our shapes, they'll be centered!
- Now, here's where the magic happens! We're going to create two wavy circles that move in a circle in opposite directions.
- We start by calculating two wave values using the sin and cos trigonometric functions. We use radians(frameCount) as the input to these functions, where frameCount is a p5.js variable that keeps track of the number of frames that have been drawn. By multiplying the result by 50, we control the amplitude of the wave.
- Next, we use the circle function to draw two circles. The first circle's x-coordinate is determined by wave, and its y-coordinate is determined by waveTwo. The second circle's x-coordinate is determined by waveTwo, and its y-coordinate is determined by wave. The third argument to the circle function specifies the diameter of the circles, which we set to size.
🎉 And voilà! We've created a beautiful piece of circle art! 🎉
As the draw function loops, the frameCount variable increases and the sin and cos functions create a harmonious circular path which the two circles follows, and the result is a simple animation.
But back to business...
Run Code from our Local Computer on the Published Webflow Site
Reference screenshots below for steps 4, 5, and 7.
- Create a new folder where you like to save code on your computer. For this example we can just create a folder on the desktop called p5-typescript
- In Visual Studio Code, click on “Open Folder” in the File menu, and select the folder we created in step 1.
- Create a new file by right clicking in the explorer window and call it index.js.
- Make sure you have the VS Code Live Server Extensions Installed and Click “Go Live” down at the bottom blue bar.
- When you click “Go Live,” Live Server will start a local server on port 5500. If you put http://127.0.0.1:5500/ in your browser and click enter we’ll see our code being hosted locally.
- Save and publish, and verify that our code is running as it was before.
☝️ Important note! Since our code is no longer hosted by Webflow, the code will only run when we view the website on this computer. The IP address "127.0.0.1" is known as the "loopback address" or "localhost." It is a special IP address used by a computer to refer to itself. In other words, when a computer communicates with the IP address 127.0.0.1, it is actually communicating with itself. If you run this website on a different computer, then 127.0.0.1 is likely NOT serving code on port 5500, so the console will output a 404 error file not found.
- Open up a terminal session in VS Code from the `Terminal` menu and click `new Terminal`
- Initialize npm for this project by typing npm init.
- Keep pressing enter to initialize npm with all the default settings (we can always change these later). When complete you should see a new file in explorer called package.json.
With npm installed, we can now install packages in our local project! Let’s start with p5.js.
Installing Packages with npm
- In the VS Code terminal type npm install p5. This will create a folder called node_modules in our project, which includes all the code required for p5.js. You’ll also see a package-lock.json file appear in the explorer.
- At the top of index.js, type import p5 from “p5”.
We’ve added p5 to our local project and imported it to index.js, but we need to be careful here! I see folks think they can delete the p5.js script import in Webflow now, but we’ll have some problems if we do that! Let's take a minute to explore what happens so we can better understanding what bundlers do for us.
Immediately, we’ll get an error in console: Uncaught SyntaxError: Cannot use import statement outside a module.
If you’ve used Node.js before, you may be familiar with CommonJS require syntax. The require syntax is commonly used in a Node.js environment but is not natively supported in browsers. If you're working in a Node.js environment or using a bundler like Webpack or Browserify that supports CommonJS modules, you can use require to load the p5.js library. However, if you're working directly in a browser environment without a bundler, you won't be able to use require.
If we add the type=”module” attribute to our script tag, we’ll get another error! Uncaught TypeError: Failed to resolve module specifier "p5". Relative references must start with either "/", "./", or "../".
The browser cannot find the code to run p5.js because we are only telling Webflow to load 127.0.0.1:5500/index.js. We can try to point directly to p5 inside our node_modules folder and load the code that way, but this is a messy and inefficient way to accomplish our task.
Adding our bundler, esbuild!
Let's take a look at the features listed on the esbuild website.
- Extreme speed without needing a cache
- A straightforward API for CLI, JS, and Go
- Bundles ESM and CommonJS modules
- Tree shaking, minification, and source maps
- Local server, watch mode, and plugins
The module bundling, Typescript support, and local server features are very useful for us right now, and the others will become more useful as we get more used to working with bundlers.
- In terminal type npm install -D esbuild. The -D specified that this is a development dependency. This means the package is needed for our local environment but not in our production environment. The esbuild package is something we need at compile time, but not at run time. By making it a development dependency, we can avoid shipping extra code that is not needed.
- In package.json, add the following key/value pair to the “scripts” object: "build": "esbuild index.js --bundle --outfile=out.js".
- In terminal type npm run build. We will see a file called out.js be created. Inspecting this file, it contains not only all the code for p5.js, but also our sketch code! It’s all bundled up in one!
- Unfortunately, if we change our script src attribute in Webflow to look at http://127.0.0.1:5500/out.js instead of http://127.0.0.1:5500/index.js, our code still wont work. Why is that?
Since we have switched over to ECMAScript modules and bundlers, we need to shift our code from global mode to instance mode. When importing p5.js via CDN, it made the setup() and draw() available in the global window object. This is fine for beginners and short sketches, but introduces problems if we want multiple sketched on our website or start using other libraries that could add setup() and/or draw() functions to the global namespace.
A much better practice is to protect the global namespace by wrapping our code in a custom function. When using ECMAScript modules and accompanying import statements with esbuild, we need to modify our code a bit.
In terminal, type npm run build again to recompile and bundle our code as out.js. Go check our live website and you should see the sketch running as it was before! Progress!
What about Typescript?
It’s super easy to take advantage of Typescript now with esbuild!
- Change index.js to index.ts - Now it’s a Typescript file!
- In package.json, change our build command to compile index.ts to out.js (not index.js which doesn't exist anymore). Package.json should look like it does below.
We Have a Typescript Environment, but aren’t actually leveraging Typescript yet.
See two little dots on our import statement? Typescript is trying to tell us something. It’s looking for types, but cannot find any. Types will help us get better autocomplete and code protection, so let’s add those. If we click Quick Fix… we have an option to install @types/p5 as a development dependency. Before that, Typescript doesn’t know what p5 is, and assigns it the any type, which isn’t all that useful. We could also install the types manually in terminal by running npm install -D @types/p5.
Once we have the types installed, we can add the p5 type annotation to our sketch function parameter (p: p5), and now we have awesome autocomplete! Essentially, we are telling our code that the parameter p is of type ‘p5’. Since we now have the types for p5, Typescript knows all of the properties available on that object.
If you want a complete environment without all the setup, I highly recommend checking out the Finsweet Developer Starter. It has a lot of additional configuration and developer dependencies, including Webflow types, already installed so you can have a fantastic Typescript developer environment without all the setup. I think it’s important to walk through the setup yourself though in order to understand what all is going on with servers, modules, and bundlers so that you can leverage the pre-built solutions more effectively.