Repository: MoOx/phenomic
Pull request #1024 (2017-05-22 18:12:03)

Index content by arbitrary fields
Just kept the special case for `author` and `authors` for most users confort. might change later if we consider it too much.


Patch:
diff --git a/flow/interfaces/phenomic-next.js b/flow/interfaces/phenomic-next.js
index af27d616..2dda244e 100644
--- a/flow/interfaces/phenomic-next.js
+++ b/flow/interfaces/phenomic-next.js
@@ -36,6 +36,19 @@ type PhenomicTransformResult = {
   partial: Object,
 }
 
+type PhenomicHtmlPropsType = {
+  body: React$Element<*>,
+  state?: React$Element<*>,
+  script: React$Element<*>,
+}
+
+type PhenomicHtmlType = (props: PhenomicHtmlPropsType) => React$Element<*>
+
+type PhenomicPluginRenderHTMLType = (
+  props?: { body?: string, state?: Object },
+  html?: PhenomicHtmlType,
+) => string
+
 export type PhenomicPlugin = {
   name: string,
   // transformer
@@ -55,6 +68,7 @@ export type PhenomicPlugin = {
   // renderer
   getRoutes?: Function,
   renderServer?: Function,
+  renderHTML?: PhenomicPluginRenderHTMLType,
 }
 
 export type PhenomicPlugins = Array<PhenomicPlugin>
diff --git a/packages/docs/content/docs/GettingStarted.md b/packages/docs/content/docs/GettingStarted.md
index 3e5a7006..0ff1174c 100644
--- a/packages/docs/content/docs/GettingStarted.md
+++ b/packages/docs/content/docs/GettingStarted.md
@@ -9,7 +9,8 @@ tags:
 
 ⚠️ Phenomic require at least
 **[Node.js@^4.2.0](http://nodejs.org/) / [npm@^3.0.0](https://www.npmjs.com/) or [yarn@^0.15.1](https://yarnpkg.com/)**.
-We recommend you to use [nvm](https://github.com/creationix/nvm) to manage
+
+If needed, we recommend you to use [nvm](https://github.com/creationix/nvm) to manage
 different versions of node.
 
 To start a project, in a new folder,
@@ -23,7 +24,6 @@ A minimalistic content like this is enough:
 }
 ```
 
-
 Now let's install some dependencies.
 The default preset we are going to use will work with React and Webpack.
 
@@ -548,6 +548,37 @@ Now your static render should include all meta generated by ``react-helmet``
 **To know more about all the things you can do with ``react-helmet``,
 please read [the documentation](https://github.com/nfl/react-helmet#readme).**
 
+
+## Building your app as static HTML files
+
+A single command you build your site into a ``dist`` folder
+
+```console
+npm run build
+```
+
+### Debugging static build
+
+During the static build, you might get some different kind of errors
+
+#### ``Invariant Violation``
+
+```
+Invariant Violation: Minified React error #61; visit http://facebook.github.io/react/docs/error-decoder.html?invariant=61 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
+```
+
+By default, static build have ``process.env.NODE_ENV`` set to ``production``.
+You can just start the static build like this
+
+### Deploying static build
+
+You can deploy this folder to any kind of static hosting.
+
+@todo link to doc
+
+```console
+NODE_ENV=development npm run build
+```
 @todo
 - json as data
 - loading state
diff --git a/packages/phenomic-plugin-bundler-webpack/src/webpack.config.js b/packages/phenomic-plugin-bundler-webpack/src/webpack.config.js
index 45a32688..862ec9a1 100644
--- a/packages/phenomic-plugin-bundler-webpack/src/webpack.config.js
+++ b/packages/phenomic-plugin-bundler-webpack/src/webpack.config.js
@@ -6,9 +6,9 @@ import ExtractTextPlugin from "extract-text-webpack-plugin"
 module.exports = (/* config: PhenomicConfig */) => ({
   entry: {
     bundle: [
-      process.env.NODE_ENV !== "production" &&
+      process.env.PHENOMIC_ENV !== "static" &&
         require.resolve("webpack-hot-middleware/client"),
-      process.env.NODE_ENV !== "production" &&
+      process.env.PHENOMIC_ENV !== "static" &&
         require.resolve("react-hot-loader/patch"),
       "./App.js",
     ].filter(item => item),
@@ -42,9 +42,9 @@ module.exports = (/* config: PhenomicConfig */) => ({
   plugins: [
     new ExtractTextPlugin({
       filename: "styles.css",
-      disable: process.env.NODE_ENV !== "production",
+      disable: process.env.PHENOMIC_ENV !== "static",
     }),
-    process.env.NODE_ENV !== "production" &&
+    process.env.PHENOMIC_ENV !== "static" &&
       new webpack.HotModuleReplacementPlugin(),
     process.env.NODE_ENV === "production" &&
       new webpack.optimize.UglifyJsPlugin(),
diff --git a/packages/phenomic-plugin-collector-files/src/index.js b/packages/phenomic-plugin-collector-files/src/index.js
index 9aa5812e..322ced97 100644
--- a/packages/phenomic-plugin-collector-files/src/index.js
+++ b/packages/phenomic-plugin-collector-files/src/index.js
@@ -36,6 +36,22 @@ export function getSortedKey(name: string, json: PhenomicTransformResult) {
   return key
 }
 
+export function getFields(json: PhenomicTransformResult) {
+  const keys = Object.keys(json.data)
+  return keys.filter(key => key !== "author" && key !== "authors")
+}
+
+export function getFieldValue(json: PhenomicTransformResult, key: string) {
+  if (Array.isArray(json.data[key])) {
+    return json.data[key]
+  }
+  const type = typeof json.data[key]
+  if (type === "string" || type === "number" || type === "boolean") {
+    return [json.data[key]]
+  }
+  return []
+}
+
 export function getAuthors(json: PhenomicTransformResult) {
   if (typeof json.data.author === "string") {
     return [json.data.author]
@@ -46,13 +62,6 @@ export function getAuthors(json: PhenomicTransformResult) {
   return []
 }
 
-export function getTags(json: PhenomicTransformResult) {
-  if (Array.isArray(json.data.tags)) {
-    return json.data.tags
-  }
-  return []
-}
-
 const dateLength = "YYYY-MM-DD".length
 export function injectDateFromFilename(
   name: string,
@@ -98,15 +107,19 @@ export default function() {
             db.put(["authors", collectionName], author, { id: author }),
           ])
         }),
-        ...getTags(json).map(tag => {
-          return Promise.all([
-            // sorted list, filtered by tags
-            db.put([collectionName, "tags", tag], sortedKey, { id: key }),
-            // global tag list
-            db.put(["tags"], tag, { id: tag }),
-            db.put(["tags", "default"], tag, { id: tag }),
-            db.put(["tags", "collection", collectionName], tag, { id: tag }),
-          ])
+        ...getFields(json).map(type => {
+          return getFieldValue(json, type).map(value =>
+            Promise.all([
+              // sorted list, filtered by tags
+              db.put([collectionName, type, value], sortedKey, { id: key }),
+              // global tag list
+              db.put([type], value, { id: value }),
+              db.put([type, "default"], value, { id: value }),
+              db.put([type, "collection", collectionName], value, {
+                id: value,
+              }),
+            ]),
+          )
         }),
       ])
     },
diff --git a/packages/phenomic-plugin-renderer-react/src/components/HTML.js b/packages/phenomic-plugin-renderer-react/src/components/HTML.js
index ec7e0e65..bc1979f0 100644
--- a/packages/phenomic-plugin-renderer-react/src/components/HTML.js
+++ b/packages/phenomic-plugin-renderer-react/src/components/HTML.js
@@ -1,14 +1,6 @@
 import React from "react"
 
-export type HtmlType = (props: PropsType) => React$Element<*>
-
-type PropsType = {
-  body: React$Element<*>,
-  state?: React$Element<*>,
-  script: React$Element<*>,
-}
-
-const Html = (props: PropsType) => (
+const Html = (props: PhenomicHtmlPropsType) => (
   <html>
     <head>
       <meta charSet="utf-8" />
diff --git a/packages/phenomic-plugin-renderer-react/src/createApp.js b/packages/phenomic-plugin-renderer-react/src/createApp.js
index d689e73d..2c2745d2 100644
--- a/packages/phenomic-plugin-renderer-react/src/createApp.js
+++ b/packages/phenomic-plugin-renderer-react/src/createApp.js
@@ -1,12 +1,14 @@
 import render from "./render/client"
-import type { HtmlType } from "./components/HTML"
 
 export type AppType = {
   routes: React$Element<any>,
-  Html?: HtmlType,
+  Html?: (props: PhenomicHtmlPropsType) => React$Element<*>,
 }
 
-export default (routes: () => React$Element<any>, Html?: HtmlType): AppType => {
+export default (
+  routes: () => React$Element<any>,
+  Html?: PhenomicHtmlType,
+): AppType => {
   if (typeof window !== "undefined") {
     render(routes)
   }
diff --git a/packages/phenomic-plugin-renderer-react/src/server/renderHTML.js b/packages/phenomic-plugin-renderer-react/src/server/renderHTML.js
index 31f2bb2e..9983c148 100644
--- a/packages/phenomic-plugin-renderer-react/src/server/renderHTML.js
+++ b/packages/phenomic-plugin-renderer-react/src/server/renderHTML.js
@@ -2,14 +2,11 @@ import React from "react"
 import ReactDOMServer from "react-dom/server"
 
 import DefaultHtml from "../components/HTML"
-import type { HtmlType } from "../components/HTML"
 
-type renderHTMLType = (
-  props: { body?: string, state?: Object },
-  html?: HtmlType,
-) => string
-
-const renderHTML: renderHTMLType = (props, Html = DefaultHtml) => {
+const renderHTML: PhenomicPluginRenderHTMLType = (
+  props = {},
+  Html = DefaultHtml,
+) => {
   const html = (
     <Html
       body={
diff --git a/packages/phenomic/src/commands/build.js b/packages/phenomic/src/commands/build.js
index 57c98a4f..925aa772 100644
--- a/packages/phenomic/src/commands/build.js
+++ b/packages/phenomic/src/commands/build.js
@@ -85,7 +85,7 @@ async function prerenderFileAndDependencies(config, renderer, app, fetch, url) {
 async function build(config) {
   process.env.NODE_ENV = process.env.NODE_ENV || "production"
   process.env.BABEL_ENV = process.env.BABEL_ENV || "production"
-  // process.env.PHENOMIC_ENV = "static"
+  process.env.PHENOMIC_ENV = "static"
   debug("building")
   const phenomicServer = createServer(db, config.plugins)
   const port = await getPort()
diff --git a/packages/phenomic/src/commands/start.js b/packages/phenomic/src/commands/start.js
index 02468abb..73107d57 100644
--- a/packages/phenomic/src/commands/start.js
+++ b/packages/phenomic/src/commands/start.js
@@ -25,7 +25,7 @@ function createBundlerServer(config: PhenomicConfig) {
 function start(config: PhenomicConfig) {
   process.env.NODE_ENV = process.env.NODE_ENV || "development"
   process.env.BABEL_ENV = process.env.BABEL_ENV || "development"
-  // process.env.PHENOMIC_ENV = "development"
+  process.env.PHENOMIC_ENV = "development"
   debug("starting phenomic server")
   const phenomicServer = createServer(db, config.plugins)
   const bundlerServer = createBundlerServer(config)


Test patch:
diff --git a/packages/phenomic-plugin-collector-files/src/__tests__/index.js b/packages/phenomic-plugin-collector-files/src/__tests__/index.js
index be7ed04e..8ead7220 100644
--- a/packages/phenomic-plugin-collector-files/src/__tests__/index.js
+++ b/packages/phenomic-plugin-collector-files/src/__tests__/index.js
@@ -2,7 +2,8 @@ import {
   getKey,
   getSortedKey,
   getAuthors,
-  getTags,
+  getFields,
+  getFieldValue,
   injectDateFromFilename,
 } from ".."
 
@@ -40,11 +41,32 @@ it("should be able to generate authors list", () => {
   ).toEqual(["test", "test2"])
 })
 
-it("should be able to generate tag list", () => {
-  expect(getTags({ partial: {}, data: { tags: ["test", "test2"] } })).toEqual([
-    "test",
-    "test2",
-  ])
+it("should be able to get arbitrary fields", () => {
+  expect(
+    getFields({ partial: {}, data: { tags: ["test", "test2"] } }),
+  ).toEqual(["tags"])
+})
+
+it("should be able to generate fields lists from arrays", () => {
+  expect(
+    getFieldValue({ partial: {}, data: { tags: ["test", "test2"] } }, "tags"),
+  ).toEqual(["test", "test2"])
+})
+
+it("should be able to generate fields lists from strings", () => {
+  expect(
+    getFieldValue({ partial: {}, data: { tags: "test" } }, "tags"),
+  ).toEqual(["test"])
+})
+
+it("should be able to generate fields lists from numbers", () => {
+  expect(getFieldValue({ partial: {}, data: { tags: 2 } }, "tags")).toEqual([2])
+})
+
+it("should be able to generate fields lists from booleans", () => {
+  expect(
+    getFieldValue({ partial: {}, data: { tags: false } }, "tags"),
+  ).toEqual([false])
 })
 
 it("should be able to inject date from filename in data", () => {