├── .gitignore ├── Makefile ├── Readme.md ├── build ├── index.js ├── index.min.js └── index.min.js.gz ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | development: install 2 | @NODE_PATH=lib PORT=5000 ./node_modules/.bin/scooby index.js --open 3 | 4 | install: 5 | @npm install 6 | 7 | build: 8 | @NODE_PATH=lib ./node_modules/.bin/scooby index.js 9 | 10 | minify: build/index.js 11 | @curl -s \ 12 | -d compilation_level=SIMPLE_OPTIMIZATIONS \ 13 | -d output_format=text \ 14 | -d output_info=compiled_code \ 15 | --data-urlencode "js_code@$<" \ 16 | http://closure-compiler.appspot.com/compile \ 17 | > $<.tmp 18 | @mv $<.tmp build/index.min.js 19 | @gzip -c build/index.min.js > build/index.min.js.gz 20 | 21 | dist: install build minify 22 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # React + Redux + Routing Stack for 28kb 2 | 3 | Just because your stack is awesome, doesn't mean it has to be fat. 4 | 5 | This repo serves as a minimally viable example of how to combine the great ideas of React + Redux without compromising on your build size. 6 | 7 | ## Kilobytes as currency 8 | 9 | My goal for this project is to treat kilobytes as a sort of currency. 10 | 11 | > I'll give you React, Redux and a Routing Stack for just 28kb. What can you offer me? 12 | 13 | Heavy frameworks aren't just slow for end users to download, they slow down our transpilers, they are harder to understand internally when the framework inevitably breaks, and they're typically more difficult to extend and make our own. 14 | 15 | ## Behind the Curtain 16 | 17 | - React (via [Preact](https://github.com/developit/preact)) 18 | - Redux (via [Socrates](https://github.com/matthewmueller/socrates)) 19 | - Action Logging (via [Redux Logger](https://github.com/fcomb/redux-logger)) 20 | - Functional Routing (via [enroute](https://github.com/lapwinglabs/enroute) + [simple history middleware](https://github.com/matthewmueller/redux-routes)) 21 | 22 | ## See for yourself 23 | 24 | ``` 25 | git clone https://github.com/matthewmueller/28kb-react-redux-routing 26 | npm install 27 | npm start 28 | ``` 29 | 30 | ## Kudos 31 | 32 | All the hard work here was done by [@developit](https://github.com/developit) for his work on [Preact](https://github.com/developit/preact) and [@gaearon](https://github.com/gaearon) for his work on [redux](https://github.com/reactjs/redux). The "custom" modules you see above are just a small amount of UX grease on top of great implementations. 33 | -------------------------------------------------------------------------------- /build/index.min.js: -------------------------------------------------------------------------------- 1 | var $jscomp={scope:{},getGlobal:function(a){return"undefined"!=typeof window&&window===a?a:"undefined"!=typeof global?global:a}};$jscomp.global=$jscomp.getGlobal(this);$jscomp.initSymbol=function(){$jscomp.global.Symbol||($jscomp.global.Symbol=$jscomp.Symbol);$jscomp.initSymbol=function(){}};$jscomp.symbolCounter_=0;$jscomp.Symbol=function(a){return"jscomp_symbol_"+a+$jscomp.symbolCounter_++}; 2 | $jscomp.initSymbolIterator=function(){$jscomp.initSymbol();$jscomp.global.Symbol.iterator||($jscomp.global.Symbol.iterator=$jscomp.global.Symbol("iterator"));$jscomp.initSymbolIterator=function(){}}; 3 | $jscomp.makeIterator=function(a){$jscomp.initSymbolIterator();if(a[$jscomp.global.Symbol.iterator])return a[$jscomp.global.Symbol.iterator]();if(!(a instanceof Array||"string"==typeof a||a instanceof String))throw new TypeError(a+" is not iterable");var b=0;return{next:function(){return b==a.length?{done:!0}:{done:!1,value:a[b++]}}}};$jscomp.arrayFromIterator=function(a){for(var b,d=[];!(b=a.next()).done;)d.push(b.value);return d}; 4 | $jscomp.arrayFromIterable=function(a){return a instanceof Array?a:$jscomp.arrayFromIterator($jscomp.makeIterator(a))};$jscomp.arrayFromArguments=function(a){for(var b=[],d=0;db;)--d in this?this[--a]=this[d]:delete this[a];return this};$jscomp.array.copyWithin$install=function(){Array.prototype.copyWithin||(Array.prototype.copyWithin=$jscomp.array.copyWithin)}; 10 | $jscomp.array.fill=function(a,b,d){null!=d&&a.length||(d=this.length||0);d=Number(d);for(b=Number((void 0===b?0:b)||0);b>>0;if(0===a)return 32;var b=0;0===(a&4294901760)&&(a<<=16,b+=16);0===(a&4278190080)&&(a<<=8,b+=8);0===(a&4026531840)&&(a<<=4,b+=4);0===(a&3221225472)&&(a<<=2,b+=2);0===(a&2147483648)&&b++;return b};$jscomp.math.imul=function(a,b){a=Number(a);b=Number(b);var d=a&65535,c=b&65535;return d*c+((a>>>16&65535)*c+d*(b>>>16&65535)<<16>>>0)|0};$jscomp.math.sign=function(a){a=Number(a);return 0===a||isNaN(a)?a:0a&&-.25a&&-.25a?-b:b};$jscomp.math.acosh=function(a){a=Number(a);return Math.log(a+Math.sqrt(a*a-1))};$jscomp.math.asinh=function(a){a=Number(a);if(0===a)return a;var b=Math.log(Math.abs(a)+Math.sqrt(a*a+1));return 0>a?-b:b}; 23 | $jscomp.math.atanh=function(a){a=Number(a);return($jscomp.math.log1p(a)-$jscomp.math.log1p(-a))/2}; 24 | $jscomp.math.hypot=function(a,b,d){for(var c=[],e=2;ek){a/=k;b/=k;n=a*a+b*b;c=$jscomp.makeIterator(c);for(e=c.next();!e.done;e=c.next())e=e.value,e=Number(e)/k,n+=e*e;return Math.sqrt(n)*k}k=a*a+b*b;c=$jscomp.makeIterator(c);for(e=c.next();!e.done;e=c.next())e=e.value,k+=e*e;return Math.sqrt(k)}; 25 | $jscomp.math.trunc=function(a){a=Number(a);if(isNaN(a)||Infinity===a||-Infinity===a||0===a)return a;var b=Math.floor(Math.abs(a));return 0>a?-b:b};$jscomp.math.cbrt=function(a){if(0===a)return a;a=Number(a);var b=Math.pow(Math.abs(a),1/3);return 0>a?-b:b};$jscomp.number=$jscomp.number||{};$jscomp.number.isFinite=function(a){return"number"!==typeof a?!1:!isNaN(a)&&Infinity!==a&&-Infinity!==a};$jscomp.number.isInteger=function(a){return $jscomp.number.isFinite(a)?a===Math.floor(a):!1}; 26 | $jscomp.number.isNaN=function(a){return"number"===typeof a&&isNaN(a)};$jscomp.number.isSafeInteger=function(a){return $jscomp.number.isInteger(a)&&Math.abs(a)<=$jscomp.number.MAX_SAFE_INTEGER};$jscomp.number.EPSILON=Math.pow(2,-52);$jscomp.number.MAX_SAFE_INTEGER=9007199254740991;$jscomp.number.MIN_SAFE_INTEGER=-9007199254740991;$jscomp.object=$jscomp.object||{}; 27 | $jscomp.object.assign=function(a,b){for(var d=[],c=1;cc||1114111=c?d+=String.fromCharCode(c):(c-=65536,d+=String.fromCharCode(c>>>10&1023|55296),d+=String.fromCharCode(c&1023|56320))}return d}; 33 | $jscomp.string.repeat=function(a){var b=this.toString();if(0>a||1342177279>>=1)b+=b;return d};$jscomp.string.repeat$install=function(){String.prototype.repeat||(String.prototype.repeat=$jscomp.string.repeat)}; 34 | $jscomp.string.codePointAt=function(a){var b=this.toString(),d=b.length;a=Number(a)||0;if(0<=a&&ac||56319a||57343=e};$jscomp.string.startsWith$install=function(){String.prototype.startsWith||(String.prototype.startsWith=$jscomp.string.startsWith)}; 37 | $jscomp.string.endsWith=function(a,b){$jscomp.string.noRegExp_(a,"endsWith");var d=this.toString();a+="";void 0===b&&(b=d.length);for(var c=Math.max(0,Math.min(b|0,d.length)),e=a.length;0=e};$jscomp.string.endsWith$install=function(){String.prototype.endsWith||(String.prototype.endsWith=$jscomp.string.endsWith)}; 38 | (function e$$0(b,d,c){function e(f,h){if(!d[f]){if(!b[f]){var g="function"==typeof require&&require;if(!h&&g)return g(f,!0);if(k)return k(f,!0);g=Error("Cannot find module '"+f+"'");throw g.code="MODULE_NOT_FOUND",g;}g=d[f]={exports:{}};b[f][0].call(g.exports,function(c){var g=b[f][1][c];return e(g?g:c)},g,g.exports,e$$0,b,d,c)}return d[f].exports}for(var k="function"==typeof require&&require,n=0;n=arguments.length||void 0===arguments[0]?{}:arguments[0],c=b.level,d=void 0===c?"log":c,c=b.logger,m=void 0===c?console:c,c=b.logErrors,k=void 0===c?!0:c,w=b.collapsed,z=b.predicate,c=b.duration,y=void 0===c?!1:c,c=b.timestamp,x=void 0===c?!0:c,c= 43 | b.stateTransformer,B=void 0===c?function(a){return a}:c,c=b.actionTransformer,q=void 0===c?function(a){return a}:c,c=b.errorTransformer,r=void 0===c?function(a){return a}:c,c=b.colors,u=void 0===c?{title:function(){return"#000000"},prevState:function(){return"#9E9E9E"},action:function(){return"#03A9F4"},nextState:function(){return"#4CAF50"},error:function(){return"#F20404"}}:c;if("undefined"===typeof m)return function(){return function(a){return function(c){return a(c)}}};b.transformer&&console.error("Option 'transformer' is deprecated, use stateTransformer instead"); 44 | var C=[];return function(c){var b=c.getState;return function(c){return function(e){if("function"===typeof z&&!z(b,e))return c(e);var g={};C.push(g);g.started=f.now();g.startedTime=new Date;g.prevState=B(b());g.action=e;var d=void 0;if(k)try{d=c(e)}catch(l){g.error=r(l)}else d=c(e);g.took=f.now()-g.started;g.nextState=B(b());a();if(g.error)throw g.error;return d}}}}},{}],"node_modules/mako-js/node_modules/punycode/punycode.js":[function(a,b,d){(function(a){(function(e){function k(a){throw new RangeError(u[a]); 45 | }function n(a,c){for(var b=a.length,g=[];b--;)g[b]=c(a[b]);return g}function f(a,c){var b=a.split("@"),g="";1=e&&c>>10&1023|55296),a= 46 | 56320|a&1023);return b+=L(a)}).join("")}function l(a,b){return a+22+75*(26>a)-((0!=b)<<5)}function p(a,b,c){var g=0;a=c?C(a/700):a>>1;for(a+=C(a/b);455m&&(m=0);for(h=0;h=c&&k("invalid-input");r=a.charCodeAt(m++);r=10>r-48?r-22:26>r-65?r-65:26>r-97?r-97:36;(36<= 47 | r||r>C((2147483647-f)/e))&&k("overflow");f+=r*e;n=q<=l?1:q>=l+26?26:q-l;if(rC(2147483647/r)&&k("overflow");e*=r}e=b.length+1;l=p(f-h,e,0==h);C(f/e)>2147483647-d&&k("overflow");d+=C(f/e);f%=e;b.splice(f++,0,d)}return g(b)}function t(a){var b,c,e,g,f,d,m,r,q,n=[],t,u,z;a=h(a);t=a.length;b=128;c=0;f=72;for(d=0;dq&&n.push(L(q));for((e=g=n.length)&&n.push("-");e=b&&qC((2147483647-c)/u)&&k("overflow");c+= 48 | (m-b)*u;b=m;for(d=0;d=f+26?26:m-f;if(r= 0x80 (not a basic code point)","invalid-input":"Invalid input"},C=Math.floor,L=String.fromCharCode,M;x={version:"1.3.2",ucs2:{decode:h,encode:g},decode:m,encode:t,toASCII:function(a){return f(a,function(a){return q.test(a)?"xn--"+t(a):a})},toUnicode:function(a){return f(a,function(a){return B.test(a)?m(a.slice(4).toLowerCase()):a})}};if("function"==typeof define&&"object"==typeof define.amd&&define.amd)define("punycode",function(){return x});else if(w&&z)if(b.exports== 50 | w)z.exports=x;else for(M in x)x.hasOwnProperty(M)&&(w[M]=x[M]);else e.punycode=x})(this)}).call(this,"undefined"!==typeof global?global:"undefined"!==typeof self?self:"undefined"!==typeof window?window:{})},{}],"node_modules/enroute/node_modules/object-assign/index.js":[function(a,b,d){var c=Object.prototype.hasOwnProperty,e=Object.prototype.propertyIsEnumerable;b.exports=Object.assign||function(a,b){var f,d;if(null===a||void 0===a)throw new TypeError("Object.assign cannot be called with null or undefined"); 51 | d=Object(a);for(var g,l=1;lb&&(f=b);for(b=0;bb?Math.max(0,b+f):b||0;for(void 0!==d&&(f=0>d?d+ 73 | f:d);f-- >b;)n[f-b]=a[f];return n}},{}],"node_modules/socrates/node_modules/object-assign/index.js":[function(a,b,d){var c=Object.prototype.hasOwnProperty,e=Object.prototype.propertyIsEnumerable;b.exports=Object.assign||function(a,b){var d,h;if(null===a||void 0===a)throw new TypeError("Object.assign cannot be called with null or undefined");h=Object(a);for(var g,l=1;l=arguments.length?void 0:arguments[0];var a=b[b.length-1];return b.slice(0,-1).reduceRight(function(a,b){return b(a)},a.apply(void 0,arguments))}}},{}],"node_modules/socrates/node_modules/redux/lib/utils/warning.js":[function(a,b,d){d.__esModule=!0;d["default"]=function(a){"undefined"!==typeof console&&"function"===typeof console.error&&console.error(a);try{throw Error(a);}catch(b){}}},{}],"node_modules/preact-socrates/node_modules/component-raf/index.js":[function(a, 79 | b,d){function c(a){var b=(new Date).getTime();a=setTimeout(a,Math.max(0,16-(b-e)));e=b;return a}d=b.exports=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||c;var e=(new Date).getTime(),k=window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.clearTimeout;d.cancel=function(a){k.call(window,a)}},{}],"node_modules/preact-socrates/node_modules/object-assign/index.js":[function(a,b,d){var c=Object.prototype.hasOwnProperty, 80 | e=Object.prototype.propertyIsEnumerable;b.exports=Object.assign||function(a,b){var d,h;if(null===a||void 0===a)throw new TypeError("Object.assign cannot be called with null or undefined");h=Object(a);for(var g,l=1;lb.length)try{return a.apply(this,b.concat(d))}catch(l){return d(l)}return c(a,d).apply(this,b)}}},{co:"node_modules/socrates/node_modules/vo/node_modules/wrapped/node_modules/co/index.js",sliced:"node_modules/socrates/node_modules/sliced/index.js"}],"node_modules/socrates/node_modules/@f/combine-reducers/node_modules/@f/clone-shallow/node_modules/@f/clone-obj/node_modules/@f/foreach/node_modules/@f/is-object/lib/index.js":[function(a, 122 | b,d){var c=a("@f/is-function");b.exports=function(a){var b;(b=!!a)&&!(b=a.constructor===Object)&&(a=a.constructor,b=!!a&&c(a)&&Function.prototype.toString.call(a)===e);return b};var e=Function.prototype.toString.call(Object)},{"@f/is-function":"node_modules/socrates/node_modules/@f/combine-reducers/node_modules/@f/clone-shallow/node_modules/@f/clone-obj/node_modules/@f/foreach/node_modules/@f/is-object/node_modules/@f/is-function/lib/index.js"}],"node_modules/mako-js/node_modules/url/url.js":[function(a, 123 | b,d){function c(){this.href=this.path=this.pathname=this.query=this.search=this.hash=this.hostname=this.port=this.host=this.auth=this.slashes=this.protocol=null}function e(a,b,d){if(a&&n.isObject(a)&&a instanceof c)return a;var e=new c;e.parse(a,b,d);return e}var k=a("punycode"),n=a("./util");d.parse=e;d.resolve=function(a,b){return e(a,!1,!0).resolve(b)};d.resolveObject=function(a,b){return a?e(a,!1,!0).resolveObject(b):b};d.format=function(a){n.isString(a)&&(a=e(a));return a instanceof c?a.format(): 124 | c.prototype.format.call(a)};d.Url=c;var f=/^([a-z0-9.+-]+:)/i,h=/:[0-9]*$/,g=/^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/;b="{}|\\^`".split("").concat('<>"` \r\n\t'.split(""));var l=["'"].concat(b),p=["%","/","?",";","#"].concat(l),m=["/","?","#"],t=/^[+a-z0-9A-Z_-]{0,63}$/,w=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,z={javascript:!0,"javascript:":!0},y={javascript:!0,"javascript:":!0},x={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0},B=a("querystring");c.prototype.parse= 125 | function(a,b,c){if(!n.isString(a))throw new TypeError("Parameter 'url' must be a string, not "+typeof a);var d=a.indexOf("?"),d=-1!==d&&df.length&&f.unshift("");b.pathname=f.join("/")}b.search=a.search;b.query=a.query;b.host=a.host||"";b.auth=a.auth;b.hostname=a.hostname||a.host;b.port=a.port;if(b.pathname|| 133 | b.search)b.path=(b.pathname||"")+(b.search||"");b.slashes=b.slashes||a.slashes;b.href=b.format();return b}var d=b.pathname&&"/"===b.pathname.charAt(0),l=a.host||a.pathname&&"/"===a.pathname.charAt(0),m=d=l||d||b.host&&a.pathname,e=b.pathname&&b.pathname.split("/")||[],f=a.pathname&&a.pathname.split("/")||[];if(g=b.protocol&&!x[b.protocol])b.hostname="",b.port=null,b.host&&(""===e[0]?e[0]=b.host:e.unshift(b.host)),b.host="",a.protocol&&(a.hostname=null,a.port=null,a.host&&(""===f[0]?f[0]=a.host:f.unshift(a.host)), 134 | a.host=null),d=d&&(""===f[0]||""===e[0]);if(l)b.host=a.host||""===a.host?a.host:b.host,b.hostname=a.hostname||""===a.hostname?a.hostname:b.hostname,b.search=a.search,b.query=a.query,e=f;else if(f.length)e||(e=[]),e.pop(),e=e.concat(f),b.search=a.search,b.query=a.query;else if(!n.isNullOrUndefined(a.search))return g&&(b.hostname=b.host=e.shift(),g=b.host&&0=arguments.length||void 0===arguments[0]?{}:arguments[0],d=arguments[1];if(y)throw y;if("production"!==b.env.NODE_ENV){var l=e(a,f,d);l&&(0,g["default"])(l)}for(var h=!1,k={},n=0;nb.length||"string"===typeof b[0]&&(b=[{type:b[0],payload:e.apply(null, 175 | b.slice(1))}]);return z.dispatch.apply(z,b)}g(a)||(b=a,a=[]);a=[n].concat(a);b=b||c;var z=p(k(b),{},l.apply(null,a));d.subscribe=function(a){return z.subscribe(function(){return a(f(z.getState()))})};return d}},{"./freeze":"node_modules/socrates/lib/freeze.js","./reducer":"node_modules/socrates/lib/reducer.js","./resolve":"node_modules/socrates/lib/resolve.js","object-assign":"node_modules/socrates/node_modules/object-assign/index.js",redux:"node_modules/socrates/node_modules/redux/lib/index.js", 176 | sliced:"node_modules/socrates/node_modules/sliced/index.js"}],"index.js":[function(a,b,d){function c(a){return a&&a.__esModule?a:{"default":a}}var e=Object.assign||function(a){for(var b=1;b ( 31 | Route({ 32 | '/blog': (params) => , 33 | '*': (params) => 34 | })(props.url) 35 | ) 36 | 37 | /** 38 | * Home 39 | */ 40 | 41 | const Home = ({ dispatch, greeting }) => ( 42 |
43 |

{greeting}

44 | 45 |
46 | ) 47 | 48 | /** 49 | * Blog 50 | */ 51 | 52 | const Blog = ({ dispatch }) => ( 53 |
54 |

Welcome to the Blog!

55 | 56 |
57 | ) 58 | 59 | /** 60 | * Initialize the store 61 | */ 62 | 63 | store('boot', { 64 | url: document.location.pathname, 65 | greeting: 'Welcome to the website, friend!' 66 | }) 67 | 68 | /** 69 | * Render 70 | */ 71 | 72 | render(App, store, document.body) 73 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "28kb-react-redux-routing", 3 | "description": "your awesome stack doesnt have to be fat", 4 | "private": true, 5 | "dependencies": { 6 | "enroute": "^1.0.1", 7 | "mako": "^0.9.2", 8 | "mako-babel": "^0.10.0", 9 | "mako-js": "^0.15.0", 10 | "mako-stat": "^0.8.0", 11 | "mako-text": "^0.8.0", 12 | "preact-socrates": "^1.0.0", 13 | "redux-logger": "^2.6.1", 14 | "redux-routes": "^1.0.0", 15 | "socrates": "^1.0.1", 16 | "uglify-js": "^2.6.2" 17 | }, 18 | "babel": { 19 | "presets": [ 20 | "es2015" 21 | ], 22 | "plugins": [ 23 | [ 24 | "transform-react-jsx", 25 | { 26 | "pragma": "h" 27 | } 28 | ] 29 | ] 30 | }, 31 | "devDependencies": { 32 | "babel-plugin-transform-react-jsx": "^6.6.4", 33 | "babel-preset-es2015": "^6.6.0", 34 | "scooby": "0.0.21" 35 | }, 36 | "scripts": { 37 | "start": "make development" 38 | } 39 | } 40 | --------------------------------------------------------------------------------