Permalink
Browse files

DevDocs: Generate an index of proptypes for client consumption (#9919)

* DevDocs: Generate an index of proptypes for client consumption

One of the first steps in rendering the propTypes in the client is getting them to the client. This creates a new artifact during a build (194k -- 24k gzip'd) that parses all the index.jsx and example.jsx files.
  • Loading branch information...
1 parent 2052434 commit 9be31c25fa8247a3797f24cd955ca55af45ca8fa @withinboredom withinboredom committed with ehg Dec 22, 2016
Showing with 158 additions and 3 deletions.
  1. +1 −0 .gitignore
  2. +14 −3 Makefile
  3. +14 −0 npm-shrinkwrap.json
  4. +1 −0 package.json
  5. +128 −0 server/devdocs/bin/generate-proptypes-index.js
View
@@ -37,6 +37,7 @@ env-config.sh
/public/images/flags/*.png
/server/devdocs/search-index.js
/server/devdocs/components-usage-stats.json
+/server/devdocs/proptypes-index.json
/server/bundler/assets-*.json
*.rdb
View
@@ -25,6 +25,7 @@ AUTOPREFIXER ?= $(NODE_BIN)/postcss -r --use autoprefixer --autoprefixer.browser
RECORD_ENV ?= $(BIN)/record-env
ALL_DEVDOCS_JS ?= server/devdocs/bin/generate-devdocs-index
COMPONENTS_USAGE_STATS_JS ?= server/devdocs/bin/generate-components-usage-stats.js
+COMPONENTS_PROPTYPES_JS ?= server/devdocs/bin/generate-proptypes-index.js
# files used as prereqs
SASS_FILES := $(shell \
@@ -59,6 +60,13 @@ COMPONENTS_USAGE_STATS_FILES = $(shell \
-type f \
\( -name '*.js' -or -name '*.jsx' \) \
)
+COMPONENTS_PROPTYPE_FILES = $(shell \
+ find client \
+ -name 'index.jsx' \
+ -or -name 'index.js' \
+ -or -name 'example.jsx' \
+ -and -not -path '*/test/*' \
+)
CLIENT_CONFIG_FILE := client/config/index.js
# variables
@@ -149,6 +157,9 @@ server/devdocs/search-index.js: $(MD_FILES) $(ALL_DEVDOCS_JS)
server/devdocs/components-usage-stats.json: $(COMPONENTS_USAGE_STATS_FILES) $(COMPONENTS_USAGE_STATS_JS)
@$(COMPONENTS_USAGE_STATS_JS) $(COMPONENTS_USAGE_STATS_FILES)
+server/devdocs/proptypes-index.json: $(COMPONENTS_PROPTYPE_FILES) $(COMPONENTS_PROPTYPES_JS)
+ @$(COMPONENTS_PROPTYPES_JS) $(COMPONENTS_PROPTYPE_FILES)
+
build-dll: node_modules
@mkdir -p build
@CALYPSO_ENV=$(CALYPSO_ENV) $(NODE_BIN)/webpack --display-error-details --config webpack-dll.config.js
@@ -161,9 +172,9 @@ build: install build-$(CALYPSO_ENV)
build-css: public/style.css public/style-rtl.css public/style-debug.css public/editor.css
-build-development: server/devdocs/components-usage-stats.json build-server build-dll $(CLIENT_CONFIG_FILE) server/devdocs/search-index.js build-css
+build-development: server/devdocs/proptypes-index.json server/devdocs/components-usage-stats.json build-server build-dll $(CLIENT_CONFIG_FILE) server/devdocs/search-index.js build-css
-build-wpcalypso: server/devdocs/components-usage-stats.json build-server build-dll $(CLIENT_CONFIG_FILE) server/devdocs/search-index.js build-css
+build-wpcalypso: server/devdocs/proptypes-index.json server/devdocs/components-usage-stats.json build-server build-dll $(CLIENT_CONFIG_FILE) server/devdocs/search-index.js build-css
@$(BUNDLER)
build-horizon build-stage build-production: build-server build-dll $(CLIENT_CONFIG_FILE) build-css
@@ -175,7 +186,7 @@ build-desktop build-desktop-mac-app-store: build-server $(CLIENT_CONFIG_FILE) bu
# the `clean` rule deletes all the files created from `make build`, but not
# those created by `make install`
clean:
- @rm -rf public/style*.css public/style-debug.css.map public/*.js $(CLIENT_CONFIG_FILE) server/devdocs/search-index.js server/devdocs/components-usage-stats.json public/editor.css build/* server/bundler/*.json .babel-cache
+ @rm -rf public/style*.css public/style-debug.css.map public/*.js $(CLIENT_CONFIG_FILE) server/devdocs/search-index.js server/devdocs/proptypes-index.json server/devdocs/components-usage-stats.json public/editor.css build/* server/bundler/*.json .babel-cache
# the `distclean` rule deletes all the files created from `make install`
distclean: clean
View
@@ -3093,6 +3093,20 @@
"react-day-picker": {
"version": "2.4.1"
},
+ "react-docgen": {
+ "version": "2.13.0",
+ "dependencies": {
+ "async": {
+ "version": "1.5.2"
+ },
+ "babylon": {
+ "version": "5.8.38"
+ },
+ "commander": {
+ "version": "2.9.0"
+ }
+ }
+ },
"react-dom": {
"version": "15.4.0"
},
View
@@ -101,6 +101,7 @@
"react-addons-update": "15.4.0",
"react-click-outside": "2.1.0",
"react-day-picker": "2.4.1",
+ "react-docgen": "2.13.0",
"react-dom": "15.4.0",
"react-masonry-component": "4.2.2",
"react-pure-render": "1.0.2",
@@ -0,0 +1,128 @@
+#!/usr/bin/env node
+
+/**
+ * This file generates an index of proptypes by component displayname, slug and folder name
+ */
+
+/**
+ * External Dependencies
+ */
+
+const fs = require( 'fs' );
+const path = require( 'path' );
+const reactDocgen = require( 'react-docgen' );
+
+const root = path.dirname( path.join( __dirname, '..', '..' ) );
+const pathSwap = new RegExp(path.sep, 'g');
+
+/**
+ * Converts a camel cased string into a slug
+ * @param {String} name The camel cased string to slugify
+ * @return {String}
+ */
+const camelCaseToSlug = ( name ) => {
+ if ( ! name ) {
+ return name;
+ }
+
+ return name
+ .replace( /\.?([A-Z])/g, ( x, y ) => '-' + y.toLowerCase() )
+ .replace( /^-/, '' );
+};
+
+/**
+ * Wraps fs.readFile in a Promise
+ * @param {string} filePath The path to of the file to read
+ * @return {string} The file contents
+ */
+const readFile = ( filePath ) => {
+ return fs.readFileSync( filePath, { encoding: 'utf8' } );
+};
+
+/**
+ * Calculates a filepath's include path and begins reading the file for parsing
+ * Calls back with null, if an error occurs or an object if it succeeds
+ * @param {string} filePath The path to read
+ */
+const processFile = ( filePath ) => {
+ const filename = path.basename( filePath );
+ const includePathRegEx = new RegExp(`^client${ path.sep }(.*?)${ path.sep }${ filename }$`);
+ const includePathSuffix = ( filename === 'index.jsx' ? '' : path.sep + path.basename( filename, '.jsx' ) );
+ const includePath = ( includePathRegEx.exec( filePath )[1] + includePathSuffix ).replace( pathSwap, '/' ) ;
+ try {
+ const usePath = path.isAbsolute( filePath ) ? filePath : path.join( process.cwd(), filePath );
+ const document = readFile( usePath );
+ return {
+ document,
+ includePath
+ };
+ } catch ( error ) {
+ console.log(`Skipping ${ filePath } due to fs error: ${ error }`);
+ }
+ return null;
+};
+
+/**
+ * Given a processed file object, parses the file for proptypes and calls the callback
+ * Calls back with null on any error, or a parsed object if it succeeds
+ * @param {Object} docObj The processed document object
+ */
+const parseDocument = ( docObj ) => {
+ try {
+ const parsed = reactDocgen.parse( docObj.document );
+ parsed.includePath = docObj.includePath;
+ if ( parsed.displayName ) {
+ parsed.slug = camelCaseToSlug( parsed.displayName );
+ }
+ return parsed;
+ } catch ( error ) {
+ // skipping, probably because the file couldn't be parsed for many reasons (there are lots of them!)
+ return null;
+ }
+};
+
+/**
+ * Creates an index of the files
+ * @param {Array} parsed
+ * @return {{data: Array, index: {displayName: {}, slug: {}, includePath: {}}}}
+ */
+const createIndex = ( parsed ) => {
+ return parsed.filter( ( component ) => {
+ if ( ! component ) {
+ return false;
+ }
+
+ const displayName = component.displayName;
+
+ return ! ( displayName === undefined || displayName === '' );
+ } );
+};
+
+/**
+ * Write the file
+ * @param {Object} contents The contents of the file
+ */
+const writeFile = ( contents ) => {
+ fs.writeFileSync( path.join( root, 'server/devdocs/proptypes-index.json' ), JSON.stringify( contents ) );
+};
+
+const main = ( () => {
+ const fileList = process
+ .argv
+ .splice( 2, process.argv.length )
+ .map( ( fileWithPath ) => {
+ return fileWithPath.replace( /^\.\//, '' );
+ } );
+
+ if ( fileList.length === 0 ) {
+ process.stderr.write( 'You must pass a list of files to process' );
+ process.exit( 1 );
+ }
+
+ const documents = createIndex(
+ fileList
+ .map( processFile )
+ .map( parseDocument )
+ );
+ writeFile( documents );
+} )();

0 comments on commit 9be31c2

Please sign in to comment.