├── .babelrc
├── .env_example
├── .eslintrc
├── .flowconfig
├── .gitignore
├── .nvmrc
├── CHANGELOG.md
├── LICENSE
├── README.md
├── package.json
├── public
├── favicon.ico
└── robots.txt
├── src
├── client
│ └── index.js
├── server
│ └── index.js
├── shared
│ └── universal
│ │ ├── components
│ │ └── App
│ │ │ ├── App.js
│ │ │ ├── globals.css
│ │ │ ├── index.js
│ │ │ ├── lib
│ │ │ └── Logo
│ │ │ │ ├── Logo.js
│ │ │ │ ├── index.js
│ │ │ │ └── logo.png
│ │ │ └── views
│ │ │ ├── About
│ │ │ ├── About.js
│ │ │ └── index.js
│ │ │ └── Home
│ │ │ ├── Home.js
│ │ │ └── index.js
│ │ ├── routes
│ │ └── index.js
│ │ └── utils
│ │ └── guards.js
└── universalMiddleware
│ ├── clientAssets.js
│ ├── index.js
│ └── render.js
└── tools
├── config
└── envVars.js
├── development
├── createNotification.js
├── hotClient.js
├── hotServer.js
├── index.js
├── listenerManager.js
└── universalDevMiddleware.js
├── flow
├── definitions
│ ├── commonjs.js
│ ├── es6modules.js
│ └── react.js
├── flow-typed
│ ├── README.md
│ └── npm
│ │ └── express_v4.x.x.js
└── stubs
│ └── WebpackAsset.js.flow
├── utils
└── index.js
└── webpack
├── client.config.js
├── configFactory.js
├── server.config.js
└── universalMiddleware.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | // We don't use the .babelrc file for our babel configuration, preferring for
2 | // our webpack loaders to have full control over the babel configuration.
3 | // This allows us to build up our babel config in a much more dynamic fashion
4 | // easily targetting a mix mash of server vs browser vs dev vs prod etc.
5 | // @see webpackConfigFactory.js
6 | {}
7 |
--------------------------------------------------------------------------------
/.env_example:
--------------------------------------------------------------------------------
1 | # This is an example environment configuration file. You can create your own
2 | # .env implementation or even just set these values directly on the environment
3 | # via your CI server for example.
4 |
5 | # NOTE!
6 | # These keys will be used by the webpack configFactory. Specifically webpack
7 | # parses the code and will replace any "process.env.{VAR_NAME}" instances with
8 | # the matching value from your vars below.
9 |
10 | # The port on which to run our server.
11 | SERVER_PORT=1337
12 |
13 | # The port on which to run our client bundle dev server.
14 | CLIENT_DEVSERVER_PORT=7331
15 |
16 | # Disable SSR?
17 | DISABLE_SSR=false
18 |
19 | # Where should we output our bundles? (relative to project root)
20 | BUNDLE_OUTPUT_PATH=./build
21 |
22 | # What should we name the file that contains details of all the files contained
23 | # within our bundles?
24 | BUNDLE_ASSETS_FILENAME=assets.json
25 |
26 | # What is the public http path at which we will serve our client bundle from?
27 | CLIENT_BUNDLE_HTTP_PATH=/client/
28 |
29 | # How long should we set the browser cache for the client bundle assets? Don't
30 | # worry, we add hashes to the files, so if they change the new files will be
31 | # served to browsers.
32 | # We are using the "ms" format to set the length.
33 | # @see https://www.npmjs.com/package/ms
34 | CLIENT_BUNDLE_CACHE_MAXAGE=365d
35 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": "airbnb",
4 | "plugins": [
5 | "flow-vars"
6 | ],
7 | "env": {
8 | "browser": true,
9 | "es6": true,
10 | "node": true
11 | },
12 | "ecmaFeatures": {
13 | "defaultParams": true
14 | },
15 | "rules": {
16 | # We use the 'import' plugin which allows for cases "flow" awareness.
17 | "no-duplicate-imports": 0,
18 |
19 | "flow-vars/define-flow-type": 1,
20 | "flow-vars/use-flow-type": 1,
21 |
22 | "react/jsx-filename-extension": [0],
23 |
24 | "import/no-extraneous-dependencies": [0],
25 | "import/no-unresolved": [2, { ignore: ['^react(-dom(\/server)?)?$'] }],
26 | "import/extensions": [2, { "react": "never" }]
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [include]
2 |
3 | [ignore]
4 |
5 | # Including these files causes issues.
6 | .*/node_modules/fbjs/.*
7 |
8 | [libs]
9 |
10 | # Official "flow-typed" repository definitions.
11 | tools/flow/flow-typed/npm
12 |
13 | # Custom definitions.
14 | tools/flow/definitions/
15 |
16 | # Note: the following definitions come bundled with flow. It can be handy
17 | # to reference them.
18 | # React: https://github.com/facebook/flow/blob/master/lib/react.js
19 | # Javascript: https://github.com/facebook/flow/blob/master/lib/core.js
20 | # Node: https://github.com/facebook/flow/blob/master/lib/node.js
21 | # DOM: https://github.com/facebook/flow/blob/master/lib/dom.js
22 | # BOM: https://github.com/facebook/flow/blob/master/lib/bom.js
23 | # CSSOM: https://github.com/facebook/flow/blob/master/lib/cssom.js
24 | # IndexDB: https://github.com/facebook/flow/blob/master/lib/indexeddb.js
25 |
26 | [options]
27 |
28 | # This is so that we can import static files in our webpack supported components
29 | # and not have flow throw a hissy fit.
30 | module.name_mapper='^\(.*\)\.\(css\|eot\|gif\|ico\|jpg\|jpeg\|less\|otf\|mp3\|mp4\|ogg\|png\|sass\|scss\|sss\|svg\|swf\|ttf\|webp\|woff\|woff2\)$' -> '/tools/flow/stubs/WebpackAsset.js.flow'
31 |
32 | [version]
33 | 0.32.0
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Environment Configuration
2 | .env
3 |
4 | # Build output folders
5 | build/
6 |
7 | # Logs
8 | logs
9 | *.log
10 |
11 | # Runtime data
12 | pids
13 | *.pid
14 | *.seed
15 |
16 | # Coverage directory used by tools like istanbul
17 | coverage
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Dependency directory
23 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
24 | node_modules
25 |
26 | # Debug log from npm
27 | npm-debug.log
28 |
29 | # IntelliJ IDE ignore
30 | .idea
31 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v6.6.0
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | All notable changes to this project will be documented in this file.
3 | This project adheres to [Semantic Versioning](http://semver.org/).
4 |
5 | ## [3.1.0] - 2016-08-11
6 |
7 | ### Changed
8 |
9 | Updated to match v3.3.1 API of react-universally.
10 |
11 | ## [3.0.0] - 2016-08-09
12 |
13 | ### Breaking Changes
14 |
15 | Updated to match v3.0.0 API of react-universally.
16 |
17 | ## [2.0.0] - 2016-08-08
18 |
19 | ### Breaking Changes
20 |
21 | The server side render method has been massively simplified as we are now using
22 | react-helmet to control our page header from our components.
23 |
24 | ### Added
25 |
26 | A 'public' static files endpoint, with a favicon implementation.
27 |
28 | ### Changed
29 |
30 | The server render helper now resolves the 'assets.json' via the webpack configuration for the client bundle.
31 |
32 | ### Fixed
33 |
34 | Small issue with the dev server not hot reloading when just the server code changes.
35 |
36 | The projects dependencies structure so that the "dependencies" section contains ALL libraries that are required for the server runtime. The "devDependencies" section contains the libraries required for building the project.
37 |
38 | ## [1.0.0] - 2016-07-18
39 |
40 | ### Added
41 |
42 | Version 1 of the react-universally-skinny boilerplate.
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Sean Matheson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### DEPRECATED
2 |
3 | I am no longer managing this repo. I am instead looking to integrate a branch on the [main repo](https://github.com/ctrlplusb/react-universally).
4 |
5 | Beware. This repo is out of date.
6 |
7 | ----------
8 |
9 |
10 |
React, Universally - Skinny
11 |
12 |
A "when size matters" adaptation of the react-universally starter kit.
13 |
14 |
15 | ## TOC
16 |
17 | - [About](https://github.com/ctrlplusb/react-universally#about)
18 | - [References](https://github.com/ctrlplusb/react-universally#references)
19 |
20 | ## About
21 |
22 | This is an alternative version of [`react-universally`](https://github.com/ctrlplusb/react-universally) (a starter kit that contains all the build tooling and configuration you need to kick off your next universal react project, whilst containing a minimal "project" set up allowing you to make your own architecture decisions). Please reference the [`react-universally`](https://github.com/ctrlplusb/react-universally) documentation for the features contained within this starter kit.
23 |
24 | This adaptation of the starter kit attempts to provide you with as small as a client bundle size as possible whilst maintaining the equivalent features/functionality. It does so by making use of the amazing `preact` and `preact-compat` libraries.
25 |
26 | Take a look at the differences in bundle size output...
27 |
28 | ### [`react-universally`](https://github.com/ctrlplusb/react-universally)
29 |
30 | | Chunk Name | Size (GZipped) |
31 | |-------------------------------|------------------|
32 | | index.js | 72.9 KB |
33 |
34 | ### [`react-universally-skinny`](https://github.com/ctrlplusb/react-universally-skinny)
35 |
36 | | Chunk Name | Size (GZipped) |
37 | |-------------------------------|------------------|
38 | | index.js | 37.4 KB |
39 |
40 | BOOM, ___48%___ size savings!
41 |
42 | Of course these don't come without a cost. As we are using `preact` we have had to drop `react-hot-loader` and instead replace it with a native implementation of `webpack` HMR feature. You still have hot reloading, it's not as powerful, but it's probably good enough.
43 |
44 | ## References ##
45 |
46 | - __react-universally__ - https://github.com/ctrlplusb/react-universally
47 | - __react-router v3 changelog__ - https://github.com/reactjs/react-router/blob/next/CHANGES.md
48 | - __preact__ - https://github.com/developit/preact
49 | - __Webpack 2__ - https://gist.github.com/sokra/27b24881210b56bbaff7
50 | - __React Hot Loader v3__ - https://github.com/gaearon/react-hot-boilerplate/pull/61
51 | - __dotenv__ - https://github.com/bkeepers/dotenv
52 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-universally-skinny",
3 | "version": "4.0.0",
4 | "description": "A \"when size matters\" adaptation of the react-universally starter kit.",
5 | "main": "build/server/main.js",
6 | "engines": {
7 | "node": "~6.6.0"
8 | },
9 | "scripts": {
10 | "typecheck": "flow",
11 | "lint": "eslint src",
12 | "clean": "rimraf build",
13 | "development": "node ./tools/development",
14 | "build": "npm run clean && webpack --config ./tools/webpack/client.config.js && webpack --config ./tools/webpack/universalMiddleware.config.js && webpack --config ./tools/webpack/server.config.js",
15 | "start": "node build/server"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "git+https://github.com/ctrlplusb/react-universally.git"
20 | },
21 | "keywords": [
22 | "react",
23 | "preact",
24 | "boilerplate",
25 | "starter kit",
26 | "universal",
27 | "javascript",
28 | "express",
29 | "webpack"
30 | ],
31 | "author": "Sean Matheson ",
32 | "license": "MIT",
33 | "bugs": {
34 | "url": "https://github.com/ctrlplusb/react-universally/issues"
35 | },
36 | "homepage": "https://github.com/ctrlplusb/react-universally#readme",
37 | "dependencies": {
38 | "app-root-path": "2.0.1",
39 | "compression": "1.6.2",
40 | "express": "4.14.0",
41 | "helmet": "2.2.0",
42 | "hpp": "0.2.1",
43 | "normalize.css": "4.2.0",
44 | "path": "0.12.7",
45 | "preact": "6.2.1",
46 | "preact-compat": "3.6.0",
47 | "react-helmet": "3.1.0",
48 | "react-router": "2.8.1",
49 | "serialize-javascript": "1.3.0",
50 | "source-map-support": "0.4.3"
51 | },
52 | "devDependencies": {
53 | "assets-webpack-plugin": "3.4.0",
54 | "babel-cli": "6.16.0",
55 | "babel-core": "6.16.0",
56 | "babel-eslint": "7.0.0",
57 | "babel-loader": "6.2.5",
58 | "babel-preset-latest": "6.16.0",
59 | "babel-preset-react": "6.16.0",
60 | "chokidar": "1.6.0",
61 | "colors": "1.1.2",
62 | "css-loader": "0.25.0",
63 | "dotenv": "2.0.0",
64 | "eslint": "3.6.1",
65 | "eslint-config-airbnb": "12.0.0",
66 | "eslint-plugin-flow-vars": "0.5.0",
67 | "eslint-plugin-import": "2.0.0",
68 | "eslint-plugin-jsx-a11y": "2.2.2",
69 | "eslint-plugin-react": "6.3.0",
70 | "extract-text-webpack-plugin": "2.0.0-beta.4",
71 | "file-loader": "0.9.0",
72 | "flow-bin": "0.32.0",
73 | "json-loader": "0.5.4",
74 | "node-notifier": "4.6.1",
75 | "regenerator-runtime": "0.9.5",
76 | "rimraf": "2.5.4",
77 | "style-loader": "0.13.1",
78 | "url-loader": "0.5.7",
79 | "webpack": "2.1.0-beta.25",
80 | "webpack-dev-middleware": "1.8.3",
81 | "webpack-hot-middleware": "2.12.2",
82 | "webpack-md5-hash": "0.0.5",
83 | "webpack-node-externals": "1.4.3"
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ctrlplusb/react-universally-skinny/664841f607f748d1c0c84ba2ee503f09dd9b79cb/public/favicon.ico
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Allow: /
3 |
--------------------------------------------------------------------------------
/src/client/index.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import React from 'react';
4 | import { render } from 'react-dom';
5 | import Router from 'react-router/lib/Router';
6 | import browserHistory from 'react-router/lib/browserHistory';
7 | import match from 'react-router/lib/match';
8 | import routes from '../shared/universal/routes';
9 |
10 | // Get the DOM Element that will host our React application.
11 | const container = document.querySelector('#app');
12 |
13 | function routerError(error) {
14 | console.error('==> Router match failed.'); // eslint-disable-line no-console
15 | if (error) { console.error(error); } // eslint-disable-line no-console
16 | }
17 |
18 | function renderApp(appRoutes) {
19 | // As we are using dynamic react-router routes we have to use the following
20 | // asynchronous routing mechanism supported by the `match` function.
21 | // @see https://github.com/reactjs/react-router/blob/master/docs/guides/ServerRendering.md
22 | match({ history: browserHistory, routes: appRoutes }, (error, redirectLocation, renderProps) => {
23 | if (error) {
24 | routerError(error);
25 | } else if (redirectLocation) {
26 | return;
27 | } else if (renderProps) {
28 | render(
29 | // We need to explicly render the Router component here instead of have
30 | // this embedded within a shared App type of component as we use different
31 | // router base components for client vs server rendering.
32 | ,
33 | container
34 | );
35 | } else {
36 | routerError();
37 | }
38 |
39 | render(
40 | // We need to explicly render the Router component here instead of have
41 | // this embedded within a shared App type of component as we use different
42 | // router base components for client vs server rendering.
43 | ,
44 | container
45 | );
46 | });
47 | }
48 |
49 | // The following is needed so that we can support hot reloading our application.
50 | if (process.env.NODE_ENV === 'development' && module.hot) {
51 | // Accept changes to this file for hot reloading.
52 | module.hot.accept('./index.js');
53 | // Any changes to our routes will cause a hotload re-render.
54 | module.hot.accept(
55 | '../shared/universal/routes',
56 | () => renderApp(require('../shared/universal/routes').default) // eslint-disable-line global-require
57 | );
58 | }
59 |
60 | renderApp(routes);
61 |
--------------------------------------------------------------------------------
/src/server/index.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | // This grants us source map support, which combined with our webpack source
4 | // maps will give us nice stack traces.
5 | import 'source-map-support/register';
6 | import path from 'path';
7 | import appRoot from 'app-root-path';
8 | import express from 'express';
9 | import compression from 'compression';
10 | import hpp from 'hpp';
11 | import helmet from 'helmet';
12 | import universalMiddleware from '../universalMiddleware';
13 | import { notEmpty } from '../shared/universal/utils/guards';
14 |
15 | const appRootPath = appRoot.toString();
16 |
17 | // Create our express based server.
18 | const app = express();
19 |
20 | // Don't expose any software information to hackers.
21 | app.disable('x-powered-by');
22 |
23 | // Prevent HTTP Parameter pollution.
24 | app.use(hpp());
25 |
26 | // Content Security Policy
27 | app.use(helmet.contentSecurityPolicy({
28 | defaultSrc: ["'self'"],
29 | scriptSrc: ["'self'"],
30 | styleSrc: ["'self'"],
31 | imgSrc: ["'self'"],
32 | connectSrc: ["'self'", 'ws:'],
33 | fontSrc: ["'self'"],
34 | objectSrc: ["'none'"],
35 | mediaSrc: ["'none'"],
36 | frameSrc: ["'none'"],
37 | }));
38 | app.use(helmet.xssFilter());
39 | app.use(helmet.frameguard('deny'));
40 | app.use(helmet.ieNoOpen());
41 | app.use(helmet.noSniff());
42 |
43 | // Response compression.
44 | app.use(compression());
45 |
46 | // Configure static serving of our webpack bundled client files.
47 | app.use(
48 | notEmpty(process.env.CLIENT_BUNDLE_HTTP_PATH),
49 | express.static(
50 | path.resolve(appRootPath, notEmpty(process.env.BUNDLE_OUTPUT_PATH), './client'),
51 | { maxAge: notEmpty(process.env.CLIENT_BUNDLE_CACHE_MAXAGE) }
52 | )
53 | );
54 |
55 | // Configure static serving of our "public" root http path static files.
56 | app.use(express.static(path.resolve(appRootPath, './public')));
57 |
58 | // Bind our universal react app middleware as the handler for all get requests.
59 | if (process.env.NODE_ENV === 'development') {
60 | // In development mode we will use a special wrapper middleware which will
61 | // allow us to flush our node module cache effectively, and it will thereby
62 | // allow us to "hot" reload any builds/updates to our middleware bundle.
63 | const universalDevMiddleware = require('../../tools/development/universalDevMiddleware'); // eslint-disable-line global-require,max-len
64 |
65 | app.get('*', universalDevMiddleware);
66 | } else {
67 | app.get('*', universalMiddleware);
68 | }
69 |
70 | // Create an http listener for our express app.
71 | const port = parseInt(notEmpty(process.env.SERVER_PORT), 10);
72 | const listener = app.listen(port);
73 | console.log(`Server listening on port ${port}`); // eslint-disable-line no-console
74 |
75 | // We export the listener as it will be handy for our development hot reloader.
76 | export default listener;
77 |
--------------------------------------------------------------------------------
/src/shared/universal/components/App/App.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import React from 'react';
4 | import Link from 'react-router/lib/Link';
5 | import Helmet from 'react-helmet';
6 |
7 | import 'normalize.css/normalize.css';
8 | import './globals.css';
9 |
10 | import Logo from './lib/Logo';
11 |
12 | const websiteDescription =
13 | 'A starter kit giving you the minimum requirements for a production ready ' +
14 | 'universal react application.';
15 |
16 | function App(props : { children : $React$Children }) {
17 | const { children } = props;
18 | return (
19 |
20 | {/*
21 | All of the following will be injected into our page header.
22 | @see https://github.com/nfl/react-helmet
23 | */}
24 |
33 |
34 |
12 | This starter kit contains all the build tooling and configuration you
13 | need to kick off your next universal react project, whilst containing a
14 | minimal project set up allowing you to make your own architecture
15 | decisions (redux/mobx etc).
16 |
17 |
18 | );
19 | }
20 |
21 | export default Home;
22 |
--------------------------------------------------------------------------------
/src/shared/universal/components/App/views/Home/index.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import Home from './Home';
4 |
5 | export default Home;
6 |
--------------------------------------------------------------------------------
/src/shared/universal/routes/index.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import React from 'react';
4 | import Route from 'react-router/lib/Route';
5 | import IndexRoute from 'react-router/lib/IndexRoute';
6 | import App from '../components/App';
7 |
8 | /**
9 | * Use this function to provide `getComponent` implementations for your
10 | * application "views" (i.e. pages). It makes use of webpack 2's System.import
11 | * feature which allows for the async module loading behavior, and this results
12 | * in webpack doing code splitting. So essentially we will get a different
13 | * code split bundle for each of our views. Sweet.
14 | *
15 | * Notes:
16 | * 1. Your view components have to reside within the
17 | * ~/src/shared/components/App/views folder. You need to create a folder to
18 | * represent your view and then have an index.js file within that will return
19 | * the respective view component.
20 | * 2. The regex that webpack uses to statically calculate which components it
21 | * should expose as async has been overridden within the config factory. It
22 | * has been overridden so that only the "root" folders within
23 | * ~/src/shared/components/App/views will be recognised as async components.
24 | * None of the sub folders will be considered.
25 | *
26 | * @see https://gist.github.com/sokra/27b24881210b56bbaff7#code-splitting-with-es6
27 | */
28 | function asyncAppViewResolver(viewName: string) {
29 | const errorHandler = (err) => {
30 | console.log(`==> Failed to load async view "${viewName}".`); // eslint-disable-line no-console
31 | console.log(err); // eslint-disable-line no-console
32 | };
33 |
34 | return (nextState, cb) =>
35 | System.import('../components/App/views/' + viewName + '/index.js') // eslint-disable-line prefer-template
36 | .then(module => cb(null, module.default))
37 | .catch(errorHandler);
38 | }
39 |
40 | /**
41 | * Our routes.
42 | *
43 | * Note: We load our routes asynhronously using the `getComponent` API of
44 | * react-router, doing so combined with the `System.import` support by
45 | * webpack 2 allows us to get code splitting based on our routes.
46 | *
47 | * @see https://github.com/reactjs/react-router/blob/master/docs/guides/DynamicRouting.md
48 | */
49 | const routes = (
50 |
51 |
52 |
53 |
54 | );
55 |
56 | export default routes;
57 |
--------------------------------------------------------------------------------
/src/shared/universal/utils/guards.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 | /* eslint-disable import/prefer-default-export */
3 |
4 | export function notEmpty(x: ?T, message?: string) : T {
5 | if (x == null) {
6 | throw new Error(message || 'Expected value to not be empty.');
7 | }
8 |
9 | return x;
10 | }
11 |
--------------------------------------------------------------------------------
/src/universalMiddleware/clientAssets.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | // This file resolves the assets available from our client bundle.
4 |
5 | import fs from 'fs';
6 | import path from 'path';
7 | import appRoot from 'app-root-path';
8 | import { notEmpty } from '../shared/universal/utils/guards';
9 |
10 | const appRootPath = appRoot.toString();
11 |
12 | const assetsBundleFilePath = path.resolve(
13 | appRootPath,
14 | notEmpty(process.env.BUNDLE_OUTPUT_PATH),
15 | './client',
16 | `./${notEmpty(process.env.BUNDLE_ASSETS_FILENAME)}`
17 | );
18 |
19 | if (!fs.existsSync(assetsBundleFilePath)) {
20 | throw new Error(
21 | `We could not find the "${assetsBundleFilePath}" file, which contains a ` +
22 | 'list of the assets of the client bundle. Please ensure that the client ' +
23 | 'bundle has been built before the server bundle and that the required ' +
24 | 'environment variables are configured (BUNDLE_OUTPUT_PATH & ' +
25 | 'BUNDLE_ASSETS_FILENAME)'
26 | );
27 | }
28 |
29 | const assetsBundleFileContents = JSON.parse(
30 | fs.readFileSync(assetsBundleFilePath, 'utf8')
31 | );
32 |
33 | // Convert the assets json it into an object that contains all the paths to our
34 | // javascript and css files. Doing this is required as for production
35 | // configurations we add a hash to our filenames, therefore we won't know the
36 | // paths of the output by webpack unless we read them from the assets.json file.
37 | const assets = Object.keys(assetsBundleFileContents)
38 | .map(key => assetsBundleFileContents[key])
39 | .reduce((acc, chunk) => {
40 | if (chunk.js) {
41 | acc.scripts.push(chunk.js);
42 | }
43 | if (chunk.css) {
44 | acc.styles.push(chunk.css);
45 | }
46 | return acc;
47 | }, { scripts: [], styles: [] });
48 |
49 | export default assets;
50 |
--------------------------------------------------------------------------------
/src/universalMiddleware/index.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import type { $Request, $Response, Middleware } from 'express';
4 | import React from 'react';
5 | import RouterContext from 'react-router/lib/RouterContext';
6 | import createMemoryHistory from 'react-router/lib/createMemoryHistory';
7 | import match from 'react-router/lib/match';
8 | import render from './render';
9 | import routes from '../shared/universal/routes';
10 |
11 | /**
12 | * An express middleware that is capabable of doing React server side rendering.
13 | */
14 | function universalReactAppMiddleware(request: $Request, response: $Response) {
15 | if (process.env.DISABLE_SSR === 'true') {
16 | if (process.env.NODE_ENV === 'development') {
17 | console.log('==> Handling react route without SSR'); // eslint-disable-line no-console
18 | }
19 | // SSR is disabled so we will just return an empty html page and will
20 | // rely on the client to populate the initial react application state.
21 | const html = render();
22 | response.status(200).send(html);
23 | return;
24 | }
25 |
26 | const history = createMemoryHistory(request.originalUrl);
27 |
28 | // Server side handling of react-router.
29 | // Read more about this here:
30 | // https://github.com/reactjs/react-router/blob/master/docs/guides/ServerRendering.md
31 | match({ routes, history }, (error, redirectLocation, renderProps) => {
32 | if (error) {
33 | response.status(500).send(error.message);
34 | } else if (redirectLocation) {
35 | response.redirect(302, redirectLocation.pathname + redirectLocation.search);
36 | } else if (renderProps) {
37 | // You can check renderProps.components or renderProps.routes for
38 | // your "not found" component or route respectively, and send a 404 as
39 | // below, if you're using a catch-all route.
40 |
41 | const html = render();
42 | response.status(200).send(html);
43 | } else {
44 | response.status(404).send('Not found');
45 | }
46 | });
47 | }
48 |
49 | export default (universalReactAppMiddleware : Middleware);
50 |
--------------------------------------------------------------------------------
/src/universalMiddleware/render.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { renderToString } from 'react-dom/server';
4 | import serialize from 'serialize-javascript';
5 | import Helmet from 'react-helmet';
6 | import clientAssets from './clientAssets';
7 |
8 | function styleTags(styles : Array) {
9 | return styles
10 | .map(style =>
11 | ``
12 | )
13 | .join('\n');
14 | }
15 |
16 | function scriptTags(scripts : Array) {
17 | return scripts
18 | .map(script =>
19 | ``
20 | )
21 | .join('\n');
22 | }
23 |
24 | const styles = styleTags(clientAssets.styles);
25 | const scripts = scriptTags(clientAssets.scripts);
26 |
27 | /**
28 | * Generates a full HTML page containing the render output of the given react
29 | * element.
30 | *
31 | * @param rootReactElement
32 | * [Optional] The root React element to be rendered on the page.
33 | * @param initialState
34 | * [Optional] The initial state for the redux store which will be used by the
35 | * client to mount the redux store into the desired state.
36 | *
37 | * @return The full HTML page in the form of a React element.
38 | */
39 | function render(rootReactElement : ?$React$Element, initialState : ?Object) {
40 | const reactRenderString = rootReactElement
41 | ? renderToString(rootReactElement)
42 | : null;
43 |
44 | const helmet = rootReactElement
45 | // We run 'react-helmet' after our renderToString call so that we can fish
46 | // out all the attributes which need to be attached to our page.
47 | // React Helmet allows us to control our page header contents via our
48 | // components.
49 | // @see https://github.com/nfl/react-helmet
50 | ? Helmet.rewind()
51 | // There was no react element, so we just us an empty helmet.
52 | : null;
53 |
54 | return `
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | ${helmet ? helmet.title.toString() : ''}
63 | ${helmet ? helmet.meta.toString() : ''}
64 | ${helmet ? helmet.link.toString() : ''}
65 |
66 | ${styles}
67 | ${helmet ? helmet.style.toString() : ''}
68 |
69 |
70 |
71 |
72 |
${reactRenderString || ''}
73 |
74 |
79 |
80 | ${scripts}
81 | ${helmet ? helmet.script.toString() : ''}
82 |
83 | `;
84 | }
85 |
86 | export default render;
87 |
--------------------------------------------------------------------------------
/tools/config/envVars.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 | const dotenv = require('dotenv');
6 | const appRoot = require('app-root-path');
7 |
8 | const appRootPath = appRoot.toString();
9 |
10 | // This is to support deployment to the "now" host. See the README for more info.
11 | const envFile = process.env.NOW
12 | ? './.envnow'
13 | : './.env';
14 |
15 | // @see https://github.com/motdotla/dotenv
16 | const envVars = dotenv.parse(
17 | fs.readFileSync(path.resolve(appRootPath, envFile), 'utf8')
18 | );
19 |
20 | module.exports = envVars;
21 |
--------------------------------------------------------------------------------
/tools/development/createNotification.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | /* eslint-disable global-require */
3 | /* eslint-disable no-underscore-dangle */
4 | /* eslint-disable import/no-extraneous-dependencies */
5 |
6 | const notifier = require('node-notifier');
7 | const colors = require('colors');
8 |
9 | function createNotification(options = {}) {
10 | const title = options.title
11 | ? `${options.title.toUpperCase()}`
12 | : undefined;
13 |
14 | notifier.notify({
15 | title,
16 | message: options.message,
17 | open: options.open,
18 | });
19 |
20 | const level = options.level || 'info';
21 | const msg = `==> ${title} -> ${options.message}`;
22 |
23 | switch (level) {
24 | case 'warn': console.log(colors.red(msg)); break;
25 | case 'error': console.log(colors.bgRed.white(msg)); break;
26 | case 'info':
27 | default: console.log(colors.green(msg));
28 | }
29 | }
30 |
31 | module.exports = createNotification;
32 |
--------------------------------------------------------------------------------
/tools/development/hotClient.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | /* eslint-disable global-require */
3 | /* eslint-disable no-underscore-dangle */
4 | /* eslint-disable import/no-extraneous-dependencies */
5 |
6 | const express = require('express');
7 | const createWebpackMiddleware = require('webpack-dev-middleware');
8 | const createWebpackHotMiddleware = require('webpack-hot-middleware');
9 | const envVars = require('../config/envVars');
10 | const ListenerManager = require('./listenerManager');
11 | const createNotification = require('./createNotification');
12 |
13 | class HotClient {
14 | constructor(compiler) {
15 | const app = express();
16 |
17 | this.webpackDevMiddleware = createWebpackMiddleware(compiler, {
18 | quiet: true,
19 | noInfo: true,
20 | headers: {
21 | 'Access-Control-Allow-Origin': '*',
22 | },
23 | // The path at which the client bundles are served from. Note: in this
24 | // case as we are running a seperate dev server the public path should
25 | // be absolute, i.e. including the "http://..."
26 | publicPath: compiler.options.output.publicPath,
27 | });
28 |
29 | app.use(this.webpackDevMiddleware);
30 | app.use(createWebpackHotMiddleware(compiler));
31 |
32 | const listener = app.listen(envVars.CLIENT_DEVSERVER_PORT);
33 |
34 | this.listenerManager = new ListenerManager(listener, 'client');
35 |
36 | compiler.plugin('compile', () => {
37 | createNotification({
38 | title: 'client',
39 | level: 'info',
40 | message: 'Building new bundle...',
41 | });
42 | });
43 |
44 | compiler.plugin('done', (stats) => {
45 | if (stats.hasErrors()) {
46 | createNotification({
47 | title: 'client',
48 | level: 'error',
49 | message: 'Build failed, please check the console for more information.',
50 | });
51 | console.log(stats.toString());
52 | } else {
53 | createNotification({
54 | title: 'client',
55 | level: 'info',
56 | message: 'Running with latest changes.',
57 | });
58 | }
59 | });
60 | }
61 |
62 | dispose() {
63 | this.webpackDevMiddleware.close();
64 |
65 | return this.listenerManager
66 | ? this.listenerManager.dispose()
67 | : Promise.resolve();
68 | }
69 | }
70 |
71 | module.exports = HotClient;
72 |
--------------------------------------------------------------------------------
/tools/development/hotServer.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | /* eslint-disable global-require */
3 | /* eslint-disable no-underscore-dangle */
4 | /* eslint-disable import/no-extraneous-dependencies */
5 | /* eslint-disable import/no-dynamic-require */
6 |
7 | const path = require('path');
8 | const chokidar = require('chokidar');
9 | const envVars = require('../config/envVars');
10 | const ListenerManager = require('./listenerManager');
11 | const createNotification = require('./createNotification');
12 |
13 | class HotServer {
14 | constructor(compiler) {
15 | this.listenerManager = null;
16 |
17 | const runCompiler = () => {
18 | createNotification({
19 | title: 'server',
20 | level: 'info',
21 | message: 'Building new server bundle...',
22 | });
23 |
24 | compiler.run(() => undefined);
25 | };
26 |
27 | const compiledOutputPath = path.resolve(
28 | compiler.options.output.path, `${Object.keys(compiler.options.entry)[0]}.js`
29 | );
30 |
31 | compiler.plugin('done', (stats) => {
32 | if (stats.hasErrors()) {
33 | createNotification({
34 | title: 'server',
35 | level: 'error',
36 | message: 'Build failed, check the console for more information.',
37 | });
38 | console.log(stats.toString());
39 | return;
40 | }
41 |
42 | // Make sure our newly built server bundles aren't in the module cache.
43 | Object.keys(require.cache).forEach((modulePath) => {
44 | if (modulePath.indexOf(compiler.options.output.path) !== -1) {
45 | delete require.cache[modulePath];
46 | }
47 | });
48 |
49 | try {
50 | // The server bundle will automatically start the web server just by
51 | // requiring it. It returns the http listener too.
52 | const listener = require(compiledOutputPath).default;
53 | this.listenerManager = new ListenerManager(listener, 'server');
54 |
55 | const url = `http://localhost:${envVars.SERVER_PORT}`;
56 |
57 | createNotification({
58 | title: 'server',
59 | level: 'info',
60 | message: `Running on ${url} with latest changes.`,
61 | open: url,
62 | });
63 | } catch (err) {
64 | createNotification({
65 | title: 'server',
66 | level: 'error',
67 | message: 'Failed to start, please check the console for more information.',
68 | });
69 | console.log(err);
70 | }
71 | });
72 |
73 | // Now we will configure `chokidar` to watch our server specific source folder.
74 | // Any changes will cause a rebuild of the server bundle.
75 |
76 | const compileHotServer = () => {
77 | // Shut down any existing running listener if necessary before starting the
78 | // compile, else just compile.
79 | if (this.listenerManager) {
80 | this.listenerManager.dispose(true).then(runCompiler);
81 | } else {
82 | runCompiler();
83 | }
84 | };
85 |
86 | this.watcher = chokidar.watch([path.resolve(__dirname, '../../src/server')]);
87 | this.watcher.on('ready', () => {
88 | this.watcher
89 | .on('add', compileHotServer)
90 | .on('addDir', compileHotServer)
91 | .on('change', compileHotServer)
92 | .on('unlink', compileHotServer)
93 | .on('unlinkDir', compileHotServer);
94 | });
95 |
96 | // Lets start the compile.
97 | runCompiler();
98 | }
99 |
100 | dispose() {
101 | return this.listenerManager
102 | ? this.listenerManager.dispose()
103 | : Promise.resolve();
104 | }
105 | }
106 |
107 | module.exports = HotServer;
108 |
--------------------------------------------------------------------------------
/tools/development/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | /* eslint-disable global-require */
3 | /* eslint-disable no-underscore-dangle */
4 | /* eslint-disable import/no-extraneous-dependencies */
5 | /* eslint-disable import/newline-after-import */
6 |
7 | const path = require('path');
8 | const chokidar = require('chokidar');
9 | const webpack = require('webpack');
10 | const createNotification = require('./createNotification');
11 | const HotServer = require('./hotServer');
12 | const HotClient = require('./hotClient');
13 |
14 | class HotDevelopment {
15 | constructor() {
16 | try {
17 | const clientConfigFactory = require('../webpack/client.config');
18 | const clientConfig = clientConfigFactory({ mode: 'development' });
19 | this.clientCompiler = webpack(clientConfig);
20 |
21 | const middlewareConfigFactory = require('../webpack/universalMiddleware.config');
22 | const middlewareConfig = middlewareConfigFactory({ mode: 'development' });
23 | this.middlewareCompiler = webpack(middlewareConfig);
24 |
25 | const serverConfigFactory = require('../webpack/server.config');
26 | const serverConfig = serverConfigFactory({ mode: 'development' });
27 | this.serverCompiler = webpack(serverConfig);
28 | } catch (err) {
29 | createNotification({
30 | title: 'development',
31 | level: 'error',
32 | message: 'Webpack configs are invalid, please check the console for more information.',
33 | });
34 | console.log(err);
35 | return;
36 | }
37 |
38 | this.prepHotServer();
39 | this.prepHotUniversalMiddleware();
40 | this.prepHotClient();
41 | }
42 |
43 | prepHotClient() {
44 | this.clientBundle = new HotClient(this.clientCompiler);
45 | }
46 |
47 | prepHotUniversalMiddleware() {
48 | let started = false;
49 |
50 | const runMiddlewareCompiler = () => {
51 | this.middlewareCompiler.watch({}, () => undefined);
52 | };
53 |
54 | this.clientCompiler.plugin('done', (stats) => {
55 | if (!stats.hasErrors() && !started) {
56 | started = true;
57 | runMiddlewareCompiler();
58 | }
59 | });
60 |
61 | this.middlewareCompiler.plugin('compile', () => {
62 | createNotification({
63 | title: 'universalMiddleware',
64 | level: 'info',
65 | message: 'Building new bundle...',
66 | });
67 | });
68 |
69 | this.middlewareCompiler.plugin('done', (stats) => {
70 | if (!stats.hasErrors()) {
71 | // Make sure our newly built bundle is removed from the module cache.
72 | Object.keys(require.cache).forEach((modulePath) => {
73 | if (modulePath.indexOf('universalMiddleware') !== -1) {
74 | delete require.cache[modulePath];
75 | }
76 | });
77 |
78 | createNotification({
79 | title: 'universalMiddleware',
80 | level: 'info',
81 | message: 'Available with latest changes.',
82 | });
83 | } else {
84 | createNotification({
85 | title: 'universalMiddleware',
86 | level: 'error',
87 | message: 'Build failed, please check the console for more information.',
88 | });
89 | console.log(stats.toString());
90 | }
91 | });
92 | }
93 |
94 | prepHotServer() {
95 | let clientBuilt = false;
96 | let middlewareBuilt = false;
97 | let started = false;
98 |
99 | const startServerBundleWhenReady = () => {
100 | if (!started && (clientBuilt && middlewareBuilt)) {
101 | started = true;
102 | this.serverBundle = new HotServer(this.serverCompiler);
103 | }
104 | };
105 |
106 | this.clientCompiler.plugin('done', (stats) => {
107 | if (!stats.hasErrors() && !clientBuilt) {
108 | clientBuilt = true;
109 | startServerBundleWhenReady();
110 | }
111 | });
112 |
113 | this.middlewareCompiler.plugin('done', (stats) => {
114 | if (!stats.hasErrors() && !middlewareBuilt) {
115 | middlewareBuilt = true;
116 | startServerBundleWhenReady();
117 | }
118 | });
119 | }
120 |
121 | dispose() {
122 | const safeDisposer = bundle => () => (bundle ? bundle.dispose() : Promise.resolve());
123 | const safeDisposeClient = safeDisposer(this.clientBundle);
124 | const safeDisposeServer = safeDisposer(this.serverBundle);
125 |
126 | return safeDisposeClient()
127 | .then(() => console.log('disposed client'))
128 | .then(safeDisposeServer);
129 | }
130 | }
131 |
132 | const hotDevelopment = new HotDevelopment();
133 |
134 | // Any changes to our webpack configs should be notified as requiring a restart
135 | // of the development tool.
136 | const watcher = chokidar.watch(
137 | path.resolve(__dirname, '../webpack')
138 | );
139 | watcher.on('ready', () => {
140 | watcher.on('change', () => {
141 | createNotification({
142 | title: 'webpack',
143 | level: 'warn',
144 | message: 'Webpack config changed. Please restart your development server to use the latest version of the configs.',
145 | });
146 | });
147 | });
148 |
149 | // If we receive a kill cmd then we will first try to dispose our listeners.
150 | process.on('SIGTERM', () => hotDevelopment.dispose().then(() => process.exit(0)));
151 |
--------------------------------------------------------------------------------
/tools/development/listenerManager.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | /* eslint-disable global-require */
3 | /* eslint-disable no-underscore-dangle */
4 | /* eslint-disable import/no-extraneous-dependencies */
5 |
6 | const createNotification = require('./createNotification');
7 |
8 | class ListenerManager {
9 | constructor(listener, name) {
10 | this.name = name || 'listener';
11 | this.lastConnectionKey = 0;
12 | this.connectionMap = {};
13 | this.listener = listener;
14 |
15 | // Track all connections to our server so that we can close them when needed.
16 | this.listener.on('connection', (connection) => {
17 | // Generate a new key to represent the connection
18 | const connectionKey = this.lastConnectionKey + 1;
19 | // Add the connection to our map.
20 | this.connectionMap[connectionKey] = connection;
21 | // Remove the connection from our map when it closes.
22 | connection.on('close', () => {
23 | delete this.connectionMap[connectionKey];
24 | });
25 | });
26 | }
27 |
28 | killAllConnections() {
29 | Object.keys(this.connectionMap).forEach((connectionKey) => {
30 | this.connectionMap[connectionKey].destroy();
31 | });
32 | }
33 |
34 | dispose() {
35 | return new Promise((resolve) => {
36 | if (this.listener) {
37 | this.killAllConnections();
38 |
39 | createNotification({
40 | title: this.name,
41 | level: 'info',
42 | message: 'Destroyed all existing connections.',
43 | });
44 |
45 | this.listener.close(() => {
46 | this.killAllConnections();
47 |
48 | createNotification({
49 | title: this.name,
50 | level: 'info',
51 | message: 'Closed listener.',
52 | });
53 | });
54 |
55 | resolve();
56 | } else {
57 | resolve();
58 | }
59 | });
60 | }
61 | }
62 |
63 | module.exports = ListenerManager;
64 |
--------------------------------------------------------------------------------
/tools/development/universalDevMiddleware.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable global-require */
2 |
3 | // We have to use this wrapper for our universalMiddleware in development mode
4 | // as webpack has it's own module require/resolver system which can get in the
5 | // way when we try to flush our node js module cache in the hope that our
6 | // server will then use the newly built version of our middleware bundle.
7 | // Having this wrapped version allows us to "dodge" webpack taking over the
8 | // module resolution.
9 | const universalDevMiddleware = (req, resp) => {
10 | const wrappedMiddleware = require('../../build/universalMiddleware/index.js').default;
11 |
12 | wrappedMiddleware(req, resp);
13 | };
14 |
15 | module.exports = universalDevMiddleware;
16 |
--------------------------------------------------------------------------------
/tools/flow/definitions/commonjs.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | type WebpackHot = {
4 | accept(path: ?string) : void;
5 | }
6 |
7 | declare var module: {
8 | exports: any;
9 | require(id: string): any;
10 | id: string;
11 | filename: string;
12 | loaded: boolean;
13 | parent: any;
14 | children: Array;
15 | // We extend the common js format with the following to allow for the
16 | // webpack related feature.
17 | hot: WebpackHot
18 | };
19 |
--------------------------------------------------------------------------------
/tools/flow/definitions/es6modules.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | declare var System: {
4 | import(path: string): any;
5 | };
6 |
--------------------------------------------------------------------------------
/tools/flow/definitions/react.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | // Note: we already have the definitions from
4 | // https://github.com/facebook/flow/blob/master/lib/react.js
5 | // so the below are merely helpful extensions.
6 |
7 | import React from 'react';
8 |
9 | declare type $React$Element = React.Element<*>;
10 |
11 | declare type $React$Node = string | number | $React$Element | Array<$React$Element>;
12 |
13 | declare type $React$Child = $React$Node | boolean | void | null;
14 |
15 | declare type $React$Children = $React$Child | Array<$React$Children>;
16 |
--------------------------------------------------------------------------------
/tools/flow/flow-typed/README.md:
--------------------------------------------------------------------------------
1 | These definitions come from the "flow-typed" repo.
2 |
3 | Install/upgrade them by opening the `/tools/flow` directory in your console and then executing the `flow-typed` cli.
4 |
--------------------------------------------------------------------------------
/tools/flow/flow-typed/npm/express_v4.x.x.js:
--------------------------------------------------------------------------------
1 | // flow-typed signature: ce6eddb651fe9035366f36c446364736
2 | // flow-typed version: 08386ddd61/express_v4.x.x/flow_>=v0.25.x
3 |
4 | // @flow
5 | import type { Server } from 'http';
6 | declare module 'express' {
7 | declare type RouterOptions = {
8 | caseSensitive?: boolean,
9 | mergeParams?: boolean,
10 | strict?: boolean
11 | };
12 | declare class RequestResponseBase {
13 | app: Application;
14 | get(field: string): string | void;
15 | }
16 | declare class Request extends http$IncomingMessage mixins RequestResponseBase {
17 | baseUrl: string;
18 | body: mixed;
19 | cookies: {[cookie: string]: string};
20 | fresh: boolean;
21 | hostname: boolean;
22 | ip: string;
23 | ips: Array;
24 | method: string;
25 | originalUrl: string;
26 | params: {[param: string]: string};
27 | path: string;
28 | protocol: 'https' | 'http';
29 | query: {[name: string]: string};
30 | route: string;
31 | secure: boolean;
32 | signedCookies: {[signedCookie: string]: string};
33 | stale: boolean;
34 | subdomains: Array;
35 | xhr: boolean;
36 | accepts(types: string): string | false;
37 | acceptsCharsets(...charsets: Array): string | false;
38 | acceptsEncodings(...encoding: Array): string | false;
39 | acceptsLanguages(...lang: Array): string | false;
40 | header(field: string): string | void;
41 | is(type: string): boolean;
42 | param(name: string, defaultValue?: string): string | void;
43 | }
44 |
45 |
46 | declare type CookieOptions = {
47 | domain?: string,
48 | encode?: (value: string) => string,
49 | expires?: Date,
50 | httpOnly?: boolean,
51 | maxAge?: string,
52 | path?: string,
53 | secure?: boolean,
54 | signed?: boolean
55 | };
56 |
57 | declare type RenderCallback = (err: Error | null, html?: string) => mixed;
58 |
59 | declare type SendFileOptions = {
60 | maxAge?: number,
61 | root?: string,
62 | lastModified?: boolean,
63 | headers?: {[name: string]: string},
64 | dotfiles?: 'allow' | 'deny' | 'ignore'
65 | };
66 | declare class Response extends http$ClientRequest mixins RequestResponseBase {
67 | headersSent: boolean;
68 | locals: {[name: string]: mixed};
69 | append(field: string, value?: string): this;
70 | attachment(filename?: string): this;
71 | cookie(name: string, value: string, options?: CookieOptions): this;
72 | clearCookie(name: string, options?: CookieOptions): this;
73 | download(path: string, filename?: string, callback?: (err?: ?Error) => void): this;
74 | format(typesObject: {[type: string]: Function}): this;
75 | json(body?: mixed): this;
76 | jsonp(body?: mixed): this;
77 | links(links: {[name: string]: string}): this;
78 | location(path: string): this;
79 | redirect(url: string, ...args: Array): this;
80 | redirect(status: number, url: string, ...args: Array): this;
81 | render(view: string, locals?: mixed, callback: RenderCallback): this;
82 | send(body?: mixed): this;
83 | sendFile(path: string, options?: SendFileOptions, callback?: (err?: ?Error) => mixed): this;
84 | sendStatus(statusCode: number): this;
85 | set(field: string, value?: string): this;
86 | status(statusCode: number): this;
87 | type(type: string): this;
88 | vary(field: string): this;
89 | }
90 | declare type $Response = Response;
91 | declare type $Request = Request;
92 | declare type NextFunction = (err?: ?Error) => mixed;
93 | declare type Middleware =
94 | ((req: Request, res: Response, next: NextFunction) => mixed) |
95 | ((error: ?Error, req : Request, res: Response, next: NextFunction) => mixed);
96 | declare interface RouteMethodType {
97 | (middleware: Middleware): T;
98 | (...middleware: Array): T;
99 | (path: string|RegExp|string[], ...middleware: Array): T;
100 | }
101 | declare interface RouterMethodType {
102 | (middleware: Middleware): T;
103 | (...middleware: Array): T;
104 | (path: string|RegExp|string[], ...middleware: Array): T;
105 | (path: string, router: Router): T;
106 | }
107 | declare class Route {
108 | all: RouteMethodType;
109 | get: RouteMethodType;
110 | post: RouteMethodType;
111 | put: RouteMethodType;
112 | head: RouteMethodType;
113 | delete: RouteMethodType;
114 | options: RouteMethodType;
115 | trace: RouteMethodType;
116 | copy: RouteMethodType;
117 | lock: RouteMethodType;
118 | mkcol: RouteMethodType;
119 | move: RouteMethodType;
120 | purge: RouteMethodType;
121 | propfind: RouteMethodType;
122 | proppatch: RouteMethodType;
123 | unlock: RouteMethodType;
124 | report: RouteMethodType;
125 | mkactivity: RouteMethodType;
126 | checkout: RouteMethodType;
127 | merge: RouteMethodType;
128 |
129 | // @TODO Missing 'm-search' but get flow illegal name error.
130 |
131 | notify: RouteMethodType;
132 | subscribe: RouteMethodType;
133 | unsubscribe: RouteMethodType;
134 | patch: RouteMethodType;
135 | search: RouteMethodType;
136 | connect: RouteMethodType;
137 | }
138 | declare class Router extends Route {
139 | constructor(options?: RouterOptions): void;
140 |
141 | use: RouterMethodType;
142 |
143 | route(path: string): Route;
144 |
145 | static (): Router;
146 | }
147 |
148 | declare function serveStatic(root: string, options?: Object): Middleware;
149 |
150 | declare class Application extends Router mixins events$EventEmitter {
151 | constructor(): void;
152 | locals: {[name: string]: mixed};
153 | mountpath: string;
154 | listen(port: number, hostname?: string, backlog?: number, callback?: (err?: ?Error) => mixed): Server;
155 | listen(port: number, hostname?: string, callback?: (err?: ?Error) => mixed): Server;
156 | listen(port: number, callback?: (err?: ?Error) => mixed): Server;
157 | listen(path: string, callback?: (err?: ?Error) => mixed): Server;
158 | listen(handle: Object, callback?: (err?: ?Error) => mixed): Server;
159 | disable(name: string): void;
160 | disabled(name: string): boolean;
161 | enable(name: string): void;
162 | enabled(name: string): boolean;
163 | engine(name: string, callback: Function): void;
164 | /**
165 | * Mixed will not be taken as a value option. Issue around using the GET http method name and the get for settings.
166 | */
167 | // get(name: string): mixed;
168 | set(name: string, value: mixed): mixed;
169 | render(name: string, optionsOrFunction: {[name: string]: mixed}, callback: RenderCallback): void;
170 | }
171 |
172 | declare type $Application = Application;
173 |
174 | declare module.exports: {
175 | (): Application, // If you try to call like a function, it will use this signature
176 | static: serveStatic, // `static` property on the function
177 | Router: typeof Router, // `Router` property on the function
178 | };
179 | }
180 |
--------------------------------------------------------------------------------
/tools/flow/stubs/WebpackAsset.js.flow:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | declare export default string
4 |
--------------------------------------------------------------------------------
/tools/utils/index.js:
--------------------------------------------------------------------------------
1 | // :: [Any] -> [Any]
2 | function removeEmpty(x) {
3 | return x.filter(y => !!y);
4 | }
5 |
6 | // :: bool -> (Any, Any) -> Any
7 | function ifElse(condition) {
8 | return (then, or) => (condition ? then : or);
9 | }
10 |
11 | // :: ...Object -> Object
12 | function merge() {
13 | const funcArgs = Array.prototype.slice.call(arguments); // eslint-disable-line prefer-rest-params
14 |
15 | return Object.assign.apply(
16 | null,
17 | removeEmpty([{}].concat(funcArgs))
18 | );
19 | }
20 |
21 | module.exports = {
22 | removeEmpty,
23 | ifElse,
24 | merge,
25 | };
26 |
--------------------------------------------------------------------------------
/tools/webpack/client.config.js:
--------------------------------------------------------------------------------
1 | const webpackConfigFactory = require('./configFactory');
2 |
3 | module.exports = function clientConfigFactory(options = {}, args = {}) {
4 | const { mode = 'production' } = options;
5 | return webpackConfigFactory({ target: 'client', mode }, args);
6 | };
7 |
--------------------------------------------------------------------------------
/tools/webpack/configFactory.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console,import/no-extraneous-dependencies */
2 |
3 | const path = require('path');
4 | const webpack = require('webpack');
5 | const AssetsPlugin = require('assets-webpack-plugin');
6 | const nodeExternals = require('webpack-node-externals');
7 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
8 | const appRoot = require('app-root-path');
9 | const WebpackMd5Hash = require('webpack-md5-hash');
10 | const { removeEmpty, ifElse, merge } = require('../utils');
11 | const envVars = require('../config/envVars');
12 |
13 | const appRootPath = appRoot.toString();
14 |
15 | function webpackConfigFactory({ target, mode }, { json }) {
16 | if (!target || ['client', 'server', 'universalMiddleware'].findIndex(valid => target === valid) === -1) {
17 | throw new Error(
18 | 'You must provide a "target" (client|server|universalMiddleware) to the webpackConfigFactory.'
19 | );
20 | }
21 |
22 | if (!mode || ['development', 'production'].findIndex(valid => mode === valid) === -1) {
23 | throw new Error(
24 | 'You must provide a "mode" (development|production) to the webpackConfigFactory.'
25 | );
26 | }
27 |
28 | if (!json) {
29 | // Our bundle is outputing json for bundle analysis, therefore we don't
30 | // want to do this console output as it will interfere with the json output.
31 | //
32 | // You can run a bundle analysis by executing the following:
33 | //
34 | // $(npm bin)/webpack \
35 | // --env.mode production \
36 | // --config webpack.client.config.js \
37 | // --json \
38 | // --profile \
39 | // > build/client/analysis.json
40 | //
41 | // And then upload the build/client/analysis.json to http://webpack.github.io/analyse/
42 | // This allows you to analyse your webpack bundle to make sure it is
43 | // optimal.
44 | console.log(`==> Creating webpack config for "${target}" in "${mode}" mode`);
45 | }
46 |
47 | const isDev = mode === 'development';
48 | const isProd = mode === 'production';
49 | const isClient = target === 'client';
50 | const isServer = target === 'server';
51 | const isUniversalMiddleware = target === 'universalMiddleware';
52 | const isNodeTarget = isServer || isUniversalMiddleware;
53 |
54 | const ifNodeTarget = ifElse(isNodeTarget);
55 | const ifReactTarget = ifElse(isClient || isUniversalMiddleware);
56 | const ifDev = ifElse(isDev);
57 | const ifClient = ifElse(isClient);
58 | const ifServer = ifElse(isServer);
59 | const ifDevServer = ifElse(isDev && isServer);
60 | const ifDevClient = ifElse(isDev && isClient);
61 | const ifProdClient = ifElse(isProd && isClient);
62 |
63 | return {
64 | // We need to state that we are targetting "node" for our server bundle.
65 | target: ifNodeTarget('node', 'web'),
66 | // We have to set this to be able to use these items when executing a
67 | // server bundle. Otherwise strangeness happens, like __dirname resolving
68 | // to '/'. There is no effect on our client bundle.
69 | node: {
70 | __dirname: true,
71 | __filename: true,
72 | },
73 | // Anything listed in externals will not be included in our bundle.
74 | externals: removeEmpty([
75 | // Don't allow the server to bundle the universal middleware bundle. We
76 | // want the server to natively require it from the build dir.
77 | ifServer(/universalMiddleware/),
78 | ifDevServer(/universalDevMiddleware/),
79 |
80 | // We don't want our node_modules to be bundled with our server package,
81 | // prefering them to be resolved via native node module system. Therefore
82 | // we use the `webpack-node-externals` library to help us generate an
83 | // externals config that will ignore all node_modules.
84 | ifNodeTarget(nodeExternals({
85 | // NOTE: !!!
86 | // However the node_modules may contain files that will rely on our
87 | // webpack loaders in order to be used/resolved, for example CSS or
88 | // SASS. For these cases please make sure that the file extensions
89 | // are added to the below list. We have added the most common formats.
90 | whitelist: [
91 | /\.(eot|woff|woff2|ttf|otf)$/,
92 | /\.(svg|png|jpg|jpeg|gif|ico)$/,
93 | /\.(mp4|mp3|ogg|swf|webp)$/,
94 | /\.(css|scss|sass|sss|less)$/,
95 |
96 | // We need to add any react modules to our whitelist as we need
97 | // webpack to alias any imports of react/react-dom to the respective
98 | // preact libraries.
99 | /react/,
100 | ],
101 | })),
102 | ]),
103 | devtool: ifElse(isNodeTarget || isDev)(
104 | // We want to be able to get nice stack traces when running our server
105 | // bundle. To fully support this we'll also need to configure the
106 | // `node-source-map-support` module to execute at the start of the server
107 | // bundle. This module will allow the node to make use of the
108 | // source maps.
109 | // We also want to be able to link to the source in chrome dev tools
110 | // whilst we are in development mode. :)
111 | 'source-map',
112 | // When in production client mode we don't want any source maps to
113 | // decrease our payload sizes.
114 | // This form has almost no cost.
115 | 'hidden-source-map'
116 | ),
117 | // Define our entry chunks for our bundle.
118 | entry: merge(
119 | {
120 | index: removeEmpty([
121 | ifDevClient(`webpack-hot-middleware/client?reload=true&path=http://localhost:${envVars.CLIENT_DEVSERVER_PORT}/__webpack_hmr`),
122 | // We are using polyfill.io instead of the very heavy babel-polyfill.
123 | // Therefore we need to add the regenerator-runtime as the babel-polyfill
124 | // included this, which polyfill.io doesn't include.
125 | ifClient('regenerator-runtime/runtime'),
126 | path.resolve(appRootPath, `./src/${target}/index.js`),
127 | ]),
128 | }
129 | ),
130 | output: {
131 | // The dir in which our bundle should be output.
132 | path: path.resolve(appRootPath, envVars.BUNDLE_OUTPUT_PATH, `./${target}`),
133 | // The filename format for our bundle's entries.
134 | filename: ifProdClient(
135 | // We include a hash for client caching purposes. Including a unique
136 | // has for every build will ensure browsers always fetch our newest
137 | // bundle.
138 | '[name]-[chunkhash].js',
139 | // We want a determinable file name when running our server bundles,
140 | // as we need to be able to target our server start file from our
141 | // npm scripts. We don't care about caching on the server anyway.
142 | // We also want our client development builds to have a determinable
143 | // name for our hot reloading client bundle server.
144 | '[name].js'
145 | ),
146 | chunkFilename: '[name]-[chunkhash].js',
147 | // This is the web path under which our webpack bundled output should
148 | // be considered as being served from.
149 | publicPath: ifDev(
150 | // As we run a seperate server for our client and server bundles we
151 | // need to use an absolute http path for our assets public path.
152 | `http://localhost:${envVars.CLIENT_DEVSERVER_PORT}${envVars.CLIENT_BUNDLE_HTTP_PATH}`,
153 | // Otherwise we expect our bundled output to be served from this path.
154 | envVars.CLIENT_BUNDLE_HTTP_PATH
155 | ),
156 | // When in server mode we will output our bundle as a commonjs2 module.
157 | libraryTarget: ifNodeTarget('commonjs2', 'var'),
158 | },
159 | resolve: {
160 | // These extensions are tried when resolving a file.
161 | extensions: [
162 | '.js',
163 | '.jsx',
164 | '.json',
165 | ],
166 | // We alias out our react dependencies and replace them with the
167 | // lightweight preact library.
168 | // @see https://github.com/developit/preact-compat
169 | alias: {
170 | react: 'preact-compat',
171 | 'react-dom': 'preact-compat',
172 | 'react-dom/server': 'preact-compat',
173 | },
174 | },
175 | plugins: removeEmpty([
176 | // We use the System.import feature of webpack with a "dynamic" component
177 | // path. (e.g. System.import(`../components/App/views/${viewName}/index.js`))
178 | // This causes webpack to create a regex that will match for the dynamic
179 | // part of the path (i.e. ${viewName}). By default a greedy ".*" regex
180 | // pattern will be used, and therefore any subfolders containing a
181 | // "index.js" file will be considered a match and webpack will then
182 | // create a seperate bundle for the path. This is probably not going
183 | // to be desirable for us, so I have overridden the regex that will be
184 | // used below and have specified it in such a manner that only the root
185 | // folders within "~/src/shared/components/App/views" that contain an
186 | // "index.js" will be considered an async view component that should be
187 | // used by webpack for code splitting.
188 | // @see https://github.com/webpack/webpack/issues/87
189 | ifReactTarget(
190 | new webpack.ContextReplacementPlugin(
191 | /components[\/\\]App[\/\\]views$/,
192 | new RegExp(String.raw`^\.[\\\/](\w|\s|-|_)*[\\\/]index\.js$`)
193 | )
194 | ),
195 |
196 | // We use this so that our generated [chunkhash]'s are only different if
197 | // the content for our respective chunks have changed. This optimises
198 | // our long term browser caching strategy for our client bundle, avoiding
199 | // cases where browsers end up having to download all the client chunks
200 | // even though 1 or 2 may have only changed.
201 | ifClient(new WebpackMd5Hash()),
202 |
203 | // The DefinePlugin is used by webpack to substitute any patterns that it
204 | // finds within the code with the respective value assigned below.
205 | //
206 | // For example you may have the following in your code:
207 | // if (process.env.NODE_ENV === 'development') {
208 | // console.log('Foo');
209 | // }
210 | //
211 | // If we assign the NODE_ENV variable in the DefinePlugin below a value
212 | // of 'production' webpack will replace your code with the following:
213 | // if ('production' === 'development') {
214 | // console.log('Foo');
215 | // }
216 | //
217 | // This is very useful as we are compiling/bundling our code and we would
218 | // like our environment variables to persist within the code.
219 | //
220 | // Each key passed into DefinePlugin is an identifier.
221 | // The values for each key will be inlined into the code replacing any
222 | // instances of the keys that are found.
223 | // If the value is a string it will be used as a code fragment.
224 | // If the value isn’t a string, it will be stringified (including functions).
225 | // If the value is an object all keys are removeEmpty the same way.
226 | // If you prefix typeof to the key, it’s only removeEmpty for typeof calls.
227 | new webpack.DefinePlugin(
228 | merge(
229 | {
230 | // NOTE: The NODE_ENV key is especially important for production
231 | // builds as React relies on process.env.NODE_ENV for optimizations.
232 | 'process.env.NODE_ENV': JSON.stringify(mode),
233 | // NOTE: If you are providing any environment variables from the
234 | // command line rather than the .env files then you must make sure
235 | // you add them here so that webpack can use them in during the
236 | // compiling process.
237 | // e.g.
238 | // 'process.env.MY_CUSTOM_VAR': JSON.stringify(process.env.MY_CUSTOM_VAR)
239 | },
240 | // Now we will expose all of the .env config variables to webpack
241 | // so that it can make all the subtitutions for us.
242 | // Note: ALL of these values will be given as string types. Even if you
243 | // set numeric/boolean looking values within your .env file. The parsing
244 | // that we do of the .env file always returns the values as strings.
245 | // Therefore in your code you may need to do operations like the
246 | // following:
247 | // const MY_NUMBER = parseInt(process.env.MY_NUMBER, 10);
248 | // const MY_BOOL = process.env.MY_BOOL === 'true';
249 | Object.keys(envVars).reduce((acc, cur) => {
250 | acc[`process.env.${cur}`] = JSON.stringify(envVars[cur]); // eslint-disable-line no-param-reassign
251 | return acc;
252 | }, {})
253 | )
254 | ),
255 |
256 | ifClient(
257 | // Generates a JSON file containing a map of all the output files for
258 | // our webpack bundle. A necessisty for our server rendering process
259 | // as we need to interogate these files in order to know what JS/CSS
260 | // we need to inject into our HTML.
261 | new AssetsPlugin({
262 | filename: envVars.BUNDLE_ASSETS_FILENAME,
263 | path: path.resolve(appRootPath, envVars.BUNDLE_OUTPUT_PATH, `./${target}`),
264 | })
265 | ),
266 |
267 | // We don't want webpack errors to occur during development as it will
268 | // kill our dev servers.
269 | ifDev(new webpack.NoErrorsPlugin()),
270 |
271 | // We need this plugin to enable hot module reloading for our dev server.
272 | ifDevClient(new webpack.HotModuleReplacementPlugin()),
273 |
274 | // Adds options to all of our loaders.
275 | ifProdClient(
276 | new webpack.LoaderOptionsPlugin({
277 | // Indicates to our loaders that they should minify their output
278 | // if they have the capability to do so.
279 | minimize: true,
280 | // Indicates to our loaders that they should enter into debug mode
281 | // should they support it.
282 | debug: false,
283 | })
284 | ),
285 |
286 | ifProdClient(
287 | // JS Minification.
288 | new webpack.optimize.UglifyJsPlugin({
289 | // sourceMap: true,
290 | compress: {
291 | screw_ie8: true,
292 | warnings: false,
293 | },
294 | mangle: {
295 | screw_ie8: true,
296 | },
297 | output: {
298 | comments: false,
299 | screw_ie8: true,
300 | },
301 | })
302 | ),
303 |
304 | ifProdClient(
305 | // This is actually only useful when our deps are installed via npm2.
306 | // In npm2 its possible to get duplicates of dependencies bundled
307 | // given the nested module structure. npm3 is flat, so this doesn't
308 | // occur.
309 | new webpack.optimize.DedupePlugin()
310 | ),
311 |
312 | ifProdClient(
313 | // This is a production client so we will extract our CSS into
314 | // CSS files.
315 | new ExtractTextPlugin({ filename: '[name]-[chunkhash].css', allChunks: true })
316 | ),
317 | ]),
318 | module: {
319 | rules: [
320 | // Javascript
321 | {
322 | test: /\.jsx?$/,
323 | loader: 'babel-loader',
324 | include: [
325 | path.resolve(appRootPath, './src'),
326 | // preact-compat has some es6 requirements. :)
327 | path.resolve(appRootPath, './node_modules/preact-compat'),
328 | ],
329 | query: {
330 | presets: [
331 | // JSX
332 | 'react',
333 | // All the latest JS goodies, except for ES6 modules which
334 | // webpack has native support for and uses in the tree shaking
335 | // process.
336 | // TODO: When babel-preset-latest-minimal has stabilised use it
337 | // for our node targets so that only the missing features for
338 | // our respective node version will be transpiled.
339 | ['latest', { modules: false }],
340 | ],
341 | },
342 | },
343 |
344 | // JSON
345 | {
346 | test: /\.json$/,
347 | loader: 'json-loader',
348 | },
349 |
350 | // Images and Fonts
351 | {
352 | test: /\.(jpg|jpeg|png|gif|ico|eot|svg|ttf|woff|woff2|otf)$/,
353 | loader: 'url-loader',
354 | query: {
355 | // Any file with a byte smaller than this will be "inlined" via
356 | // a base64 representation.
357 | limit: 10000,
358 | // We only emit files when building a client bundle, for the server
359 | // bundles this will just make sure any file imports will not fall
360 | // over.
361 | emitFile: isClient,
362 | },
363 | },
364 |
365 | // CSS
366 | merge(
367 | { test: /\.css$/ },
368 | // When targetting the server we use the "/locals" version of the
369 | // css loader.
370 | ifNodeTarget({
371 | loaders: [
372 | 'css-loader/locals',
373 | ],
374 | }),
375 | // For a production client build we use the ExtractTextPlugin which
376 | // will extract our CSS into CSS files. The plugin needs to be
377 | // registered within the plugins section too.
378 | ifProdClient({
379 | loader: ExtractTextPlugin.extract({
380 | fallbackLoader: 'style-loader',
381 | loader: 'css-loader',
382 | }),
383 | }),
384 | // For a development client we will use a straight style & css loader
385 | // along with source maps. This combo gives us a better development
386 | // experience.
387 | ifDevClient({
388 | loaders: [
389 | 'style-loader',
390 | { loader: 'css-loader', query: { sourceMap: true } },
391 | ],
392 | })
393 | ),
394 | ],
395 | },
396 | };
397 | }
398 |
399 | module.exports = webpackConfigFactory;
400 |
--------------------------------------------------------------------------------
/tools/webpack/server.config.js:
--------------------------------------------------------------------------------
1 | const webpackConfigFactory = require('./configFactory');
2 |
3 | module.exports = function serverConfigFactory(options = {}, args = {}) {
4 | const { mode = 'production' } = options;
5 | return webpackConfigFactory({ target: 'server', mode }, args);
6 | };
7 |
--------------------------------------------------------------------------------
/tools/webpack/universalMiddleware.config.js:
--------------------------------------------------------------------------------
1 | const webpackConfigFactory = require('./configFactory');
2 |
3 | module.exports = function serverConfigFactory(options = {}, args = {}) {
4 | const { mode = 'production' } = options;
5 | return webpackConfigFactory({ target: 'universalMiddleware', mode }, args);
6 | };
7 |
--------------------------------------------------------------------------------