;
19 | }
20 | }
21 | TodoItem.propTypes = {
22 | id: React.PropTypes.string.isRequired,
23 | text: React.PropTypes.string.isRequired,
24 | done: React.PropTypes.bool,
25 | sending: React.PropTypes.bool,
26 | onUpdate: React.PropTypes.func.isRequired
27 | };
28 |
--------------------------------------------------------------------------------
/app/components/README.md:
--------------------------------------------------------------------------------
1 | # components
2 |
3 | > These components are used by `../containers` or by other components.
4 |
5 | A component doesn't do any data fetching and expects data pass via `props`. It represents a reusesable component that can be used in different contexts. A component can have state. A component expects high-level data.
6 |
7 | A component can have styles for layouting. It should not have styles for visual stuff.
8 |
9 | A component is allowed to import the following stuff:
10 | * `components/*`
11 | * `elements/*`
12 |
13 | Don't import:
14 | * `store-helpers/*` -> do it from the container instead and pass data via `props`.
15 | * `actions` -> pass a callback via `props` and call the action in the container instead.
16 | * external styles and components -> wrap the style or component in an element
17 | * `route-handlers/*`
18 |
--------------------------------------------------------------------------------
/Vagrantfile:
--------------------------------------------------------------------------------
1 | # -*- mode: ruby -*-
2 | # vi: set ft=ruby :
3 |
4 | _script = <` to `app/prerender.html`.
136 | 4. Modify the server code to require, serve and prerender the other entry point.
137 | 5. Restart compilation.
138 |
139 | ### Switch devtool to SourceMaps
140 |
141 | Change `devtool` property in `webpack-dev-server.config.js` and `webpack-hot-dev-server.config.js` to `"source-map"` (better module names) or `"eval-source-map"` (faster compilation).
142 |
143 | SourceMaps have a performance impact on compilation.
144 |
145 | ### Enable SourceMaps in production
146 |
147 | 1. Uncomment the `devtool` line in `webpack-production.config.js`.
148 | 2. Make sure that the folder `build\public\debugging` is access controlled, i. e. by password.
149 |
150 | SourceMaps have a performance impact on compilation.
151 |
152 | SourceMaps contains your unminimized source code, so you need to restrict access to `build\public\debugging`.
153 |
154 | ### Coffeescript
155 |
156 | Coffeescript is not installed/enabled by default to not disturb non-coffee developer, but you can install it easily:
157 |
158 | 1. `npm install coffee-redux-loader --save`
159 | 2. In `make-webpack-config.js` add `".coffee"` to the `var extensions = ...` line.
160 |
161 |
162 | ## License
163 |
164 | Copyright (c) 2012-2015 Tobias Koppers [](https://www.gittip.com/sokra/)
165 |
166 | MIT (http://www.opensource.org/licenses/mit-license.php)
167 |
--------------------------------------------------------------------------------
/make-webpack-config.js:
--------------------------------------------------------------------------------
1 | var path = require("path");
2 | var webpack = require("webpack");
3 | var ExtractTextPlugin = require("extract-text-webpack-plugin");
4 | var StatsPlugin = require("stats-webpack-plugin");
5 | var loadersByExtension = require("./config/loadersByExtension");
6 |
7 | module.exports = function(options) {
8 | var entry = {
9 | main: options.prerender ? "./config/mainPrerenderer" : "./config/mainApp"
10 | // second: options.prerender ? "./config/secondPrerenderer" : "./config/secondApp"
11 | };
12 | var loaders = {
13 | "jsx": options.hotComponents ? ["react-hot-loader", "babel-loader?stage=0"] : "babel-loader?stage=0",
14 | "js": {
15 | loader: "babel-loader?stage=0",
16 | include: path.join(__dirname, "app")
17 | },
18 | "json": "json-loader",
19 | "coffee": "coffee-redux-loader",
20 | "json5": "json5-loader",
21 | "txt": "raw-loader",
22 | "png|jpg|jpeg|gif|svg": "url-loader?limit=10000",
23 | "woff|woff2": "url-loader?limit=100000",
24 | "ttf|eot": "file-loader",
25 | "wav|mp3": "file-loader",
26 | "html": "html-loader",
27 | "md|markdown": ["html-loader", "markdown-loader"]
28 | };
29 | var cssLoader = options.minimize ? "css-loader?module" : "css-loader?module&localIdentName=[path][name]---[local]---[hash:base64:5]";
30 | var stylesheetLoaders = {
31 | "css": cssLoader,
32 | "less": [cssLoader, "less-loader"],
33 | "styl": [cssLoader, "stylus-loader"],
34 | "scss|sass": [cssLoader, "sass-loader"]
35 | };
36 | var additionalLoaders = [
37 | // { test: /some-reg-exp$/, loader: "any-loader" }
38 | ];
39 | var alias = {
40 |
41 | };
42 | var aliasLoader = {
43 |
44 | };
45 | var externals = [
46 |
47 | ];
48 | var modulesDirectories = ["web_modules", "node_modules"];
49 | var extensions = ["", ".web.js", ".js", ".jsx"];
50 | var root = path.join(__dirname, "app");
51 | var publicPath = options.devServer ?
52 | "http://localhost:2992/_assets/" :
53 | "/_assets/";
54 | var output = {
55 | path: path.join(__dirname, "build", options.prerender ? "prerender" : "public"),
56 | publicPath: publicPath,
57 | filename: "[name].js" + (options.longTermCaching && !options.prerender ? "?[chunkhash]" : ""),
58 | chunkFilename: (options.devServer ? "[id].js" : "[name].js") + (options.longTermCaching && !options.prerender ? "?[chunkhash]" : ""),
59 | sourceMapFilename: "debugging/[file].map",
60 | libraryTarget: options.prerender ? "commonjs2" : undefined,
61 | pathinfo: options.debug || options.prerender
62 | };
63 | var excludeFromStats = [
64 | /node_modules[\\\/]react(-router)?[\\\/]/,
65 | /node_modules[\\\/]items-store[\\\/]/
66 | ];
67 | var plugins = [
68 | new webpack.PrefetchPlugin("react"),
69 | new webpack.PrefetchPlugin("react/lib/ReactComponentBrowserEnvironment")
70 | ];
71 | if(options.prerender) {
72 | plugins.push(new StatsPlugin(path.join(__dirname, "build", "stats.prerender.json"), {
73 | chunkModules: true,
74 | exclude: excludeFromStats
75 | }));
76 | aliasLoader["react-proxy$"] = "react-proxy/unavailable";
77 | aliasLoader["react-proxy-loader$"] = "react-proxy-loader/unavailable";
78 | externals.push(
79 | /^react(\/.*)?$/,
80 | /^reflux(\/.*)?$/,
81 | "superagent",
82 | "async"
83 | );
84 | plugins.push(new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }));
85 | } else {
86 | plugins.push(new StatsPlugin(path.join(__dirname, "build", "stats.json"), {
87 | chunkModules: true,
88 | exclude: excludeFromStats
89 | }));
90 | }
91 | if(options.commonsChunk) {
92 | plugins.push(new webpack.optimize.CommonsChunkPlugin("commons", "commons.js" + (options.longTermCaching && !options.prerender ? "?[chunkhash]" : "")));
93 | }
94 | var asyncLoader = {
95 | test: require("./app/route-handlers/async").map(function(name) {
96 | return path.join(__dirname, "app", "route-handlers", name);
97 | }),
98 | loader: options.prerender ? "react-proxy-loader/unavailable" : "react-proxy-loader"
99 | };
100 |
101 |
102 |
103 | Object.keys(stylesheetLoaders).forEach(function(ext) {
104 | var stylesheetLoader = stylesheetLoaders[ext];
105 | if(Array.isArray(stylesheetLoader)) stylesheetLoader = stylesheetLoader.join("!");
106 | if(options.prerender) {
107 | stylesheetLoaders[ext] = stylesheetLoader.replace(/^css-loader/, "css-loader/locals");
108 | } else if(options.separateStylesheet) {
109 | stylesheetLoaders[ext] = ExtractTextPlugin.extract("style-loader", stylesheetLoader);
110 | } else {
111 | stylesheetLoaders[ext] = "style-loader!" + stylesheetLoader;
112 | }
113 | });
114 | if(options.separateStylesheet && !options.prerender) {
115 | plugins.push(new ExtractTextPlugin("[name].css" + (options.longTermCaching ? "?[contenthash]" : "")));
116 | }
117 | if(options.minimize && !options.prerender) {
118 | plugins.push(
119 | new webpack.optimize.UglifyJsPlugin({
120 | compressor: {
121 | warnings: false
122 | }
123 | }),
124 | new webpack.optimize.DedupePlugin()
125 | );
126 | }
127 | if(options.minimize) {
128 | plugins.push(
129 | new webpack.DefinePlugin({
130 | "process.env": {
131 | NODE_ENV: JSON.stringify("production")
132 | }
133 | }),
134 | new webpack.NoErrorsPlugin()
135 | );
136 | }
137 |
138 | return {
139 | entry: entry,
140 | output: output,
141 | target: options.prerender ? "node" : "web",
142 | module: {
143 | loaders: [asyncLoader].concat(loadersByExtension(loaders)).concat(loadersByExtension(stylesheetLoaders)).concat(additionalLoaders)
144 | },
145 | devtool: options.devtool,
146 | debug: options.debug,
147 | resolveLoader: {
148 | root: path.join(__dirname, "node_modules"),
149 | alias: aliasLoader
150 | },
151 | externals: externals,
152 | resolve: {
153 | root: root,
154 | modulesDirectories: modulesDirectories,
155 | extensions: extensions,
156 | alias: alias
157 | },
158 | plugins: plugins,
159 | devServer: {
160 | stats: {
161 | cached: false,
162 | exclude: excludeFromStats
163 | }
164 | }
165 | };
166 | };
167 |
--------------------------------------------------------------------------------
/NOTES/HowStuffWorks.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | *An arrow means: `imports`*
4 |
5 | # How Stuff Works
6 | This documentation is for grasping the overall design. No details by intention. To get up and running quickly see `README.md`.
7 |
8 | At first sight, it may not be immediately clear why/how certain things are called/triggered.
9 | The below explanation points you in the right direction, so you can research further. If you want more details or documentation, see the relevant package.
10 | Some (inconspicuously) used/relevant node modules are explicitly mentioned with package name.
11 |
12 | ## Table of contents
13 |
14 | * [How the app is served.](#how-the-app-is-served)
15 | * [development mode](#development-mode)
16 | * [production mode](#production-mode)
17 | * [How the page updates while you are programming.](#how-the-page-updates-while-you-are-programming)
18 | * [page reloading](#page-reloading)
19 | * [hot reloading](#hot-reloading)
20 | * [How the routes work.](#how-the-routes-work)
21 | * [How the server JSON API works.](#how-the-server-json-api-works)
22 | * [How the stores work.](#how-the-stores-work)
23 | * [Stores setup](#stores-setup)
24 | * [Stores in use](#stores-in-use)
25 | * [How the actions work.](#how-the-actions-work)
26 | * [How the server DB works.](#how-the-server-db-works)
27 | * [How the 'Random fail!' works.](#how-the-random-fail-works)
28 | * [How the build works.](#how-the-build-works)
29 | * [Q&A Why](#qa-why)
30 |
31 | *****
32 |
33 | ## How the app is served.
34 | A JS webserver is included in the form of `lib/server.js`. It can be run in two modes:
35 |
36 | ### development mode
37 | Run the server using `npm run start-dev`.
38 | It will use `lib/server-development.js` which will use `lib/server.js` which will use `config/simple.js` which will use `app/simple.html`. A tiny HTML file is loaded, the JS is downloaded + executed + rendered, and ultimately output is shown.
39 |
40 | ### production mode
41 | In this mode, the React HTML output is (pre)rendered (and populated) on the server (a.k.a. isomorphic).
42 |
43 | Run the server using `npm run start`.
44 | It will use `lib/server-production.js` which will use `lib/server.js` which will use `config/mainPrerenderer.jsx` which will use `app/mainPrerender.html`. A big HTML file is loaded (and output is shown immediately), the JS is downloaded + executed + rendered, and output is updated.
45 |
46 | This server side render (prerender) is possible because the React JS can be executed on the server.
47 | In your browser the main `React.render(, document.getElementById("content"))` call (in `config/mainApp.jsx`) outputs to the browser DOM.
48 | But when pre-rendering, the main `React.renderToString()` call (in `app/prerender.jsx`) outputs to a string, which is inserted into the HTML (including React component states) as content.
49 | The browser now can show the HTML instantly in the DOM, but proceeds to run the React JS that resumes the usual DOM mutations.
50 |
51 | Note: Routes that are asynchronously loaded can not be pre-rendered.
52 |
53 |
54 | ## How the page updates while you are programming.
55 | After running the app webserver in development mode (see above) you'd have to manually reload the page after changing a JSX file.
56 | It is possible to automatically reload _or_ update the page to reflect your changes:
57 |
58 | ### page reloading
59 | Ensure that you are running the app webserver in development mode.
60 |
61 | Then run another server using `npm run dev-server`.
62 | It will rebuild while watching for file changes, and it will trigger your page to reload afterwards.
63 |
64 | ### hot reloading
65 | Ensure that you are running the app webserver in development mode.
66 |
67 | Then run another server using `npm run hot-dev-server`.
68 | It will rebuild while watching for file changes, and it will update the currently shown and affected component(s) while keeping their state.
69 | Note this is experimental, and in some cases you'll need to refresh manually.
70 |
71 |
72 | ## How the routes work.
73 | In React after opening the app and going to some page, there is no actual HTML loaded from the server. React app just replaces a component that acts as a page, including any child components.
74 | But you'd like to have the URL reflect this, and allow user to use browser history (back/forward). A router takes care of these things. (package react-router)
75 |
76 | In this case, the root of your app is not the Application React component.
77 | This starts at `lib/server.js` which will use `config/mainApp.jsx` which instantiates the router and ultimately uses `app/mainRoutes.jsx` to load routes.
78 | You'll find that all pages are subroutes within the `app` route, which instantiates `app/Application/index.jsx`, which contains a `` component that inserts subroute output.
79 |
80 |
81 | ## How the server JSON API works.
82 | The `lib/server.js` serves the application, but it also serves the API URLs (`lib/api.js`) that the stores talk with.
83 | It initializes two databases (todolist and todoitem) once, and then continues to listen for GET/POST requests on specific URLs.
84 |
85 |
86 | ## How the stores work.
87 | As in Flux; the Stores affect the React components. (package `items-store`) And the stores talk to the JSON API (`app/fetch-helpers/rest.js`).
88 | See [Q&A Why](#qa-why)
89 |
90 | ### Stores setup
91 | The stores are constructed as such: startpoint is `config/mainApp.jsx` which will use `app/mainStores.js`. This then:
92 |
93 | - defines routines to handle JSON API read/writes (package `superagent`), and
94 | - sets up a queue that only allows one REST request at a time, and aggregates subsequent changes.
95 | These are then sent as one request..
96 | - and ultimately constructs the two respective `ItemStore` objects (based on `app/mainStoresDescriptions.js`) to which it assigns these routines, queue, and the initial data. This initial data may have been inserted via `app/prerender.html` and `app/`.
97 | - These are then exported back to `config/mainApp.jsx`, along with a store named `Router`.
98 |
99 | The store `Router` just holds a `transition` value which is later read in `app/containers/Application.jsx` to show "loading..." in UI.
100 | The stores `TodoList` and `TodoItem` only have minor differences.
101 |
102 | If you wonder where the default data is inserted, see (#how-the-server-db-works).
103 |
104 | ### Stores in use
105 |
106 | todo :question: this section needs review and should be extended.
107 |
108 | Note: Components should not access the stores except for reading in `getProps()`. Everything else should be done with actions.
109 |
110 |
111 | ## How the actions work.
112 | The Actions affect the stores. (package items-store)
113 |
114 | The actions are setup in `app/mainStores.js` (bottom) from `app/actions.js` which uses the implementation supplied with items-store.
115 | They are triggered/made by the input fields in containers, such as `app/containers/TodoListPage.jsx`.
116 | They end up affecting a store. See [How the stores work.](#how-the-stores-work)
117 |
118 |
119 | ## How the server DB works
120 | When you run `npm run start-dev` (or without `-dev` ofcourse) this will start the server, as you can see defined in `package.json`. This lets node execute `lib/server-development.js` which uses `lib/server.js` where the default data is loaded, and a server (package express) is thrown together that responds to GET POST and DELETE.
121 |
122 | This (REST API with JSON data format) server is accessible via `http://localhost:8080/_/list/mylist` for example, and this is what the application uses to fetch data for the stores.
123 |
124 |
125 | ## How the 'Random fail!' works.
126 | This [Chaos Monkey](https://github.com/Netflix/SimianArmy/wiki) lives in `lib/server.js` and helps you experience realistic server-client retrieval times and errors while developing.
127 | At some time your application is requesting 3 things from the server, and they return in the wrong order and incomplete. Will it break?
128 | Or a form could not be sent to the server. Will it notify the user?
129 |
130 |
131 | ## How the build works.
132 | A build can compile your JS files into one big file, while applying some optimisations (to make it smaller, faster, or obfuscated).
133 | Its also used to add files and features (otherwise not supported by JS in the browser) to your project, such as `JSX`, `markdown`, or `SASS`.
134 |
135 | When you start run a dev-server (like `npm run dev-server` or `npm run hot-dev-server`) it does the builds for you, while it watches for changing files.
136 | Or you can manually do a normal build using the `npm run build`. Note: these `npm run` scripts are defined in `package.json`.
137 |
138 | Depending on what way you used to do this build, a different build configuration is used. For example the normal build script as seen in `package.json` starts webpack with custom config `webpack-production.config.js` which uses shared config `make-webpack-config.js`.
139 | Most webpack configuration is in that shared config file, per entry. Only one main entry is defined. Pre-rendering happens depending on the custom config.
140 | This is where a lot of node modules (packages) come into play: loaders add JSX support, debug options are set, and the output is set to `build/` is set.
141 |
142 | # Q&A Why
143 | **Design choices explained.**
144 | Ok, so now you know the how. But why do it that way? Questions about design choices, answered by the author(s). (Tobias Koppers)
145 |
146 | **(interim, this document should only concern the How question) :exclamation: TODO extract Q&A info and document it.**
147 |
148 | What is the argument for using items-store? (from https://github.com/webpack/react-starter/pull/49 )
149 | > Q: What was the argument for using items-store?
150 | > A:
151 | >
152 | > I didn't want to write a new module. I actually tried using Reflux.js, but couldn't find a good workflow for pre-rendering, optimistic updates and merging of multiple writes. You could do it manually but that is something I would expect from a flux implementation. items-store is a very simple store base class that coordinate this behavior (the repo actually also contains a simple helper for actions and a react mixin, but these are theoretically independent and you don't have to use them with the store).
153 | >
154 | > items-store allows to serialize and deserialize the store data to inject the data after pre-rendering. It manages optimistic updates and merges multiple writes. But items-store only offers a simple key-value store API and forces you to map more complex operations (via store-helpers) to this model. It's just a caching layer with events to the remote API (which need to be provided in the constructor).
155 | >
156 | > items-store basically provides a "raw data" store, while other implementations give you the ability to write higher level stores. In items-store you need to put this higher-level behavior in store-helpers. The advantage is that this way basic functionality is already provided by items-store.
157 | >
158 | > Now there is an alternative to items-store, which could provide "pre-rendering" part too: http://fluxible.io/ they use dehydrate and rehydrate to (de)serialize to data from the stores and provide it to the client stores.
159 |
160 | Regarding the paths that store data travels (from https://github.com/webpack/react-starter/pull/51 )
161 | > Q: How is it triggered to refresh everything from the server,
162 | > A: `config/mainApp.jsx` invalidates store data when you change the page. When the pages read the data again it is invalid and will be refetched.
163 |
164 | > Q: how does it propagate changes when the user edits items, and
165 | > A: The component fires an action (from `app/actions.jsx`) on edit. The action is handled in `app/mainStores.jsx` and writes some items on stores. The stores update their internal cache (which is displayed on the page as optimistic update) and do server requests (through their registered handlers in `app/mainStores.jsx`.
166 |
167 | > Q: how do values travel before ending up in some components render() ?
168 | > A: component requests values from store in getProps() -> `app/mainStores.jsx` read (unavailable or invalidated) data from server -> internal `items-store` cache for the store -> getProps() -> components this.props -> render()
169 |
170 | > Q: how does the queue combine multiple requests? I cant imagine subsequent add/remove/edits would result in just one rest call.. do they?
171 | > A: items-store store a single update per entry. Any subsequent updateItem() call causes both updates to be merged (mergeUpdates from items-store). Requests from stores to the server are queued (queueRequest in app/mainStores.jsx). Here only a single ongoing request is allowed. All writes that happen in the meantime are merged into a single write.
172 |
--------------------------------------------------------------------------------