Update web-starter
This commit is contained in:
26
.eslintrc
Normal file
26
.eslintrc
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"plugins": [
|
||||||
|
"import",
|
||||||
|
"react"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"es6": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:import/errors",
|
||||||
|
"plugin:react/recommended"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"import/resolver": "webpack"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"array-bracket-spacing": [ 2, "always" ],
|
||||||
|
"eqeqeq": [ 2, "smart" ],
|
||||||
|
"prefer-arrow-callback": 2,
|
||||||
|
"react/no-danger": 0,
|
||||||
|
"semi": [ 2, "never" ]
|
||||||
|
}
|
||||||
|
}
|
5
modules/client/.eslintrc
Normal file
5
modules/client/.eslintrc
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,11 @@ import { findDOMNode } from 'react-dom'
|
|||||||
import Window from './Window'
|
import Window from './Window'
|
||||||
|
|
||||||
class Layout extends React.Component {
|
class Layout extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
location: PropTypes.object,
|
||||||
|
children: PropTypes.node
|
||||||
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
underlineLeft: 0,
|
underlineLeft: 0,
|
||||||
underlineWidth: 0,
|
underlineWidth: 0,
|
||||||
|
@ -5,7 +5,8 @@ class NumberTextInput extends React.Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
value: PropTypes.number,
|
value: PropTypes.number,
|
||||||
parseNumber: PropTypes.func,
|
parseNumber: PropTypes.func,
|
||||||
formatNumber: PropTypes.func
|
formatNumber: PropTypes.func,
|
||||||
|
onChange: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import React, { PropTypes } from 'react'
|
||||||
import { findDOMNode } from 'react-dom'
|
|
||||||
import formatBytes from 'byte-size'
|
import formatBytes from 'byte-size'
|
||||||
import formatDate from 'date-fns/format'
|
import formatDate from 'date-fns/format'
|
||||||
import parseDate from 'date-fns/parse'
|
import parseDate from 'date-fns/parse'
|
||||||
@ -21,8 +20,12 @@ const addValues = (a, b) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Stats extends React.Component {
|
class Stats extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
stats: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
stats: window.NPMCDN_STATS
|
stats: window.npmcdnStats
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -83,13 +86,13 @@ class Stats extends React.Component {
|
|||||||
})
|
})
|
||||||
|
|
||||||
topContinents.forEach(continent => {
|
topContinents.forEach(continent => {
|
||||||
const name = ContinentsIndex[continent]
|
const continentName = ContinentsIndex[continent]
|
||||||
const { countries, requests, bandwidth } = continentData[continent]
|
const { countries, requests, bandwidth } = continentData[continent]
|
||||||
|
|
||||||
if (bandwidth !== 0) {
|
if (bandwidth !== 0) {
|
||||||
regionRows.push(
|
regionRows.push(
|
||||||
<tr key={continent} className="continent-row">
|
<tr key={continent} className="continent-row">
|
||||||
<td>{ContinentsIndex[continent]}</td>
|
<td>{continentName}</td>
|
||||||
<td>{formatNumber(requests)} ({formatPercent(requests / totalRequests)}%)</td>
|
<td>{formatNumber(requests)} ({formatPercent(requests / totalRequests)}%)</td>
|
||||||
<td>{formatBytes(bandwidth)} ({formatPercent(bandwidth / totalBandwidth)}%)</td>
|
<td>{formatBytes(bandwidth)} ({formatPercent(bandwidth / totalBandwidth)}%)</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -31,6 +31,29 @@ const createBundle = (webpackStats) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An express middleware that sets req.manifest from the build manifest
|
||||||
|
* in the given file. Should be used in production to get consistent hashes.
|
||||||
|
*/
|
||||||
|
export const assetsManifest = (webpackManifestFile) => {
|
||||||
|
let manifest
|
||||||
|
try {
|
||||||
|
manifest = JSON.parse(fs.readFileSync(webpackManifestFile, 'utf8'))
|
||||||
|
} catch (error) {
|
||||||
|
invariant(
|
||||||
|
false,
|
||||||
|
'assetsManifest middleware cannot read the manifest file "%s"; ' +
|
||||||
|
'do `npm run build` before starting the server',
|
||||||
|
webpackManifestFile
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (req, res, next) => {
|
||||||
|
req.manifest = manifest
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An express middleware that sets req.bundle from the build
|
* An express middleware that sets req.bundle from the build
|
||||||
* info in the given stats file. Should be used in production.
|
* info in the given stats file. Should be used in production.
|
||||||
|
@ -26,11 +26,15 @@ const fetchStats = (callback) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const sendHomePage = (req, res, next) => {
|
export const sendHomePage = (req, res, next) => {
|
||||||
|
const chunks = [ 'vendor', 'home' ]
|
||||||
const props = {
|
const props = {
|
||||||
styles: req.bundle.getStyleAssets([ 'vendor', 'home' ]),
|
styles: req.bundle.getStyleAssets(chunks),
|
||||||
scripts: req.bundle.getScriptAssets([ 'vendor', 'home' ])
|
scripts: req.bundle.getScriptAssets(chunks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (req.manifest)
|
||||||
|
props.webpackManifest = req.manifest
|
||||||
|
|
||||||
fetchStats((error, stats) => {
|
fetchStats((error, stats) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
next(error)
|
next(error)
|
||||||
|
@ -2,10 +2,12 @@ import path from 'path'
|
|||||||
|
|
||||||
export const id = 1
|
export const id = 1
|
||||||
export const port = parseInt(process.env.PORT, 10) || 5000
|
export const port = parseInt(process.env.PORT, 10) || 5000
|
||||||
export const timeout = parseInt(process.env.TIMEOUT, 10) || 20000
|
export const webpackConfig = require('../../webpack.config')
|
||||||
export const statsFile = path.resolve(__dirname, '../../stats.json')
|
export const statsFile = path.resolve(__dirname, '../../stats.json')
|
||||||
export const publicDir = path.resolve(__dirname, '../../public')
|
export const publicDir = path.resolve(__dirname, '../../public')
|
||||||
export const webpackConfig = require('../../webpack.config')
|
export const manifestFile = path.resolve(publicDir, '__assets__/chunk-manifest.json')
|
||||||
|
export const timeout = parseInt(process.env.TIMEOUT, 10) || 20000
|
||||||
|
export const maxAge = process.env.MAX_AGE || '365d'
|
||||||
|
|
||||||
export const registryURL = process.env.REGISTRY_URL || 'https://registry.npmjs.org'
|
export const registryURL = process.env.REGISTRY_URL || 'https://registry.npmjs.org'
|
||||||
export const bowerBundle = process.env.BOWER_BUNDLE || '/bower.zip'
|
export const bowerBundle = process.env.BOWER_BUNDLE || '/bower.zip'
|
||||||
|
@ -2,33 +2,39 @@ import React, { PropTypes } from 'react'
|
|||||||
|
|
||||||
class HomePage extends React.Component {
|
class HomePage extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
webpackManifest: PropTypes.object,
|
||||||
styles: PropTypes.arrayOf(PropTypes.string),
|
styles: PropTypes.arrayOf(PropTypes.string),
|
||||||
scripts: PropTypes.arrayOf(PropTypes.string),
|
scripts: PropTypes.arrayOf(PropTypes.string),
|
||||||
stats: PropTypes.object
|
stats: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
webpackManifest: {},
|
||||||
styles: [],
|
styles: [],
|
||||||
scripts: [],
|
scripts: [],
|
||||||
stats: {}
|
stats: {}
|
||||||
}
|
}
|
||||||
|
|
||||||
render = () => {
|
render() {
|
||||||
const { styles, scripts, stats } = this.props
|
const { webpackManifest, styles, scripts, stats } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
<meta charSet="utf-8"/>
|
||||||
<meta httpEquiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
|
<meta httpEquiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
|
||||||
<meta name="viewport" content="width=700,maximum-scale=1"/>
|
<meta name="viewport" content="width=700,maximum-scale=1"/>
|
||||||
<meta name="timestamp" content={(new Date).toISOString()}/>
|
<meta name="timestamp" content={(new Date).toISOString()}/>
|
||||||
<title>npmcdn</title>
|
<title>npmcdn</title>
|
||||||
{styles.map(style => <link key={style} rel="stylesheet" href={style}/>)}
|
<script dangerouslySetInnerHTML={{ __html: "window.Promise || document.write('\\x3Cscript src=\"/es6-promise.min.js\">\\x3C/script>\\x3Cscript>ES6Promise.polyfill()\\x3C/script>')" }}/>
|
||||||
<script dangerouslySetInnerHTML={{ __html: `window.NPMCDN_STATS=${JSON.stringify(stats)}` }}/>
|
<script dangerouslySetInnerHTML={{ __html: "window.fetch || document.write('\\x3Cscript src=\"/fetch.min.js\">\\x3C/script>')" }}/>
|
||||||
|
<script dangerouslySetInnerHTML={{ __html: "window.webpackManifest = " + JSON.stringify(webpackManifest) }}/>
|
||||||
|
<script dangerouslySetInnerHTML={{ __html: "window.npmcdnStats = " + JSON.stringify(stats) }}/>
|
||||||
|
{styles.map(s => <link rel="stylesheet" key={s} href={s}/>)}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"/>
|
<div id="app"/>
|
||||||
{scripts.map(script => <script key={script} src={script}/>)}
|
{scripts.map(s => <script key={s} src={s}/>)}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
)
|
)
|
||||||
|
@ -8,7 +8,7 @@ import devErrorHandler from 'errorhandler'
|
|||||||
import WebpackDevServer from 'webpack-dev-server'
|
import WebpackDevServer from 'webpack-dev-server'
|
||||||
import { createRequestHandler } from 'npm-http-server'
|
import { createRequestHandler } from 'npm-http-server'
|
||||||
import * as DefaultServerConfig from './ServerConfig'
|
import * as DefaultServerConfig from './ServerConfig'
|
||||||
import { staticAssets, devAssets, createDevCompiler } from './AssetsUtils'
|
import { assetsManifest, staticAssets, devAssets, createDevCompiler } from './AssetsUtils'
|
||||||
import { sendHomePage } from './MainController'
|
import { sendHomePage } from './MainController'
|
||||||
import { logStats } from './StatsUtils'
|
import { logStats } from './StatsUtils'
|
||||||
|
|
||||||
@ -38,7 +38,8 @@ export const createServer = (config) => {
|
|||||||
|
|
||||||
app.use(errorHandler)
|
app.use(errorHandler)
|
||||||
app.use(cors())
|
app.use(cors())
|
||||||
app.use(express.static(config.publicDir, { maxAge: 60000 }))
|
app.use(express.static(config.publicDir, { maxAge: config.maxAge }))
|
||||||
|
app.use(assetsManifest(config.manifestFile))
|
||||||
app.use(staticAssets(config.statsFile))
|
app.use(staticAssets(config.statsFile))
|
||||||
app.use(createRouter(config))
|
app.use(createRouter(config))
|
||||||
|
|
||||||
|
24
package.json
24
package.json
@ -6,16 +6,21 @@
|
|||||||
"build": "npm run build-assets && npm run build-lib",
|
"build": "npm run build-assets && npm run build-lib",
|
||||||
"build-assets": "NODE_ENV=production webpack -p --json > stats.json",
|
"build-assets": "NODE_ENV=production webpack -p --json > stats.json",
|
||||||
"build-lib": "rimraf lib && babel ./modules -d lib --copy-files",
|
"build-lib": "rimraf lib && babel ./modules -d lib --copy-files",
|
||||||
"heroku-postbuild": "npm run build"
|
"heroku-postbuild": "npm run build",
|
||||||
|
"lint": "eslint modules",
|
||||||
|
"test": "npm run lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"autoprefixer": "^6.3.6",
|
"autoprefixer": "^6.3.6",
|
||||||
"babel-cli": "^6.8.0",
|
"babel-cli": "^6.8.0",
|
||||||
|
"babel-core": "^6.9.1",
|
||||||
"babel-loader": "^6.2.4",
|
"babel-loader": "^6.2.4",
|
||||||
|
"babel-plugin-transform-object-assign": "^6.8.0",
|
||||||
"babel-preset-es2015": "^6.6.0",
|
"babel-preset-es2015": "^6.6.0",
|
||||||
"babel-preset-react": "^6.5.0",
|
"babel-preset-react": "^6.5.0",
|
||||||
"babel-preset-stage-1": "^6.5.0",
|
"babel-preset-stage-1": "^6.5.0",
|
||||||
"byte-size": "^2.0.0",
|
"byte-size": "^2.0.0",
|
||||||
|
"chunk-manifest-webpack-plugin": "^0.1.0",
|
||||||
"cors": "^2.7.1",
|
"cors": "^2.7.1",
|
||||||
"countries-list": "^1.1.0",
|
"countries-list": "^1.1.0",
|
||||||
"css-loader": "^0.23.1",
|
"css-loader": "^0.23.1",
|
||||||
@ -35,18 +40,24 @@
|
|||||||
"npm-http-server": "^2.14.2",
|
"npm-http-server": "^2.14.2",
|
||||||
"on-finished": "^2.3.0",
|
"on-finished": "^2.3.0",
|
||||||
"postcss-loader": "^0.9.1",
|
"postcss-loader": "^0.9.1",
|
||||||
"react": "^15.0.2",
|
"react": "^15.1.0",
|
||||||
"react-dom": "^15.0.2",
|
"react-dom": "^15.1.0",
|
||||||
"react-motion": "^0.4.3",
|
"react-motion": "^0.4.3",
|
||||||
"redis": "^2.6.0-1",
|
"redis": "^2.6.0-1",
|
||||||
"rimraf": "^2.5.2",
|
"rimraf": "^2.5.2",
|
||||||
"style-loader": "^0.13.1",
|
"style-loader": "^0.13.1",
|
||||||
"throng": "^4.0.0",
|
"throng": "^4.0.0",
|
||||||
"webpack": "^1.13.0",
|
"webpack": "^1.13.0",
|
||||||
"webpack-dev-server": "^1.14.1"
|
"webpack-dev-server": "^1.14.1",
|
||||||
|
"webpack-md5-hash": "0.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-register": "^6.8.0"
|
"babel-eslint": "^6.0.4",
|
||||||
|
"babel-register": "^6.8.0",
|
||||||
|
"eslint": "^3.0.1",
|
||||||
|
"eslint-import-resolver-webpack": "^0.4.0",
|
||||||
|
"eslint-plugin-import": "^1.8.1",
|
||||||
|
"eslint-plugin-react": "^5.1.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "5.8.x"
|
"node": "5.8.x"
|
||||||
@ -56,6 +67,9 @@
|
|||||||
"es2015",
|
"es2015",
|
||||||
"react",
|
"react",
|
||||||
"stage-1"
|
"stage-1"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"transform-object-assign"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const webpack = require('webpack')
|
const webpack = require('webpack')
|
||||||
const autoprefixer = require('autoprefixer')
|
const autoprefixer = require('autoprefixer')
|
||||||
|
const ChunkManifestPlugin = require('chunk-manifest-webpack-plugin')
|
||||||
const ExtractTextPlugin = require('extract-text-webpack-plugin')
|
const ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||||
|
const WebpackMD5Hash = require('webpack-md5-hash')
|
||||||
const outputPrefix = '[chunkhash:8]-'
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: {
|
entry: {
|
||||||
vendor: [ 'react' ],
|
vendor: [ 'react', 'react-dom' ],
|
||||||
home: path.resolve(__dirname, 'modules/client/home.js')
|
home: path.resolve(__dirname, 'modules/client/home.js')
|
||||||
},
|
},
|
||||||
|
|
||||||
output: {
|
output: {
|
||||||
filename: `${outputPrefix}[name].js`,
|
filename: '[chunkhash:8]-[name].js',
|
||||||
path: path.resolve(__dirname, 'public/__assets__'),
|
path: path.resolve(__dirname, 'public/__assets__'),
|
||||||
publicPath: '/__assets__/'
|
publicPath: '/__assets__/'
|
||||||
},
|
},
|
||||||
@ -28,12 +28,20 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.optimize.OccurrenceOrderPlugin(),
|
new WebpackMD5Hash(),
|
||||||
new webpack.optimize.CommonsChunkPlugin({ name: 'vendor' }),
|
new webpack.optimize.CommonsChunkPlugin({
|
||||||
new ExtractTextPlugin(`${outputPrefix}styles.css`),
|
name: 'vendor',
|
||||||
|
minChunks: Infinity
|
||||||
|
}),
|
||||||
|
new ChunkManifestPlugin({
|
||||||
|
filename: 'chunk-manifest.json',
|
||||||
|
manifestVariable: 'webpackManifest'
|
||||||
|
}),
|
||||||
|
new ExtractTextPlugin('[chunkhash:8]-styles.css'),
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
|
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
|
||||||
})
|
}),
|
||||||
|
new webpack.optimize.OccurrenceOrderPlugin()
|
||||||
],
|
],
|
||||||
|
|
||||||
postcss: () => [ autoprefixer ]
|
postcss: () => [ autoprefixer ]
|
||||||
|
Reference in New Issue
Block a user