Repository for my website. Started from scratch using react: built with webpack and babel.
A commit to the master branch of this branch automatically triggers a production build and deploy to website (via netlify.com
).
All npm dependencies required for a production build should be made regular dependencies
in package.json
. devDependencies
contain dependencies that are only required when developing the app and running it locally.
Dependencies explained:
- react: UI library
react
: Core libraryreact-dom
: Renderer for HTMLreact-router
: core routing library (used byreact-router-dom
)react-router-dom
: client-side routing library
- webpack: module bundler
webpack
: core bundler toolwebpack-dev-server
: dev server for local developmentwebpack-cli
: command-line interface for webpackbabel-loader
: Loads ES2015+ code and transpiles to ES5 using Babelcss-loader
: Loads CSS file with resolved imports and returns CSS codeeslint-webpack-plugin
: PreLoader for linting code using ESLinthtml-webpack-plugin
: Creates HTML file from template and injects bundlesstyle-loader
: Add exports of a module as style to DOM
mdx-loader
: loader for markdown and markdown-with-jsx documentsval-loader
: executes imported script and replaces it with script outputGoogleFontsPlugin
: webpack plugin that downloads google fonts via google-webfonts-helper
- babel: transpiler
@babel/core
: core transpiler library@babel/cli
: command-line interface for babel@babel/preset-env
: smart preset that allows you to use the latest JavaScript without needing to micromanage which syntax transforms (and optionally, browser polyfills) are needed by your target environment(s)@babel/preset-react
: essentially adds JSX transpilation support
- eslint: linter and syntax checker
eslint
: yes, the linterbabel-eslint
: parser to make eslint parse babel-transpiled codeeslint-plugin-import
: linting support for es6 modules (import/export)eslint-plugin-react
: react-specfic linting rules
- other
glob
: search/select files using glob patternsgray-matter
: markdown frontmatter parser
- Production build:
npm run build
(builds site into thebuild
folder) - Development build:
npm run start
(will launch an in-memory dev server, build results will not show up in file system) - Debug build:
npm run webpack-debug
uses dev-config and producescompilation-stats.json
that can be uploaded to (http://webpack.github.io/analyse/#home)[http://webpack.github.io/analyse/#home] for analysis
Blog articles that fulfill the following criteria are automatically detected, parsed from markdown and shown on the blog.
- frontmatter field
published
set totrue
- filename of markdown file begins with digits followed by an underscorce:
17_example.md
- markdown file is stored in its individual folder below
content/blog
. Example:content/blog/article_folder_17/17_example.md
Goal: Writing articles is all that is needed, the blog builds itself from the available articles.
-
List of articles with metadata: External node script
makePostList.val.js
usesglob
(for file search) andgray-matter
for frontmatter parsing. It is imported into a react component viaimport articleMetadata from "../../makePostList.val.js";
. Theval-loader
is used with webpack to evaluate the script and import its output into the react component file. (An alternative solution would have been a custom webpack loader, that strips out the parsed frontmatter and forwards markdown to the actual markdown loaders, or mdx loaders in this case.) -
Import of all articles: Implemented via
require.context()
. All articles (fulfilling the filepath/filename criteria above) are imported.import articleMetadata from "../../makePostList.val.js"; const articleRequireContext = require.context( "../../content/blog/", true, /\/.*\/[0-9]+_[^/]*\.md(x)?$/ ); const articleCache = {}; articleMetadata.forEach(article => { articleCache[article.id] = articleRequireContext(article.filepath); });
-
React Component: The corresponding react component is quite simple. It uses ReactRouter to switch displayed content between the list of articles and the different articles themselves. A route is created for each article. The list of articles is contains a Router Link for each article. Note the line
{React.createElement(articleCache[article.id].default)}
. Here we are not using JSX syntax but regular React calls. Remember that JSX translation is based on element names starting with a capital letter. It would be possible to trick the transpiler, but directly providing the translation outcome seems easier. Also we are accessing the imported and transpiled markdown via.default
which I figured out through trial and error, and do not understand.import React from "react"; import { Switch, Route, Link } from "react-router-dom"; // import article data const List = () => ( <ul> {articleMetadata.map((article, i) => ( <li key={i}> <Link to={article.id}> {article.title} ({article.date}) </Link> </li> ))} </ul> ); const ArticlesList = () => ( <div className="article-list"> <Switch> <Route path="/posts"> <List /> </Route> {articleMetadata.map((article, i) => ( <Route path={"/" + article.id} key={i}> <article> {React.createElement( articleCache[article.id].default )} </article> </Route> ))} </Switch> </div> ); export default ArticlesList;