├── .gitignore
├── LICENSE
├── README.md
├── bin
└── sambell.js
├── package.json
├── scripts
├── build.js
├── run.js
└── watch.js
├── template
├── .gitignore
├── client.js
├── components
│ ├── App.js
│ ├── Moon.js
│ └── Outside.js
├── gerty.js
├── package.json
└── server.js
├── webpack
├── create-config.js
├── handle-stats.js
├── ready-banner.js
├── replace-entry.js
├── sambell-client.js
├── sambell-server.js
├── timer-plugin.js
├── webpack.config.client.dev.js
├── webpack.config.client.prod.js
├── webpack.config.server.dev.js
└── webpack.config.server.prod.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | npm-debug.log
2 | node_modules
3 | build
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright JS Foundation and other contributors
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | 'Software'), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DEPRECATED
2 |
3 | This project is deprecated. Use next.js, etc.
4 |
5 | ---
6 |
7 | # :new_moon::new_moon::new_moon: sambell :new_moon::new_moon::new_moon:
8 | create performant server-rendered React applications with no build configuration; ideal for universal react-router projects.
9 |
10 | - similar to [create-react-app](https://github.com/facebookincubator/create-react-app), but server side rendering.
11 | - similar to [next.js](https://github.com/zeit/next.js), but with [react-router](https://github.com/ReactTraining/react-router).
12 | - `sambell` came first! commit history proof :stuck_out_tongue_closed_eyes:
13 |
14 | Both [create-react-app](https://github.com/facebookincubator/create-react-app) and [next.js](https://github.com/zeit/next.js) are great projects, try them! I like aspects of both. But you don't get a **universal react-router** application out of the box.
15 |
16 | ## What will my app look like?!?
17 |
18 | Check out [the template](template)!
19 |
20 | ## Install
21 |
22 | ```
23 | yarn global add sambell
24 | sambell new app
25 | cd app
26 | yarn start
27 | ```
28 |
29 | ## Features
30 |
31 | **Dev experience**
32 |
33 | - Everything you (or at least, I) want without setting anything up!
34 | - Client side SPA with [react-router](https://github.com/ReactTraining/react-router) **version 4**.
35 | - [styled-jsx](https://github.com/zeit/styled-jsx) is a great feature of Next.js that I bring in here. I find it to be more pleasant than `css-modules`, and eaiser to work with for a universal application (critical styles, etc).
36 |
37 | **Performant**
38 |
39 | - React **16**
40 | - Server side rendering. Universal.
41 | - Critical styles with [styled-jsx](https://github.com/zeit/styled-jsx).
42 | - Async loading of routes with `react-loadable` (forked version `@humblespark/react-loadable`).
43 | - Async (``) loading of all webpack scripts.
44 | - Webpack build optimized for production.
45 |
46 | **Async components**
47 |
48 | - Full client & server side support for async loading components, with `react-loadable`
49 | - Forked version (`@humblespark/react-loadable`) to work with server side webpack build & a fix for checksum mismatch.
50 |
51 | ```
52 | const Moon = Loadable(() => import(/* webpackChunkName: "components/Moon" */'components/Moon'));
53 | ```
54 |
55 | **Webpack / Babel**
56 |
57 | - Webpack 2 (code splitting, tree shaking, etc).
58 | - Webpack runs for **both** client and server code.
59 | - Minimal loaders (only a JS loader). But it is configurable if you want to add more.
60 | - **absolute path** requires from your project root. `import App from 'App'`.
61 | - Sourcemaps for client & server.
62 | - Babel Presets: es2015, stage-1, react
63 | - Babel Plugins: [styled-jsx](https://github.com/zeit/styled-jsx)
64 | - Polyfills: `isomorphic-fetch`, `babel-polyfill`
65 |
66 | **Configurable**
67 |
68 | \**gerty.js* (basic configuration to control where stuff goes)
69 |
70 | ```javascript
71 | module.exports = {
72 | clientEntry: 'client',
73 | serverEntry: 'server',
74 | clientOutputDirectory: '.sambell/client',
75 | serverOutputDirectory: '.sambell/server',
76 | publicPath: '/static/webpack/',
77 | webpack: config => config,
78 | };
79 | ```
80 |
81 | :rocket: -> :no_entry_sign: :earth_americas:
82 |
83 | :alien:
84 |
--------------------------------------------------------------------------------
/bin/sambell.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | const minimist = require('minimist');
3 | const argv = minimist(process.argv.slice(2));
4 | const fs = require('fs');
5 | const ncp = require('ncp').ncp;
6 | const chalk = require('chalk');
7 | const { _: [command] } = argv;
8 | const packageJson = require('./../package.json');
9 |
10 | const spawn = require('child_process').spawn;
11 | const path = require('path');
12 |
13 | if (command === 'run') {
14 | spawn('node', [path.resolve(__dirname, '..', 'scripts/run.js')], {
15 | stdio: 'inherit',
16 | });
17 | } else if (command === 'build') {
18 | spawn('node', [path.resolve(__dirname, '..', 'scripts/build.js')], {
19 | stdio: 'inherit',
20 | });
21 | } else if (command === 'watch') {
22 | spawn('node', [path.resolve(__dirname, '..', 'scripts/watch.js')], {
23 | stdio: 'inherit',
24 | });
25 | } else if (command === 'new') {
26 | console.log(chalk.green('Cloning...'));
27 | const { _: [, dest] } = argv;
28 | const finalDest = path.resolve(process.cwd(), dest);
29 | ncp(path.resolve(__dirname, '..', 'template'), finalDest, function(err) {
30 | if (err) return console.error(err);
31 | try {
32 | fs.renameSync(
33 | path.resolve(finalDest, '.npmignore'),
34 | path.resolve(finalDest, '.gitignore'),
35 | );
36 | } catch (e) {} // if no .npmignore, already .gitignore
37 | process.chdir(finalDest);
38 | spawn('yarn', ['install'], { stdio: 'inherit' });
39 | });
40 | } else if (!command && (argv.v || argv.version)) {
41 | console.log(chalk.cyan(`sambell ${packageJson.version}`));
42 | } else {
43 | console.log(chalk.red('Valid commands: run; build; watch; new'));
44 | }
45 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sambell",
3 | "version": "8.0.0",
4 | "license": "MIT",
5 | "files": ["bin", "scripts", "template", "webpack"],
6 | "homepage": "https://github.com/humblespark/sambell",
7 | "repository": {
8 | "type": "git",
9 | "url": "git+https://github.com/humblespark/sambell.git"
10 | },
11 | "bugs": {
12 | "url": "https://github.com/humblespark/sambell/issues"
13 | },
14 | "bin": {
15 | "sambell": "./bin/sambell.js"
16 | },
17 | "dependencies": {
18 | "babel-core": "6.26.0",
19 | "babel-loader": "7.1.2",
20 | "babel-plugin-import-inspector": "2.0.0",
21 | "babel-plugin-module-resolver": "2.7.1",
22 | "babel-polyfill": "6.26.0",
23 | "babel-preset-es2015": "6.24.1",
24 | "babel-preset-react": "6.24.1",
25 | "babel-preset-stage-1": "6.24.1",
26 | "chalk": "2.2.0",
27 | "filesize": "3.5.11",
28 | "gzip-size": "4.0.0",
29 | "import-inspector": "2.0.0",
30 | "isomorphic-fetch": "2.2.1",
31 | "minimist": "1.2.0",
32 | "ncp": "2.0.0",
33 | "react-dev-utils": "4.1.0",
34 | "source-map-support": "0.5.0",
35 | "styled-jsx": "2.1.2",
36 | "webpack": "3.8.1",
37 | "webpack-node-externals": "1.6.0"
38 | },
39 | "peerDependencies": {
40 | "react": "^16.0.0",
41 | "styled-jsx": "^2.0.0"
42 | },
43 | "tags": ["react", "universal", "starter-kit"],
44 | "keywords": ["react", "universal", "starter-kit"]
45 | }
46 |
--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const webpackClientProdConfig = require('./../webpack/webpack.config.client.prod');
3 | const webpackServerProdConfig = require('./../webpack/webpack.config.server.prod');
4 | const handleWebpackStats = require('./../webpack/handle-stats');
5 | const replaceEntry = require('./../webpack/replace-entry');
6 | const chalk = require('chalk');
7 | const path = require('path');
8 |
9 | var clientEntriesByChunk = '';
10 | const finalize = (
11 | serverEntriesByChunk = null,
12 | _clientEntriesByChunk = null,
13 | ) => {
14 | if (_clientEntriesByChunk) clientEntriesByChunk = _clientEntriesByChunk;
15 | if (!serverEntriesByChunk || !clientEntriesByChunk) {
16 | console.log(
17 | chalk.red('No client/server entry.'),
18 | clientEntriesByChunk,
19 | serverEntriesByChunk,
20 | );
21 | return;
22 | }
23 |
24 | const serverEntry = serverEntriesByChunk.run[0];
25 | const serverPath = path.resolve(
26 | webpackServerProdConfig.output.path,
27 | serverEntry,
28 | );
29 |
30 | replaceEntry(
31 | webpackServerProdConfig,
32 | webpackClientProdConfig,
33 | serverEntriesByChunk,
34 | clientEntriesByChunk,
35 | () => {
36 | console.log(
37 | chalk.green(
38 | `Production version built. Run... ${chalk.bold(
39 | `node ${path.relative(process.cwd(), serverPath)}`,
40 | )}`,
41 | ),
42 | );
43 | console.log('');
44 | },
45 | );
46 | };
47 |
48 | webpack([webpackClientProdConfig, webpackServerProdConfig]).run(
49 | handleWebpackStats(finalize, webpackClientProdConfig),
50 | );
51 |
--------------------------------------------------------------------------------
/scripts/run.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const webpackClientDevConfig = require('./../webpack/webpack.config.client.dev');
3 | const webpackServerDevConfig = require('./../webpack/webpack.config.server.dev');
4 | const handleWebpackStats = require('./../webpack/handle-stats');
5 | const replaceEntry = require('./../webpack/replace-entry');
6 | const spawn = require('child_process').spawn;
7 | const chalk = require('chalk');
8 | const path = require('path');
9 |
10 | var server = null;
11 | var clientEntriesByChunk = '';
12 | const refreshServer = (
13 | serverEntriesByChunk = null,
14 | _clientEntriesByChunk = null,
15 | ) => {
16 | if (_clientEntriesByChunk) clientEntriesByChunk = _clientEntriesByChunk;
17 | if (!serverEntriesByChunk || !clientEntriesByChunk) return;
18 |
19 | const serverEntry = serverEntriesByChunk.run[0];
20 | const serverPath = path.resolve(
21 | webpackServerDevConfig.output.path,
22 | serverEntry,
23 | );
24 |
25 | replaceEntry(
26 | webpackServerDevConfig,
27 | webpackClientDevConfig,
28 | serverEntriesByChunk,
29 | clientEntriesByChunk,
30 | () => {
31 | if (server) server.kill();
32 | console.log(
33 | chalk.green(`${server ? 'Restarting' : 'Starting'} sambell...`),
34 | );
35 | server = spawn('node', [serverPath], {
36 | stdio: 'inherit',
37 | env: process.env,
38 | });
39 | console.log(
40 | chalk.green(
41 | `${chalk.bold('RUN!')} (localhost:${process.env.PORT || 3000})`,
42 | ),
43 | );
44 | },
45 | );
46 | };
47 |
48 | webpack([webpackClientDevConfig, webpackServerDevConfig]).watch(
49 | {},
50 | handleWebpackStats(refreshServer, webpackClientDevConfig),
51 | );
52 |
--------------------------------------------------------------------------------
/scripts/watch.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const webpackClientDevConfig = require('./../webpack/webpack.config.client.dev');
3 | const webpackServerDevConfig = require('./../webpack/webpack.config.server.dev');
4 | const handleWebpackStats = require('./../webpack/handle-stats');
5 | const replaceEntry = require('./../webpack/replace-entry');
6 | const chalk = require('chalk');
7 | const path = require('path');
8 |
9 | var clientEntriesByChunk = '';
10 | const finalize = (
11 | serverEntriesByChunk = null,
12 | _clientEntriesByChunk = null,
13 | ) => {
14 | if (_clientEntriesByChunk) clientEntriesByChunk = _clientEntriesByChunk;
15 | if (!serverEntriesByChunk || !clientEntriesByChunk) return;
16 |
17 | const serverEntry = serverEntriesByChunk.run[0];
18 | const serverPath = path.resolve(
19 | webpackServerDevConfig.output.path,
20 | serverEntry,
21 | );
22 |
23 | replaceEntry(
24 | webpackServerDevConfig,
25 | webpackClientDevConfig,
26 | serverEntriesByChunk,
27 | clientEntriesByChunk,
28 | () => {
29 | console.log(
30 | chalk.green(
31 | `Development version compiled. Run... ${chalk.bold(
32 | `node ${path.relative(process.cwd(), serverPath)}`,
33 | )}`,
34 | ),
35 | );
36 | console.log('');
37 | },
38 | );
39 | };
40 |
41 | webpack([webpackClientDevConfig, webpackServerDevConfig]).watch(
42 | {},
43 | handleWebpackStats(finalize, webpackClientDevConfig),
44 | );
45 |
--------------------------------------------------------------------------------
/template/.gitignore:
--------------------------------------------------------------------------------
1 | npm-debug.log
2 | node_modules
3 | .sambell
4 |
--------------------------------------------------------------------------------
/template/client.js:
--------------------------------------------------------------------------------
1 | import { scriptsReady } from 'sambell/client';
2 |
3 | // @NOTE
4 | // sambellReady tells us all our async chunks have loaded.
5 | // all other files can use es6 module imports. this cant for async module load reasons.
6 | // you likely will not need to touch this file.
7 |
8 | scriptsReady(() => {
9 | const React = require('react');
10 | const { hydrate } = require('react-dom');
11 | const domReady = require('domready');
12 | const { BrowserRouter } = require('react-router-dom');
13 | const App = require('components/App').default;
14 |
15 | domReady(() => {
16 | hydrate(
17 |
Wake me
7 |{`When it's quitting time`}
8 | Go outside. 9 | 20 |