Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React Templating #21

Merged
merged 29 commits into from Mar 11, 2019
Merged
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

@@ -0,0 +1,25 @@
// Conceivably, one day I could get convert my metalsmith entry to .mjs
// So I can write everything in ESM. But that day is not today.
// A couple of problematic things I remember running into when trying it:
//
// 1. imports weren't working as expected. When doing page reloads on file changes
// things were reloading. The old component was still rendering the template.
// I think this is a feature of ESM where things are statically resolved
// and not dynamically called again when metalsmith runs a second time.
// I changed some of those pieces to do dynamic imports (import()) and that
// kind of worked, but if a component had a syntax error, it would get stuck
// in that "error" state even if i corrected it and saved again.
//
// 2. I tried using just `node --experimental-modules` but that was too limited
// because i couldn't get it working with @babel/register (which wasn't
// officially supported with experimental modules anyway)
// So instead I was using the `esm` package, but had problems with no.1 above
// plus it was actually pretty slow.
// require = require("esm")(module);

require("@babel/register")({
babelrc: false,
presets: ["@babel/preset-react"]
});

module.exports = require("./metalsmith.js");
@@ -1,138 +1,59 @@
var argv = require("minimist")(process.argv.slice(2));
var themeId = getThemeId();

var path = require("path");
var moment = require("moment");

var Metalsmith = require("metalsmith");
var layouts = require("metalsmith-layouts");
var rename = require("metalsmith-rename");
var timer = require("metalsmith-timer");
var pagination = require("metalsmith-pagination");
var watch = require("metalsmith-watch");
var serve = require("metalsmith-serve");
var inplace = require("metalsmith-in-place");
var debugui = require("metalsmith-debug-ui");
var ignore = require("metalsmith-ignore");
var debug = require("debug")("metalsmith");

var fileLimit = require("./plugins/file-limit");
var iconMetadata = require("./plugins/icon-metadata");
var iconPermalinks = require("./plugins/icon-permalinks");
var iconArtwork = require("./plugins/icon-artwork");
var iconCollection = require("./plugins/icon-collection");
var jsTranspilation = require("./plugins/js-transpilation");
var buildInfo = require("./plugins/build-info");

var sharedConfig = require("./config.json");
var config = require(`./config.${themeId}.json`);

var isDevelopment = process.env.NODE_ENV !== "production";

var PATH_TEMPLATES = path.join(__dirname, "./src/templates");
const Metalsmith = require("metalsmith");
const path = require("path");
const minimist = require("minimist");
const debug = require("debug");
const ignore = require("metalsmith-ignore");
const watch = require("metalsmith-watch");
const serve = require("metalsmith-serve");
const debugui = require("metalsmith-debug-ui");
const limitIcons = require("./plugins/limit-icons.js");
const transpileJSX = require("./plugins/transpile-jsx.js");
const addIconMetadata = require("./plugins/add-icon-metadata.js");
const copyIconArtwork = require("./plugins/copy-icon-artwork");
const createIconCollection = require("./plugins/create-icon-collection.js");
const paginate = require("./plugins/paginate.js");
const renderTemplates = require("./plugins/render-templates.js");

const argv = minimist(process.argv.slice(2));
const isDevelopment = process.env.NODE_ENV !== "production";
const themeId = getThemeId();

/**
* Metalsmith app
*/
let app = Metalsmith(__dirname)
.metadata({
site: {
themeId: themeId,
...sharedConfig,
...config,
time: moment()
},
__DEV__: isDevelopment
// ...helpers
// Merge shared config with site-specific config
...require("./config.json"),
...require(`./config.${themeId}.json`),

themeId,
icons: [], // we'll set this in a plugin below
buildTime: new Date(),
isDevelopment
}
})
.source("./src/www")
.source("./src/www") // @TODO consider move www up one
.destination("./build")
.clean(true)
.use(timer("start"))

// Filter down the number of posts we'll actually use, if the correct argument
// is present, i.e. `node metalsmith.js --limit=100`
.use(fileLimit({ limit: argv.limit, pattern: "icons/*.md" }))

// FYI instead of static HTML redirects for every file that needs one, we
// write one giant `_redirects` file for netlify. See `_redirects`
// However if you ever needed, you could add a .html file for each redirect
// by following the example in the `plugins/icon-redirects`

// Let's start off by transpiling our jsx components
.use(jsTranspilation({ pattern: "assets/scripts/search/components/*.js" }))
.use(timer("transpile js"))

// Add metadata to posts (must be first)
.use(iconMetadata({ pattern: "icons/*.md" }))
.use(timer("add icon metadata"))
// Add permalinks to the post (and rename .md -> .html)
.use(iconPermalinks({ pattern: "icons/*.md" }))
.use(timer("add icon permalinks"))
// Copy source artwork into metalsmith build chain
.use(iconArtwork({ pattern: "icons/**/*.html" }))
.use(timer("add icon artwork"))
// I create my own collections because of this
// https://github.com/segmentio/metalsmith-collections/issues/27
.use(iconCollection({ pattern: "icons/**/*.html" }))
.use(timer("add an icon collection"))

// Pagination (depends on the icon collection above)
.use(
pagination({
"site.icons": {
perPage: config.iconsPerPage,
layout: "index.ejs",
first: "index.html",
path: "p/:num/index.html",
pageMetadata: {
title: ""
}
}
})
)
.use(timer("add icon pagination"))

// Template in place all our pages that have an extension of `.ejs`
.use(
inplace({
engine: "ejs",
pattern: "**/*.ejs",
engineOptions: {
// We have to pass this in so it knows where to look for includes
views: [PATH_TEMPLATES]
},
suppressNoFilesError: true
})
)
.use(timer("template in place"))

// Now template everything
// Then rename `_redirects` because `layouts` automatically sticks .html on it
.use(
layouts({
// We set blank as the default, as generators like `alias` will need a blank
// template. This means any file you want to render with a template, you should
// explicitly set it. Otherwise, it'll just be a blank template.
default: "blank.ejs",
engine: "ejs",
directory: PATH_TEMPLATES
// pattern: "**/*.html", // @TODO may want this?
// engineOptions: {
// cache: true,
// filename: "test.json",
// async: true
// }
})
)
.use(rename([[/_redirects.html/, "_redirects"]]))
.use(timer("template layouts"))

// For some reason, these files show up so we have to remove them
.use(ignore(["**/.DS_Store"]))

// Log out the stuff we're writing, so we know we're getting what we want
.use(buildInfo());
.use(limitIcons({ limit: argv.limit, pattern: "icons/*.md" }))
// Transpile JSX for code we ship to the client
.use(transpileJSX({ pattern: "assets/scripts/**/*.jsx" }))
// Add metadata to icons (must be first)
.use(addIconMetadata({ pattern: "icons/*.md" }))
// Copy the atwork for each icon to its respective page
.use(copyIconArtwork({ pattern: "icons/**/*.html" }))
// Create a collection of icons under `.site` metadata
.use(createIconCollection({ pattern: "icons/**/*.html" }))
// Create pagination
.use(paginate())
// Render templates
.use(renderTemplates())
// Ignore files we know we're not going to have in the output
.use(ignore(["**/.DS_Store"]));

/**
* Development-related tasks, only if we're in dev mode
@@ -145,18 +66,13 @@ if (isDevelopment) {
paths: {
// change to files in source will rebuild themselves
"${source}/**/*": true,
// changes to templates will rebuild everything
[PATH_TEMPLATES + "/**/*"]: "**/*"
// changes to templates on server or shared files should rebuild everything
[path.join(__dirname, "src/server/**/*")]: "**/*"
},
livereload: true
})
)
.use(
serve({
host: "0.0.0.0"
})
)
.use(timer("serve site"));
.use(serve());
}

/**
@@ -166,7 +82,8 @@ app.build((err, files) => {
if (err) {
throw err;
}
debug("Done!");

debug("metalsmith-log")("done");
});

/**
@@ -2,38 +2,36 @@
"name": "icon-galleries",
"private": true,
"scripts": {
"copy-content": "sync-files ../_posts ./src/www/icons",
"create-post": "node scripts/create-post.js",
"prestart": "npm run copy-content",
"start": "NODE_ENV=development DEBUG=metalsmith-timer,metalsmith-build-info node metalsmith.js",
"prebuild": "npm run copy-content",
"build": "NODE_ENV=production DEBUG=metalsmith-timer,metalsmith-build-info,metalsmith node metalsmith.js",
"prestart": "sync-files ../_posts/ ./src/www/icons/",
"start": "TZ=UTC DEBUG=metalsmith-timer,metalsmith-log node index.js",
"prebuild": "cp -rf ../_posts/ ./src/www/icons/",
"build": "TZ=UTC NODE_ENV=production DEBUG=metalsmith-timer,metalsmith-log node index.js",
"watchos-get-icon": "babel-node save-watch-os-512-img.js --presets es2015,stage-2"
},
"devDependencies": {
"babel-core": "^6.26.3",
"babel-preset-react-app": "^3.1.2",
"ejs": "^2.6.1",
"fs-extra": "^7.0.1",
"@babel/core": "^7.2.2",
"@babel/plugin-proposal-class-properties": "^7.3.0",
"@babel/preset-react": "^7.0.0",
"@babel/register": "^7.0.0",
"common-tags": "^1.8.0",
"date-fns": "^1.30.1",
"imagemin": "^6.0.0",
"imagemin-pngquant": "^6.0.0",
"jstransformer-ejs": "^0.2.0",
"metalsmith": "^2.1.0",
"metalsmith-debug": "^1.2.0",
"metalsmith-debug-ui": "^0.3.0",
"metalsmith-ignore": "^0.1.2",
"metalsmith-in-place": "^4.2.0",
"metalsmith-layouts": "^1.4.1",
"metalsmith-pagination": "^1.4.0",
"metalsmith-rename": "^1.0.0",
"metalsmith-serve": "0.0.7",
"metalsmith-timer": "0.0.2",
"metalsmith-watch": "^1.0.3",
"minimist": "^1.2.0",
"moment": "^2.22.2",
"node-fetch": "^2.2.0",
"pretty": "^2.0.0",
"prompt": "^1.0.0",
"prompts": "^0.1.14",
"prop-types": "^15.6.2",
"react": "^16.8.3",
"react-dom": "^16.8.3",
"readline-sync": "^1.4.9",
"request": "2.85.0",
"sharp": "^0.21.0",
@@ -1,40 +1,36 @@
var debug = require("debug")("metalsmith-posts");
var multimatch = require("multimatch");
var URL = require("url");
var slug = require("slug");
var moment = require("moment");
var path = require("path");
var fs = require("fs");
const path = require("path");
const fs = require("fs");
const debug = require("debug");
const multimatch = require("multimatch");
const URL = require("url");
const slug = require("slug");
const dateFns = require("date-fns");

const slugify = str => slug(str, { lower: true });

function iconMetadata(opts) {
opts.pattern = opts.pattern || [];

return function(files, metalsmith, done) {
setImmediate(done);

/**
* Process icons with additional metadata and transform them from .md -> .html
* @param {String|Array} pattern
*/
const addIconMetadata = ({ pattern = [] }) => {
return (files, metalsmith, done) => {
const { site } = metalsmith.metadata();

Object.keys(files).forEach(file => {
if (multimatch(file, opts.pattern).length) {
debug("myplugin working on: %s", file);
if (multimatch(file, pattern).length) {
files[file].isIcon = true;

// Set the `layout` for all posts
files[file].layout = "icon.ejs";

// Set the `date` for all posts.
//
// Note: Metalsmith turns all the `date` properties in our .md files into
// JS Date objects. We want to just keep them as YYYY-MM-DD strings
// for convenience. But we'll also provide a moment date object for
// other places where we need formatting
files[file].date = moment.utc(files[file].date);
// Note on `date`:
// Metalsmith turns all the `date` properties in our .md files into
// JS Date objects (in UTC time). We'll keep it as such, as it makes
// manipulating the `date` easier on a case-by-case basis.

// Set an `id` for each icon, which is a combination of slug + date
// i.e. 'facebook-events-2017-08-11'
files[file].id =
files[file].slug + "-" + files[file].date.format("YYYY-MM-DD");
files[file].slug +
"-" +
dateFns.format(files[file].date, "YYYY-MM-DD");

// Set other metadata based on the current metadata
const {
@@ -80,12 +76,30 @@ function iconMetadata(opts) {

// Add `isPreIos7` bool for ios icons only
if (site.themeId === "ios") {
files[file].isPreIos7 = files[file].date.isBefore("2013-09-13");
files[file].isPreIos7 = dateFns.isBefore(
files[file].date,
new Date("2013-09-13")
);
}

// Transform file path from .md to .html and add a permalink
const id = files[file].id;
files[`icons/${id}/index.html`] = {
...files[file],
permalink: `/icons/${id}/`
};
delete files[file];
}
});

debug("metalsmith-log")(
"add-icon-metadata: %s icons",
Object.keys(files).filter(file => files[file].isIcon).length
);

done();
};
}
};

function getDomain(str) {
let url = URL.parse(str).hostname;
@@ -95,4 +109,4 @@ function getDomain(str) {
return url;
}

module.exports = iconMetadata;
module.exports = addIconMetadata;
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.