diff --git a/.gitignore b/.gitignore index 9b8f780c4..83fde97d0 100644 --- a/.gitignore +++ b/.gitignore @@ -12,9 +12,6 @@ coverage # build lib -# some tests output some files -src/phenomic-loader/__tests__/output/ - # tests results **/__tests__/_output* diff --git a/docs/content/docs/usage/configuration/build.md b/docs/content/docs/usage/configuration/build.md index 2f130fbcb..9bd34198f 100644 --- a/docs/content/docs/usage/configuration/build.md +++ b/docs/content/docs/usage/configuration/build.md @@ -19,7 +19,7 @@ is crucial (in the phenomic-theme-base, it's the first loader) : - it allows you to control what text engine to use (default to Markdown using [remark](http://remark.js.org/) - using a solid [default](https://github.com/MoOx/phenomic/blob/master/src/phenomic-loader-plugin-markdown-transform-body-property-to-html/index.js)) + using a solid [default](https://github.com/MoOx/phenomic/blob/master/src/loader-plugin-markdown-transform-body-property-to-html/index.js)) and will generate JSON files, that will be consumed for the front-end, - it handles the generation of the collection data, - it allows you to generate some RSS feeds. @@ -39,7 +39,7 @@ Here is a commented part of a webpack configuration that use all options //... import pkg from "./package.json" -import { phenomicLoader, phenomicLoaderPlugins } from "phenomic" +import { phenomicLoader } from "phenomic" export const makeConfig = (config = {}) => { return { @@ -67,50 +67,29 @@ export const makeConfig = (config = {}) => { // below are the default values, // you don't need those by default plugins: [ - phenomicLoaderPlugins.initHeadPropertyFromConfig, - phenomicLoaderPlugins.initHeadPropertyFromContent, - phenomicLoaderPlugins.initBodyPropertyFromContent, - phenomicLoaderPlugins.markdownInitHeadDescriptionPropertyFromContent, - phenomicLoaderPlugins.markdownTransformBodyPropertyToHtml, + require("phenomic/lib/loader-plugin-init-head-property-from-config").default, + require("phenomic/lib/loader-plugin-init-head-property-from-content").default, + require("phenomic/lib/loader-plugin-init-body-property-from-content").default, + require("phenomic/lib/loader-plugin-markdown-init-head.description-property-from-content").default, + require("phenomic/lib/loader-plugin-markdown-transform-body-property-to-html").default, // here you can add/replace any function you want // for examples, see // https://github.com/MoOx/phenomic/blob/master/src/ // eg: if you need the raw file content in your pages, // you can add the following plugin that will add a `raw` property - // phenomicLoaderPlugins.addRawProperty, + // require("phenomic/lib/loader-plugin-init-raw-property-from-content").default, // if you want raw body (text content without the front-matter) // you can add the following plugin that will add a `rawBody` property - // phenomicLoaderPlugins.addRawBodyProperty, + // require("phenomic/lib/loader-plugin-init-rawBody-property-from-content").default, ] // default values for `head` // this value can be defined and used by the plugin - // initHeadPropertyFromConfig + // "phenomic/lib/loader-plugin-init-head-property-from-config" defaultHead: { layout: "Post", comments: true, } - - // RSS global options - feedsOptions: { - title: pkg.name, - site_url: pkg.homepage, - }, - - feeds: { - // RSS - "feed.xml": { - collectionOptions: { - // here, you can filter using - // phenomic/lib/enhance-collection API - // see /docs/usage/collections/ - filter: { layout: "Post" }, - sort: "date", - reverse: true, - limit: 20, - }, - }, - }, }, // ... } diff --git a/docs/content/docs/usage/feeds.md b/docs/content/docs/usage/feeds.md index ded9abba6..679826005 100644 --- a/docs/content/docs/usage/feeds.md +++ b/docs/content/docs/usage/feeds.md @@ -11,36 +11,46 @@ last 20 posts. Here is a commented part of the webpack configuration that will help: ```js +import pkg from "./package.json" import { phenomicLoader } from "phenomic" +import PhenomicLoaderFeedWebpackPlugin + from "phenomic/lib/loader-feed-webpack-plugin" // ... module: { - loaders: [ - { - test: /\.md$/, - loader: phenomicLoader, - query: { - context: path.join(config.cwd, config.source), + loaders: [ + { + test: /\.md$/, + loader: phenomicLoader, + query: { + context: path.join(config.cwd, config.source), + }, - // here you define generic metadata for your feed - feedsOptions: { - title: pkg.name, - site_url: pkg.homepage, - }, - feeds: { - // here we define one feed, but you can generate multiple, based - // on different filters - "feed.xml": { - collectionOptions: { - filter: { layout: "Post" }, - sort: "date", - reverse: true, - limit: 20, - }, - }, - }, - }, - }, + // ... + } + ], + }, + plugins: [ + new PhenomicLoaderFeedWebpackPlugin({ + // here you define generic metadata for your feed + feedsOptions: { + title: pkg.name, + site_url: pkg.homepage, + }, + feeds: { + // here we define one feed, but you can generate multiple, based + // on different filters + "feed.xml": { + collectionOptions: { + filter: { layout: "Post" }, + sort: "date", + reverse: true, + limit: 20, + }, + }, + }, + }), + } // ... ``` diff --git a/docs/content/docs/usage/plugins.md b/docs/content/docs/usage/plugins.md index 40b99ad7d..2cb1f48e3 100644 --- a/docs/content/docs/usage/plugins.md +++ b/docs/content/docs/usage/plugins.md @@ -36,60 +36,45 @@ See the last section to know how to write a plugin (spoiler: it's easy). ## Existing Plugins -For now, plugins are accessible in ``phenomicLoaderPlugins`` in ``phenomic``. +For now, plugins are accessible in ``phenomic/lib/loader-plugin-*``. ```js -import { phenomicLoaderPlugins } from "phenomic" - -phenomicLoaderPlugins.[camelCasedNameOfThePlugin] -// eg: phenomic-loader-plugin-init-body-property-from-content -// will be accessible under -// phenomicLoaderPlugins.initBodyPropertyFromContent +import initHeadPropertyFromConfig from "phenomic/lib/loader-plugin-init-head-property-from-config" ``` *This might change if phenomic is split into multiple packages.* See [#598](https://github.com/MoOx/phenomic/issues/598) for more informations. -### ``phenomic-loader-plugin-init-body-property-from-content`` +### ``phenomic/lib/loader-plugin-init-body-property-from-content`` This plugin initializes the ``body`` property from data retrieved in the file. It takes the content of the input that is below the front-matter. -*For now accessible via ``phenomicLoaderPlugins.initBodyPropertyFromContent``.* - -### ``phenomic-loader-plugin-init-head-property-from-config`` +### ``phenomic/lib/loader-plugin-init-head-property-from-config`` This plugin initializes in the ``head`` property from ``defaultConfig`` in the webpack ``phenomic`` configuration section. It won't override existing key/values in the ``head`` if there is any. -*For now accessible via ``phenomicLoaderPlugins.initHeadPropertyFromConfig``.* - -### ``phenomic-loader-plugin-init-head-property-from-content`` +### ``phenomic/lib/loader-plugin-init-head-property-from-content`` This plugin initializes in the ``head`` property from data retrieved in the file. It takes the front-matter of the input and map it as key => value. -*For now accessible via ``phenomicLoaderPlugins.initHeadPropertyFromContent``.* - -### ``phenomic-loader-plugin-init-raw-property-from-content`` +### ``phenomic/lib/loader-plugin-init-raw-property-from-content`` This plugin initializes in a ``raw`` property. This property contains the entire file as raw data. Useful if you front-end need to handle the content of the file. -*For now accessible via ``phenomicLoaderPlugins.initRawPropertyFromContent``.* - -### ``phenomic-loader-plugin-init-rawBody-property-from-content`` +### ``phenomic/lib/loader-plugin-init-rawBody-property-from-content`` This plugin initializes in a ``rawBody`` property. This property contains the content of the file that is below the front-matter, as raw data. Useful if you front-end need to handle the content of the file. -*For now accessible via ``phenomicLoaderPlugins.initRawBodyPropertyFromContent``.* - -### ``phenomic-loader-plugin-markdown-init-head.description-property-from-content`` +### ``phenomic/lib/loader-plugin-markdown-init-head.description-property-from-content`` This plugin initializes a ``description`` property in the ``head``, based on the content accessible below the front-matter. @@ -105,16 +90,12 @@ You can pass options to ``phenomic`` section in webpack configuration. } ``` -*For now accessible via ``phenomicLoaderPlugins.markdownInitHeadDescriptionPropertyFromContent``.* - -### ``phenomic-loader-plugin-markdown-transform-body-property-to-html`` +### ``phenomic/lib/loader-plugin-markdown-transform-body-property-to-html`` This plugin will transform the ``body`` property into html. This plugin will assumes your content is markdown and will use [``remark``](http://remark.js.org/) with -[some plugins](https://github.com/MoOx/phenomic/blob/master/src/phenomic-loader-plugin-markdown-transform-body-property-to-html/index.js) for the transformation. - -*For now accessible via ``phenomicLoaderPlugins.markdownTransformBodyPropertyToHtml``.* +[some plugins](https://github.com/MoOx/phenomic/blob/master/src/phenomic/lib/loader-plugin-markdown-transform-body-property-to-html/index.js) for the transformation. ## Presets @@ -140,21 +121,21 @@ notation is more verbose, especially if you have multiple plugins. Phenomic provides the following presets: -### ``phenomic-loader-preset-default`` +### ``phenomic/lib/loader-preset-default`` -- ``phenomic-loader-plugin-init-head-property-from-config`` -- ``phenomic-loader-plugin-init-head-property-from-content`` -- ``phenomic-loader-plugin-init-body-property-from-content`` +- ``phenomic/lib/loader-plugin-init-head-property-from-config`` +- ``phenomic/lib/loader-plugin-init-head-property-from-content`` +- ``phenomic/lib/loader-plugin-init-body-property-from-content`` 🛠 This preset is kind of the phenomic default requirement. Use it if you want to use classic files with a front-matter and any text format. **Feel free to take a look to markdown preset to implement your own engine!** -### ``phenomic-loader-preset-markdown`` +### ``phenomic/lib/loader-preset-markdown`` -- ``phenomic-loader-preset-default`` -- ``phenomic-loader-plugin-markdown-init-head.description-property-from-content`` -- ``phenomic-loader-plugin-markdown-transform-body-property-to-html`` +- ``phenomic/lib/loader-preset-default`` +- ``phenomic/lib/loader-plugin-markdown-init-head.description-property-from-content`` +- ``phenomic/lib/loader-plugin-markdown-transform-body-property-to-html`` ❤️ This preset is the one used by default in Phenomic. It allows you to consume common markdown files that have a front-matter out of the box. @@ -167,9 +148,7 @@ You can use some plugins, but not the one that will transform the markdown as HTML. ```js -import { phenomicLoader, phenomicLoaderPresets } from "phenomic" -// or if you want to use some each plugins manually -// import { phenomicLoader, phenomicLoaderPlugins } from "phenomic" +import { phenomicLoader } from "phenomic" // ... @@ -183,18 +162,18 @@ import { phenomicLoader, phenomicLoaderPresets } from "phenomic" plugins: [ // here are the unopininated default plugins - ...phenomicLoaderPresets.default, + ...require("phenomic/lib/loader-preset-default").default, // Instead of specifing via the preset, you can cherry pick some, - // phenomicLoaderPlugins.initHeadPropertyFromConfig, - // phenomicLoaderPlugins.initHeadPropertyFromContent, - // phenomicLoaderPlugins.initBodyPropertyFromContent, + // require("phenomic/lib/loader-plugin-init-head-property-from-config").default, + // require("phenomic/lib/loader-plugin-init-head-property-from-content").default, + // require("phenomic/lib/loader-plugin-init-body-property-from-content").default, - // ...phenomicLoaderPresets.markdown + // ...require("phenomic/lib/loader-preset-markdown").default // The commented preset above is part of the default renderer. // You can also cherry pick on plugin or the other - // phenomicLoaderPlugins.markdownInitHeadDescriptionPropertyFromContent, - // phenomicLoaderPlugins.transformMdBodyPropertyToHtml, + // require("phenomic/lib/loader-plugin-markdown-init-head.description-property-from-content").default, + // require("phenomic/lib/loader-plugin-transform-md-body-property-to-html").default, // here is an example of another transformation ({ result }) => { @@ -210,10 +189,13 @@ import { phenomicLoader, phenomicLoaderPresets } from "phenomic" // https://github.com/MoOx/phenomic/blob/master/src/ } } - ] - }, - }, - ... + ], + }, + }, + + // ... + ] + } ``` ## Writing plugins diff --git a/docs/package.json b/docs/package.json index 4c03b6dda..4201e6307 100644 --- a/docs/package.json +++ b/docs/package.json @@ -17,20 +17,20 @@ "lint:css": "stylelint \"src/**/*.css\"", "lint": "npm-run-all --parallel lint:*", "start": "phenomic start", + "restart": "cd ../ && npm -s run transpile && cd docs && phenomic start", "build": "phenomic build", "pretest": "npm run lint", "test": "npm run build" }, "devDependencies": { - "babel-cli": "^6.3.17", - "babel-core": "^6.3.26", - "babel-eslint": "^6.0.0-beta.0", - "babel-loader": "^6.2.0", - "babel-preset-es2015": "^6.3.13", - "babel-preset-react": "^6.3.13", - "babel-preset-react-hmre": "^1.0.1", + "babel-cli": "^6.14.0", + "babel-core": "^6.14.0", + "babel-eslint": "^6.1.2", + "babel-loader": "^6.2.5", + "babel-preset-es2015": "^6.14.0", + "babel-preset-react": "^6.11.1", "babel-preset-react-optimize": "^1.0.1", - "babel-preset-stage-1": "^6.3.13", + "babel-preset-stage-1": "^6.13.0", "classnames": "^2.2.3", "color": "^0.11.1", "css-loader": "^0.23.0", @@ -38,7 +38,7 @@ "eslint-config-i-am-meticulous": "^4.1.1", "eslint-loader": "^1.3.0", "eslint-plugin-react": "^4.3.0", - "extract-text-webpack-plugin": "^0.8.2", + "extract-text-webpack-plugin": "^2.0.0-beta", "file-loader": "^0.8.1", "history": "^2.0.0", "invariant": "^2.1.1", @@ -61,22 +61,55 @@ "react-svg-inline": "^1.1.0", "react-topbar-progress-indicator": "^1.0.0", "redux": "^3.0.0", + "react-hot-loader": "^3.0.0-beta", "style-loader": "^0.12.3", "stylelint": "^6.8.0", "stylelint-config-standard": "^10.0.0", - "webpack": "^1.12.1", + "webpack": "^2.1.0-beta.21", "whatwg-fetch": "^0.11.0" }, "babel": { - "presets": [ - "babel-preset-react", - "babel-preset-es2015", - "babel-preset-stage-1" - ], "env": { + "development": { + "presets": [ + "babel-preset-es2015", + "babel-preset-stage-1", + "babel-preset-react" + ] + }, "production": { "presets": [ - "babel-preset-react-optimize" + "babel-preset-es2015", + "babel-preset-stage-1", + "babel-preset-react" + ] + }, + "webpack-development": { + "presets": [ + [ + "babel-preset-es2015", + { + "modules": false + } + ], + "babel-preset-stage-1", + "babel-preset-react" + ], + "plugins": [ + "react-hot-loader/babel" + ] + }, + "webpack-production": { + "presets": [ + "babel-preset-react-optimize", + [ + "babel-preset-es2015", + { + "modules": false + } + ], + "babel-preset-stage-1", + "babel-preset-react" ] } } diff --git a/docs/scripts/phenomic.browser.js b/docs/scripts/phenomic.browser.js index 495b65f5f..30e78d12b 100644 --- a/docs/scripts/phenomic.browser.js +++ b/docs/scripts/phenomic.browser.js @@ -1,24 +1,32 @@ +// Hot loading HRM Patch +import "react-hot-loader/patch" + +// fetch polyfill import "whatwg-fetch" -import phenomicClient from "phenomic/lib/client" + import metadata from "../src/metadata.js" import routes from "../src/routes.js" import store from "../src/store.js" +import phenomicClient from "phenomic/lib/client" +phenomicClient({ metadata, routes, store }) -phenomicClient({ - metadata, - routes, - store, -}) - -// hot loading -// md files → JSON && generate collection + hot loading for dev +// md files processed via phenomic-loader to JSON && generate collection let mdContext = require.context("../content", true, /\.md$/) mdContext.keys().forEach(mdContext) + +// hot loading if (module.hot) { - const mdHotUpdater = require("phenomic/lib/client/hot-md").default + + // hot load md module.hot.accept(mdContext.id, () => { mdContext = require.context("../content", true, /\.md$/) + const mdHotUpdater = require("phenomic/lib/client/hot-md").default const requireUpdate = mdHotUpdater(mdContext, window.__COLLECTION__, store) mdContext.keys().forEach(requireUpdate) }) + + module.hot.accept( + [ "../src/metadata.js", "../src/routes.js", "../src/store.js" ], + () => phenomicClient({ metadata, routes, store }) + ) } diff --git a/docs/scripts/phenomic.node.js b/docs/scripts/phenomic.node.js index d40451b18..4a24948b0 100644 --- a/docs/scripts/phenomic.node.js +++ b/docs/scripts/phenomic.node.js @@ -1,13 +1,7 @@ import metadata from "../src/metadata.js" import routes from "../src/routes.js" import store from "../src/store.js" - import phenomicStatic from "phenomic/lib/static" module.exports = (options) => - phenomicStatic({ - ...options, - metadata, - routes, - store, - }) + phenomicStatic({ ...options, metadata, routes, store }) diff --git a/docs/webpack.config.babel.js b/docs/webpack.config.babel.js index 513d59ad3..2be3884e2 100644 --- a/docs/webpack.config.babel.js +++ b/docs/webpack.config.babel.js @@ -3,6 +3,8 @@ import path from "path" import webpack from "webpack" import ExtractTextPlugin from "extract-text-webpack-plugin" import { phenomicLoader } from "phenomic" +import PhenomicLoaderFeedWebpackPlugin + from "phenomic/lib/loader-feed-webpack-plugin" import pkg from "./package.json" @@ -32,45 +34,38 @@ export const makeConfig = (config = {}) => { }, { test: /\.js$/, - loaders: [ - `babel-loader${ - config.dev - ? "?cacheDirectory=true&presets[]=babel-preset-react-hmre" - : "?cacheDirectory=true" - }`, - "eslint-loader?fix", - ], include: [ path.resolve(__dirname, "scripts"), path.resolve(__dirname, "src"), - - // BECAUSE DOCS USE NON TRANSPILED CODE - path.resolve(__dirname, "..", "src"), - // FOR DOCS ONLY, SINCE DOCS USE NON TRANSPILED PHENOMIC SOURCES + ], + loaders: [ + "babel-loader?cacheDirectory=true", + "eslint-loader?fix", ], }, { test: /\.css$/, exclude: /\.global\.css$/, include: path.resolve(__dirname, "src"), - loader: ExtractTextPlugin.extract( - "style-loader", - [ `css-loader?modules&localIdentName=${ + loader: ExtractTextPlugin.extract({ + fallbackLoader: "style-loader", + loader: [ + `css-loader?modules&localIdentName=${ config.production ? "[hash:base64:5]" : "[path][name]--[local]--[hash:base64:5]" }`, "postcss-loader", - ].join("!"), - ), + ], + }), }, { test: /\.global\.css$/, include: path.resolve(__dirname, "src"), - loader: ExtractTextPlugin.extract( - "style-loader", - [ "css-loader", "postcss-loader" ].join("!"), - ), + loader: ExtractTextPlugin.extract({ + fallbackLoader: "style-loader", + loader: [ "css-loader", "postcss-loader" ], + }), }, { test: /\.(html|ico|jpe?g|png|gif)$/, @@ -87,22 +82,8 @@ export const makeConfig = (config = {}) => { phenomic: { context: path.join(__dirname, config.source), - // plugins: [ ...phenomicLoaderPresets.markdown ] + // plugins: [ ...require("phenomic/lib/loader-preset-markdown").default ] // see https://phenomic.io/docs/usage/plugins/ - feedsOptions: { - title: pkg.name, - site_url: pkg.homepage, - }, - feeds: { - "feed.xml": { - collectionOptions: { - filter: { layout: "Post" }, - sort: "date", - reverse: true, - limit: 20, - }, - }, - }, }, postcss: () => [ @@ -115,10 +96,35 @@ export const makeConfig = (config = {}) => { ], plugins: [ - new ExtractTextPlugin("[name].[hash].css", { disable: config.dev }), + new PhenomicLoaderFeedWebpackPlugin({ + // here you define generic metadata for your feed + feedsOptions: { + title: pkg.name, + site_url: pkg.homepage, + }, + feeds: { + // here we define one feed, but you can generate multiple, based + // on different filters + "feed.xml": { + collectionOptions: { + filter: { layout: "Post" }, + sort: "date", + reverse: true, + limit: 20, + }, + }, + }, + }), + + new ExtractTextPlugin({ + filename: "[name].[hash].css", + disable: config.dev, + }), ...config.production && [ - new webpack.optimize.DedupePlugin(), + // DedupePlugin does not work correctly with Webpack 2, yet ;) + // https://github.com/webpack/webpack/issues/2644 + // new webpack.optimize.DedupePlugin(), new webpack.optimize.UglifyJsPlugin( { compress: { warnings: false } } ), diff --git a/flow/interfaces/globals.js b/flow/interfaces/globals.js index 2d76e5b7e..039899172 100644 --- a/flow/interfaces/globals.js +++ b/flow/interfaces/globals.js @@ -1,6 +1,7 @@ declare var process: { env: { - NODE_ENV? : "production", + BABEL_ENV?: string, + NODE_ENV?: string, PHENOMIC_USER_URL: string, PHENOMIC_USER_PATHNAME: string, PHENOMIC_NAME: string, diff --git a/flow/interfaces/node-modules/webpack-sources.js b/flow/interfaces/node-modules/webpack-sources.js new file mode 100644 index 000000000..14e00287d --- /dev/null +++ b/flow/interfaces/node-modules/webpack-sources.js @@ -0,0 +1,8 @@ +/* eslint-disable no-unused-vars */ +type SourceType = Object + +type RawSourceType = (s: string) => SourceType + +declare module "webpack-sources" { + declare var RawSource: RawSourceType +} diff --git a/package.json b/package.json index 581b23198..46b37c0c5 100644 --- a/package.json +++ b/package.json @@ -45,13 +45,13 @@ "connect-history-api-fallback": "^1.2.0", "debug": "^2.2.0", "express": "^4.13.3", - "extract-text-webpack-plugin": "^1.0.1", + "extract-text-webpack-plugin": "^2.0.0-beta", "find-cache-dir": "^0.1.1", "fs-extra": "^0.26.4", "fs-promise": "^0.3.1", "globby": "^6.0.0", "gray-matter": "^2.0.0", - "hard-source-webpack-plugin": "0.0.28", + "hard-source-webpack-plugin": "0.0.37", "inquirer": "^1.0.0", "loader-utils": "^0.2.11", "mkdirp": "^0.5.1", @@ -75,23 +75,21 @@ "strip-markdown": "^0.3.1", "url-join": "^1.1.0", "valid-url": "^1.0.9", - "webpack": "^1.11.0", - "webpack-dev-middleware": "^1.2.0", - "webpack-hot-middleware": "^2.0.0", + "webpack": "^2.1.0-beta.21", + "webpack-dev-middleware": "^1.6.1", + "webpack-hot-middleware": "^2.12.2", "webpack-notifier": "^1.4.0", "yargs": "^4.3.1" }, "devDependencies": { "ava": "^0.16.0", - "babel-cli": "^6.3.17", - "babel-core": "^6.3.26", - "babel-eslint": "^6.0.0-beta.0", + "babel-cli": "^6.14.0", + "babel-core": "^6.14.0", + "babel-eslint": "^6.1.2", "babel-plugin-flow-react-proptypes": "^0.7.1", "babel-plugin-istanbul": "^2.0.0", - "babel-preset-es2015": "^6.3.13", - "babel-preset-react": "^6.3.13", - "babel-preset-react-hmre": "^1.0.1", - "babel-preset-stage-1": "^6.3.13", + "babel-preset-react": "^6.11.1", + "babel-preset-stage-1": "^6.13.0", "cmd-shim": "^2.0.2", "coveralls": "^2.11.8", "cross-env": "^2.0.0", @@ -125,12 +123,11 @@ "suppose": "^0.6.1" }, "peerDependencies": { - "babel-cli": "^6.3.17", - "babel-core": "^6.3.26", - "babel-loader": "^6.2.0", - "babel-preset-es2015": "^6.3.13", + "babel-cli": "^6.14.0", + "babel-core": "^6.14.0", + "babel-loader": "^6.2.5", + "babel-preset-es2015": "^6.14.0", "babel-preset-react": "^6.3.13", - "babel-preset-react-hmre": "^1.0.1", "invariant": "^2.1.1", "history": "^2.0.0", "react": "^0.14.0 || ^15.0.0-rc.1", @@ -139,19 +136,20 @@ "react-redux": "^4.0.0", "react-router": "^2.3.0", "redux": "^3.0.0", - "webpack": "^1.12.1", + "webpack": "^2.1.0-beta.21", "whatwg-fetch": "^0.11.0" }, "optionalPeerDependencies": { - "babel-eslint": "^6.0.0-beta.0", - "babel-preset-stage-1": "^6.3.13", + "babel-eslint": "^6.1.2", + "babel-preset-es2015": "^6.14.0", "babel-preset-react-optimize": "^1.0.1", + "babel-preset-stage-1": "^6.13.0", "css-loader": "^0.23.0", "eslint": "^2.0.0", "eslint-config-i-am-meticulous": "^4.1.1", "eslint-loader": "^1.3.0", "eslint-plugin-react": "^4.3.0", - "extract-text-webpack-plugin": "^1.0.1", + "extract-text-webpack-plugin": "^2.0.0-beta", "file-loader": "^0.8.1", "json-loader": "^0.5.2", "postcss-browser-reporter": "^0.4.0", @@ -160,6 +158,7 @@ "postcss-reporter": "^1.3.0", "npm-run-all": "^1.7.0", "raw-loader": "^0.5.1", + "react-hot-loader": "^3.0.0-beta", "react-svg-inline": "^1.1.0", "react-topbar-progress-indicator": "^1.0.0", "style-loader": "^0.12.3", @@ -209,9 +208,9 @@ }, "babel": { "presets": [ - "babel-preset-react", "babel-preset-es2015", - "babel-preset-stage-1" + "babel-preset-stage-1", + "babel-preset-react" ], "plugins": [ "babel-plugin-flow-react-proptypes" @@ -219,7 +218,7 @@ "env": { "test": { "plugins": [ - "istanbul" + "babel-plugin-istanbul" ] } } diff --git a/scripts/docs.js b/scripts/docs.js index c69511ae9..2c4b0c205 100644 --- a/scripts/docs.js +++ b/scripts/docs.js @@ -1,3 +1,3 @@ import testFolder from "./utils/test-folder.js" -testFolder("docs", { lib: "src" }) +testFolder("docs") diff --git a/scripts/utils/test-folder.js b/scripts/utils/test-folder.js index 482e31724..4e2af66f8 100644 --- a/scripts/utils/test-folder.js +++ b/scripts/utils/test-folder.js @@ -17,7 +17,7 @@ export function exec(cmd, opts) { export default async function test( target, - { cleanup = noop, init = noop, lib = "lib", test = true } + { cleanup = noop, init = noop, lib = "lib", test = true } = {} ) { try { const targetModules = `${ target }/node_modules` diff --git a/src/bin/commands/setup/template.js b/src/bin/commands/setup/template.js index de1fe8bbe..b650392bf 100644 --- a/src/bin/commands/setup/template.js +++ b/src/bin/commands/setup/template.js @@ -16,15 +16,47 @@ const template = { }, "phenomic": {/* placeholder */}, "babel": { - "presets": [ - "babel-preset-react", - "babel-preset-es2015", - "babel-preset-stage-1", - ], "env": { + "development": { + "presets": [ + "babel-preset-es2015", + "babel-preset-stage-1", + "babel-preset-react", + ], + }, "production": { + "presets": [ + "babel-preset-es2015", + "babel-preset-stage-1", + "babel-preset-react", + ], + }, + "webpack-development": { + "presets": [ + [ + "babel-preset-es2015", + { + "modules": false, + }, + ], + "babel-preset-stage-1", + "babel-preset-react", + ], + "plugins": [ + "react-hot-loader/babel", + ], + }, + "webpack-production": { "presets": [ "babel-preset-react-optimize", + [ + "babel-preset-es2015", + { + "modules": false, + }, + ], + "babel-preset-stage-1", + "babel-preset-react", ], }, }, diff --git a/src/builder/index.js b/src/builder/index.js index cea0070f9..7529fcec1 100644 --- a/src/builder/index.js +++ b/src/builder/index.js @@ -8,12 +8,12 @@ import webpack from "./webpack" import sortAssets from "./webpack/sortAssets" import devServer from "./server" -import collection from "../phenomic-loader/cache" - import webpackConfigBrowser from "./webpack/config.browser.js" import webpackConfigNode from "./webpack/config.node.js" import dynamicRequire from "./dynamic-require.js" +import PhenomicLoaderWebpackPlugin from "../loader/plugin.js" + export default function(config: Object): void { const log = debug("phenomic:builder") // log(JSON.stringify(config, null, 2)) @@ -37,6 +37,8 @@ export default function(config: Object): void { const destination = join(config.cwd, config.destination) fs.emptyDirSync(destination) + process.env.BABEL_ENV = "webpack-" + (process.env.NODE_ENV || "development") + if (config.static) { // Copy static assets to build folder if (config.assets) { @@ -57,7 +59,7 @@ export default function(config: Object): void { config.webpackConfigNode.output.filename ))({ ...config, - collection, + collection: PhenomicLoaderWebpackPlugin.collection, assetsFiles, }) .then(() => { diff --git a/src/builder/server.js b/src/builder/server.js index 30b651802..c542049c4 100644 --- a/src/builder/server.js +++ b/src/builder/server.js @@ -10,10 +10,9 @@ import opn from "opn" import debug from "debug" import portFinder from "portfinder" -import minifyCollection from "../phenomic-loader/minify" +import PhenomicLoaderWebpackPlugin from "../loader/plugin.js" +import minifyCollection from "../loader/minify" import serialize from "../_utils/serialize" - -import collection from "../phenomic-loader/cache.js" import pathToUri from "../_utils/path-to-uri" const log = debug("phenomic:builder:server") @@ -43,30 +42,26 @@ export default (config) => { const devConfig = { ...webpackConfig, - // debug: true, - // watch: true, - // colors: true, entry: { // add devEntries - ...Object.keys(webpackConfig.entry) - .reduce((acc, key) => { - // some entries do not need extra stuff - acc[key] = [ - ...devEntries, - ...Array.isArray(webpackConfig.entry[key]) - ? webpackConfig.entry[key] - : [ webpackConfig.entry[key] ], - ] - return acc - }, - {} - ), + ...Object.keys(webpackConfig.entry).reduce((acc, key) => ({ + ...acc, + [key]: [ + ...devEntries, + ...Array.isArray(webpackConfig.entry[key]) + ? webpackConfig.entry[key] + : [ webpackConfig.entry[key] ], + ], + }), {}), }, plugins: [ ...(webpackConfig.plugins || []), - new webpack.optimize.OccurenceOrderPlugin(), + + // for hot-middleware + new webpack.optimize.OccurrenceOrderPlugin(), new webpack.HotModuleReplacementPlugin(), new webpack.NoErrorsPlugin(), + new WebpackNotifierPlugin(), ], eslint: { @@ -75,16 +70,13 @@ export default (config) => { }, } - // webpack requirements + // webpack dev + hot middlewares const webpackCompiler = webpack(devConfig) - server.use(webpackDevMiddleware(webpackCompiler, { publicPath: webpackConfig.output.publicPath, noInfo: !config.verbose, ...devConfig.devServer, })) - - // HMR server.use(webpackHotMiddleware(webpackCompiler)) let entries = [] @@ -124,7 +116,9 @@ export default (config) => { // hardcoded entry point router.get("/index.html", (req, res) => { - const collectionMin = minifyCollection(collection) + const collectionMin = minifyCollection( + PhenomicLoaderWebpackPlugin.collection + ) res.setHeader("Content-Type", "text/html") /* eslint-disable max-len */ res.end( diff --git a/src/builder/webpack/config.browser.js b/src/builder/webpack/config.browser.js index 9e1c876f1..6309c656e 100644 --- a/src/builder/webpack/config.browser.js +++ b/src/builder/webpack/config.browser.js @@ -3,6 +3,7 @@ import { join } from "path" import commonWebpackConfig from "./config.common.js" import { offlinePlugin, offlineEntry } from "../../_utils/offline/webpack.js" +import PhenomicLoaderWebpackPlugin from "../../loader/plugin.js" const chunkNameBrowser = "phenomic.browser" @@ -13,6 +14,7 @@ export default (config: PhenomicConfig): WebpackConfig => { return { ...webpackConfig, plugins: [ + new PhenomicLoaderWebpackPlugin(), ...webpackConfig.plugins, ...offlinePlugin(config), ], diff --git a/src/builder/webpack/config.node.js b/src/builder/webpack/config.node.js index ed00cb3fc..862e6a68b 100644 --- a/src/builder/webpack/config.node.js +++ b/src/builder/webpack/config.node.js @@ -43,8 +43,9 @@ export default (config: PhenomicConfig): WebpackConfig => { externals: [ ...webpackConfig.externals || defaultExternals, - // we need this to be the same between the entire node runtime - "phenomic/lib/phenomic-loader/cache", + // keep the loader plugin cache in memory + "phenomic/lib/loader/index", + "phenomic/lib/loader/plugin", ], // sourcemaps @@ -54,10 +55,11 @@ export default (config: PhenomicConfig): WebpackConfig => { ...webpackConfig.plugins.filter( (plugin) => !(plugin instanceof UglifyJsPlugin) ) || [], - new BannerPlugin( - "require('source-map-support').install();", - { raw: true, entryOnly: false } - ), + new BannerPlugin({ + banner: "require('source-map-support').install();", + raw: true, + entryOnly: false, + }), ], } } diff --git a/src/content-loader/index.js b/src/content-loader/index.js deleted file mode 100644 index 87d063181..000000000 --- a/src/content-loader/index.js +++ /dev/null @@ -1,14 +0,0 @@ -// deprecated -import colors from "chalk" -const loader = require("../phenomic-loader") - -console.log("⚠️ " + colors.yellow( - "'phenomic/lib/content-loader' reference is deprecated.\n" + - "Please use `import { loader } from \"phenomic\"` " + - "and use `loader` variable as the reference in webpack configuration.\n" + - "You will need to update the key used to define phenomic loader options " + - "in your webpack configuration. Check out the CHANGELOG for more " + - "informations." -)) - -module.exports = loader diff --git a/src/index.js b/src/index.js index 647987cdc..825820f38 100644 --- a/src/index.js +++ b/src/index.js @@ -9,20 +9,4 @@ export { default as PageContainer } from "./components/PageContainer" export { default as BodyContainer } from "./components/BodyContainer" export { default as joinUri } from "url-join" -export const phenomicLoader = "phenomic/lib/phenomic-loader" - -/* eslint-disable max-len */ -export const phenomicLoaderPlugins = { - initBodyPropertyFromContent: require("./phenomic-loader-plugin-init-body-property-from-content").default, - initHeadPropertyFromConfig: require("./phenomic-loader-plugin-init-head-property-from-config").default, - initHeadPropertyFromContent: require("./phenomic-loader-plugin-init-head-property-from-content").default, - initRawPropertyFromContent: require("./phenomic-loader-plugin-init-raw-property-from-content").default, - initRawBodyPropertyFromContent: require("./phenomic-loader-plugin-init-rawBody-property-from-content").default, - markdownInitHeadDescriptionPropertyFromContent: require("./phenomic-loader-plugin-markdown-init-head.description-property-from-content").default, - markdownTransformBodyPropertyToHtml: require("./phenomic-loader-plugin-markdown-transform-body-property-to-html").default, -} - -export const phenomicLoaderPresets = { - default: require("./phenomic-loader-preset-default").default, - markdown: require("./phenomic-loader-preset-markdown").default, -} +export const phenomicLoader = "phenomic/lib/loader" diff --git a/src/phenomic-loader/__tests__/feed.js b/src/loader-feed-webpack-plugin/__tests__/feed.js similarity index 100% rename from src/phenomic-loader/__tests__/feed.js rename to src/loader-feed-webpack-plugin/__tests__/feed.js diff --git a/src/loader-feed-webpack-plugin/__tests__/fixtures/one.md b/src/loader-feed-webpack-plugin/__tests__/fixtures/one.md new file mode 100644 index 000000000..25ca13422 --- /dev/null +++ b/src/loader-feed-webpack-plugin/__tests__/fixtures/one.md @@ -0,0 +1,5 @@ +--- +title: One +--- + +1 2 3 diff --git a/src/phenomic-loader/__tests__/fixtures/script.js b/src/loader-feed-webpack-plugin/__tests__/fixtures/script.js similarity index 100% rename from src/phenomic-loader/__tests__/fixtures/script.js rename to src/loader-feed-webpack-plugin/__tests__/fixtures/script.js diff --git a/src/loader-feed-webpack-plugin/__tests__/fixtures/two.md b/src/loader-feed-webpack-plugin/__tests__/fixtures/two.md new file mode 100644 index 000000000..504053d46 --- /dev/null +++ b/src/loader-feed-webpack-plugin/__tests__/fixtures/two.md @@ -0,0 +1,5 @@ +--- +title: Two +--- + +2 3 4 diff --git a/src/loader-feed-webpack-plugin/__tests__/index.js b/src/loader-feed-webpack-plugin/__tests__/index.js new file mode 100644 index 000000000..2c09f84e5 --- /dev/null +++ b/src/loader-feed-webpack-plugin/__tests__/index.js @@ -0,0 +1,69 @@ +import test from "ava" +import webpack from "webpack" +import { sync as rimraf } from "rimraf" + +import PhenomicLoaderWebpackPlugin from "../../loader/plugin.js" +import PhenomicLoaderFeedWebpackPlugin from "../index.js" + +const outputPath = __dirname + "/_output/" +rimraf(outputPath) + +test.cb("loader feed webpack plugin", (t) => { + webpack( + { + module: { + loaders: [ + { + test: /\.md$/, + loader: __dirname + "/../../loader/index.js", + exclude: /node_modules/, + }, + ], + }, + entry: __dirname + "/fixtures/script.js", + resolve: { extensions: [ "" ] }, + output: { + path: outputPath + "/routes", + filename: "routes.js", + }, + plugins: [ + new PhenomicLoaderWebpackPlugin(), + new PhenomicLoaderFeedWebpackPlugin({ + feedsOptions: { + title: "title", + site_url: "site_url", + }, + feeds: { + "feed.xml": {}, + }, + }), + ], + }, + function(err, stats) { + if (err) { + throw err + } + + t.falsy(stats.hasErrors(), "doesn't give any error") + if (stats.hasErrors()) { + console.error(stats.compilation.errors) + } + + t.falsy(stats.hasWarnings(), "doesn't give any warning") + if (stats.hasWarnings()) { + console.log(stats.compilation.warnings) + } + + const feed = stats.compilation.assets["feed.xml"] + if (!feed) { + console.log(stats.compilation.assets) + } + t.truthy( + feed && feed._value, + "should create a xml for the feed" + ) + + t.end() + } + ) +}) diff --git a/src/phenomic-loader/feed.js b/src/loader-feed-webpack-plugin/feed.js similarity index 100% rename from src/phenomic-loader/feed.js rename to src/loader-feed-webpack-plugin/feed.js diff --git a/src/loader-feed-webpack-plugin/index.js b/src/loader-feed-webpack-plugin/index.js new file mode 100644 index 000000000..14abae4d3 --- /dev/null +++ b/src/loader-feed-webpack-plugin/index.js @@ -0,0 +1,48 @@ +// @flow + +import { RawSource } from "webpack-sources" +import enhanceCollection from "../enhance-collection" +import feed from "./feed" +import minify from "../loader/minify.js" + +import PhenomicLoaderWebpackPlugin from "../loader/plugin.js" + +function PhenomicLoaderFeedWebpackPlugin(options: Object) { + this.options = options +} + +PhenomicLoaderFeedWebpackPlugin.prototype.apply = function(compiler) { + compiler.plugin("compilation", (compilation/* , params */) => { + compilation.plugin("additional-assets", (callback) => { + if (!PhenomicLoaderWebpackPlugin.collection) { + throw new Error( + "Missing Phenomic collection in webpack compilation object. " + + "This probably means you are playing with fire." + ) + } + const collection = minify(PhenomicLoaderWebpackPlugin.collection) + + const feeds = this.options.feeds || [] + const feedsOptions = this.options.feedsOptions || {} + Object.keys(feeds).forEach((name) => { + const { feedOptions, collectionOptions } = feeds[name] + compilation.assets[name] = new RawSource(feed({ + feedOptions: { ...feedsOptions, ...feedOptions }, + destination: name, + collection: enhanceCollection( + collection.map((item) => ({ + ...item.head, + description: item.body, + __url: item.__url, + })), + collectionOptions + ), + })) + }) + + callback() + }) + }) +} + +module.exports = PhenomicLoaderFeedWebpackPlugin diff --git a/src/phenomic-loader-plugin-init-body-property-from-content/__tests__/index.js b/src/loader-plugin-init-body-property-from-content/__tests__/index.js similarity index 100% rename from src/phenomic-loader-plugin-init-body-property-from-content/__tests__/index.js rename to src/loader-plugin-init-body-property-from-content/__tests__/index.js diff --git a/src/phenomic-loader-plugin-init-body-property-from-content/index.js b/src/loader-plugin-init-body-property-from-content/index.js similarity index 100% rename from src/phenomic-loader-plugin-init-body-property-from-content/index.js rename to src/loader-plugin-init-body-property-from-content/index.js diff --git a/src/phenomic-loader-plugin-init-head-property-from-config/__tests__/index.js b/src/loader-plugin-init-head-property-from-config/__tests__/index.js similarity index 100% rename from src/phenomic-loader-plugin-init-head-property-from-config/__tests__/index.js rename to src/loader-plugin-init-head-property-from-config/__tests__/index.js diff --git a/src/phenomic-loader-plugin-init-head-property-from-config/index.js b/src/loader-plugin-init-head-property-from-config/index.js similarity index 100% rename from src/phenomic-loader-plugin-init-head-property-from-config/index.js rename to src/loader-plugin-init-head-property-from-config/index.js diff --git a/src/phenomic-loader-plugin-init-head-property-from-content/__tests__/index.js b/src/loader-plugin-init-head-property-from-content/__tests__/index.js similarity index 100% rename from src/phenomic-loader-plugin-init-head-property-from-content/__tests__/index.js rename to src/loader-plugin-init-head-property-from-content/__tests__/index.js diff --git a/src/phenomic-loader-plugin-init-head-property-from-content/index.js b/src/loader-plugin-init-head-property-from-content/index.js similarity index 100% rename from src/phenomic-loader-plugin-init-head-property-from-content/index.js rename to src/loader-plugin-init-head-property-from-content/index.js diff --git a/src/phenomic-loader-plugin-init-raw-property-from-content/__tests__/index.js b/src/loader-plugin-init-raw-property-from-content/__tests__/index.js similarity index 100% rename from src/phenomic-loader-plugin-init-raw-property-from-content/__tests__/index.js rename to src/loader-plugin-init-raw-property-from-content/__tests__/index.js diff --git a/src/phenomic-loader-plugin-init-raw-property-from-content/index.js b/src/loader-plugin-init-raw-property-from-content/index.js similarity index 100% rename from src/phenomic-loader-plugin-init-raw-property-from-content/index.js rename to src/loader-plugin-init-raw-property-from-content/index.js diff --git a/src/phenomic-loader-plugin-init-rawBody-property-from-content/__tests__/index.js b/src/loader-plugin-init-rawBody-property-from-content/__tests__/index.js similarity index 100% rename from src/phenomic-loader-plugin-init-rawBody-property-from-content/__tests__/index.js rename to src/loader-plugin-init-rawBody-property-from-content/__tests__/index.js diff --git a/src/phenomic-loader-plugin-init-rawBody-property-from-content/index.js b/src/loader-plugin-init-rawBody-property-from-content/index.js similarity index 100% rename from src/phenomic-loader-plugin-init-rawBody-property-from-content/index.js rename to src/loader-plugin-init-rawBody-property-from-content/index.js diff --git a/src/phenomic-loader-plugin-markdown-init-head.description-property-from-content/__tests__/index.js b/src/loader-plugin-markdown-init-head.description-property-from-content/__tests__/index.js similarity index 100% rename from src/phenomic-loader-plugin-markdown-init-head.description-property-from-content/__tests__/index.js rename to src/loader-plugin-markdown-init-head.description-property-from-content/__tests__/index.js diff --git a/src/phenomic-loader-plugin-markdown-init-head.description-property-from-content/index.js b/src/loader-plugin-markdown-init-head.description-property-from-content/index.js similarity index 100% rename from src/phenomic-loader-plugin-markdown-init-head.description-property-from-content/index.js rename to src/loader-plugin-markdown-init-head.description-property-from-content/index.js diff --git a/src/phenomic-loader-plugin-markdown-init-head.description-property-from-content/prune/__tests__/index.js b/src/loader-plugin-markdown-init-head.description-property-from-content/prune/__tests__/index.js similarity index 100% rename from src/phenomic-loader-plugin-markdown-init-head.description-property-from-content/prune/__tests__/index.js rename to src/loader-plugin-markdown-init-head.description-property-from-content/prune/__tests__/index.js diff --git a/src/phenomic-loader-plugin-markdown-init-head.description-property-from-content/prune/index.js b/src/loader-plugin-markdown-init-head.description-property-from-content/prune/index.js similarity index 100% rename from src/phenomic-loader-plugin-markdown-init-head.description-property-from-content/prune/index.js rename to src/loader-plugin-markdown-init-head.description-property-from-content/prune/index.js diff --git a/src/phenomic-loader-plugin-markdown-transform-body-property-to-html/__tests__/index.js b/src/loader-plugin-markdown-transform-body-property-to-html/__tests__/index.js similarity index 100% rename from src/phenomic-loader-plugin-markdown-transform-body-property-to-html/__tests__/index.js rename to src/loader-plugin-markdown-transform-body-property-to-html/__tests__/index.js diff --git a/src/phenomic-loader-plugin-markdown-transform-body-property-to-html/index.js b/src/loader-plugin-markdown-transform-body-property-to-html/index.js similarity index 100% rename from src/phenomic-loader-plugin-markdown-transform-body-property-to-html/index.js rename to src/loader-plugin-markdown-transform-body-property-to-html/index.js diff --git a/src/phenomic-loader-preset-default/__tests__/index.js b/src/loader-preset-default/__tests__/index.js similarity index 100% rename from src/phenomic-loader-preset-default/__tests__/index.js rename to src/loader-preset-default/__tests__/index.js diff --git a/src/phenomic-loader-preset-default/index.js b/src/loader-preset-default/index.js similarity index 63% rename from src/phenomic-loader-preset-default/index.js rename to src/loader-preset-default/index.js index f27bda408..8702a0e79 100644 --- a/src/phenomic-loader-preset-default/index.js +++ b/src/loader-preset-default/index.js @@ -1,11 +1,11 @@ // @flow import initHeadPropertyFromConfig - from "../phenomic-loader-plugin-init-head-property-from-config" + from "../loader-plugin-init-head-property-from-config" import initHeadPropertyFromContent - from "../phenomic-loader-plugin-init-head-property-from-content" + from "../loader-plugin-init-head-property-from-content" import initBodyPropertyFromContent - from "../phenomic-loader-plugin-init-body-property-from-content" + from "../loader-plugin-init-body-property-from-content" export default [ // $FlowFixMe Missing annotation wtf? diff --git a/src/phenomic-loader-preset-markdown/__tests__/index.js b/src/loader-preset-markdown/__tests__/index.js similarity index 100% rename from src/phenomic-loader-preset-markdown/__tests__/index.js rename to src/loader-preset-markdown/__tests__/index.js diff --git a/src/phenomic-loader-preset-markdown/index.js b/src/loader-preset-markdown/index.js similarity index 51% rename from src/phenomic-loader-preset-markdown/index.js rename to src/loader-preset-markdown/index.js index b7717cedf..544b6c9d7 100644 --- a/src/phenomic-loader-preset-markdown/index.js +++ b/src/loader-preset-markdown/index.js @@ -1,12 +1,12 @@ // @flow -import defaultPlugins from "../phenomic-loader-preset-default" +import defaultPlugins from "../loader-preset-default" import initHeadDescriptionPropertyFromContent // eslint-disable-next-line max-len - from "../phenomic-loader-plugin-markdown-init-head.description-property-from-content" + from "../loader-plugin-markdown-init-head.description-property-from-content" import transformBodyPropertyToHtml - from "../phenomic-loader-plugin-markdown-transform-body-property-to-html" + from "../loader-plugin-markdown-transform-body-property-to-html" export default [ ...defaultPlugins, diff --git a/src/phenomic-loader/__tests__/fixtures/custom-route-folder-trailing-slash.md b/src/loader/__tests__/fixtures/custom-route-folder-trailing-slash.md similarity index 100% rename from src/phenomic-loader/__tests__/fixtures/custom-route-folder-trailing-slash.md rename to src/loader/__tests__/fixtures/custom-route-folder-trailing-slash.md diff --git a/src/phenomic-loader/__tests__/fixtures/custom-route-folder.md b/src/loader/__tests__/fixtures/custom-route-folder.md similarity index 100% rename from src/phenomic-loader/__tests__/fixtures/custom-route-folder.md rename to src/loader/__tests__/fixtures/custom-route-folder.md diff --git a/src/phenomic-loader/__tests__/fixtures/custom-route-root-index.md b/src/loader/__tests__/fixtures/custom-route-root-index.md similarity index 100% rename from src/phenomic-loader/__tests__/fixtures/custom-route-root-index.md rename to src/loader/__tests__/fixtures/custom-route-root-index.md diff --git a/src/phenomic-loader/__tests__/fixtures/custom-route.md b/src/loader/__tests__/fixtures/custom-route.md similarity index 100% rename from src/phenomic-loader/__tests__/fixtures/custom-route.md rename to src/loader/__tests__/fixtures/custom-route.md diff --git a/src/loader/__tests__/fixtures/script.js b/src/loader/__tests__/fixtures/script.js new file mode 100644 index 000000000..d0ad619e6 --- /dev/null +++ b/src/loader/__tests__/fixtures/script.js @@ -0,0 +1 @@ +require.context(".", true, /\.md$/) diff --git a/src/phenomic-loader/__tests__/fixtures/script.md b/src/loader/__tests__/fixtures/script.md similarity index 100% rename from src/phenomic-loader/__tests__/fixtures/script.md rename to src/loader/__tests__/fixtures/script.md diff --git a/src/phenomic-loader/__tests__/index.js b/src/loader/__tests__/index.js similarity index 90% rename from src/phenomic-loader/__tests__/index.js rename to src/loader/__tests__/index.js index 0f9e03780..57c3a28f6 100644 --- a/src/phenomic-loader/__tests__/index.js +++ b/src/loader/__tests__/index.js @@ -2,7 +2,9 @@ import test from "ava" import webpack from "webpack" import { sync as rimraf } from "rimraf" -const outputPath = __dirname + "/output/" +import PhenomicLoaderWebpackPlugin from "../plugin.js" + +const outputPath = __dirname + "/_output/" rimraf(outputPath) test.cb("phenomic loader", (t) => { @@ -17,6 +19,9 @@ test.cb("phenomic loader", (t) => { }, ], }, + plugins: [ + new PhenomicLoaderWebpackPlugin(), + ], entry: __dirname + "/fixtures/script.js", resolve: { extensions: [ "" ] }, output: { @@ -114,6 +119,9 @@ test.cb("phenomic loader can be used with plugins", (t) => { }, ], }, + plugins: [ + new PhenomicLoaderWebpackPlugin(), + ], phenomic: { plugins: [ () => { @@ -146,10 +154,13 @@ test.cb("phenomic loader can be used with plugins", (t) => { Object.keys(stats.compilation.assets) .filter((key) => key.endsWith(".json")) .forEach((key) => { - t.is( - JSON.parse(stats.compilation.assets[key]._value).test, - "dumb" - ) + const result = JSON.parse(stats.compilation.assets[key]._value) + if (result.test) { + t.is( + result.test, + "dumb" + ) + } }) t.plan(2+5) // 2, err, warn, 5 => array t.end() diff --git a/src/phenomic-loader/__tests__/minify.js b/src/loader/__tests__/minify.js similarity index 91% rename from src/phenomic-loader/__tests__/minify.js rename to src/loader/__tests__/minify.js index b262336e3..171e68a35 100644 --- a/src/phenomic-loader/__tests__/minify.js +++ b/src/loader/__tests__/minify.js @@ -2,7 +2,7 @@ import test from "ava" import minify from "../minify" -test("phenomic/lib/phenomic-loader/minify", (t) => { +test("phenomic/lib/loader/minify", (t) => { t.deepEqual( minify([ { diff --git a/src/loader/index.js b/src/loader/index.js new file mode 100644 index 000000000..1b6e1e5f8 --- /dev/null +++ b/src/loader/index.js @@ -0,0 +1,82 @@ +// @flow + +import fs from "fs" +import path from "path" +import loaderUtils from "loader-utils" +import frontMatterParser from "gray-matter" + +import pathToUri from "../_utils/path-to-uri" +import urlify from "../_utils/urlify" + +// Use the path of the loader directory to avoid conflicts on the loaderContext +const NS = fs.realpathSync(__dirname) + +const loader = function(input: string) { + const webpackInstance: WebpackInstance = this + + loader.getCollection = () => this[NS] + + const options = { + ...webpackInstance.options.phenomic, + ...loaderUtils.parseQuery(webpackInstance.query), + } + const context = options.context || webpackInstance.options.context + const plugins = ( + options.plugins || + require("../loader-preset-markdown").default + ) + + const relativePath = path.relative(context, webpackInstance.resourcePath) + + const frontMatter = frontMatterParser(input) + const pluginsResult = plugins.reduce((result, plugin) => { + return plugin({ + frontMatter, + result, + options, + }) + }, {}) + + let tmpUrl = urlify( + pluginsResult.head && pluginsResult.head.route + // custom route + ? pluginsResult.head.route + // default route + : relativePath + ) + tmpUrl = (tmpUrl.substring(0, 1) === "/") ? tmpUrl.slice(1) : tmpUrl + + const url = urlify(tmpUrl) + const resourceUrl = urlify(tmpUrl, true) + const contentHash = loaderUtils.getHashDigest(input) + const dataUrl = resourceUrl + "." + contentHash + ".json" + + const metadata = { + __filename: relativePath, + __url: pathToUri("/", url), + __resourceUrl: pathToUri("/", resourceUrl), + __dataUrl: pathToUri("/", dataUrl), + } + + const result = { + ...pluginsResult, + ...metadata, + } + + webpackInstance.emitFile(dataUrl, JSON.stringify(result)) + + if (typeof webpackInstance[NS] !== "function") { + throw new Error( + "You are using phenomic loader without the corresponding plugin. " + + "This plugin should be added automatically by Phenomic, so if you are " + + "facing this issue, you are probably playing with the fire. " + + "To get more information, you can reach us on our community chat. " + + "https://phenomic.io/" + ) + } + webpackInstance[NS](result) + + return "module.exports = " + JSON.stringify(pathToUri("/", dataUrl)) +} + +module.exports = loader diff --git a/src/phenomic-loader/minify.js b/src/loader/minify.js similarity index 100% rename from src/phenomic-loader/minify.js rename to src/loader/minify.js diff --git a/src/loader/plugin.js b/src/loader/plugin.js new file mode 100644 index 000000000..6cd4f027c --- /dev/null +++ b/src/loader/plugin.js @@ -0,0 +1,39 @@ +// @flow + +import fs from "fs" +// import { RawSource } from "webpack-sources" + +// Use the path of the plugin/loader directory to avoid conflicts on the +// loaderContext +const NS = fs.realpathSync(__dirname) + +function PhenomicLoaderWebpackPlugin() {} + +// Phenomic collection cache: it avoid to have to re-read content files +// between client and static build! +// We can do without this (eg: emitting a json + read the json later), +// but this will be an issue to consider for big websites. +PhenomicLoaderWebpackPlugin.collection = [] + +PhenomicLoaderWebpackPlugin.prototype.apply = function(compiler) { + compiler.plugin("compilation", (compilation/* , params */) => { + compilation.plugin("normal-module-loader", (loaderContext, module) => { + loaderContext[NS] = (loaderResult) => module.meta[NS] = loaderResult + }) + + compilation.plugin("additional-assets", (callback) => { + const results = compilation.modules + .map((module) => module.meta[NS]) + .filter((result) => result !== undefined) + + PhenomicLoaderWebpackPlugin.collection = results + // const collection = JSON.stringify(results, null, 2) + // compilation.assets["phenomic.collection.json"] = + // new RawSource(collection) + + callback() + }) + }) +} + +module.exports = PhenomicLoaderWebpackPlugin diff --git a/src/phenomic-loader/cache.js b/src/phenomic-loader/cache.js deleted file mode 100644 index 6c686b632..000000000 --- a/src/phenomic-loader/cache.js +++ /dev/null @@ -1,2 +0,0 @@ -const cache = [] -module.exports = cache diff --git a/src/phenomic-loader/index.js b/src/phenomic-loader/index.js deleted file mode 100644 index 46f594926..000000000 --- a/src/phenomic-loader/index.js +++ /dev/null @@ -1,128 +0,0 @@ -// @flow - -import path from "path" -import loaderUtils from "loader-utils" -import frontMatterParser from "gray-matter" -import colors from "chalk" - -import pathToUri from "../_utils/path-to-uri" -import urlify from "../_utils/urlify" -import enhanceCollection from "../enhance-collection" -import feed from "./feed" -import cache from "./cache" - -let timeout - -module.exports = function(input: string) { - const webpackInstance: WebpackInstance = this - - // deprecated - if ( - webpackInstance.options.phenomic && - ( - webpackInstance.options.phenomic.loader || - webpackInstance.options.phenomic.contentLoader - ) - ) { - console.log("⚠️ " + colors.red( - "Phenomic loader should now be defined directly under `phenomic` key, " + - "not `phenomic.loader` or `phenomic.contentLoader` in your webpack " + - "configuration." - )) - } - - const options = { - ...webpackInstance.options.phenomic, - ...loaderUtils.parseQuery(webpackInstance.query), - } - const context = options.context || webpackInstance.options.context - const plugins = ( - options.plugins || - require("../phenomic-loader-preset-markdown").default - ) - - const relativePath = path.relative(context, webpackInstance.resourcePath) - - const frontMatter = frontMatterParser(input) - const pluginsResult = plugins.reduce((result, plugin) => { - return plugin({ - frontMatter, - result, - options, - }) - }, {}) - - let tmpUrl = urlify( - pluginsResult.head && pluginsResult.head.route - // custom route - ? pluginsResult.head.route - // default route - : relativePath - ) - tmpUrl = (tmpUrl.substring(0, 1) === "/") ? tmpUrl.slice(1) : tmpUrl - - const url = urlify(tmpUrl) - const resourceUrl = urlify(tmpUrl, true) - const contentHash = loaderUtils.getHashDigest(input) - const dataUrl = resourceUrl + "." + contentHash + ".json" - - const metadata = { - __filename: relativePath, - __url: pathToUri("/", url), - __resourceUrl: pathToUri("/", resourceUrl), - __dataUrl: pathToUri("/", dataUrl), - } - - const result = { - ...pluginsResult, - ...metadata, - } - - webpackInstance.emitFile(dataUrl, JSON.stringify(result)) - - // update collection - // replace or add depending on the cache state - let previousIndex - cache.forEach((md, index) => { - if (md.__filename === relativePath) { - previousIndex = index - } - }) - if (previousIndex) { - cache[previousIndex] = result - } - else { - cache.push(result) - } - - if (timeout) { - clearTimeout(timeout) - } - else { - setTimeout(() => { - // emit updated feeds - const feeds = options.feeds || [] - const feedsOptions = options.feedsOptions || {} - Object.keys(feeds).forEach((name) => { - const { feedOptions, collectionOptions } = feeds[name] - webpackInstance.emitFile(name, feed({ - feedOptions: { - ...feedsOptions, - ...feedOptions, - }, - destination: name, - collection: enhanceCollection( - cache.map((item) => ({ - ...item.head, - description: item.body, - __url: item.__url, - })), - collectionOptions - ), - })) - }) - }, 100) - } - - return "module.exports = " + JSON.stringify(pathToUri("/", dataUrl)) -} diff --git a/src/static/index.js b/src/static/index.js index dd7f81205..41c1334ae 100644 --- a/src/static/index.js +++ b/src/static/index.js @@ -2,13 +2,13 @@ import color from "chalk" import debug from "debug" -import toStaticHtml from "./to-html" +import toHtml from "./to-html" import postBuild from "./postbuild" const log = debug("phenomic:static") export default function(config: PhenomicConfig): Promise { - return toStaticHtml(config) + return toHtml(config) .then(files => postBuild(config, files, log)) .catch((error) => { log(color.red("✗ Static build failed")) diff --git a/src/static/to-html/url-as-html.js b/src/static/to-html/url-as-html.js index 947a8af9a..70fcbd855 100644 --- a/src/static/to-html/url-as-html.js +++ b/src/static/to-html/url-as-html.js @@ -12,7 +12,7 @@ import Html from "./Html" import PhenomicContextProvider from "../../components/ContextProvider" import serialize from "../../_utils/serialize" -import minifyCollection from "../../phenomic-loader/minify" +import minifyCollection from "../../loader/minify" export default function( url: string, diff --git a/themes/phenomic-theme-base/scripts/phenomic.browser.js b/themes/phenomic-theme-base/scripts/phenomic.browser.js index 365a35065..abdb80bdf 100644 --- a/themes/phenomic-theme-base/scripts/phenomic.browser.js +++ b/themes/phenomic-theme-base/scripts/phenomic.browser.js @@ -1,26 +1,32 @@ +// Hot loading HRM Patch +import "react-hot-loader/patch" + +// fetch polyfill import "whatwg-fetch" import metadata from "../src/metadata.js" import routes from "../src/routes.js" import store from "../src/store.js" - import phenomicClient from "phenomic/lib/client" +phenomicClient({ metadata, routes, store }) -phenomicClient({ - metadata, - routes, - store, -}) +// md files processed via phenomic-loader to JSON && generate collection +const mdContext = require.context("../content", true, /\.md$/) +mdContext.keys().forEach(mdContext) // hot loading -// md files → JSON && generate collection + hot loading for dev -let mdContext = require.context("../content", true, /\.md$/) -mdContext.keys().forEach(mdContext) if (module.hot) { + + // hot load md const mdHotUpdater = require("phenomic/lib/client/hot-md").default module.hot.accept(mdContext.id, () => { - mdContext = require.context("../content", true, /\.md$/) + // mdContext = require.context("../content", true, /\.md$/) const requireUpdate = mdHotUpdater(mdContext, window.__COLLECTION__, store) mdContext.keys().forEach(requireUpdate) }) + + module.hot.accept( + [ "../src/metadata.js", "../src/routes.js", "../src/store.js" ], + () => phenomicClient({ metadata, routes, store }) + ) } diff --git a/themes/phenomic-theme-base/scripts/phenomic.node.js b/themes/phenomic-theme-base/scripts/phenomic.node.js index d40451b18..4a24948b0 100644 --- a/themes/phenomic-theme-base/scripts/phenomic.node.js +++ b/themes/phenomic-theme-base/scripts/phenomic.node.js @@ -1,13 +1,7 @@ import metadata from "../src/metadata.js" import routes from "../src/routes.js" import store from "../src/store.js" - import phenomicStatic from "phenomic/lib/static" module.exports = (options) => - phenomicStatic({ - ...options, - metadata, - routes, - store, - }) + phenomicStatic({ ...options, metadata, routes, store }) diff --git a/themes/phenomic-theme-base/webpack.config.babel.js b/themes/phenomic-theme-base/webpack.config.babel.js index 92f12d84c..c45160590 100644 --- a/themes/phenomic-theme-base/webpack.config.babel.js +++ b/themes/phenomic-theme-base/webpack.config.babel.js @@ -3,6 +3,8 @@ import path from "path" import webpack from "webpack" import ExtractTextPlugin from "extract-text-webpack-plugin" import { phenomicLoader } from "phenomic" +import PhenomicLoaderFeedWebpackPlugin + from "phenomic/lib/loader-feed-webpack-plugin" import pkg from "./package.json" @@ -39,18 +41,14 @@ export const makeConfig = (config = {}) => { // *.js => babel + eslint { test: /\.js$/, - loaders: [ - `babel-loader${ - config.dev - ? "?cacheDirectory=true&presets[]=babel-preset-react-hmre" - : "?cacheDirectory=true" - }`, - "eslint-loader?fix", - ], include: [ path.resolve(__dirname, "scripts"), path.resolve(__dirname, "src"), ], + loaders: [ + "babel-loader?cacheDirectory=true", + "eslint-loader?fix", + ], }, // ! \\ @@ -62,25 +60,26 @@ export const makeConfig = (config = {}) => { test: /\.css$/, exclude: /\.global\.css$/, include: path.resolve(__dirname, "src"), - loader: ExtractTextPlugin.extract( - "style-loader", - [ `css-loader?modules&localIdentName=${ + loader: ExtractTextPlugin.extract({ + fallbackLoader: "style-loader", + loader: [ + `css-loader?modules&localIdentName=${ config.production ? "[hash:base64:5]" : "[path][name]--[local]--[hash:base64:5]" }`, "postcss-loader", - ].join("!"), - ), + ], + }), }, // *.global.css => global (normal) css { test: /\.global\.css$/, include: path.resolve(__dirname, "src"), - loader: ExtractTextPlugin.extract( - "style-loader", - [ "css-loader", "postcss-loader" ].join("!"), - ), + loader: ExtractTextPlugin.extract({ + fallbackLoader: "style-loader", + loader: [ "css-loader", "postcss-loader" ], + }), }, // ! \\ // If you want global CSS only, just remove the 2 sections above @@ -127,22 +126,8 @@ export const makeConfig = (config = {}) => { phenomic: { context: path.join(__dirname, config.source), - // plugins: [ ...phenomicLoaderPresets.markdown ] + // plugins: [ ...require("phenomic/lib/loader-preset-markdown").default ] // see https://phenomic.io/docs/usage/plugins/ - feedsOptions: { - title: pkg.name, - site_url: pkg.homepage, - }, - feeds: { - "feed.xml": { - collectionOptions: { - filter: { layout: "Post" }, - sort: "date", - reverse: true, - limit: 20, - }, - }, - }, }, postcss: () => [ @@ -155,9 +140,34 @@ export const makeConfig = (config = {}) => { ], plugins: [ - new ExtractTextPlugin("[name].[hash].css", { disable: config.dev }), + new PhenomicLoaderFeedWebpackPlugin({ + // here you define generic metadata for your feed + feedsOptions: { + title: pkg.name, + site_url: pkg.homepage, + }, + feeds: { + // here we define one feed, but you can generate multiple, based + // on different filters + "feed.xml": { + collectionOptions: { + filter: { layout: "Post" }, + sort: "date", + reverse: true, + limit: 20, + }, + }, + }, + }), + new ExtractTextPlugin({ + filename: "[name].[hash].css", + disable: config.dev, + }), + ...config.production && [ - new webpack.optimize.DedupePlugin(), + // DedupePlugin does not work correctly with Webpack 2, yet ;) + // https://github.com/webpack/webpack/issues/2644 + // new webpack.optimize.DedupePlugin(), new webpack.optimize.UglifyJsPlugin( { compress: { warnings: false } } ),