-
Notifications
You must be signed in to change notification settings - Fork 10.3k
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
[feat] Extract internal plugin for automatically creating pages so we can reuse for other directories #4490
Changes from 4 commits
3d1b166
e116257
601c8f1
87e5a16
d636e09
c0588de
9e11a16
aac2e89
8f803f4
e58a328
9f7e69c
87001d7
1f8c7d6
8939a4b
eb02d69
45c5827
3919910
778e427
32edb33
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# Logs | ||
logs | ||
*.log | ||
|
||
# Runtime data | ||
pids | ||
*.pid | ||
*.seed | ||
|
||
# Directory for instrumented libs generated by jscoverage/JSCover | ||
lib-cov | ||
|
||
# Coverage directory used by tools like istanbul | ||
coverage | ||
|
||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) | ||
.grunt | ||
|
||
# node-waf configuration | ||
.lock-wscript | ||
|
||
# Compiled binary addons (http://nodejs.org/api/addons.html) | ||
build/Release | ||
|
||
# Dependency directory | ||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git | ||
node_modules | ||
|
||
decls | ||
dist | ||
|
||
/*.js |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# Logs | ||
logs | ||
*.log | ||
|
||
# Runtime data | ||
pids | ||
*.pid | ||
*.seed | ||
|
||
# Directory for instrumented libs generated by jscoverage/JSCover | ||
lib-cov | ||
|
||
# Coverage directory used by tools like istanbul | ||
coverage | ||
|
||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) | ||
.grunt | ||
|
||
# node-waf configuration | ||
.lock-wscript | ||
|
||
# Compiled binary addons (http://nodejs.org/api/addons.html) | ||
build/Release | ||
|
||
# Dependency directory | ||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git | ||
node_modules | ||
*.un~ | ||
yarn.lock | ||
src | ||
flow-typed | ||
coverage | ||
decls | ||
examples |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# gatsby-plugin-page-creator | ||
|
||
Plugin to auto create pages using custom directory paths. | ||
|
||
## Install | ||
|
||
`npm install --save gatsby-plugin-page-creator` | ||
|
||
## How to use | ||
|
||
```javascript | ||
// gatsby-config.js | ||
|
||
module.exports = { | ||
plugins: [ | ||
// You can have multiple instances of this plugin | ||
// to read source nodes from different locations on your | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "source nodes" should be "React components" |
||
// filesystem. | ||
// | ||
// The following sets up the Jekyll pattern of having multiple | ||
// "pages" directories in your project | ||
{ | ||
resolve: `gatsby-plugin-page-creator`, | ||
options: { | ||
path: `${__dirname}/src/pages/`, | ||
}, | ||
}, | ||
{ | ||
resolve: `gatsby-plugin-page-creator`, | ||
options: { | ||
path: `${__dirname}/src/data/`, | ||
}, | ||
}, | ||
], | ||
}; | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`JavaScript page creator create-path should create unix paths 1`] = ` | ||
Array [ | ||
"/b/c/de/", | ||
"/bee/", | ||
"/b/d/c/", | ||
] | ||
`; | ||
|
||
exports[`JavaScript page creator create-path should deal with windows paths 1`] = ` | ||
Array [ | ||
"/404/", | ||
"/", | ||
"/blog/", | ||
] | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
const validatePath = require(`../validate-path`) | ||
const createPath = require(`../create-path`) | ||
|
||
describe(`JavaScript page creator`, () => { | ||
it(`filters out files that start with underscores`, () => { | ||
const files = [ | ||
{ | ||
path: `something/blah.js`, | ||
}, | ||
{ | ||
path: `something/_blah.js`, | ||
}, | ||
{ | ||
path: `_blah.js`, | ||
}, | ||
] | ||
|
||
expect(files.filter(file => validatePath(file.path)).length).toEqual(1) | ||
}) | ||
|
||
it(`filters out files that start with template-*`, () => { | ||
const files = [ | ||
{ | ||
path: `something/blah.js`, | ||
}, | ||
{ | ||
path: `something/template-cool-page-type.js`, | ||
}, | ||
{ | ||
path: `template-cool-page-type.js`, | ||
}, | ||
] | ||
|
||
expect(files.filter(file => validatePath(file.path)).length).toEqual(1) | ||
}) | ||
|
||
it(`filters out files that have TypeScript declaration extensions`, () => { | ||
const files = [ | ||
{ | ||
path: `something/foo.ts`, | ||
}, | ||
{ | ||
path: `something/bar.tsx`, | ||
}, | ||
{ | ||
path: `something/declaration-file.d.ts`, | ||
}, | ||
{ | ||
path: `something-else/other-declaration-file.d.tsx`, | ||
}, | ||
] | ||
|
||
expect(files.filter(file => validatePath(file.path)).length).toEqual(2) | ||
}) | ||
|
||
it(`filters out test files`, () => { | ||
const files = [ | ||
{ | ||
path: `__tests__/something.test.js`, | ||
}, | ||
{ | ||
path: `foo.spec.js`, | ||
}, | ||
{ | ||
path: `bar.test.js`, | ||
}, | ||
{ | ||
path: `page.js`, | ||
}, | ||
{ | ||
path: `page.jsx`, | ||
}, | ||
] | ||
|
||
expect(files.filter(file => validatePath(file.path)).length).toEqual(2) | ||
}) | ||
|
||
describe(`create-path`, () => { | ||
it(`should create unix paths`, () => { | ||
const basePath = `/a/` | ||
const paths = [`/a/b/c/de`, `/a/bee`, `/a/b/d/c/`] | ||
|
||
expect(paths.map(p => createPath(basePath, p))).toMatchSnapshot() | ||
}) | ||
|
||
it(`should deal with windows paths`, () => { | ||
const basePath = `D:/Projets/gatsby-starter/src/pages` | ||
const paths = [ | ||
`D:\\Projets\\gatsby-starter\\src\\pages\\404.tsx`, | ||
`D:\\Projets\\gatsby-starter\\src\\pages\\index.tsx`, | ||
`D:\\Projets\\gatsby-starter\\src\\pages\\blog.tsx`, | ||
] | ||
|
||
expect(paths.map(p => createPath(basePath, p))).toMatchSnapshot() | ||
}) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
// noop |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
{ | ||
"name": "gatsby-plugin-page-creator", | ||
"version": "1.0.0", | ||
"description": "An internal Gatsby plugin that creates pages from component files in src/pages", | ||
"main": "index.js", | ||
"scripts": { | ||
"build": "babel src --out-dir . --ignore __tests__", | ||
"watch": "babel -w src --out-dir . --ignore __tests__", | ||
"prepublish": "cross-env NODE_ENV=production npm run build" | ||
}, | ||
"keywords": [ | ||
"gatsby" | ||
], | ||
"author": "Kyle Mathews <mathews.kyle@gmail.com>", | ||
"contributors": [ | ||
"Steven Natera <tektekpush@gmail.com> (https://twitter.com/stevennatera)" | ||
], | ||
"license": "MIT", | ||
"dependencies": { | ||
"babel-runtime": "^6.26.0" | ||
}, | ||
"devDependencies": { | ||
"babel-cli": "^6.26.0", | ||
"cross-env": "^5.0.5" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// @flow | ||
import parsePath from "parse-filepath" | ||
import path from "path" | ||
import slash from "slash" | ||
|
||
module.exports = (basePath: string, filePath: string): string => { | ||
const relativePath = path.posix.relative(slash(basePath), slash(filePath)) | ||
const { dirname, name } = parsePath(relativePath) | ||
const parsedName = name === `index` ? `` : name | ||
|
||
return path.posix.join(`/`, dirname, parsedName, `/`) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
const globCB = require(`glob`) | ||
const Promise = require(`bluebird`) | ||
const _ = require(`lodash`) | ||
const chokidar = require(`chokidar`) | ||
const systemPath = require(`path`) | ||
const fs = require(`fs`) | ||
|
||
const glob = Promise.promisify(globCB) | ||
|
||
const createPath = require(`./create-path`) | ||
const validatePath = require(`./validate-path`) | ||
|
||
// Path creator. | ||
// Auto-create pages. | ||
// algorithm is glob /pages directory for js/jsx/cjsx files *not* | ||
// underscored. Then create url w/ our path algorithm *unless* user | ||
// takes control of that page component in gatsby-node. | ||
exports.createPagesStatefully = async ( | ||
{ store, boundActionCreators, reporter }, | ||
pluginOptions, | ||
doneCb | ||
) => { | ||
const { createPage, deletePage } = boundActionCreators | ||
const program = store.getState().program | ||
const exts = program.extensions.map(e => `${e.slice(1)}`).join(`,`) | ||
|
||
if (!(pluginOptions && pluginOptions.path)) { | ||
reporter.panic( | ||
` | ||
"path" is a required option for gatsby-plugin-page-creator | ||
|
||
See docs here - https://www.gatsbyjs.org/packages/gatsby-plugin-page-creator/ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. link should go to |
||
` | ||
) | ||
} | ||
|
||
// Validate that the path exists. | ||
if (!fs.existsSync(pluginOptions.path)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One downside to this is that if someone doesn't want to have a src directory or src/pages directory then this will error. Perhaps the core instance of this plugin could say it is optional so if src/pages doesn't exist, then it just does nothing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How should this be implemented? I can't see I understand fully? @KyleAMathews @m-allanson |
||
reporter.panic( | ||
` | ||
The path passed to gatsby-plugin-page-creator does not exist on your file system: | ||
|
||
${pluginOptions.path} | ||
|
||
Please pick a path to an existing directory. | ||
` | ||
) | ||
} | ||
|
||
const pagesDirectory = systemPath.posix.join(pluginOptions.path) | ||
const pagesGlob = `${pagesDirectory}/**/*.{${exts}}` | ||
|
||
// Get initial list of files. | ||
let files = await glob(pagesGlob) | ||
files.forEach(file => _createPage(file, pagesDirectory, createPage)) | ||
|
||
// Listen for new component pages to be added or removed. | ||
chokidar | ||
.watch(pagesGlob) | ||
.on(`add`, path => { | ||
if (!_.includes(files, path)) { | ||
_createPage(path, pagesDirectory, createPage) | ||
files.push(path) | ||
} | ||
}) | ||
.on(`unlink`, path => { | ||
// Delete the page for the now deleted component. | ||
store | ||
.getState() | ||
.pages.filter(p => p.component === path) | ||
.forEach(page => { | ||
deletePage({ | ||
path: createPath(pagesDirectory, path), | ||
component: path, | ||
}) | ||
files = files.filter(f => f !== path) | ||
}) | ||
}) | ||
.on(`ready`, () => doneCb()) | ||
} | ||
const _createPage = (filePath, pagesDirectory, createPage) => { | ||
// Filter out special components that shouldn't be made into | ||
// pages. | ||
if (!validatePath(systemPath.posix.relative(pagesDirectory, filePath))) { | ||
return | ||
} | ||
|
||
// Create page object | ||
const page = { | ||
path: createPath(pagesDirectory, filePath), | ||
component: filePath, | ||
} | ||
|
||
// Add page | ||
createPage(page) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
const systemPath = require(`path`) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It'd be nice to keep sharing these internal utility functions with gatsby core There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually this isn't necessary now since we're removing layouts in v2 — so this is the only package using this. |
||
|
||
const tsDeclarationExtTest = /\.d\.tsx?$/ | ||
|
||
function isTestFile(path) { | ||
const testFileTest = new RegExp(`(/__tests__/.*|(\\.|/)(test|spec))\\.jsx?$`) | ||
return path.match(testFileTest) | ||
} | ||
|
||
module.exports = path => { | ||
// Disallow paths starting with an underscore | ||
// and template-. | ||
// and .d.ts | ||
const parsedPath = systemPath.parse(path) | ||
return ( | ||
parsedPath.name.slice(0, 1) !== `_` && | ||
parsedPath.name.slice(0, 9) !== `template-` && | ||
!tsDeclarationExtTest.test(parsedPath.base) && | ||
!isTestFile(parsedPath.base) | ||
) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sentence should make it clear this plugin only creates pages from React components.