# Node.js project with Substrate

This tutorial will guide you through creating a Node.js project containing Substrate documents, using npm (opens new window) and rollup (opens new window).

It demonstrates how to use the JavaScript output of Substrate documents outside of the Substrate viewer, in practical applications. While the starting point here is an entirely new project, Substrate documents can be integrated in an existing one just as easily.

# Setup a project

Create a new folder for the project.

In this folder, run npm init to initialize a Node.js project. When prompted for the entry point, enter app.js.

# Install and configure rollup

Install rollup and the rollup plugin for Substrate by running these commands one by one:

$ npm install rollup --save-dev
$ npm install rollup-plugin-substrate --save-dev

Create a configuration file for rollup in the project's folder: rollup.config.mjs

Make sure to use .mjs for the extension rather than .js, otherwise rollup will throw an error when trying to use the Substrate plugin.

Fill that file with the following content:

import substrate    from 'rollup-plugin-substrate'

export default {
    input: 'app.js',
    output: {
        file: 'app-bundle.js'
    },
    plugins: [
        substrate()
    ]
}

This basically instructs rollup to use app.js as our entry point, and package everything imported from there into app-bundle.js. We're also preparing rollup to use the Substrate plugin.

# What and why

Rollup is a JavaScript module bundler. It bundles together the JS code used by an application, starting from an entry point and working its way down the depencency tree. It's quite useful for combining multiple code chunks into one distributable file.

By default, rollup will properly resolve one type of dependency: local JavaScript files exporting ES modules. rollup-plugin-substrate gives it the ability to resolve Substrate documents as well.

TIP

If you'd like to also to import commonJS modules or installed Node.js modules in a project, you'll need @rollup:plugin-commonjs or @rollup:plugin-node-resolve respectively. Both of these rollup plugins are fairly frequent in Node.js build setups and don't interfere at all with the Substrate plugin.

# Add code files

In the project's folder, create app.js :

import foo from './foo.explorable.md'   // a Substrate document which outputs an ES module
import bar from './bar.js'              // a typical JavaScript file which outputs an ES module

console.log(bar(foo.name))              // output a bar joke featuring foo's name

Then a Substrate document, foo.explorable.md :

#Foo

It's got some incredible properties.

```js
const foo = {
    name: 'Foo',
    amazing: true,
    great: true,
    equilateral: false
}
```
##Export

```js
export default foo
```

and bar.js:

export default function bar(name){
    return `${name} goes to a bar. Oops they pass below it. I guess they set the bar too high!`
}

Finally, create index.html and have it use app-bundle.js as a script :

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script src="app-bundle.js"></script>
</body>
</html>

# Build

Now let's actually bake app.js and its dependencies into app-bundle.js.

In package.json, remove the placeholder "test" script and instead add one for unleashing rollup's magic:

"scripts": {
    "watch": "rollup -c rollup.config.mjs --watch.chokidar"
},

"rollup -c rollup.confic.mjs" would have been sufficient for building app-bundle.js once. However, adding --watch.chokidar will make rollup also watch for changes in app.js and its dependencies, and rebuild app-bundle.js immediately after.

Run the script in the terminal :

npm run watch

app-bundle.js should have been created.

Open index.html in a browser. Not much to see here, isn't it? For now, everything this app does is importing modules from foo.explorable.md and bar.js. Let's put these modules to incredibly good use.

Add this one line to app.js :




 

import foo from './foo.explorable.md'   // a Substrate document which outputs an ES module
import bar from './bar.js'              // a typical JavaScript file which outputs an ES module

console.log(bar(foo.name))              // laughter!

As soon as you save your changes, rollup rebuilds app-bundle.js

Now refresh the page in the browser. An hilarious story featuring 'Foo' should be logged to the console!

It is the result of passing information obtained from foo.explorable.md to the function obtained from bar.js. Which means that modules exported from both file types were succesfully imported. Good job!

TIP

While integrating Substrate documents to such a project, it's always possible to run substrate in the project's folder to visualize these documents in their glorious optimized-for-humans form.

# Resolve absolute URLs

As we've seen in previous tutorials, Substrate supports importing modules from absolute URLs, often pointing to remote CDNs. This is quite handy, namely, to use Node.js modules without npm.

However rollup, by default, doesn't bundle modules imported from these dependencies into the bundled JS file. Rather, it just copies their import statement as-is in the bundled JS file. This, in turn, causes an error on the page: Uncaught SyntaxError: Cannot use import statement outside a module.

# Fetch modules on page load

This can be solved by adding the <script type="module"> attribute to the <script> tag for the bundled JS file. When the page loads, all imports statements in the bundled JS file will be resolved by fetching the module their URL points to, before the rest of the code executes. This is, in fact, the same behavior as when viewing documents in the Substrate viewer.

The catch is that this will only work in browsers supporting modules natively. At the time of writing, that is the the case of most modern browsers (opens new window).

# Bundle them in with a plugin

But what if we'd like to also target browsers deprived of that feature? It is, after all, one of the reasons for using a JavaScript bundler.

Well, good news. It's possible - and almost just as simple - to package modules imported from absolute URLs into the bundled JS file, using another rollup plugin.

Let's try it out!

In foo.explorable.md, add position as a property of foo :






 


const foo = {
    name: 'Foo',
    amazing: true,
    great: true,
    equilateral: false,
    position: vec2.fromValues(100,200)      // define a 2D point
}

And add a JavaScript block with an import statement for vec2 :

##Dependencies
```js
import { vec2 } from 'https://cdn.skypack.dev/gl-matrix'
```

In app.js, make use of that new information to enhance an already amazing story. And, since it's so good, let's add it to the page instead of just outputing it to the console:




 
 
 
 

import foo from './foo.explorable.md'   // a Substrate document which outputs an ES module
import bar from './bar.js'              // a typical JavaScript file which outputs an ES module

document.body.insertAdjacentHTML('beforeend',
    `<p>${bar(foo.name)}
    <p>Indeed, ${foo.name} is ${foo.position[1]} meters below that bar!`
)

If you refresh the page in the browser at this point, you'll see the infamous Uncaught SyntaxError: Cannot use import statement outside a module logged to the console, because import { vec2 } from 'https://cdn.skypack.dev/gl-matrix' hasn't been resolved but rather copied as-is in app-bundle.js.

Enter another another handy rollup plugin: rollup-plugin-url-resolve.

Install it:

$ npm install rollup-plugin-url-resolve --save-dev

Add it to rollup.config.mjs :


 








 
 
 



import substrate    from 'rollup-plugin-substrate'
import urlResolve   from 'rollup-plugin-url-resolve'

export default {
    input: 'app.js',
    output: {
        file: 'app-bundle.js'
    },
    plugins: [
        substrate(),
        urlResolve({
            cacheManager: '.cache'
        }),
    ]
}

Run npm run watch again for the changes to the configuration file to apply, refresh the page and enjoy!

An image

Rollup now fetches dependencies from their respective URL and bundle them into app-bundle.js, truly the only JavaScript file our page needs.

The cacheManager option is used to specify a folder to cache modules fetched from absolute URLs, so they're not fetched from remote servers every time rollup rebuilds app-bundle.js. This can make a great difference in building time, making testing way more pleasant.

# Centralize and reuse dependencies

When importing some module from the same absolute URL in many different files, it can get tiring and unpractical to write the specific URL every time, especially if it might change at some point for some reason. This can be circumvented by importing all such modules in one file, from which all other files will import them.

Let's make use of vec2 in app.js to try and improve foo's situation.

In app.js, add this:

// create a vec2 representing x/y the distance covered by a jump
const jumpDistance = vec2.fromValues(0,-1000)

// increase foo's position by jumpDistance
vec2.add(foo.position, foo.position, jumpDistance)

// supplement the story with foo's new y position
document.body.insertAdjacentHTML('beforeend',
    `<p>What a jump! Now ${foo.name} is ${-foo.position[1]} meters above the bar. Oh well.`
)

// make sure to import vec2 here as well
import { vec2 } from 'https://cdn.skypack.dev/gl-matrix'

It does work, as it is. Now let's rework things a bit so we only have the URL for vec2, 'https://cdn.skypack.dev/gl-matrix', specified in only one place in the whole project.

In the lib subfolder, create a new file, deps.explorable.md:

```js
import { vec2 } from 'https://cdn.skypack.dev/gl-matrix'

export { vec2 } 
```

Then, edit the import statements for vec2 in app.js

import { vec2 } from './lib/deps.explorable.md'

And in foo.js :

import { vec2 } from './deps.explorable.md'

It still works, and it's more convenient under the hood! Every dependency that is an absolute URL can be treated the same way: imported in deps.explorable.md and exported from there as a named export, to be imported elsewhere in the project.

For example, deps.explorable.md could eventually look like this:

```js
import { vec2 }             from 'https://cdn.skypack.dev/gl-matrix'
import html                 from 'https://cdn.skypack.dev/snabby'
import aabbSegmentOverlap   from 'https://cdn.jsdelivr.net/gh/mreinstein/collision-2d/src/aabb-segment-overlap.js'

export { vec2, html, aabbSegmentOverlap } 
```

With dependencies that are absolute URLs all in the same place, it's really easy to see which are used in our project. deps.explorable.md becomes a potent alternative - or complement - to package.json.

TIP

This way to organize absolute URLs is of course valid for all Substrate projects, including those that aren't part of a Node.js build setup and instead rely on the Substrate viewer.