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

Serve dependent modules from cdnjs #65

Merged
merged 7 commits into from
Oct 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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