RSS
 

Archive for the ‘Programming’ Category

Adding Markdown (mdx) Support to NextJS with App Router

04 Jul

Markdown allows text content to be written with implicit formatting that nearly matches how one would write text without thought of formatting. A lot of web content is also simply text content; a lot of web content is built on the ReactJS library; and NextJS has become a popular framework, extending ReactJS. With that, I was motivated to use the Markdown content of my NextJS web app, but I had trouble simply relying on their documentation, so I thought I would document what I got working.

  1. Use Markdown for a text-heavy portion of a page of my web app, and
  2. To be able to have an entire page of content come from a single Markdown page

The former is a feature of NextJS to allow markdown content to be imported as a “component,” which can be rendered like any component in ReactJs.

The latter would rely NextJS’s App Router that assumes many URL paths for the app parallel the file/directory hierarchy of the app itself. So, merely placing a “page” file in the app’s directory will create a new URL path for the website. That means that you can create a “page.md” or “page.mdx” file with Markdown text content to define the content for that URL. .mdx files also allow JSX syntax so that React/Next components can be embedded within the Markdown.

Setup

The first issue was getting things set up. Here were the steps that worked for me.

  1. Add packages. The highlighted line was missing from the documentation.
    npm install @mdx-js/loader @mdx-js/react @next/mdx
    npm install -D @types/mdx
    
  2. In next.config.*, add
    1. import withMDX from "@next/mdx";
    2. Add “md” (optional) and “mdx” extensions for App Router support: nextConfig.pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx']
    3. Create, then wrap the Next config with mdx-configwithMDX = createMDX({})export default withMDX(nextConfig)

    Note: I had to avoid using .ts to avoid syntax errors to support the “remark-gfm” plugin. I recommend using .js or .mjs extensions, I chose the latter.

    import type {NextConfig} from 'next';
    
    const nextConfig: NextConfig = {
      typescript: {
        ignoreBuildErrors: true,
      },
      eslint: {
        ignoreDuringBuilds: true,
      },
    };
    
    export default nextConfig;
    asdfasdf asdf asdf asd f
    import type {NextConfig} from 'next';
    import withMDX from "@next/mdx";
    
    const nextConfig: NextConfig = {
      pageExtensions: ["js", "jsx", "mdx", "ts", "tsx"],
      typescript: {
        ignoreBuildErrors: true,
      },
      eslint: {
        ignoreDuringBuilds: true,
      },
    };
    
    const withMdxConfig = withMDX({
      options: {
        remarkPlugins: [],
        rehypePlugins: [],
      },
    });
    
    export default withMdxConfig(nextConfig);
  3. Add the mdx-components.tsx file to src/. This file allows overriding how tags and components will be rendered, providing a mapping from tag/component name to an overriding render function; see the official documentation, mdx-components.js, but I will elaborate on how we’ll use this in the following sections.
    import type { MDXComponents } from 'mdx/types'
    
    export function useMDXComponents(components: MDXComponents): MDXComponents {
       return {
          ...components,
       }
    }
    
  4. Help VSCode resolve imports
    1. Open settings
    2. Search for “file associations”
    3. Add item key = *.mdx, value = markdown. (Articles say, “react-markdown,” but that didn’t work for me).
  5. Styles will need to be defined for all the generated HTML tags. See the next sections. 

Defining Markdown Output Style

Markdown syntax is translated to HTML. By default, the HTML tags have had their formatting stripped, for some reason. That means that the tags need to have style/class definitions applied to render properly. See How to use markdown and MDX in Next.js, for the official word.

The following are tags that correspond to Markdown syntax:

  Markdown HTML
Headings # Level 1
## Level 2
### Level 3

###### Level 6
<h1>Level 1</h1>
<h2>Level 2</h2>
<h3>Level 3</h3>

<h6>Level 6</h6>
Bold, Italic, Bold Italic * and _ are interchangeable
**bold** or __bold__
*italic* or _italic_
***bold-italic***
<b>bold</b>
<i>italic</i>
<b><i>bold-italic</i></b> or <i><b>bold-italic</b></i>
Bullet list - Item 1
- Item 2
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
Ordered list 1. Item 1
2. Item 2
<ol>
<li>Item 1</li>
<li>Item 2</li>
</ol>
Unformatted 

```
unformatted 
   text
```

<pre>
unformatted
  text
</pre>

Link [description](http://…) <a href="http://…">description</a>
Plain or paragraph text text … <p>text …</p>
Checkbox (an extended feature supported by the remark-gfm plugin) 

- [ ]Incomplete task
- [x]Completed task

<ul class="contains-task-list">
  <li class="task-list-item"><input disabled="" type="checkbox">Incomplete task</li>
  <input disabled="" type="checkbox"><li class="task-list-item" checked>Completed task</li>
</ul>

By default, NextJS removes the styling for the Markdown HTML tags, so we have to define them. 

Wrapper

Styles and classnames can be defined for any tag and component by redefining it to render with a style or className property, as desired. Instead, we will take advantage of a special Component named “wrapper” that wraps any .mdx content and define tag styles within that container context. Then we define all tag’s styles in an importable CSS module. 

import type { MDXComponents } from 'mdx/types'
import styles from './mdx-components.module.css'

export function useMDXComponents(components: MDXComponents): MDXComponents { 
   return { 
      ...components, 
      wrapper: ({ children }) => <div className={styles.mdxWrapper}>{children}</div>,
   } 
}

The file name and the wrapper class names are irrelevant so long as they match. Here is a good starting point for the “mdx-components.module.css” that I use. Note the class name, “_mdxWrapper” parent style definition. 

/**
 * Style definitions for tags corresponding to Markdown for MDX components
 * This file provides a reset for common HTML elements used in MDX content
 * to ensure consistent styling.
 */
._mdxWrapper {
  h1, h2, h3, h4, h5, h6, p, blockquote, pre, table {
    display: block;
    margin-block: initial;
    margin-inline: initial;
  }

  h1, h2, h3, h4, h5, h6 { font-weight: bold; }

  h1 {
    border-top: 1px solid lightgray;
    margin-block-start: 0.67em;
    font-size: 2em;
  }
  h2 { margin-block-start: 0.83em; font-size: 1.5em; }
  h3 { margin-block-start: 1em;    font-size: 1.17em; }
  h4 { margin-block-start: 1.67em; font-size: 1em; }
  h5 { margin-block-start: 1.33em; font-size: 0.83em; }
  h6 { margin-block-start: 2.33em; font-size: 0.67em; }

  p { margin-block: 0.4em; }

  a {
    color: -webkit-link;
    text-decoration: underline;
    cursor: pointer;
  }

  ul, ol {
    display: block;
    padding-inline-start: 1.5625rem;
  }
  ul { list-style-type: disc; }
  ul ul { list-style-type: circle; }
  ul ul ul { list-style-type: square; }
  ol { list-style-type: decimal; }
  
  blockquote {
    margin-block: 1em;
    margin-inline: 40px;
  }
  code, pre {
    font-family: monospace;
  }
  pre {
    display: block;
    margin-block: 1em;
    white-space: pre;
  }

  img {
    display: inline-block;
    max-width: 100%;
    height: auto;
  }

  table {
    /* Table styling variables */
    /* --table-border-color: blue; */
    /* --table-border-width: 2px;  */    /* Enable to non-0 to surround table with border */
    /* --table-header-background-color: #f2f2f2; */ /* Enable for distinct header background	color */
    --table-header-border-color: #BBB;   /* Defaults to cell border color */
    --table-header-border-width: 2px;    /* Defaults to row border width */
    --table-body-row-border-width: 1px;  /* Enable to non-0 show row dividers */
    --table-body-col-border-width: var(--table-body-row-border-width,1px);  /* Enable to non-0 show col dividers */
    --table-body-cell-border-color: lightgray;  /* Override border color */
    --table-body-background-color: white;/* Even row background color */
    --table-body-background-color-alt: #f9f9f9; /* Odd row background color */
    margin-left: var(--table-body-row-border-width,0);
    border-collapse: collapse;
    text-indent: initial;
  }
  thead, tbody, tfoot {
    vertical-align: middle;
  }
  thead {
    border-color: var(--table-border-color);
    border-width: var(--table-border-width,0) var(--table-border-width,0) 0 var(--table-border-width,0);
  }
  tbody {
    border-color: var(--table-border-color);
    border-width: 0 var(--table-border-width,0) var(--table-border-width,0) var(--table-border-width,0);
  }
  thead th {
    background-color: var(--table-header-background-color, inherit);
    /* border-style: solid; */
    border-color: var(--table-header-border-color, var(--table-body-cell-border-color));
    border-bottom-width: var(--table-header-border-width,var(--table-body-row-border-width,0));
  }
  tr {
    vertical-align: inherit;
    border: none; /* Disable row borders */
    border-color: inherit;
  }
  td, th {
    /* No top border, conditional side/bottom borders */
    border-width: 0 var(--table-body-col-border-width, 0px) var(--table-body-row-border-width, 0px) var(--table-body-col-border-width, 0px);
    border-color: var(--table-body-cell-border-color);
    padding-inline: 8px;
  }
  /* No side borders */
  td:last-child, th:last-child { border-right: none; }
  td:first-child, th:first-child { border-left: none; }

  /* Alternating background colors for table rows */
  tbody tr:nth-child(odd)  { background-color: var(--table-body-background-color); }
  tbody tr:nth-child(even) { background-color: var(--table-body-background-color-alt); }
} /* ._mdxWrapper */

Checkbox Support via “remark-gfm”

Github markup is supported via the remark-gfm plugin, however I was never able to get it to work as documented (i.e., import and add  remarkGfm  to the plugins array. Instead I had to use the string name of the plugin and add an array to the plugins array:

const withMdxConfig = withMDX({
  options: {
    remarkPlugins: [['remark-gfm']],
    rehypePlugins: [],
  },
});

export default withMdxConfig(nextConfig);
import withMDX from "@next/mdx"
import remarkGfm from 'remark-gfm'

const nextConfig: NextConfig = {
  pageExtensions: ["js", "jsx", "ts", "tsx", "md", "mdx"],
}

const withMdxConfig = withMDX({
  options: {
    // If you use remark-gfm, you'll need to use next.config.mjs
    remarkPlugins: [remarkGfm],
    rehypePlugins: [],
  },
})

export default withMdxConfig(nextConfig);

The styling for task/checkboxes needs to be set. Unfortunately, remark-gfm uses class names to identify the list and list item tags. Since importing a CSS modules mangles names, we have to import the CSS definitions directly. We can create a CSS file, randomly named, “remark-gfm.css”

/* remarkGfm MDX Checkbox support for remarkGfm */
ul.contains-task-list li.task-list-item {
  list-style-type: none; /* Remove list style for task list items */
  text-indent: -18px;    /* ...and outdent it so it aligns        */
}
/* Undo <li> inherited styling within nested chckbox lists */
ul.contains-task-list li:not(.task-list-item) {
  list-style-type: initial;
  text-indent: initial;
}

Then import this directly into “mdx-components.tsx.” Alternatively you can add these style definitions in global.css.

import type { MDXComponents } from 'mdx/types'
import styles from './mdx-components.module.css'
import './remark-gfm.css'

export function useMDXComponents(components: MDXComponents): MDXComponents {
   return {
      ...components,
      wrapper: ({ children }) => <div className={styles._mdxWrapper}>{children}</div>,
   }
}

Note: This style definition will affect all content on the page, even outside an MDX component. 

Summary

I was led down a bunch of rabbit holes due to NextJS’s Markdown support not matching the documented steps. With the steps described in this post, others should be able to get it working without the headaches I had to go through.

  1. Npm-install mdx support packages
  2. Update VSCode file association settings (optionally)
  3. Update “next.config.mjs”
  4. Create src/mdx-components.tsx and import a CSS module to define and wrap tag style definitions
  5. If Github markdown support is desired, add the plugin and import CSS definitions into mdx-components.tsx

Then you can use Markdown content for App Router pages as page.mdx and React components, implicitly.

# Page Title

This is an example a NextJS App Router page, written with Markdown syntax. 

 

import UpdateContent from "./updates.mdx" // Markdown content

export default async function UpdatesPage() { // App Router page component

return (
  <>
     <h1>Updated News</h1>
     <UpdateContent/> {/* Render Markdown component */}
  </>
)

 

 
 

ChatGPT: What is the common way to parse command line parameters with Rust?

04 Mar

Rust Programming Language logoIn Rust, the most common way to parse command-line parameters is by using the std::env::args function, which returns an iterator over the command-line arguments passed to the program. Read the rest of this entry »

 

Ruby RSpec FactoryBot traits and transient via ChatGPT

15 Dec

The following was edited together from a series of questions to ChatGPT on the topic. Currently, I cannot identify sources of the originating content. My role was to edit ChatGPT into the following.

FactoryBot

RSpec is a testing framework for the Ruby programming language, and FactoryBot (formerly known as Factory Girl) is a library for creating test data in Ruby. Together, these tools can be used to write unit-tests for a Ruby application.

In FactoryBot, a factory is a blueprint for creating test data objects. A factory can define various attributes of the objects it creates, such as the object’s type, the values of its attributes, and any associations it has with other objects.

Read the rest of this entry »
 
 

Frontend Security Recipe Checklist

03 Jan

Even as the number of frontend programming vulnerabilities grows continually, many are not difficult to combat; you simply need to remember to fortify your frontend security against them.

 
 

ReactJS: componentWillReceiveProps() is Dead! (*sniff*)

21 Apr

ReactJSThings evolve. ReactJS evolves. With version 16.3, there were several changes to the component life-cycle methods. In particular, componentWillReceiveProps is disappearing. In its place, they say, you can use the getDerivedStateFromProps static function. I found this a bit challenging, but I did find an interesting pattern when the component-state was dependent on fetched information.

I should mention that I had a specific goal to better encapsulate data within the component. While I could pass in all the needed data as properties, that would require the surrounding component know what to pass and how to get it. That shouldn’t necessarily be necessary; the component knows what it needs and how to retrieve it.

For example, say you have a component which accepts a phone number and displays the phone number and the state that it’s from. Certainly, you could write a simple component that accepts both pieces of information as properties.

<ShowPhoneLocation number="+12065551212" city="Seattle" />

Which might be implemented as:

class ShowPhoneLocation extends React.Component {
  render() {
    return (
      <div>{this.props.number} is from {this.props.city}</div>
    )
  } // render()
} // class ShowPhoneLocation

But, since the component should be able to infer the state from the phone number (by its area code), it shouldn’t be incumbent on its container to know what it is.

class ShowPhoneLocation extends React.Component {
  static getDerivedStateFromProps(nextProps, prevState) {
    let location = getCityFromPhone(nextProps.number)
    return {
      city: location
    }
  }
  render() {
    return (
      <div>{this.props.number} is from {this.state.city}</div>
    )
  } // render()
} // class ShowPhoneLocation

That’s all well and good, but what if getCityFromPhone() has to call a web service? We don’t want getDerivedStateFromProps() to stall, waiting for a response. However, it is static and does not have a this reference to the object for which it is returning state; so an asynchronous fetch doesn’t know what object’s state to set. Instead, don’t wait for the result to save in the state, save the request’s Promise in the state and update the state, once the promise resolves.

function getCityFromPhone(number) {
  return fetch('http://saas.com/get/'+number+'/city') // Returns fetch promise
}
class ShowPhoneLocation extends React.Component {
  static getDerivedStateFromProps(nextProps, prevState) {
    let location = getCityFromPhone(nextProps.number)
    return {
      city: location
    }
  }
  componentDidUpdate() {
    let location = this.state.city
    if (location instanceof Promise) {
      this.setState({ city: '...waiting...' })
      location.then(city => this.setState({ city: city }) )
        .catch(() => this.setState({ city: 'not found' }) )
    }
  }
  render() {
    return (
      <div>
        {this.props.number} is from {this.state.city instanceof Promise
         ? '...'
        : this.state.city}</div>
    )
  } // render()
} // class ShowPhoneLocation

In componentDidUpdate() you can define the completion handlers to set the object’s state, base on the returned information from the service.

It is a common pattern to perform a fetch in componentDidMount(). The problem is that there may not be enough information to perform the fetch, that early, or the information for the fetch changes after the component has been mounted.

I am going to miss componentWillReceiveProps()… without it, things become a bit more convoluted but it’s going the way of the Dodo.

 

Promises, Promises… Timeout!

25 Apr

I was playing with ES6’s new Promises, for the first time, this week. Then, I was looking at the ugliness of using a browser’s setTimeout() function and thought that it would look better as a Promise.

tl;dr summary: A Simple Promise Version of “setTimeout()”

If we do it right, you simply specify the timeout period and implement a handler for then() to invoke:

timeout(5000) // Delay for 5000 ms
   .then(function () {
      // Do, here, whatever should happen when the time has elapsed…
   });

Or, since this is ES6, we might as well use arrow-function shorthand:

timeout(5000).then( () => {
   // do your thing, here…
})

Implementation

The setTimeout() already functions a bit like a Promise, without matching the promise-pattern and syntax, so converting it to a Promise is pretty easy: Read the rest of this entry »

 
 

Simplicity via PHP Frameworks

05 Apr

PHP language and web-technologyA long time ago, I’d endeavored to create my own lightweight PHP framework, ccPHP. It was a good exercise, but never reached prime time. Around that time I began to notice others endeavoring to do the same thing (i.e., create simpler, lighter weight PHP frameworks that didn’t take more time to learn than to implement the project). I’ve been away for a while and things have changed.

  1. The continued refinement and simplification of frameworks.
  2. The greater emphasis on frontend apps to do more of the work that used to be done in the middle tier.
  3. The ubiquity of RESTful interfaces, the dividing line between front-ends and servers.

Micro-PHP Frameworks

I doubt that I will ever finish my own framework, now that others are a lot closer to the goals that I was moving towards. And, right now, I simply want to find a lightweight modular frameworks that truly help me get my core work done. Dragan Gaic did a great survey of the PHP hot frameworks in Top 12 Best PHP RESTful Micro Frameworks (Pro/Con).

A key feature of such frameworks is for them to be modular to allow me to pick and choose only the components I need, rather than have to install, understand, and run a bunch of functionality that I do not need. It makes it harder for the developer and impacts performance of the results. Model-View-Controler (MVC) is a common software architecture pattern. With the client side being far more capable of managing the view part, on its own, the server side can be simpler. Instead of cluttering up the server-side app with trying to display information, it can simply focus on securing data and access and providing data services via RESTful interfaces.

So, what I am looking for are pieces to help me access persistent data and build RESTful interfaces. And something to help keep all the code flow organized. Based on the article, I can look at Silex, Slim, Lumen, or Phalcon for the latter. For database access, Ive worked with RedBeans before and liked it, so I will probably pick that up again. If one of the frameworks doesnt help implement and configure a RESTful interface simply, then I may get back into the framework building business again.

Model: RedBeans
View: Frontend client
Controler: one of Silex, Slim, Lumen, or Phalcon

A WordPress bug allowed my website to be hacked. I thought this was lost forever until I thought to reconstitute it from http://Archive.org!!

 

Activating OS X’s Builtin Apache Web Server and PHP

14 Jan

As I mentioned in my prior post OS X Command-line Dev Tools—Install Shortcut, OS X comes with a variety of built-in tools suited for developers. Unfortunately, Apple has seen fit to hide one of the most important of them: the web server. Despite that, they are easily unlocked if you know how. This applies to Mountain Lion (10.8) and (though I haven’t tried it, yet) Mavericks (10.9). Though you can install separate installations, I don’t like to install anything extra, unless I must. Read the rest of this entry »

 

PHP Recipe to Show Source-code

19 Nov

PHP source-code display recipeHere is a simple Web 1.5 (static HTML with a little bit of styling and JavaScript) recipe to allow a viewer of your web page to see the PHP source-code, behind it, with a minimal amount of JavaScript and a little CSS manipulation—good for showing the work you’ve done to others. Or for embedding in your own source, in debug mode, so that teammates can see each others’ work.

See the example code at: http://cachecrew.com/sample_code/highlight_file.php and http://cachecrew.com//sample_code/highlight_file2.php.

The PHP and HTML Read the rest of this entry »

 

OS X Command-line Dev Tools—Install Shortcut

12 Nov

Xcode command-line toolsAmong those around me, in world of tech startups (even in the vicinity of the world of Windows), MacBooks are used almost universally. I can’t explain the discrepancy between what I see around me and the data you usually read; but I do know that as a technical platform, OS X provides an easier path to development tools. These days, the world driven by web-based applications. A majority of those applications run on Linux-based machines. OS X shares with Linux, a common ancestor, Unix. So, a robust array of common commands come with OS X—ssh, ftp, bash, vi, emacs, etc. But more importantly, OS X comes pre-installed with hottest development tools used for web development, today—Ruby, Python, PHP, Apache, memcache, etc. This means a developer can write web applications without even being connected to the Internet!

There are more tools available with the free download of Xcode, Read the rest of this entry »