├── .editorconfig ├── .gitignore ├── README.md ├── assets ├── css │ ├── bootstrap-theme.min.css │ └── bootstrap.min.css ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 └── js │ ├── actions │ └── attractionActionCreators.js │ ├── app.jsx │ ├── components │ ├── App.jsx │ ├── analytics │ │ └── Chart.jsx │ ├── attractions │ │ ├── AttractionItem.jsx │ │ ├── AttractionList.jsx │ │ ├── AttractionListItem.css │ │ ├── AttractionListItem.jsx │ │ ├── AttractionPaginator.jsx │ │ └── images │ │ │ ├── arc-de-triomphe.jpg │ │ │ ├── champs-elysees.jpg │ │ │ ├── eiffel-tower.jpg │ │ │ ├── invalides.jpg │ │ │ ├── louvres.jpg │ │ │ ├── musee-d-orsay.jpg │ │ │ ├── notre-dame.jpg │ │ │ ├── palais-garnier.jpg │ │ │ ├── sacre-coeur.jpg │ │ │ └── sainte-chapelle.jpg │ ├── homepage │ │ ├── HeadingItem.css │ │ ├── HeadingItem.jsx │ │ ├── Jumbo.css │ │ ├── Jumbo.jsx │ │ └── images │ │ │ ├── background.jpg │ │ │ ├── paris.svg │ │ │ ├── paris6.svg │ │ │ └── paris8.svg │ ├── login │ │ ├── Login.css │ │ └── Login.jsx │ ├── pages │ │ ├── AnalyticsPage.jsx │ │ ├── ItemPage.jsx │ │ └── ListPage.jsx │ ├── paginator │ │ └── Paginator.jsx │ ├── plans │ │ ├── Plan.jsx │ │ └── Plans.jsx │ └── shared │ │ ├── AppWrapper.jsx │ │ ├── BootstrapCss.js │ │ ├── Footer.css │ │ ├── Footer.jsx │ │ ├── Navigation.jsx │ │ └── SignupButton.jsx │ ├── constants │ ├── attractionActions.js │ ├── conf.js │ └── pagination.js │ ├── dispatcher │ └── appDispatcher.js │ ├── homepage.jsx │ ├── login.jsx │ ├── mixins │ └── PaginatorMixin.jsx │ ├── plans.jsx │ ├── routes │ └── routes.jsx │ ├── services │ ├── attractionClient.js │ └── clients │ │ └── localStorageClient.js │ └── stores │ └── attractionStore.js ├── package.json ├── server ├── lib │ └── server-creator.js ├── server-cdn.js ├── server-dev-server.js ├── server-dev.js └── views │ └── layout.jade ├── tests └── fixtures │ └── attractions.js ├── webpack-build.config.js ├── webpack-dev-server.config.js ├── webpack-dev.config.js └── webpack └── base-config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.{md,markdown}] 13 | trim_trailing_whitespace = false 14 | 15 | [*.yml] 16 | indent_style = space 17 | indent_size = 2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | dist 4 | node_modules 5 | server/cloud 6 | server/dist 7 | server/assets.json 8 | stats.json 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Paris - Sample Application build with React and Webpack 2 | 3 | ## Installation 4 | 5 | ``` 6 | https://github.com/theasta/paris-webpack-react.git 7 | cd paris-webpack-react 8 | npm install 9 | ``` 10 | 11 | ## Demos 12 | 13 | ### Demo 1 - webpack watch 14 | 15 | 16 | 1. Build and start watching files with Webpack: `npm run webpack-dev` 17 | 2. Spin up the node.js backend server: `node server/server-dev.js` 18 | 3. Go to http://localhost:3000 19 | 4. Open homepage.jsx and replace `iconEiffelTower` in `` (line 21) by `iconNotreDame` 20 | 5. In your terminal, you can see the homepage bundle was re-emitted. Nothing changed for the other ones. 21 | 6. Reload the homepage. The second eiffel tower icon has been replaced by a Notre Dame icon. 22 | 23 | Exit the webpack task 24 | 25 | ### Demo 2 - Common Chunks 26 | 27 | app.js 1884285 0 [emitted] app 28 | homepage.js 1132531 1 [emitted] homepage 29 | plans.js 1124744 2 [emitted] plans 30 | login.js 1124834 3 [emitted] login 31 | 32 | 1. `npm run webpack-stats` 33 | 2. Check at the root of the repository. A stats.json file has been generated. 34 | 3. Open http://webpack.github.io/analyse/ , click on choose file and browse to the stats.json file 35 | 4. Go to the hints section. You can see a lot of files are shared across the 4 bundles. You can also see that the Signup.jsx is shared by only 2 bundles. 36 | 5. Open webpack-dev.config.js 37 | 6. Set the commonsChunk property to true 38 | 7. `npm run webpack-dev` 39 | 40 | A new bundle has been created: common.js. It contains all the files common to the 4 bundles 41 | 42 | 8. Edit the webpack task 43 | 9. Uncomment the commonschunkMin attribute in webpack-dev.config.js. It tells webpack that every module that is shared by at least 2 bundles has to be sent to the common bundle. 44 | 10. `npm run webpack-dev` 45 | 46 | The plans and homepage bundles are now smaller. The Signup.jsx has been moved to common.js 47 | 48 | Exit the webpack task 49 | 50 | 51 | Exit the webpack-dev-task 52 | 53 | ### Demo 3 - Code Splitting 54 | 55 | 1. Open webpack-dev.config.js 56 | 2. Set the CODE_SPLITTING to true. This will set a variable CODE_SPLITTING that acts like a feature flag. This variable is used in routes.jsx 57 | 3. `npm run webpack-dev` 58 | 4. Open Chrome's network panel 59 | 5. Go to http://localhost:3000/app. Two javascript files are loaded: common.js and app.js 60 | 6. Click on the Analytics button in the navigation bar at the top. 61 | 7. The analytics bundle (section_0.js) is loaded asynchronously and appears in the network panel. 62 | 63 | 64 | Exit the webpack-dev task and spin down the node.js backend server. 65 | 66 | ### Demo 4 - Assets Versioning 67 | 68 | Let's pretend we are releasing to production and uploading our static assets to a CDN 69 | 70 | 1. `npm run build` 71 | 2. Go to http://localhost:3000 72 | 3. Open the network panel and reload the page 73 | 4. All the assets are versioned: css, js and even images and fonts. Images referenced in CSS have also been versioned. 74 | 75 | Note: The publicPath option in the webpack configuration lets you set the path to your CDN. 76 | 77 | Exit the npm task 78 | 79 | ### Demo 5 - webpack-dev-server and Hot Module Replacement 80 | 81 | 1. `npm run webpack-dev-server` 82 | 2. `node server/server-dev-server.js` 83 | 3. Go to http://localhost:3000 84 | 4. Open homepage.jsx, replace "Best of Paris" by "Best of Paname". Save the file. 85 | On save, the browser reloads automatically the homepage and your change is applied. 86 | 5. Go to http://localhost:3000/app 87 | 6. Open AttractionListItem.jsx and replace "

{this.props.attraction.name}

" by "

Visit {this.props.attraction.name}

". 88 | This time, on save, your change is reflected right away without a page reload. 89 | 7. It also works with any file that is required with Webpack. Make a change to AttractionListItem.css and it will be applied right away too. 90 | -------------------------------------------------------------------------------- /assets/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.2 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default:disabled,.btn-default[disabled]{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary:disabled,.btn-primary[disabled]{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success:disabled,.btn-success[disabled]{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info:disabled,.btn-info[disabled]{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning:disabled,.btn-warning[disabled]{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger:disabled,.btn-danger[disabled]{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} -------------------------------------------------------------------------------- /assets/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theasta/paris-webpack-react/f0e5ab632db5d2f312637a8ec5e2288cd7b00650/assets/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /assets/fonts/glyphicons-halflings-regular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | -------------------------------------------------------------------------------- /assets/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theasta/paris-webpack-react/f0e5ab632db5d2f312637a8ec5e2288cd7b00650/assets/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /assets/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theasta/paris-webpack-react/f0e5ab632db5d2f312637a8ec5e2288cd7b00650/assets/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /assets/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theasta/paris-webpack-react/f0e5ab632db5d2f312637a8ec5e2288cd7b00650/assets/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /assets/js/actions/attractionActionCreators.js: -------------------------------------------------------------------------------- 1 | var appDispatcher = require('../dispatcher/appDispatcher'); 2 | var attractionConstants = require('../constants/attractionActions'); 3 | var attractionClient = require('../services/attractionClient'); 4 | var attractionStore = require('../stores/attractionStore'); 5 | 6 | module.exports = { 7 | loadAll: function () { 8 | appDispatcher.dispatch({ 9 | type: attractionConstants.LOAD 10 | }); 11 | attractionClient.loadAll() 12 | .then( 13 | function (data) { 14 | appDispatcher.dispatch({ 15 | type: attractionConstants.LOAD_SUCCESS, 16 | response: data 17 | }); 18 | }, 19 | function (err) { 20 | appDispatcher.dispatch({ 21 | type: attractionConstants.LOAD_ERROR, 22 | error: err 23 | }); 24 | } 25 | ); 26 | }, 27 | loadOne: function (attractionId) { 28 | appDispatcher.dispatch({ 29 | type: attractionConstants.LOAD_ATTRACTION, 30 | attractionId: attractionId 31 | }); 32 | 33 | // Is it already in the store? 34 | var attraction = attractionStore.getById(attractionId); 35 | if (attraction) { 36 | this.handleLoadAttractionSuccess(attraction); 37 | } else { 38 | // otherwise request it 39 | attractionClient.loadOne(attractionId) 40 | .then( 41 | this.handleLoadAttractionSuccess.bind(this), 42 | function (err) { 43 | appDispatcher.dispatch({ 44 | type: attractionConstants.LOAD_ATTRACTION_ERROR, 45 | error: err 46 | }); 47 | } 48 | ); 49 | } 50 | }, 51 | handleLoadAttractionSuccess: function (data) { 52 | appDispatcher.dispatch({ 53 | type: attractionConstants.LOAD_ATTRACTION_SUCCESS, 54 | response: data 55 | }); 56 | } 57 | }; 58 | 59 | -------------------------------------------------------------------------------- /assets/js/app.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Router = require('react-router'); 3 | var routes = require('./routes/routes'); 4 | 5 | Router.run(routes, function (Handler) { 6 | React.render(, document.body); 7 | }); 8 | -------------------------------------------------------------------------------- /assets/js/components/App.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var { RouteHandler } = require('react-router'); 3 | var { Grid, Row } = require('react-bootstrap'); 4 | var ParisApp = require('./shared/AppWrapper.jsx'); 5 | 6 | var attractionActions = require('../actions/attractionActionCreators'); 7 | attractionActions.loadAll(); 8 | 9 | module.exports = React.createClass({ 10 | render: function() { 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | }); -------------------------------------------------------------------------------- /assets/js/components/analytics/Chart.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var LineChart = require('react-d3').LineChart; 3 | 4 | module.exports = React.createClass({ 5 | render: function () { 6 | var lineData = [ 7 | { 8 | name: 'series1', 9 | values: [ { x: 0, y: 20 }, { x: 1, y: 30 }, { x: 2, y: 10 }, { x: 3, y: 5 }, { x: 4, y: 8 }, { x: 5, y: 15 }, { x: 6, y: 10 } ] 10 | }, 11 | { 12 | name: 'series2', 13 | values : [ { x: 0, y: 8 }, { x: 1, y: 5 }, { x: 2, y: 20 }, { x: 3, y: 12 }, { x: 4, y: 4 }, { x: 5, y: 6 }, { x: 6, y: 2 } ] 14 | }, 15 | { 16 | name: 'series3', 17 | values: [ { x: 0, y: 0 }, { x: 1, y: 5 }, { x: 2, y: 8 }, { x: 3, y: 2 }, { x: 4, y: 6 }, { x: 5, y: 4 }, { x: 6, y: 2 } ] 18 | } 19 | ]; 20 | 21 | return ( 22 | 27 | ); 28 | } 29 | }); 30 | 31 | -------------------------------------------------------------------------------- /assets/js/components/attractions/AttractionItem.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var { Navigation } = require('react-router'); 3 | var { Row, Col, Well } = require('react-bootstrap'); 4 | var attractionStore = require('../../stores/attractionStore'); 5 | var attractionActions = require('../../actions/attractionActionCreators'); 6 | 7 | 8 | module.exports = React.createClass({ 9 | mixins: [ Navigation ], 10 | getInitialState: function () { 11 | return {}; 12 | }, 13 | componentDidMount: function() { 14 | attractionStore.addChangeListener(this._onChange); 15 | attractionActions.loadOne(this.props.attractionId); 16 | }, 17 | componentWillUnmount: function() { 18 | attractionStore.removeChangeListener(this._onChange); 19 | }, 20 | _onChange: function () { 21 | this.setState(this.getStateFromStore()); 22 | }, 23 | /* ----- ----- */ 24 | getStateFromStore: function () { 25 | return attractionStore.getById(this.props.attractionId); 26 | }, 27 | render: function () { 28 | if (this.state.name == undefined) { 29 | return(
); 30 | } 31 | var picture = require('./images/' + this.state.picture); 32 | return ( 33 |
34 |

{this.state.name}

35 | 36 | 37 |

{this.state.type}

38 | {this.state.description} 39 |

Wikipedia

40 |
41 | 42 | 43 | {this.state.name}/ 44 |
45 | 46 |
47 | ); 48 | } 49 | }); 50 | -------------------------------------------------------------------------------- /assets/js/components/attractions/AttractionList.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var AttractionListItem = require('./AttractionListItem'); 3 | var attractionStore = require('../../stores/attractionStore'); 4 | var PaginatorMixin = require('../../mixins/PaginatorMixin'); 5 | var paginationConf = require('../../constants/pagination'); 6 | 7 | module.exports = React.createClass({ 8 | mixins: [ 9 | PaginatorMixin 10 | ], 11 | getDefaultProps: function() { 12 | return paginationConf; 13 | }, 14 | getInitialState: function() { 15 | return this.getStateFromStore(); 16 | }, 17 | _onChange: function () { 18 | this.setState(this.getStateFromStore()); 19 | }, 20 | componentDidMount: function() { 21 | attractionStore.addChangeListener(this._onChange); 22 | }, 23 | componentWillUnmount: function() { 24 | attractionStore.removeChangeListener(this._onChange); 25 | }, 26 | getStateFromStore: function () { 27 | return { 28 | attractions: attractionStore.getAll() 29 | }; 30 | }, 31 | render: function () { 32 | var ListItems = this.paginateItems(this.state.attractions).map(function (attraction) { 33 | return ( 34 | 38 | ); 39 | }); 40 | return ( 41 |
    42 | {ListItems} 43 |
44 | ); 45 | } 46 | }); -------------------------------------------------------------------------------- /assets/js/components/attractions/AttractionListItem.css: -------------------------------------------------------------------------------- 1 | .thumbnail { 2 | min-width: 260px; 3 | text-align: center; 4 | } 5 | 6 | .thumbnail img { 7 | width: 250px; 8 | height: 250px; 9 | } 10 | 11 | .list-inline { 12 | margin: 0; 13 | } 14 | 15 | .list-inline > li { 16 | padding-right: 26px; 17 | } 18 | 19 | h4 { 20 | width: 100%; 21 | white-space: nowrap; 22 | margin-bottom: 10px; 23 | } 24 | 25 | a.thumbnail:link, a.thumbnail:visited, a.thumbnail:hover, a.thumbnail:active { 26 | color: #5e5e5e; 27 | text-decoration: none; 28 | } 29 | -------------------------------------------------------------------------------- /assets/js/components/attractions/AttractionListItem.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var { Link } = require('react-router'); 3 | var { Glyphicon } = require('react-bootstrap'); 4 | 5 | require('./AttractionListItem.css'); 6 | 7 | module.exports = React.createClass({ 8 | propTypes: { 9 | attraction: React.PropTypes.object.isRequired 10 | }, 11 | render: function () { 12 | var picture = require('./images/'+ this.props.attraction.picture); 13 | return ( 14 |
  • 15 | 16 | {this.props.attraction.name} 17 |

    {this.props.attraction.name}

    18 | 19 |
  • 20 | ); 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /assets/js/components/attractions/AttractionPaginator.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Paginator = require('../paginator/Paginator'); 3 | var wineStore = require('../../stores/attractionStore'); 4 | 5 | module.exports = React.createClass({ 6 | getInitialState: function() { 7 | return this.getStateFromStore(); 8 | }, 9 | _onChange: function () { 10 | this.setState(this.getStateFromStore()); 11 | }, 12 | getStateFromStore: function () { 13 | return { 14 | totalItems: wineStore.getTotalCount() 15 | }; 16 | }, 17 | componentDidMount: function() { 18 | wineStore.addChangeListener(this._onChange); 19 | }, 20 | componentWillUnmount: function() { 21 | wineStore.removeChangeListener(this._onChange); 22 | }, 23 | render: function () { 24 | return ( 25 | 26 | ); 27 | } 28 | }); -------------------------------------------------------------------------------- /assets/js/components/attractions/images/arc-de-triomphe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theasta/paris-webpack-react/f0e5ab632db5d2f312637a8ec5e2288cd7b00650/assets/js/components/attractions/images/arc-de-triomphe.jpg -------------------------------------------------------------------------------- /assets/js/components/attractions/images/champs-elysees.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theasta/paris-webpack-react/f0e5ab632db5d2f312637a8ec5e2288cd7b00650/assets/js/components/attractions/images/champs-elysees.jpg -------------------------------------------------------------------------------- /assets/js/components/attractions/images/eiffel-tower.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theasta/paris-webpack-react/f0e5ab632db5d2f312637a8ec5e2288cd7b00650/assets/js/components/attractions/images/eiffel-tower.jpg -------------------------------------------------------------------------------- /assets/js/components/attractions/images/invalides.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theasta/paris-webpack-react/f0e5ab632db5d2f312637a8ec5e2288cd7b00650/assets/js/components/attractions/images/invalides.jpg -------------------------------------------------------------------------------- /assets/js/components/attractions/images/louvres.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theasta/paris-webpack-react/f0e5ab632db5d2f312637a8ec5e2288cd7b00650/assets/js/components/attractions/images/louvres.jpg -------------------------------------------------------------------------------- /assets/js/components/attractions/images/musee-d-orsay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theasta/paris-webpack-react/f0e5ab632db5d2f312637a8ec5e2288cd7b00650/assets/js/components/attractions/images/musee-d-orsay.jpg -------------------------------------------------------------------------------- /assets/js/components/attractions/images/notre-dame.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theasta/paris-webpack-react/f0e5ab632db5d2f312637a8ec5e2288cd7b00650/assets/js/components/attractions/images/notre-dame.jpg -------------------------------------------------------------------------------- /assets/js/components/attractions/images/palais-garnier.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theasta/paris-webpack-react/f0e5ab632db5d2f312637a8ec5e2288cd7b00650/assets/js/components/attractions/images/palais-garnier.jpg -------------------------------------------------------------------------------- /assets/js/components/attractions/images/sacre-coeur.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theasta/paris-webpack-react/f0e5ab632db5d2f312637a8ec5e2288cd7b00650/assets/js/components/attractions/images/sacre-coeur.jpg -------------------------------------------------------------------------------- /assets/js/components/attractions/images/sainte-chapelle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theasta/paris-webpack-react/f0e5ab632db5d2f312637a8ec5e2288cd7b00650/assets/js/components/attractions/images/sainte-chapelle.jpg -------------------------------------------------------------------------------- /assets/js/components/homepage/HeadingItem.css: -------------------------------------------------------------------------------- 1 | .headingItem .-icon { 2 | width: 24px; 3 | height: 24px; 4 | float: left; 5 | margin-top: 3px; 6 | margin-right: 3px; 7 | } -------------------------------------------------------------------------------- /assets/js/components/homepage/HeadingItem.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | var { Col, Button } = require('react-bootstrap'); 4 | var SignupButton = require('../shared/SignupButton'); 5 | 6 | require('./HeadingItem.css'); 7 | 8 | module.exports = React.createClass({ 9 | render: function () { 10 | return ( 11 | 12 | 13 |

    {this.props.title}

    14 |

    Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui.

    15 |

    16 | 17 |

    18 | 19 | ); 20 | } 21 | }); -------------------------------------------------------------------------------- /assets/js/components/homepage/Jumbo.css: -------------------------------------------------------------------------------- 1 | .jumbotron { 2 | font-size: 36px; 3 | color: white; 4 | background-size: cover; 5 | background: url('images/background.jpg') no-repeat; 6 | text-align : center; 7 | text-shadow: 1px 1px 1px #000; 8 | } 9 | -------------------------------------------------------------------------------- /assets/js/components/homepage/Jumbo.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var { Jumbotron, Button, Grid } = require('react-bootstrap'); 3 | 4 | require('./Jumbo.css'); 5 | 6 | module.exports = React.createClass({ 7 | render: function () { 8 | return ( 9 | 10 | 11 |

    Welcome To Paris

    12 |

    Sed convallis viverra mauris, eget sollicitudin nibh consectetur nec. Maecenas arcu enim, tincidunt in pharetra et, convallis eu dui. Phasellus mollis libero quis risus finibus faucibus.

    13 | 14 |
    15 |
    16 | ); 17 | } 18 | }); -------------------------------------------------------------------------------- /assets/js/components/homepage/images/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theasta/paris-webpack-react/f0e5ab632db5d2f312637a8ec5e2288cd7b00650/assets/js/components/homepage/images/background.jpg -------------------------------------------------------------------------------- /assets/js/components/homepage/images/paris.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 25 | 26 | -------------------------------------------------------------------------------- /assets/js/components/homepage/images/paris6.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /assets/js/components/homepage/images/paris8.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /assets/js/components/login/Login.css: -------------------------------------------------------------------------------- 1 | .form-signin { 2 | max-width: 330px; 3 | padding: 15px; 4 | margin: 0 auto; 5 | } 6 | -------------------------------------------------------------------------------- /assets/js/components/login/Login.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var { Input } = require('react-bootstrap'); 3 | 4 | require('./Login.css'); 5 | 6 | module.exports = React.createClass({ 7 | render: function () { 8 | return ( 9 |
    10 | Please sign in 11 | 12 | 13 | 14 | 15 |
    16 | ); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /assets/js/components/pages/AnalyticsPage.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var { Col } = require('react-bootstrap'); 3 | 4 | var Chart = require('../analytics/Chart'); 5 | 6 | module.exports = React.createClass({ 7 | render: function() { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | } 14 | }); -------------------------------------------------------------------------------- /assets/js/components/pages/ItemPage.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Router = require('react-router'); 3 | var AttractionItem = require('../attractions/AttractionItem'); 4 | 5 | module.exports = React.createClass({ 6 | mixins: [ Router.State ], 7 | render: function() { 8 | var attractionId = this.getParams().id; 9 | return ; 10 | } 11 | }); -------------------------------------------------------------------------------- /assets/js/components/pages/ListPage.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Router = require('react-router'); 3 | var { Col } = require('react-bootstrap'); 4 | var AttractionList = require('../attractions/AttractionList'); 5 | var AttractionPaginator = require('../attractions/AttractionPaginator'); 6 | 7 | module.exports = React.createClass({ 8 | mixins: [ Router.State ], 9 | render: function() { 10 | var page = +this.getQuery().page || 1; 11 | return ( 12 | 13 | 14 | 15 | 16 | ); 17 | 18 | } 19 | }); -------------------------------------------------------------------------------- /assets/js/components/paginator/Paginator.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var { Link } = require('react-router'); 3 | var { Nav } = require('react-bootstrap'); 4 | var { NavItemLink } = require('react-router-bootstrap'); 5 | var PaginatorMixin = require('../../mixins/PaginatorMixin'); 6 | var paginationConf = require('../../constants/pagination'); 7 | 8 | module.exports = React.createClass({ 9 | mixins: [ 10 | PaginatorMixin 11 | ], 12 | getDefaultProps: function() { 13 | return paginationConf; 14 | }, 15 | render: function () { 16 | if (this.props.totalItems === 0) { return null; } 17 | 18 | var links = this.paginatePages(function (i) { 19 | return (
  • {i}
  • ); 20 | }); 21 | 22 | return ( 23 | 28 | ); 29 | } 30 | }); -------------------------------------------------------------------------------- /assets/js/components/plans/Plan.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var { Panel } = require('react-bootstrap'); 3 | var SignupButton = require('../shared/SignupButton'); 4 | 5 | module.exports = React.createClass({ 6 | render: function () { 7 | var signupButton = ( 8 | 9 | ); 10 | return ( 11 | 12 |
    13 |

    ${this.props.price}/mo

    14 | 1 month FREE trial 15 |
    16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 |
    {this.props.accounts} Account
    22 | Upload your own photos - {this.props.storage}MB Storage 23 |
    26 |
    27 | ); 28 | } 29 | }); -------------------------------------------------------------------------------- /assets/js/components/plans/Plans.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var { Col } = require('react-bootstrap'); 3 | var Plan = require('./Plan'); 4 | 5 | module.exports = React.createClass({ 6 | render: function () { 7 | return ( 8 |
    9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
    19 | ); 20 | } 21 | }); -------------------------------------------------------------------------------- /assets/js/components/shared/AppWrapper.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | require('./BootstrapCss'); 4 | var Navigation = require('./Navigation'); 5 | var { Grid } = require('react-bootstrap'); 6 | 7 | var Footer = require('./Footer'); 8 | 9 | module.exports = React.createClass({ 10 | render: function () { 11 | return ( 12 |
    13 | 14 | {this.props.children} 15 | 16 |
    17 | 18 |
    19 | ); 20 | } 21 | }); -------------------------------------------------------------------------------- /assets/js/components/shared/BootstrapCss.js: -------------------------------------------------------------------------------- 1 | require('../../../css/bootstrap.min.css'); 2 | require('../../../css/bootstrap-theme.min.css'); -------------------------------------------------------------------------------- /assets/js/components/shared/Footer.css: -------------------------------------------------------------------------------- 1 | .footer { 2 | margin-top: 30px; 3 | padding-top: 10px; 4 | color: #999; 5 | border-top: solid 1px #ddd; 6 | } 7 | 8 | .footer > p { 9 | margin-bottom: 2px; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /assets/js/components/shared/Footer.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var { Row } = require('react-bootstrap'); 3 | 4 | 5 | require('./Footer.css'); 6 | 7 | 8 | 9 | module.exports = React.createClass({ 10 | render: function () { 11 | return ( 12 | 13 | 17 | 18 | ); 19 | } 20 | }); -------------------------------------------------------------------------------- /assets/js/components/shared/Navigation.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var { Nav, Navbar, NavItem, DropdownButton, MenuItem, Button } = require('react-bootstrap'); 3 | var { NavItemLink } = require('react-router-bootstrap'); 4 | 5 | module.exports = React.createClass({ 6 | render: function () { 7 | var brand = ( 8 | Paris Tourist Attractions 9 | ); 10 | return ( 11 | 12 | 16 | 27 | 28 | ); 29 | } 30 | }); -------------------------------------------------------------------------------- /assets/js/components/shared/SignupButton.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var { Button } = require('react-bootstrap'); 3 | 4 | module.exports = React.createClass({ 5 | render: function () { 6 | return ( 7 | 8 | ); 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /assets/js/constants/attractionActions.js: -------------------------------------------------------------------------------- 1 | var keyMirror = require('react/lib/keyMirror'); 2 | 3 | module.exports = keyMirror({ 4 | LOAD: null, 5 | LOAD_ERROR: null, 6 | LOAD_SUCCESS: null, 7 | LOAD_ATTRACTION: null, 8 | LOAD_ATTRACTION_SUCCESS: null, 9 | LOAD_ATTRACTION_ERROR: null 10 | }); -------------------------------------------------------------------------------- /assets/js/constants/conf.js: -------------------------------------------------------------------------------- 1 | var keyMirror = require('react/lib/keyMirror'); 2 | 3 | module.exports = keyMirror({ 4 | CHANGE_EVENT: null 5 | }); -------------------------------------------------------------------------------- /assets/js/constants/pagination.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | perPage: 8, 3 | page: 1 4 | }; -------------------------------------------------------------------------------- /assets/js/dispatcher/appDispatcher.js: -------------------------------------------------------------------------------- 1 | var Dispatcher = require('flux').Dispatcher; 2 | module.exports = new Dispatcher(); -------------------------------------------------------------------------------- /assets/js/homepage.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | var ParisApp = require('./components/shared/AppWrapper'); 4 | 5 | var Jumbo = require('./components/homepage/Jumbo'); 6 | var { Grid, Row } = require('react-bootstrap'); 7 | var HeadingItem = require('./components/homepage/HeadingItem'); 8 | 9 | var iconNotreDame = require('./components/homepage/images/paris6.svg'); 10 | var iconEiffelTower = require('./components/homepage/images/paris8.svg'); 11 | var iconCastle = require('./components/homepage/images/paris.svg'); 12 | 13 | React.render( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ,document.body); 27 | -------------------------------------------------------------------------------- /assets/js/login.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var ParisApp = require('./components/shared/AppWrapper'); 3 | 4 | var Form = require('./components/login/Login'); 5 | var { Grid, Row } = require('react-bootstrap'); 6 | 7 | React.render( 8 | 9 | 10 | 11 |
    12 | 13 | 14 | 15 | ,document.body); -------------------------------------------------------------------------------- /assets/js/mixins/PaginatorMixin.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | module.exports = { 4 | propTypes: { 5 | totalItems: React.PropTypes.number, 6 | perPage: React.PropTypes.number, 7 | page: React.PropTypes.number 8 | }, 9 | paginateItems: function (items) { 10 | var page = this.props.page; 11 | var perPage = this.props.perPage; 12 | var startPos = (page - 1) * perPage; 13 | var endPos = Math.min(startPos + perPage, items.length); 14 | return items.slice(startPos, endPos); 15 | }, 16 | paginatePages: function (cb) { 17 | var minPage = 1; 18 | var maxPage = Math.ceil(this.props.totalItems / this.props.perPage); 19 | 20 | var pages = []; 21 | for (var i = minPage; i <= maxPage; i++) { 22 | pages.push(cb.call(this, i)); 23 | } 24 | return pages; 25 | } 26 | }; -------------------------------------------------------------------------------- /assets/js/plans.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | var ParisApp = require('./components/shared/AppWrapper'); 4 | 5 | var { Grid, Row } = require('react-bootstrap'); 6 | 7 | var Plans = require('./components/plans/Plans'); 8 | 9 | React.render( 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ,document.body); -------------------------------------------------------------------------------- /assets/js/routes/routes.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var { Route, DefaultRoute } = require('react-router'); 3 | var App = require('../components/App'); 4 | var ListPage = require('../components/pages/ListPage'); 5 | var ItemPage = require('../components/pages/ItemPage'); 6 | var AnalyticsPage; 7 | 8 | if (CODE_SPLITTING) { 9 | AnalyticsPage = require('react-router-proxy!../components/pages/AnalyticsPage'); 10 | } else { 11 | AnalyticsPage = require('../components/pages/AnalyticsPage'); 12 | } 13 | 14 | module.exports = ( 15 | 16 | 17 | 18 | 19 | 20 | ); -------------------------------------------------------------------------------- /assets/js/services/attractionClient.js: -------------------------------------------------------------------------------- 1 | var client = require('./clients/localStorageClient'); 2 | module.exports = client; 3 | 4 | -------------------------------------------------------------------------------- /assets/js/services/clients/localStorageClient.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | require('es6-promise').polyfill(); 3 | var Promise = require('es6-promise').Promise; 4 | 5 | 6 | var fixtures = require('../../../../tests/fixtures/attractions'); 7 | 8 | var getAll = function () { 9 | return JSON.parse(window.localStorage.getItem("attractions")); 10 | }; 11 | 12 | var saveAll = function (wines) { 13 | window.localStorage.setItem("attractions", JSON.stringify(wines)); 14 | }; 15 | 16 | var generateId = function (item) { 17 | if (item.id) { 18 | throw new Error('Attraction already has an id'); 19 | } 20 | return item.id = _.uniqueId('attraction_'); 21 | }; 22 | 23 | var initialize = function () { 24 | var attractionsObj = {}; 25 | fixtures.forEach(function(wine) { 26 | generateId(wine); 27 | attractionsObj[wine.id] = wine; 28 | }); 29 | saveAll(attractionsObj); 30 | }; 31 | 32 | initialize(); 33 | 34 | module.exports = { 35 | loadAll: function () { 36 | return new Promise(function(resolve, reject) { 37 | _.defer(function () { 38 | var attractionsObj = getAll(); 39 | var attractions = _.values(attractionsObj); 40 | if (_.isArray(attractions)) { 41 | resolve(attractions); 42 | } else { 43 | reject(new Error("We couldn't load the attractions.")); 44 | } 45 | }, 3000); 46 | }); 47 | }, 48 | loadOne: function (attractionId) { 49 | return new Promise(function(resolve, reject) { 50 | _.defer(function () { 51 | var attractionsObj = getAll(); 52 | var attraction = attractionsObj[attractionId]; 53 | if (!attraction) { 54 | reject(new Error("We couldn't find any attraction with this id (" + attractionId + ")")); 55 | } else { 56 | resolve(attraction) 57 | } 58 | }, 3000); 59 | }); 60 | } 61 | }; -------------------------------------------------------------------------------- /assets/js/stores/attractionStore.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var EventEmitter = require('events').EventEmitter; 3 | var appDispatcher = require('../dispatcher/appDispatcher'); 4 | var assign = require('object-assign'); 5 | var attractionConstants = require('../constants/attractionActions'); 6 | var conf = require('../constants/conf'); 7 | 8 | 9 | var _attractions = []; 10 | var _attractionsById = []; 11 | 12 | /* public functions */ 13 | 14 | var store = assign({}, EventEmitter.prototype, { 15 | 16 | getAll: function() { 17 | return _attractions; 18 | }, 19 | 20 | getById: function (attractionId) { 21 | return _attractionsById[attractionId]; 22 | }, 23 | 24 | getId: function (attraction) { 25 | return attraction.id; 26 | }, 27 | 28 | getTotalCount: function () { 29 | return _attractions.length; 30 | }, 31 | 32 | indexOf: function (attraction) { 33 | for (var i = 0, l = _attractions.length; i < l; i++) { 34 | if (this.getId(attraction) == this.getId(_attractions[i])) { 35 | return i; 36 | } 37 | } 38 | return -1; 39 | }, 40 | 41 | emitChange: function() { 42 | this.emit(conf.CHANGE_EVENT); 43 | }, 44 | 45 | addChangeListener: function(callback) { 46 | this.on(conf.CHANGE_EVENT, callback); 47 | }, 48 | 49 | removeChangeListener: function(callback) { 50 | this.removeListener(conf.CHANGE_EVENT, callback); 51 | } 52 | }); 53 | 54 | /* private functions */ 55 | var _add = function (attraction) { 56 | _attractions.push(attraction); 57 | _attractionsById[this.getId(attraction)] = attraction; 58 | }.bind(store); 59 | 60 | var _set = function (attractions) { 61 | if (!_.isArray(attractions)) { 62 | attractions = [attractions]; 63 | } 64 | var existing; 65 | for (var i = 0, l = attractions.length; i < l; i++) { 66 | var attraction = attractions[i]; 67 | var id = this.getId(attraction); 68 | if (existing = this.getById(id)) { 69 | _update(attraction); 70 | } else { 71 | _add(attraction); 72 | } 73 | } 74 | }.bind(store); 75 | 76 | 77 | var _update = function (attraction) { 78 | var index = this.indexOf(attraction); 79 | _attractions[index] = attraction; 80 | _attractionsById[this.getId(attraction)] = attraction; 81 | }.bind(store); 82 | 83 | /* App Dispatcher */ 84 | 85 | appDispatcher.register(function(action) { 86 | switch(action.type) { 87 | case attractionConstants.LOAD_SUCCESS: 88 | case attractionConstants.LOAD_ATTRACTION_SUCCESS: 89 | _set(action.response); 90 | store.emitChange(); 91 | break; 92 | default: 93 | // no op 94 | } 95 | }); 96 | 97 | module.exports = store; 98 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "paris-react-webpack", 3 | "version": "0.0.1", 4 | "description": "Sample App built with React and Webpack", 5 | "private": true, 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/theasta/paris-webpack-react.git" 9 | }, 10 | "author": "Alexandrine Boissière", 11 | "license": "ISC", 12 | "dependencies": { 13 | "es6-promise": "2.0.1", 14 | "express": "4.11.2", 15 | "flux": "2.0.1", 16 | "jade": "1.9.2", 17 | "lodash": "2.4.1", 18 | "object-assign": "2.0.0", 19 | "react": "0.12.2", 20 | "react-bootstrap": "0.15.1", 21 | "react-d3": "0.2.1", 22 | "react-router": "0.12.0", 23 | "react-router-bootstrap": "0.9.1" 24 | }, 25 | "devDependencies": { 26 | "css-loader": "0.9.1", 27 | "file-loader": "0.8.1", 28 | "html-loader": "0.2.3", 29 | "jsx-loader": "0.12.2", 30 | "react-hot-loader": "1.1.1", 31 | "react-router-proxy-loader": "0.2.0", 32 | "style-loader": "0.8.3", 33 | "url-loader": "0.5.5", 34 | "webpack": "1.5.3", 35 | "webpack-configuration": "0.0.2", 36 | "webpack-dev-server": "1.7.0" 37 | }, 38 | "scripts": { 39 | "test": "echo \"Error: no test specified\" && exit 1", 40 | "clean": "rm -rf stats.json && rm -rf server/cloud && rm -rf server/dist && rm -rf server/assets.json", 41 | "webpack-dev-server": "webpack-dev-server --config webpack-dev-server.config.js --colors --port 2992 --hot --inline", 42 | "webpack-dev": "webpack --config webpack-dev.config.js --colors --hide-modules --watch", 43 | "build": "webpack --config webpack-build.config.js --colors && node server/server-cdn.js", 44 | "webpack-stats": "webpack --config webpack-dev.config.js --colors --progress --json > stats.json" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /server/lib/server-creator.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var _ = require('lodash'); 4 | 5 | module.exports = function (options) { 6 | 7 | var defaultOptions = { 8 | staticServer: true, 9 | versionMap: false 10 | }; 11 | 12 | options = _.defaults(options || {}, defaultOptions); 13 | 14 | var app = express(); 15 | app.set('views', path.join(__dirname, '../views')); 16 | app.set('view engine', 'jade'); 17 | 18 | if (!options.staticServer) { 19 | app.use('/assets', express.static(path.join(__dirname, '../dist'))); //__dirname + '/dist' 20 | } 21 | 22 | var versionMap = {}; 23 | if (options.versionMap) { 24 | versionMap = require('../assets.json'); 25 | } 26 | 27 | var getVersionName = function (fileName) { 28 | return versionMap[fileName] || fileName; 29 | }; 30 | 31 | var getTemplateData = function (routeName) { 32 | var publicPath = (options.staticServer) ? 'http://localhost:2992/assets/': 'assets/'; 33 | 34 | var templateData = { 35 | title: 'Paris', 36 | css: publicPath + getVersionName(routeName + '.css'), 37 | js: publicPath + getVersionName(routeName + '.js'), 38 | commonsjs: publicPath + getVersionName('commons.js'), 39 | commonscss: publicPath + getVersionName('commons.css') 40 | }; 41 | 42 | return templateData; 43 | }; 44 | 45 | app.get('/', function (req, res) { 46 | res.render('layout', getTemplateData('homepage')); 47 | }); 48 | 49 | app.get('/login', function (req, res) { 50 | res.render('layout', getTemplateData('login')); 51 | }); 52 | 53 | app.get('/plans', function (req, res) { 54 | res.render('layout', getTemplateData('plans')); 55 | }); 56 | 57 | app.get('/app', function (req, res) { 58 | res.render('layout', getTemplateData('app')); 59 | }); 60 | 61 | var server = app.listen(3000, function () { 62 | var host = server.address().address; 63 | var port = server.address().port; 64 | console.log('Example app listening at http://%s:%s', host, port); 65 | }); 66 | }; 67 | -------------------------------------------------------------------------------- /server/server-cdn.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | 4 | app.use('/assets', express.static(__dirname + '/cloud')); 5 | 6 | var server = app.listen(2992, function () { 7 | var host = server.address().address; 8 | var port = server.address().port; 9 | console.log('Example app listening at http://%s:%s', host, port); 10 | }); 11 | 12 | var serverCreator = require('./lib/server-creator'); 13 | 14 | serverCreator({ 15 | staticServer: true, 16 | versionMap: true 17 | }); 18 | -------------------------------------------------------------------------------- /server/server-dev-server.js: -------------------------------------------------------------------------------- 1 | var serverCreator = require('./lib/server-creator'); 2 | 3 | serverCreator({ 4 | staticServer: true 5 | }); 6 | -------------------------------------------------------------------------------- /server/server-dev.js: -------------------------------------------------------------------------------- 1 | var serverCreator = require('./lib/server-creator'); 2 | 3 | serverCreator({ 4 | staticServer: false 5 | }); 6 | -------------------------------------------------------------------------------- /server/views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype 2 | html 3 | head 4 | meta(charset='utf-8') 5 | meta(http-equiv='X-UA-Compatible', content='IE=edge') 6 | title!= title 7 | meta(name='viewport', content='width=device-width, initial-scale=1') 8 | link(rel='stylesheet', href!= commonscss) 9 | link(rel='stylesheet', href!= css) 10 | 11 | body 12 | 13 | script(src!= commonsjs) 14 | script(src!= js) 15 | -------------------------------------------------------------------------------- /tests/fixtures/attractions.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | "name": "Eiffel Tower", 4 | "type": "Tower", 5 | "link": "http://en.wikipedia.org/wiki/Eiffel_Tower", 6 | "description": "The Eiffel Tower is an iron lattice tower located on the Champ de Mars in Paris. It was named after the engineer Gustave Eiffel, whose company designed and built the tower.", 7 | "picture": "eiffel-tower.jpg", 8 | "credits": 'Tour Eiffel Wikimedia Commons" by Benh LIEU SONG - Own work. Licensed under CC BY-SA 3.0 via Wikimedia Commons.' 9 | }, { 10 | "name": "The Louvre", 11 | "type": "Art Museum", 12 | "link": "http://en.wikipedia.org/wiki/Louvre", 13 | "description": "The Louvre or the Louvre Museum is one of the world's largest museums and a historic monument. A central landmark of Paris, France, it is located on the Right Bank of the Seine in the 1st arrondissement.", 14 | "picture": "louvres.jpg", 15 | "credits": '"Paris July 2011-27a" by Alvesgaspar - Own work. Licensed under CC BY-SA 3.0 via Wikimedia Commons.' 16 | }, { 17 | "name": "Notre Dame de Paris", 18 | "type": "Cathedral", 19 | "link": "http://en.wikipedia.org/wiki/Notre_Dame_de_Paris", 20 | "description": "Notre-Dame de Paris is a historic Catholic cathedral on the eastern half of the Île de la Cité in the fourth arrondissement of Paris, France.[2] The cathedral is widely considered to be one of the finest examples of French Gothic architecture, and it is among the largest and most well-known church buildings in the world. The naturalism of its sculptures and stained glass are in contrast with earlier Romanesque architecture.", 21 | "picture": "notre-dame.jpg", 22 | "credits": '"Notre Dame dalla Senna" by Zuffe - Own work. Licensed under CC BY-SA 3.0 via Wikimedia Commons.' 23 | }, { 24 | "name": "Champs-Elysées", 25 | "type": "Boulevard", 26 | "link": "http://en.wikipedia.org/wiki/Champs-%C3%89lys%C3%A9es", 27 | "description": "The Avenue des Champs-Élysées is a boulevard in the 8th arrondissement of Paris, 1.9 kilometres long and 70 metres wide, which runs between the Place de la Concorde and the Place Charles de Gaulle, where the Arc de Triomphe is located.", 28 | "picture": "champs-elysees.jpg", 29 | "credits": '"Avenue des Champs-Élysées July 24, 2009 N1" by Josh Hallett - Flickr: The Champs. Licensed under CC BY-SA 2.0 via Wikimedia Commons.' 30 | }, { 31 | "name": "Sacré-Coeur", 32 | "type": "Basilica", 33 | "link": "http://en.wikipedia.org/wiki/Sacr%C3%A9-C%C5%93ur,_Paris", 34 | "description": "The Basilica of the Sacred Heart of Paris, commonly known as Sacré-Cœur Basilica and often simply Sacré-Cœur, is a Roman Catholic church and minor basilica, dedicated to the Sacred Heart of Jesus, in Paris.", 35 | "picture": "sacre-coeur.jpg", 36 | "credits": '"Le sacre coeur (paris - france)" by Tonchino - Own work. Licensed under CC BY-SA 3.0 via Wikimedia Commons.' 37 | }, { 38 | "name": "Musée d'Orsay", 39 | "type": "Museum", 40 | "description": "The Musée d'Orsay is a museum in Paris, France, on the left bank of the Seine. It is housed in the former Gare d'Orsay, a Beaux-Arts railway station built between 1898 and 1900.", 41 | "link": "http://en.wikipedia.org/wiki/Mus%C3%A9e_d'Orsay", 42 | "picture": "musee-d-orsay.jpg", 43 | "credits": '"Musée d\'Orsay, North-West view, Paris 7e 140402" by Daniel Vorndran / DXR. Licensed under CC BY-SA 3.0 via Wikimedia Commons.' 44 | }, { 45 | "name": "Arc de Triomphe", 46 | "type": "Triumphal Arch", 47 | "description": "The Arc de Triomphe de l'Étoile is one of the most famous monuments in Paris. It stands in the centre of the Place Charles de Gaulle, at the western end of the Champs-Élysées.", 48 | "link": "http://en.wikipedia.org/wiki/Arc_de_Triomphe", 49 | "picture": "arc-de-triomphe.jpg", 50 | "credits": '"Arc de triomphe Paris" by I, Sese Ingolstadt. Licensed under CC BY-SA 2.5 via Wikimedia Commons.' 51 | }, { 52 | "name": "Palais Garnier", 53 | "type": "Opera House", 54 | "description": "The Palais Garnier is a 1,979-seat opera house, which was built from 1861 to 1875 for the Paris Opera", 55 | "link": "http://en.wikipedia.org/wiki/Palais_Garnier", 56 | "picture": "palais-garnier.jpg", 57 | "credits": '"Paris Opera full frontal architecture, May 2009" by Peter Rivera - Paris Opera. Licensed under CC BY 2.0 via Wikimedia Commons.' 58 | }, { 59 | "name": "Sainte Chapelle", 60 | "type": "Chapel", 61 | "description": "The Sainte-Chapelle is a royal medieval Gothic chapel, located near the Palais de la Cité, on the Île de la Cité in the heart of Paris, France.", 62 | "link": "http://en.wikipedia.org/wiki/Sainte-Chapelle", 63 | "picture": "sainte-chapelle.jpg", 64 | "credits": '"Sainte Chapelle - Upper level 1" by Didier B (Sam67fr) - Own work. Licensed under CC BY-SA 2.5 via Wikimedia Commons.' 65 | }, { 66 | "name": "Les Invalides", 67 | "type": "Mausoleum", 68 | "description": "Les Invalides, officially known as L'Hôtel national des Invalides, or also as L'Hôtel des Invalides, is a complex of buildings in the 7th arrondissement of Paris, France, containing museums and monuments, , all relating to the military history of France, as well as a hospital and a retirement home for war veterans, the building's original purpose.", 69 | "link": "http://en.wikipedia.org/wiki/Les_Invalides", 70 | "picture": "invalides.jpg", 71 | "credits": '"L\'Hôtel national des Invalides" by Victor Grigas - Own work. Licensed under CC BY-SA 4.0 via Wikimedia Commons.' 72 | } 73 | ]; -------------------------------------------------------------------------------- /webpack-build.config.js: -------------------------------------------------------------------------------- 1 | var baseConfig = require('./webpack/base-config'); 2 | var configurationCreator = require('webpack-configuration'); 3 | var path = require('path'); 4 | 5 | module.exports = configurationCreator(baseConfig, { 6 | commonsChunk: true, 7 | longTermCaching: true, 8 | extractCSS: true, 9 | path: 'server/cloud/', 10 | publicPath: "http://localhost:2992/assets/", 11 | versionMap: path.join(__dirname, 'server/assets.json'), 12 | featureFlags: { 13 | CODE_SPLITTING: true 14 | }, 15 | debug: false 16 | }); 17 | -------------------------------------------------------------------------------- /webpack-dev-server.config.js: -------------------------------------------------------------------------------- 1 | var baseConfig = require('./webpack/base-config'); 2 | var configurationCreator = require('webpack-configuration'); 3 | 4 | module.exports = configurationCreator(baseConfig, { 5 | commonsChunk: true, 6 | publicPath: "http://localhost:2992/assets/", 7 | featureFlags: { 8 | CODE_SPLITTING: true 9 | }, 10 | debug: true, 11 | hot: true 12 | }); 13 | -------------------------------------------------------------------------------- /webpack-dev.config.js: -------------------------------------------------------------------------------- 1 | var baseConfig = require('./webpack/base-config'); 2 | var configurationCreator = require('webpack-configuration'); 3 | 4 | module.exports = configurationCreator(baseConfig, { 5 | commonsChunk: false, 6 | //commonsChunkMin: 2, 7 | extractCSS: false, 8 | path: 'server/dist/', 9 | publicPath: "/assets/", 10 | featureFlags: { 11 | CODE_SPLITTING: false 12 | }, 13 | debug: true 14 | }); 15 | -------------------------------------------------------------------------------- /webpack/base-config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | module.exports = { 3 | context: path.join(__dirname, '../assets/js'), 4 | entry: { 5 | homepage: "./homepage.jsx", 6 | login: "./login.jsx", 7 | plans: "./plans.jsx", 8 | app: "./app.jsx" 9 | }, 10 | module: { 11 | loaders: [ 12 | { test: /\.(png|jpg)$/, loader: "url?limit=100"}, 13 | { test: /\.(woff|woff2)$/, loader: "url?limit=10000"}, 14 | { test: /\.(ttf|eot|svg)$/, loader: "file"}, 15 | { test: /\.html$/, loader: "html"} 16 | ] 17 | }, 18 | resolve: { 19 | extensions: ["", ".webpack.js", ".web.js", ".js"], 20 | root: path.join(__dirname, '../public/js') 21 | } 22 | }; 23 | --------------------------------------------------------------------------------