Setup nstal with Next.js and MDX

This tutorial explains how to create a nstal-enabled blog using Next.js and MDX. In a few minutes, you will be able to write your own nstaller, just like the one you're reading.

Run this command from an empty directory:

Loading...
You're all set!
Connected to local nstal
Oops! Please stop nstal CLI and refresh this page

Create the app

Create your Next.js app.

Run the commands:

npx create-next-app my-nstal-and-mdx-blog
cd my-nstal-and-mdx-blog

Setup Chakra UI

Chakra UI is a components library for React. Why do we need it?

nstal's primary package is @nstaldev/react-core. This is what you are going to use to write your nstallers. It comes with components such as CreateFile, which is the action to, well, create a file. But how should it be rendered? Do we need a particular font? Rounded corners? How are the CSS provided?

In order to make nstal UI-agnostic, @nstaldev/react-core relies on external components, provided at runtime. The mechanism is similar to MDXProvider's.

This is where @nstaldev/react-components comes in. This package fulfills react-core expectations so you can use all actions and related components out of the box. And because @nstaldev/react-components is based on Chakra UI, this library must be setup too. As an alternative, we could re-write these components, using whatever libraries we like.

Install Chakra UI and related packages:

Run the command:

yarn add @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^6

Make sure Chakra UI is available in the app.

Populate pages/_app.js with:

import '../styles/globals.css'

import { ChakraProvider, Container } from '@chakra-ui/react'

function MyApp({ Component, pageProps }) {
  return (
    <ChakraProvider>
      <Container>
        <Component {...pageProps} />
      </Container>
    </ChakraProvider>
  )
}

export default MyApp

Visit Chakra UI doc for more options, such as custom theme.

Setup MDX

The optional MDX packages need to be installed.

Run the command:

yarn add @next/mdx @mdx-js/loader @mdx-js/react

Configure MDX so .mdx files are treated as MDX files.

Populate next.config.js with:

/** @type {import('next').NextConfig} */

const withMDX = require('@next/mdx')({
  extension: /\.mdx?$/,
  options: {
    remarkPlugins: [],
    rehypePlugins: [],
    providerImportSource: "@mdx-js/react",
  }
})

module.exports = withMDX({
  // Append the default value with md extensions
  pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
})

We embed the whole app in an MXProvider to override h1 and make sure titles are properly displayed.

Populate pages/_app.js with:

import '../styles/globals.css'

import { MDXProvider } from '@mdx-js/react';
import { ChakraProvider, Container, Heading } from '@chakra-ui/react'

function MyApp({ Component, pageProps }) {
  return (
    <ChakraProvider>
      <MDXProvider components={{
        h1: (props) => <Heading as="h1" fontSize="2xl" {...props} />
      }}>
        <Container>
          <Component {...pageProps} />
        </Container>
      </MDXProvider>
    </ChakraProvider>
  )
}

export default MyApp

Setup nstal

Install nstal packages.

Run the command:

yarn add @nstaldev/react-core @nstaldev/react-components

Write our nstaller

We are going to write the smallest nstaller ever and make it grow.

Hello nstal world

The most basic nstaller is wrapped in an Nstaller component. This Nstaller needs components to render the various elements.

Populate pages/my-first-nstaller.mdx with:

import { Nstaller } from '@nstaldev/react-core'
import { NstalReactComponents } from '@nstaldev/react-components'

# A minimalist nstaller

export default ({ children }) => (
  <Nstaller components={NstalReactComponents}>
    {children}
  </Nstaller>
)

Let's make sure it works.

Run the command:

yarn dev

Show time!

A static tutorial

Apart from the plumbing, our first nstaller is just a Markdown title.

Let's write a real tutorial, which simply asks the reader to create a file. Instead of writing an English sentence, telling the reader what to do, we are going to use an nstal action. In this case, CreateFile. This component expects a file name and its content.

Populate pages/my-first-nstaller.mdx with:

import { Nstaller, CreateFile } from '@nstaldev/react-core'
import { NstalReactComponents } from '@nstaldev/react-components'

# Create a file

As this nstaller is a demo, we are just creating a file.

<CreateFile
  path='README.md'
  content='Hello nstal!'
/>

export default ({ children }) => (
  <Nstaller components={NstalReactComponents}>
    {children}
  </Nstaller>
)

Now the nstaller looks like a classic tutorial:

Static tutorial

We can see that the CreateFile component was turned into human-readable, styled instructions. It's okay, but not very useful yet.

Connect!

It's time to enable the magic. An nstaller becomes useful when it can run the instructions. For this, we need to provide a Connector.

Populate pages/my-first-nstaller.mdx with:

import { Nstaller, CreateFile, Connector } from '@nstaldev/react-core'
import { NstalReactComponents } from '@nstaldev/react-components'

# Create a file

<Connector />

As this nstaller is a demo, we are just creating a file.

<CreateFile
  path='README.md'
  content='Hello nstal!'
/>

export default ({ children }) => (
  <Nstaller components={NstalReactComponents}>
    {children}
  </Nstaller>
)

The nstaller now has a blue box, asking to run npx nstal:

Connectable

When viewing our nstaller, we might get an error with a big red box.

Error

This is because we are doing something quite unusual: build an nstaller while running another one (this one). Someday nstal will handle this situation but for now we need to live with it.

Now, the reader has two options:

  • Follow the tutorial as a classic, static document.
  • Start nstal in order to automate the process.

Let's connect. Run the command provided by our nstaller in an empty directory. After a few seconds, the nstaller is connected:

Connected

Click the Run button:

Completed

Take a look to the directory where you run npx nstal: it contains README.md with the expected content.

Conclusion

We've just setup nstal and wrote our first nstaller! Although small, it demonstrates what we can achieve with nstal.

There are other actions with could use to build more complex nstallers: RunCommands, VisitLink... We could also have used Milestone to display an element only when we have run actions before it.

The project is in its infancy:

  • Breaking changes are expected
  • Security is a concern, as a malicious nstaller could run arbitrary commands.
  • Many actions are missing
  • ...

However, nstal could become a brand new way to write and consume tutorials and installation procedures. To keep in touch, follow nstal on Twitter.