Skip to content
This repository has been archived by the owner on Mar 25, 2020. It is now read-only.

Commit

Permalink
Merge pull request #65 from ks888/use-cdnjs
Browse files Browse the repository at this point in the history
Serve dependent modules from cdnjs
  • Loading branch information
ks888 committed Oct 28, 2017
2 parents ddf45da + 3b1d252 commit 19cf849
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 49 deletions.
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<img src="https://raw.githubusercontent.com/wiki/ks888/LambStatus/images/TwitterButton_h42.png" alt="Twitter" height="20px">
</a>

LambStatus is a status page system inspired by [StatusPage.io](https://www.statuspage.io/), built on AWS Lambda.
LambStatus is a serverless status page system inspired by [StatusPage.io](https://www.statuspage.io/).

With a few clicks, You can build a status page like this:

Expand All @@ -20,18 +20,19 @@ The demo pages are available:
## Goals of this project

* Offers an open source and serverless status page system.
* Enables you to deploy and maintain the status page system at minimum effort.
* Offers a pay-as-you-go pricing approach like AWS. We estimate the system takes just *$1 to handle 30,000 visitors* ([see details](https://github.com/ks888/LambStatus/wiki/Cost-estimate)).
* Enables you to build and maintain the status page system at minimum effort.

## Why Serverless?

Status page system is great with the Serverless architecture, because:

* It dramatically eases your pain caused by the scaling / availability issues. It is terrible if your service is down AND heavy traffic from stuck users stops your status page.
* It reduces your infrastructure cost. A status page usually gets very low traffic and occasionally huge traffic. You only pay for the traffic that you handle.
* It eases your pain caused by the scaling / availability issues. It is terrible if your service is down AND heavy traffic from stuck users stops your status page.
* It enables you to pay only for what you use. A status page only occasionally gets huge traffic. The system takes only $1 per 30,000 visitors and almost $0 if no visitors.

Apart from the Serverless architecture, LambStatus enables you to:

* Easily build and update the system (by the power of the CloudFormation)
* Build and update the system with a few clicks (by the power of the CloudFormation)
* Choose the AWS region different from your service's region. If both your service and its status page rely on the same region, [the region outage](https://aws.amazon.com/message/41926/) may stop both.

## Installation
Expand Down
29 changes: 29 additions & 0 deletions packages/frontend/build/cdn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { spawnSync } from 'child_process'

let depsCache

const listDependencies = function () {
if (depsCache !== undefined) return depsCache

const rawDeps = spawnSync('npm', ['list', '--json'])
depsCache = JSON.parse(rawDeps.stdout)
return depsCache
}

const getVersion = function (cdnModule) {
let curr = listDependencies()
cdnModule.dependedBy.forEach(function (dep) {
curr = curr.dependencies[dep]
})
return curr.dependencies[cdnModule.moduleName].version
}

export const buildScriptURL = function (cdnModule) {
const version = getVersion(cdnModule)
return `https://cdnjs.cloudflare.com/ajax/libs/${cdnModule.libraryName}/${version}/${cdnModule.scriptPath}`
}

export const buildCSSURL = function (cdnModule) {
const version = getVersion(cdnModule)
return `https://cdnjs.cloudflare.com/ajax/libs/${cdnModule.libraryName}/${version}/${cdnModule.cssPath}`
}
52 changes: 38 additions & 14 deletions packages/frontend/build/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,36 @@ import HtmlWebpackPlugin from 'html-webpack-plugin'
import ExtractTextPlugin from 'extract-text-webpack-plugin'
import CopyWebpackPlugin from 'copy-webpack-plugin'
import _debug from 'debug'
import { buildScriptURL, buildCSSURL } from './cdn'

const debug = _debug('app:webpack:config')

// These modules are served from cdnjs.
// 'moduleName' and 'dependedBy' are used to resolve the module version.
// 'libraryName', 'cssPath' and 'scriptPath' are used to build the cdn url.
// If defined, 'external' is used to build the 'externals' option of webpack.
/* eslint-disable max-len */
const modulesServedFromCDN = [
{moduleName: 'c3', dependedBy: [], libraryName: 'c3', cssPath: 'c3.css', scriptPath: 'c3.js', external: 'c3'},
{moduleName: 'd3', dependedBy: ['c3'], libraryName: 'd3', scriptPath: 'd3.js'},
{moduleName: 'react', dependedBy: [], libraryName: 'react', scriptPath: 'react.js', external: 'React'},
{moduleName: 'react-dom', dependedBy: [], libraryName: 'react', scriptPath: 'react-dom.js', external: 'ReactDOM'},
{moduleName: 'react-router', dependedBy: [], libraryName: 'react-router', scriptPath: 'ReactRouter.js', external: 'ReactRouter'},
{moduleName: 'moment', dependedBy: ['moment-timezone'], libraryName: 'moment.js', scriptPath: 'moment.js'},
{moduleName: 'moment-timezone', dependedBy: [], libraryName: 'moment-timezone', scriptPath: 'moment-timezone-with-data.js', external: 'moment'}
]
/* eslint-enable max-len */

const cssServedFromCDN = modulesServedFromCDN.reduce(function (prev, curr) {
if (curr.cssPath !== undefined) prev.push(buildCSSURL(curr))
return prev
}, [])

const scriptsServedFromCDN = modulesServedFromCDN.reduce(function (prev, curr) {
if (curr.scriptPath !== undefined) prev.push(buildScriptURL(curr))
return prev
}, [])

export default function (config) {
const paths = config.utils_paths
const {__DEV__, __PROD__, __TEST__, __COVERAGE__} = config.globals
Expand Down Expand Up @@ -40,8 +67,7 @@ export default function (config) {
webpackConfig.entry = {
app: __DEV__
? APP_ENTRY_PATHS.concat(`webpack-hot-middleware/client?path=${config.compiler_public_path}__webpack_hmr`)
: APP_ENTRY_PATHS,
vendor: config.compiler_vendor
: APP_ENTRY_PATHS
}

// ------------------------------------
Expand All @@ -52,6 +78,12 @@ export default function (config) {
path: paths.dist(),
publicPath: config.compiler_public_path
}
if (!__TEST__) {
webpackConfig.externals = modulesServedFromCDN.reduce(function (prev, curr) {
if (curr.external !== undefined) prev[curr.moduleName] = curr.external
return prev
}, {})
}

// ------------------------------------
// Plugins
Expand All @@ -66,12 +98,13 @@ export default function (config) {
inject: 'body',
minify: {
collapseWhitespace: true
}
},
stylesheets: cssServedFromCDN,
scripts: scriptsServedFromCDN
}),
new CopyWebpackPlugin([
{ from: 'config/settings.js' }
]),
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
])
]

if (__DEV__) {
Expand All @@ -95,15 +128,6 @@ export default function (config) {
)
}

// Don't split bundles during testing, since we only want import one bundle
if (!__TEST__) {
webpackConfig.plugins.push(
new webpack.optimize.CommonsChunkPlugin({
names: ['vendor']
})
)
}

// ------------------------------------
// Pre-Loaders
// ------------------------------------
Expand Down
26 changes: 0 additions & 26 deletions packages/frontend/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,6 @@ export default function () {
chunkModules : false,
colors : true
},
compiler_vendor : [
'history',
'react',
'react-dom',
'react-redux',
'react-router',
'react-router-redux',
'redux',
'moment-timezone'
],
compiler_alias: {},

// ----------------------------------
Expand Down Expand Up @@ -88,22 +78,6 @@ export default function () {
'__BASENAME__' : JSON.stringify(process.env.BASENAME || '')
}

// ------------------------------------
// Validate Vendor Dependencies
// ------------------------------------
const pkg = require('../package.json')

config.compiler_vendor = config.compiler_vendor
.filter((dep) => {
if (pkg.dependencies[dep]) return true

debug(
`Package "${dep}" was not found as an npm dependency in package.json; ` +
`it won't be included in the webpack vendor bundle.
Consider removing it from vendor_dependencies in ~/config/index.js`
)
})

// ------------------------------------
// Utilities
// ------------------------------------
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { PropTypes } from 'react'
import classnames from 'classnames'
import c3 from 'c3'
import 'c3/c3.css'
import { timeframes, getXAxisFormat, getTooltipTitleFormat, getIncrementTimestampFunc, getNumDates } from 'utils/status'
import classes from './MetricsGraph.scss'
import './MetricsGraph.global.scss'
Expand Down
6 changes: 6 additions & 0 deletions packages/frontend/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700" rel="stylesheet">
<link rel="stylesheet" href="https://code.getmdl.io/1.2.1/material.blue_grey-amber.min.css" />
<% for (var i in htmlWebpackPlugin.options.stylesheets) { %>
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.stylesheets[i] %>" />
<% } %>
</head>
<body style="height: 100%">
<div id="root" style="height: 100%"></div>
<% for (var i in htmlWebpackPlugin.options.scripts) { %>
<script src="<%= htmlWebpackPlugin.options.scripts[i] %>"></script>
<% } %>
<script src="https://code.getmdl.io/1.2.1/material.min.js"></script>
<script src="/settings.js"></script>
</body>
Expand Down
2 changes: 2 additions & 0 deletions packages/lambda/src/utils/cache.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const oneYearBySeconds = 31536000
const oneDayBySeconds = 86400
const tenSeconds = 10

export const getCacheControl = (contentType) => {
const prefix = 'max-age='
Expand All @@ -11,6 +12,7 @@ export const getCacheControl = (contentType) => {
return prefix + oneDayBySeconds
case 'text/html':
case 'application/json':
return prefix + tenSeconds
default:
return prefix + 0
}
Expand Down
4 changes: 2 additions & 2 deletions packages/lambda/test/aws/s3.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('S3', () => {
const objectName = 'test.html'
const body = 'data'
const contentType = 'text/html'
const cacheControl = 'max-age=0'
const cacheControl = 'max-age=10'
let actual
AWS.mock('S3', 'putObject', (params, callback) => {
actual = params
Expand Down Expand Up @@ -119,7 +119,7 @@ describe('S3', () => {
const destBucketName = 'test'
const destObjectName = 'test.html'
const contentType = 'text/html'
const cacheControl = 'max-age=0'
const cacheControl = 'max-age=10'
let actual
AWS.mock('S3', 'copyObject', (params, callback) => {
actual = params
Expand Down
2 changes: 1 addition & 1 deletion packages/lambda/test/utils/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ describe('cache', () => {
describe('getCacheControl', () => {
it('should return CacheControl value', async () => {
assert(getCacheControl('application/javascript') === 'max-age=31536000')
assert(getCacheControl('text/html') === 'max-age=0')
assert(getCacheControl('text/html') === 'max-age=10')
assert(getCacheControl() === 'max-age=0')
})
})
Expand Down

0 comments on commit 19cf849

Please sign in to comment.