Add front-end assets

The tutorials introduced a few basic approaches to building a front-end user interface for your projects. For example, the tutorials demonstrated:

This section takes a closer look at the default front-end template, front-end configuration options, and using other frameworks to build the user interface for your projects.

How the default templates are used

As you might have noticed in the tutorials, projects include template index.js and webpack.config.js files.

By default, the index.js file explicitly instantiates an instance of the HTTP agent. This agent provides an interface for interactions between the user-facing front-end and the Internet Computer. After creating the agent, the index.js file constructs an actor for the canisters defined in your project’s dfx.json file.

If you review the code in the index.js file, you see that it performs these steps using the following code block:

import { Actor, HttpAgent } from '@dfinity/agent';
import { idlFactory as hello_idl, canisterId as hello_id } from 'dfx-generated/hello';

const agent = new HttpAgent();
const hello = Actor.createActor(hello_idl, { agent, canisterId: hello_id });

After the agent and the actor are created for the project, the index.js file provides a placeholder for the JavaScript you use to interact with the document object model (DOM) and HTML for your application.

Modifying the webpack configuration

Because webpack is a popular and highly-configurable module bundler for JavaScript-based applications, new projects create a default webpack.config.js file that makes it easy to add the specific modules—such as react and markdown—that you want to use.

If you review the code in the template webpack.config.js file, you see that it constructs aliases for the canisters defined in your project’s dfx.json file. The aliases can then be used to reference the canisters when imported as modules by the front-end.

You can see these steps in the following code block:

// List of all aliases for canisters. This creates the module alias for
// the `import ... from "dfx-generated/canisters/xyz"` where xyz is the name of a
// canister.
const aliases = Object.entries(dfxJson.canisters).reduce(
  (acc, [name, _value]) => {
    // Get the network name, or `local` by default.
    const networkName = process.env["DFX_NETWORK"] || "local";
    const outputRoot = path.join(
      __dirname,
      ".dfx",
      networkName,
      "canisters",
      name
    );

    return {
      ...acc,
      ["dfx-generated/" + name]: path.join(outputRoot, name + ".js"),
    };
  },
  {}
);

Entry and output configuration

After creating canister aliases, the template webpack.config.js file generates a webpack configuration for the front-end and adds the resulting files to the output directory for your project directory.

In many cases, you can use the default webpack.config.js file as-is, without any modification, or you can add plug-ins, modules, and other custom configuration to suit your needs. The specific changes you make to the webpack.config.js configuration largely depend on the other tools and frameworks you want to use.

For example, if you have experimented with the Customize the front-end or Add a stylesheet front-end tutorials, you might have modified the following section to work with React JavaScript:

    module: {
      rules: [
        { test: /\.(ts|tsx|jsx)$/, loader: "ts-loader" },
        { test: /\.css$/, use: ['style-loader','css-loader'] }
      ]
    },
    plugins: [
      new HtmlWebpackPlugin({
        template: path.join(__dirname, info.frontend.entrypoint),
        filename: 'index.html',
        chunks: ['index'],
      })
    ],
  };
}

Ensuring node is available in a project

Because projects rely on webpack to provide the framework for the default front-end, you must have node.js installed in your development environment and accessible in the project directory.

  • If you want to develop your project without using the default webpack configuration and canister aliases, you can remove the assets canister from the dfx.json file or build your project using a specific canister name. For example, you can choose to build only the hello program without front-end assets by running the following command:

    dfx build hello
  • If you are using the default webpack configuration and running dfx build fails, you should try running npm install in the project directory then re-running dfx build.

  • If running npm install in the project directory doesn’t fix the issue, you should check the configuration of the webpack.config.js file for syntax errors.

Using other modules with the React framework

Several tutorials and sample projects in the examples repository illustrate how to add React modules using the npm install command. You can use these modules to construct the user interface components you want to use in your project. For example, you might run the following command to install the react-router module:

npm install --save react react-router-dom

You could then use the module to construct a navigation component similar to the following:

import React from 'react';
import { NavLink } from 'react-router-dom';

const Navigation = () => {
  return (
    <nav className="main-nav">
      <ul>
        <li><NavLink to="/myphotos">Remember</NavLink></li>
        <li><NavLink to="/myvids">Watch</NavLink></li>
        <li><NavLink to="/audio">Listen</NavLink></li>
        <li><NavLink to="/articles">Read</NavLink></li>
        <li><NavLink to="/contribute">Write</NavLink></li>
      </ul>
    </nav>
  );
}

export default Navigation;

Iterate faster using webpack-dev-server

There are a few simple shortcuts you can use to iterate faster in your development environment. For example, you can choose to build and deploy only specific canisters instead of all canisters in a project.

If most of your changes are in the front-end for your application, one of the most effective ways you can iterate faster is by installing and configuring the webpack development server. The webpack development server—webpack-dev-server—provides in-memory access to the webpack assets, enabling you to make changes and see them reflected in the browser right away using live reloading.

To install and configure webpack-dev-server:

  1. Create a new project and change to your project directory.

  2. Install the webpack-dev-server in the project directory by running the following command:

    npm install webpack-dev-server
  3. Open the webpack.config.js file for your project in a text editor.

  4. Add your network host name and port information after the output section of the webpack.config.js file.

    For example, if you are using the default host and port information for local development, you would add the following to the webpack.config.js file:

    devServer: {
     proxy: {
       "/api": "http://localhost:8000",
     },
    },
  5. Save your changes and close the webpack.config.js file to continue.

  6. Open the package.json file for your project in a text editor.

  7. Add a comma after "build": "webpack" in the scripts section.

  8. Add a new line with "start": "webpack serve" in the scripts section.

    For example:

    "start": "webpack serve"
  9. Save your changes and close the package.json file to continue.

  10. Start the Internet Computer locally, if necessary, and deploy as you normally would, for example, by running the dfx deploy command.

  11. Start the webpack development server by running the following command:

    npm start
  12. Open a web browser and navigate to the asset canister for your application using port 8080.

    For example:

    http://localhost:8080/?canisterId=ryjl3-tyaaa-aaaaa-aaaba-cai
  13. Open a new terminal window or tab and navigate to your project directory.

  14. Open the index.js file for your project in a text editor and make changes to the content.

    For example, you might add an element to the page using JavaScript:

    document.body.onload = addElement;

    document.body.onload = addElement;
    
    function addElement () {
      // create a new div element
      const newDiv = document.createElement("div");
    
      // and give it some content
      const newContent = document.createTextNode("Test live page reloading!");
    
      // add the text node to the newly created div
      newDiv.appendChild(newContent);
    
      // add the newly created element and its content into the DOM
      const currentDiv = document.getElementById("div1");
      document.body.insertBefore(newDiv, currentDiv);
    }
  15. Save your changes to the index.js file but leave the editor open to continue making changes.

  16. Refresh the browser or wait for it to refresh on its own to see your change.

    When you are done working on the front-end for your project, you can stop the webpack development server by pressing Control-C.