From 9a25324472192df4517f649fabb6f3eca52b3532 Mon Sep 17 00:00:00 2001 From: ehg Date: Fri, 18 Dec 2015 13:47:32 +0000 Subject: [PATCH] Build: Add pragma check webpack plugin for server side rendering Add a webpack plugin that: - Scans module sources for the ssr-ready pragma - Scans non-npm dependencies of these modules, and checks that they also have the ssr-ready pragma. Adds a webpack compilation error if not. - Bails the process if errors are found(!) --- server/pragma-checker/README.md | 7 ++++ server/pragma-checker/index.js | 72 +++++++++++++++++++++++++++++++++ webpack.config.js | 4 +- 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 server/pragma-checker/README.md create mode 100644 server/pragma-checker/index.js diff --git a/server/pragma-checker/README.md b/server/pragma-checker/README.md new file mode 100644 index 0000000000000..1ba14dbc0283e --- /dev/null +++ b/server/pragma-checker/README.md @@ -0,0 +1,7 @@ +PragmaCheckPlugin +================ + +This `webpack` plugin scans Calypso source code for the `@ssr-ready` pragma, and reports any dependencies that don't have this pragma. + +It is intended to provide immediate runtime feedback to developers modifying components used in Server Side Rendering, where adding a non-compatible dependency may break the server build. + diff --git a/server/pragma-checker/index.js b/server/pragma-checker/index.js new file mode 100644 index 0000000000000..56015c2eeac9f --- /dev/null +++ b/server/pragma-checker/index.js @@ -0,0 +1,72 @@ +/***** WARNING: ES5 code only here. Not transpiled! *****/ + +/** + * External dependecies + */ +var error = require( 'chalk' ).bold.red; +var info = require( 'chalk' ).bold.yellow; +var startsWith = require( 'lodash/string/startsWith' ); +var includes = require( 'lodash/collection/includes' ); + +var PLUGIN_TITLE = 'PragmaChecker'; +var SSR_READY = '/** @ssr-ready **/'; + +function PragmaCheckPlugin( options ) { + this.options = options || {}; +} + +function scanDependencies( module, compilation ) { + if ( ! module.dependencies ) { + return; + } + + module.dependencies.forEach( function( dep ) { + if ( ! dep.module ) { + return; + } + + // If the module is compiled through babel, we can be pretty sure it's our own module, not from npm. + if ( includes( dep.module.request, 'babel-loader' ) && + dep.module._source && + ! includes( dep.module._source._value, SSR_READY ) ) { + compilation.errors.push( PLUGIN_TITLE + ': ' + module.rawRequest + ', dependency ' + dep.module.rawRequest + ' is not ' + SSR_READY ); + } + + if ( dep.module.dependencies ) { + scanDependencies( dep.module.dependencies ); + } + } ); +} + +PragmaCheckPlugin.prototype.apply = function( compiler ) { + compiler.plugin( 'compilation', function( compilation ) { + compilation.plugin( 'optimize-modules', function( modules ) { + modules.forEach( function( module ) { + if ( module._source && includes( module._source._value, SSR_READY ) ) { + scanDependencies( module, compilation ); + } + } ); + } ); + + compiler.plugin( 'done', function( stats ) { + var pragmaError = false; + + if ( stats.compilation.errors && stats.compilation.errors.length ) { + stats.compilation.errors.forEach( function( text ) { + if ( startsWith( text, PLUGIN_TITLE ) ) { + pragmaError = true; + console.log( error( text ) ); + } + } ); + + if ( pragmaError ) { + console.log( info( PLUGIN_TITLE + ': Server Side Rendering constraints not met. Please see https://github.com/Automattic/wp-calypso/tree/master/shared#shared for details.' ) ); + // We don't want to enable webpack's bail for all errors, so exit the process instead of throwing. + process.exit( 1 ); // eslint-disable-line no-process-exit + } + } + } ); + } ); +}; + +module.exports = PragmaCheckPlugin; diff --git a/webpack.config.js b/webpack.config.js index f90e7d597d0bb..abaa02c2337ca 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -10,7 +10,8 @@ var webpack = require( 'webpack' ), * Internal dependencies */ var config = require( './server/config' ), - ChunkFileNamePlugin = require( './server/bundler/plugin' ); + ChunkFileNamePlugin = require( './server/bundler/plugin' ), + PragmaCheckPlugin = require( 'server/pragma-checker' ); /** * Internal variables @@ -96,6 +97,7 @@ jsLoader = { }; if ( CALYPSO_ENV === 'development' ) { + webpackConfig.plugins.push( new PragmaCheckPlugin() ); webpackConfig.plugins.push( new webpack.HotModuleReplacementPlugin() ); webpackConfig.entry[ 'build-' + CALYPSO_ENV ] = [ 'webpack-dev-server/client?/',