├── .gitattributes
├── .yo-rc.json
├── .bowerrc
├── app
├── robots.txt
├── favicon.ico
├── images
│ ├── agektmr.jpg
│ ├── agektmr-320.jpg
│ ├── agektmr-900.jpg
│ └── responsive
│ │ ├── abstract1.jpg
│ │ ├── abstract2.jpg
│ │ ├── abstract3.jpg
│ │ ├── abstract1-320.jpg
│ │ ├── abstract1-900.jpg
│ │ ├── abstract2-320.jpg
│ │ ├── abstract2-900.jpg
│ │ ├── abstract3-320.jpg
│ │ ├── abstract3-900.jpg
│ │ ├── illustration-home-inverted.png
│ │ ├── illustration-home-inverted-320.png
│ │ └── illustration-home-inverted-900.png
├── responsive-manifest.json
├── scripts
│ ├── main.js
│ └── worker.js
├── styles
│ └── main.scss
└── index.html
├── test
├── .bowerrc
├── bower.json
├── spec
│ └── test.js
└── index.html
├── .gitignore
├── bower.json
├── .jshintrc
├── .editorconfig
├── package.json
├── README.md
└── Gruntfile.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
--------------------------------------------------------------------------------
/.yo-rc.json:
--------------------------------------------------------------------------------
1 | {
2 | "generator-mocha": {}
3 | }
--------------------------------------------------------------------------------
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "bower_components"
3 | }
4 |
--------------------------------------------------------------------------------
/app/robots.txt:
--------------------------------------------------------------------------------
1 | # robotstxt.org/
2 |
3 | User-agent: *
4 |
--------------------------------------------------------------------------------
/test/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "bower_components"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .tmp
4 | .sass-cache
5 | bower_components
6 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agektmr/Responsive-Resource-Loader/HEAD/app/favicon.ico
--------------------------------------------------------------------------------
/app/images/agektmr.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agektmr/Responsive-Resource-Loader/HEAD/app/images/agektmr.jpg
--------------------------------------------------------------------------------
/app/images/agektmr-320.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agektmr/Responsive-Resource-Loader/HEAD/app/images/agektmr-320.jpg
--------------------------------------------------------------------------------
/app/images/agektmr-900.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agektmr/Responsive-Resource-Loader/HEAD/app/images/agektmr-900.jpg
--------------------------------------------------------------------------------
/app/images/responsive/abstract1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agektmr/Responsive-Resource-Loader/HEAD/app/images/responsive/abstract1.jpg
--------------------------------------------------------------------------------
/app/images/responsive/abstract2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agektmr/Responsive-Resource-Loader/HEAD/app/images/responsive/abstract2.jpg
--------------------------------------------------------------------------------
/app/images/responsive/abstract3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agektmr/Responsive-Resource-Loader/HEAD/app/images/responsive/abstract3.jpg
--------------------------------------------------------------------------------
/app/images/responsive/abstract1-320.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agektmr/Responsive-Resource-Loader/HEAD/app/images/responsive/abstract1-320.jpg
--------------------------------------------------------------------------------
/app/images/responsive/abstract1-900.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agektmr/Responsive-Resource-Loader/HEAD/app/images/responsive/abstract1-900.jpg
--------------------------------------------------------------------------------
/app/images/responsive/abstract2-320.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agektmr/Responsive-Resource-Loader/HEAD/app/images/responsive/abstract2-320.jpg
--------------------------------------------------------------------------------
/app/images/responsive/abstract2-900.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agektmr/Responsive-Resource-Loader/HEAD/app/images/responsive/abstract2-900.jpg
--------------------------------------------------------------------------------
/app/images/responsive/abstract3-320.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agektmr/Responsive-Resource-Loader/HEAD/app/images/responsive/abstract3-320.jpg
--------------------------------------------------------------------------------
/app/images/responsive/abstract3-900.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agektmr/Responsive-Resource-Loader/HEAD/app/images/responsive/abstract3-900.jpg
--------------------------------------------------------------------------------
/app/images/responsive/illustration-home-inverted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agektmr/Responsive-Resource-Loader/HEAD/app/images/responsive/illustration-home-inverted.png
--------------------------------------------------------------------------------
/app/images/responsive/illustration-home-inverted-320.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agektmr/Responsive-Resource-Loader/HEAD/app/images/responsive/illustration-home-inverted-320.png
--------------------------------------------------------------------------------
/app/images/responsive/illustration-home-inverted-900.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agektmr/Responsive-Resource-Loader/HEAD/app/images/responsive/illustration-home-inverted-900.png
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "resposive-resource-loader",
3 | "private": true,
4 | "dependencies": {
5 | "bootstrap-sass-official": "~3.2.0",
6 | "modernizr": "~2.8.2"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/test/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "private": true,
4 | "dependencies": {
5 | "chai": "~1.8.0",
6 | "mocha": "~1.14.0"
7 | },
8 | "devDependencies": {}
9 | }
10 |
--------------------------------------------------------------------------------
/test/spec/test.js:
--------------------------------------------------------------------------------
1 | /* global describe, it */
2 |
3 | (function () {
4 | 'use strict';
5 |
6 | describe('Give it some context', function () {
7 | describe('maybe a bit more context here', function () {
8 | it('should run here few assertions', function () {
9 |
10 | });
11 | });
12 | });
13 | })();
14 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "browser": true,
4 | "esnext": true,
5 | "bitwise": true,
6 | "camelcase": true,
7 | "curly": true,
8 | "eqeqeq": true,
9 | "immed": true,
10 | "indent": 2,
11 | "latedef": true,
12 | "newcap": true,
13 | "noarg": true,
14 | "quotmark": "single",
15 | "undef": true,
16 | "unused": true,
17 | "strict": true,
18 | "trailing": true,
19 | "smarttabs": true,
20 | "jquery": true
21 | }
22 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 |
10 | # Change these settings to your own preference
11 | indent_style = space
12 | indent_size = 2
13 |
14 | # We recommend you to keep these unchanged
15 | end_of_line = lf
16 | charset = utf-8
17 | trim_trailing_whitespace = true
18 | insert_final_newline = true
19 |
20 | [*.md]
21 | trim_trailing_whitespace = false
22 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Mocha Spec Runner
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/responsive-manifest.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "media-query": {
4 | "max-width": 320,
5 | "min-device-pixel-ratio": 1
6 | },
7 | "extensions": [
8 | "png", "jpg"
9 | ],
10 | "suffix": "-320"
11 | }, {
12 | "media-query": {
13 | "max-width": 640,
14 | "min-device-pixel-ratio": 1
15 | },
16 | "extensions": [
17 | "png", "jpg"
18 | ],
19 | "suffix": ""
20 | }, {
21 | "media-query": {
22 | "max-width": 900,
23 | "min-device-pixel-ratio": 1
24 | },
25 | "extensions": [
26 | "png", "jpg"
27 | ],
28 | "suffix": "-900"
29 | }, {
30 | "media-query": {
31 | "min-width": 900,
32 | "min-device-pixel-ratio": 2
33 | },
34 | "extensions": [
35 | "png", "jpg"
36 | ],
37 | "suffix": "-900"
38 | }
39 | ]
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "responsive-resource-loader",
3 | "devDependencies": {
4 | "apache-server-configs": "^2.7.1",
5 | "grunt": "^0.4.5",
6 | "grunt-autoprefixer": "^1.0.0",
7 | "grunt-concurrent": "^0.5.0",
8 | "grunt-contrib-clean": "^0.6.0",
9 | "grunt-contrib-concat": "^0.5.0",
10 | "grunt-contrib-connect": "^0.8.0",
11 | "grunt-contrib-copy": "^0.5.0",
12 | "grunt-contrib-cssmin": "^0.10.0",
13 | "grunt-contrib-htmlmin": "^0.3.0",
14 | "grunt-contrib-imagemin": "^0.8.0",
15 | "grunt-contrib-jshint": "^0.10.0",
16 | "grunt-contrib-sass": "^0.7.3",
17 | "grunt-contrib-uglify": "^0.5.1",
18 | "grunt-contrib-watch": "^0.6.1",
19 | "grunt-mocha": "^0.4.10",
20 | "grunt-modernizr": "^0.5.2",
21 | "grunt-newer": "^0.7.0",
22 | "grunt-rev": "^0.1.0",
23 | "grunt-svgmin": "^0.4.0",
24 | "grunt-usemin": "^2.3.0",
25 | "grunt-wiredep": "^1.7.0",
26 | "jshint-stylish": "^0.4.0",
27 | "load-grunt-tasks": "^0.4.0",
28 | "time-grunt": "^0.4.0"
29 | },
30 | "engines": {
31 | "node": ">=0.10.0"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/scripts/main.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var sw = null,
3 | viewInfo = null;
4 | var unregister = function() {
5 | sw.unregsiter();
6 | };
7 |
8 | var postViewInfo = function() {
9 | viewInfo = {
10 | 'width': window.innerWidth,
11 | 'height': window.innerHeight,
12 | 'device-pixel-ratio': window.devicePixelRatio
13 | };
14 | console.log('postViewInfo', viewInfo);
15 | navigator.serviceWorker.controller.postMessage({
16 | command: 'viewInfo',
17 | viewInfo: viewInfo
18 | });
19 | }
20 |
21 | window.addEventListener('resize', postViewInfo);
22 | window.addEventListener('message', function(e) {
23 | switch (e.data.command) {
24 | case 'viewInfo':
25 | postViewInfo();
26 | break;
27 |
28 | default:
29 | break;
30 | }
31 | });
32 |
33 | navigator.serviceWorker.register('scripts/worker.js', {
34 | scope: location.pathname
35 | }).then(function(_reg) {
36 | sw = _reg;
37 | postViewInfo();
38 | console.log(sw);
39 | }).catch(function(err) {
40 | console.error(err);
41 | });
42 |
43 | })();
44 |
--------------------------------------------------------------------------------
/app/styles/main.scss:
--------------------------------------------------------------------------------
1 | $icon-font-path: "../bower_components/bootstrap-sass-official/assets/fonts/bootstrap/";
2 | // bower:scss
3 | @import "bootstrap-sass-official/assets/stylesheets/_bootstrap.scss";
4 | // endbower
5 |
6 | .browsehappy {
7 | margin: 0.2em 0;
8 | background: #ccc;
9 | color: #000;
10 | padding: 0.2em 0;
11 | }
12 |
13 | /* Space out content a bit */
14 | body {
15 | padding-top: 20px;
16 | padding-bottom: 20px;
17 | }
18 |
19 | /* Everything but the jumbotron gets side spacing for mobile first views */
20 | .header,
21 | .marketing,
22 | .footer {
23 | padding-left: 15px;
24 | padding-right: 15px;
25 | }
26 |
27 | /* Custom page header */
28 | .header {
29 | border-bottom: 1px solid #e5e5e5;
30 |
31 | /* Make the masthead heading the same height as the navigation */
32 | h3 {
33 | margin-top: 0;
34 | margin-bottom: 0;
35 | line-height: 40px;
36 | padding-bottom: 19px;
37 | }
38 | }
39 |
40 | /* Custom page footer */
41 | .footer {
42 | padding-top: 19px;
43 | color: #777;
44 | border-top: 1px solid #e5e5e5;
45 | }
46 |
47 | .container-narrow > hr {
48 | margin: 30px 0;
49 | }
50 |
51 | /* Main marketing message and sign up button */
52 | .jumbotron {
53 | text-align: center;
54 | border-bottom: 1px solid #e5e5e5;
55 | .btn {
56 | font-size: 21px;
57 | padding: 14px 24px;
58 | }
59 | }
60 |
61 | /* Supporting marketing content */
62 | .marketing {
63 | margin: 40px 0;
64 | p + h4 {
65 | margin-top: 28px;
66 | }
67 | }
68 |
69 | /* Responsive: Portrait tablets and up */
70 | @media screen and (min-width: 768px) {
71 | .container {
72 | max-width: 730px;
73 | }
74 |
75 | /* Remove the padding we set earlier */
76 | .header,
77 | .marketing,
78 | .footer {
79 | padding-left: 0;
80 | padding-right: 0;
81 | }
82 |
83 | /* Space out the masthead */
84 | .header {
85 | margin-bottom: 30px;
86 | }
87 |
88 | /* Remove the bottom border on the jumbotron for visual effect */
89 | .jumbotron {
90 | border-bottom: 0;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Responsive Resource Loader
2 | This is a proof of concept implementation of RRL which gives you responsive images for free (almost) using ServiceWorker interception. No more `img[srcset]` and `` element!
3 |
4 | * Using huge images is redundant for mobile devices.
5 | * Assigning `srcset` or `picture` for every single `img` tag is a P.I.T.A.
6 | * Loading images with relevant size for the device is a pain.
7 |
8 | ## Example
9 | Write your `img` tag as you used to.
10 |
11 |
12 |
13 | You might expect to make it responsive using `srcset`. For example:
14 |
15 |
16 |
17 | But you don't want to write this for every single images. You can define a formula such as:
18 |
19 | - Append `-900` for all images with `640w 2x`
20 | ex) `agektmr-900.jpg`, `abstract1-900.jpg`, `abstract2-900.jpg`
21 | - Append `-640` for all images with `640w`
22 | ex) `agektmr-640.jpg`, `abstract1-640.jpg`, `abstract2-640.jpg`
23 |
24 | This will become a manifest file that looks like:
25 |
26 | [
27 | {
28 | "media-query": {
29 | "max-width": 320,
30 | "min-device-pixel-ratio": 1
31 | },
32 | "extensions": [
33 | "png", "jpg"
34 | ],
35 | "suffix": "-320"
36 | }, {
37 | "media-query": {
38 | "max-width": 640,
39 | "min-device-pixel-ratio": 1
40 | },
41 | "extensions": [
42 | "png", "jpg"
43 | ],
44 | "suffix": "-640"
45 | }, {
46 | "media-query": {
47 | "max-width": 900,
48 | "min-device-pixel-ratio": 1
49 | },
50 | "extensions": [
51 | "png", "jpg"
52 | ],
53 | "suffix": "-900"
54 | }, {
55 | "media-query": {
56 | "min-width": 900,
57 | "min-device-pixel-ratio": 2
58 | },
59 | "extensions": [
60 | "png", "jpg"
61 | ],
62 | "suffix": "-900"
63 | }
64 | ]
65 |
66 | # How to try this
67 | 1. clone this repository
68 | ```
69 | git clone git@github.com:agektmr/Responsive-Resource-Loader.git
70 | ```
71 | 2. install dependencies
72 | ```
73 | cd Responsive-Resource-Loader
74 | npm install
75 | bower install
76 | ```
77 | 3. run server
78 | ```
79 | grunt server
80 | ```
81 | 4. access on a browser at [http://localhost:9000/](http://localhost:9000/)
82 |
83 | The demo is not publicly available as ServiceWorker requires SSL connection.
84 |
85 | # Inspect in DevTools
86 | 1. access chrome://serviceworker-internals
87 | 2. click "Inspect" of registered ServiceWorker
88 | 3. using the device mode should help you try this easier. click on phone
89 | icon next to magnifier icon at top left of devtools. learn more: [DevBytes: Chrome DevTools Device Mode](https://www.youtube.com/watch?v=FrAZWiMWRa4)
90 |
91 |
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PortableCache
6 |
7 |
8 |
9 |
10 |
17 |
18 |
19 |
20 |
21 |
Responsive Resource Loader
22 |
No more hassle with <picture> and img[srcset].
23 |
24 | - Using huge images is redundant for mobile devices.
25 | - Assigning
srcset or picture for every single img tag is a P.I.T.A.
26 | - Loading images with relevant size for the device is a pain.
27 |
28 |
Get responsive images for free by just writing single manifest file that defines naming formula.
29 |
View on Github
30 |
31 |
32 |
Examples
33 |

34 |
Yeoman.
35 |

36 |
Photo by Jim Nix / Nomadic
37 |

38 |
Photo by zen
39 |

40 |
Photo by cobalt123
41 |
Write a manifest
42 |
Example Formula Manifest
43 |
[
44 | {
45 | "media-query": {
46 | "max-width": 320,
47 | "min-device-pixel-ratio": 1
48 | },
49 | "extensions": [
50 | "png", "jpg"
51 | ],
52 | "suffix": "-320"
53 | }, {
54 | "media-query": {
55 | "max-width": 640,
56 | "min-device-pixel-ratio": 1
57 | },
58 | "extensions": [
59 | "png", "jpg"
60 | ],
61 | "suffix": "-640"
62 | }, {
63 | "media-query": {
64 | "max-width": 900,
65 | "min-device-pixel-ratio": 1
66 | },
67 | "extensions": [
68 | "png", "jpg"
69 | ],
70 | "suffix": "-900"
71 | }, {
72 | "media-query": {
73 | "min-width": 900,
74 | "min-device-pixel-ratio": 2
75 | },
76 | "extensions": [
77 | "png", "jpg"
78 | ],
79 | "suffix": "-900"
80 | }
81 | ]
82 |
83 |
94 |
95 |
98 |
99 |
--------------------------------------------------------------------------------
/app/scripts/worker.js:
--------------------------------------------------------------------------------
1 | var ResponsiveResourceLoader = (function() {
2 | var DEFAULT_MANIFEST = [
3 | {
4 | "media-query": {
5 | "max-width": 320,
6 | "min-device-pixel-ratio": 1
7 | },
8 | "extensions": [
9 | "png", "jpg"
10 | ],
11 | "suffix": "-320"
12 | }, {
13 | "media-query": {
14 | "max-width": 640,
15 | "min-device-pixel-ratio": 1
16 | },
17 | "extensions": [
18 | "png", "jpg"
19 | ],
20 | "suffix": ""
21 | }, {
22 | "media-query": {
23 | "max-width": 900,
24 | "min-device-pixel-ratio": 1
25 | },
26 | "extensions": [
27 | "png", "jpg"
28 | ],
29 | "suffix": "-900"
30 | }, {
31 | "media-query": {
32 | "min-width": 900,
33 | "min-device-pixel-ratio": 2
34 | },
35 | "extensions": [
36 | "png", "jpg"
37 | ],
38 | "suffix": "-900"
39 | }
40 | ];
41 |
42 | var rrl = function() {
43 | var that = this;
44 | this.manifest = null;
45 | this.viewInfo = null;
46 | this.suffix = '';
47 |
48 | self.addEventListener('install', function(event) {
49 | console.log('install');
50 | fetch('/responsive-manifest.json').then(function(response) {
51 | return response.text();
52 | }, function(err) {
53 | console.error('Manifest couldn\'t be loaded:', err.message);
54 | that.manifest = DEFAULT_MANIFEST;
55 | }).then(function(txt) {
56 | console.log('response: ', txt);
57 | that.manifest = JSON.parse(txt);
58 | }).catch(function(err) {
59 | console.error('Invalid manifest:', err.message);
60 | console.info('Using default manifest instead.');
61 | that.manifest = DEFAULT_MANIFEST;
62 | });
63 | });
64 |
65 | self.addEventListener('activate', function(event) {
66 | console.log('activation');
67 | });
68 |
69 | var viewInfoLoadedPromise = new Promise(function(resolve, reject) {
70 | self.addEventListener('message', function(event) {
71 | switch (event.data.command) {
72 | case 'viewInfo':
73 | that.viewInfo = event.data.viewInfo;
74 | console.log('viewInfo: ', that.viewInfo);
75 | resolve();
76 | break;
77 |
78 | default:
79 | break;
80 | }
81 | });
82 | });
83 |
84 | self.addEventListener('fetch', function(event) {
85 | viewInfoLoadedPromise.then(function() {
86 | event.respondWith(
87 | that.convert(event.request.url)
88 | );
89 | });
90 | });
91 | };
92 | rrl.prototype.convert = function(url) {
93 | // TODO: change architecture of manifest and apply suffix depending on extension
94 | var suffix = this.getSuffix(url)
95 | // suffix is `null` if no match found
96 | if (suffix) {
97 | var url = url.replace(/\.(.{3,5}?)$/, suffix+'.$1')
98 | console.log('fetching: ', url);
99 | }
100 | return fetch(url);
101 | };
102 | rrl.prototype.getSuffix = function(url) {
103 | if (!this.manifest) {
104 | console.info('manifest not loaded yet.');
105 | return '';
106 | }
107 | console.log('window width: %s, height: %s, dpr: %s',
108 | this.viewInfo.width, this.viewInfo.height, this.viewInfo['device-pixel-ratio']);
109 | var last_match = null;
110 | for (var i = 0; i < this.manifest.length; i++) {
111 | var v = this.manifest[i];
112 | if (v.extensions !== '') {
113 | var regex = new RegExp('\.('+v.extensions.join('|')+')$');
114 |
115 | // if extension doesn't match, continue
116 | if (!regex.test(url)) continue;
117 | }
118 | var mq = v['media-query'];
119 | if (mq['min-width']) {
120 | if (this.viewInfo.width < mq['min-width']) continue;
121 | last_match = v;
122 | console.log('min-width: %s matched', mq['min-width']);
123 | }
124 | if (mq['max-width']) {
125 | if (this.viewInfo.width > mq['max-width']) continue;
126 | last_match = v;
127 | console.log('max-width: %s matched', mq['max-width']);
128 | }
129 | if (mq['min-height']) {
130 | if (this.viewInfo.height < mq['min-height']) continue;
131 | last_match = v;
132 | console.log('min-height: %s matched', mq['min-height']);
133 | }
134 | if (mq['max-height']) {
135 | if (this.viewInfo.height > mq['min-height']) continue;
136 | last_match = v;
137 | console.log('max-height: %s matched', mq['max-height']);
138 | }
139 | if (mq['min-device-pixel-ratio']) {
140 | if (this.viewInfo['device-pixel-ratio'] < mq['min-device-pixel-ratio']) continue;
141 | last_match = v;
142 | console.log('device-pixel-ratio: %s matched', mq['min-device-pixel-ratio']);
143 | }
144 |
145 | console.log('match found', v);
146 | return last_match['suffix'] || '';
147 | }
148 | return last_match && last_match['suffix'] || '';
149 | };
150 | return new rrl();
151 | })();
152 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | // Generated on 2014-10-21 using
2 | // generator-webapp 0.5.1
3 | 'use strict';
4 |
5 | // # Globbing
6 | // for performance reasons we're only matching one level down:
7 | // 'test/spec/{,*/}*.js'
8 | // If you want to recursively match all subfolders, use:
9 | // 'test/spec/**/*.js'
10 |
11 | module.exports = function (grunt) {
12 |
13 | // Time how long tasks take. Can help when optimizing build times
14 | require('time-grunt')(grunt);
15 |
16 | // Load grunt tasks automatically
17 | require('load-grunt-tasks')(grunt);
18 |
19 | // Configurable paths
20 | var config = {
21 | app: 'app',
22 | dist: 'dist'
23 | };
24 |
25 | // Define the configuration for all the tasks
26 | grunt.initConfig({
27 |
28 | // Project settings
29 | config: config,
30 |
31 | // Watches files for changes and runs tasks based on the changed files
32 | watch: {
33 | bower: {
34 | files: ['bower.json'],
35 | tasks: ['wiredep']
36 | },
37 | js: {
38 | files: ['<%= config.app %>/scripts/{,*/}*.js'],
39 | tasks: ['jshint'],
40 | options: {
41 | livereload: true
42 | }
43 | },
44 | jstest: {
45 | files: ['test/spec/{,*/}*.js'],
46 | tasks: ['test:watch']
47 | },
48 | gruntfile: {
49 | files: ['Gruntfile.js']
50 | },
51 | sass: {
52 | files: ['<%= config.app %>/styles/{,*/}*.{scss,sass}'],
53 | tasks: ['sass:server', 'autoprefixer']
54 | },
55 | styles: {
56 | files: ['<%= config.app %>/styles/{,*/}*.css'],
57 | tasks: ['newer:copy:styles', 'autoprefixer']
58 | },
59 | livereload: {
60 | options: {
61 | livereload: '<%= connect.options.livereload %>'
62 | },
63 | files: [
64 | '<%= config.app %>/{,*/}*.html',
65 | '.tmp/styles/{,*/}*.css',
66 | '<%= config.app %>/images/{,*/}*'
67 | ]
68 | }
69 | },
70 |
71 | // The actual grunt server settings
72 | connect: {
73 | options: {
74 | port: 9000,
75 | open: true,
76 | livereload: 35729,
77 | // Change this to '0.0.0.0' to access the server from outside
78 | hostname: 'localhost'
79 | },
80 | livereload: {
81 | options: {
82 | middleware: function(connect) {
83 | return [
84 | connect.static('.tmp'),
85 | connect().use('/bower_components', connect.static('./bower_components')),
86 | connect.static(config.app)
87 | ];
88 | }
89 | }
90 | },
91 | test: {
92 | options: {
93 | open: false,
94 | port: 9001,
95 | middleware: function(connect) {
96 | return [
97 | connect.static('.tmp'),
98 | connect.static('test'),
99 | connect().use('/bower_components', connect.static('./bower_components')),
100 | connect.static(config.app)
101 | ];
102 | }
103 | }
104 | },
105 | dist: {
106 | options: {
107 | base: '<%= config.dist %>',
108 | livereload: false
109 | }
110 | }
111 | },
112 |
113 | // Empties folders to start fresh
114 | clean: {
115 | dist: {
116 | files: [{
117 | dot: true,
118 | src: [
119 | '.tmp',
120 | '<%= config.dist %>/*',
121 | '!<%= config.dist %>/.git*'
122 | ]
123 | }]
124 | },
125 | server: '.tmp'
126 | },
127 |
128 | // Make sure code styles are up to par and there are no obvious mistakes
129 | jshint: {
130 | options: {
131 | jshintrc: '.jshintrc',
132 | reporter: require('jshint-stylish')
133 | },
134 | all: [
135 | 'Gruntfile.js',
136 | '<%= config.app %>/scripts/{,*/}*.js',
137 | '!<%= config.app %>/scripts/vendor/*',
138 | 'test/spec/{,*/}*.js'
139 | ]
140 | },
141 |
142 | // Mocha testing framework configuration options
143 | mocha: {
144 | all: {
145 | options: {
146 | run: true,
147 | urls: ['http://<%= connect.test.options.hostname %>:<%= connect.test.options.port %>/index.html']
148 | }
149 | }
150 | },
151 |
152 | // Compiles Sass to CSS and generates necessary files if requested
153 | sass: {
154 | options: {
155 | loadPath: 'bower_components'
156 | },
157 | dist: {
158 | files: [{
159 | expand: true,
160 | cwd: '<%= config.app %>/styles',
161 | src: ['*.{scss,sass}'],
162 | dest: '.tmp/styles',
163 | ext: '.css'
164 | }]
165 | },
166 | server: {
167 | files: [{
168 | expand: true,
169 | cwd: '<%= config.app %>/styles',
170 | src: ['*.{scss,sass}'],
171 | dest: '.tmp/styles',
172 | ext: '.css'
173 | }]
174 | }
175 | },
176 |
177 | // Add vendor prefixed styles
178 | autoprefixer: {
179 | options: {
180 | browsers: ['> 1%', 'last 2 versions', 'Firefox ESR', 'Opera 12.1']
181 | },
182 | dist: {
183 | files: [{
184 | expand: true,
185 | cwd: '.tmp/styles/',
186 | src: '{,*/}*.css',
187 | dest: '.tmp/styles/'
188 | }]
189 | }
190 | },
191 |
192 | // Automatically inject Bower components into the HTML file
193 | wiredep: {
194 | app: {
195 | ignorePath: /^\/|\.\.\//,
196 | src: ['<%= config.app %>/index.html'],
197 | exclude: ['bower_components/bootstrap-sass-official/assets/javascripts/bootstrap.js']
198 | },
199 | sass: {
200 | src: ['<%= config.app %>/styles/{,*/}*.{scss,sass}'],
201 | ignorePath: /(\.\.\/){1,2}bower_components\//
202 | }
203 | },
204 |
205 | // Renames files for browser caching purposes
206 | rev: {
207 | dist: {
208 | files: {
209 | src: [
210 | '<%= config.dist %>/scripts/{,*/}*.js',
211 | '<%= config.dist %>/styles/{,*/}*.css',
212 | '<%= config.dist %>/images/{,*/}*.*',
213 | '<%= config.dist %>/styles/fonts/{,*/}*.*',
214 | '<%= config.dist %>/*.{ico,png}'
215 | ]
216 | }
217 | }
218 | },
219 |
220 | // Reads HTML for usemin blocks to enable smart builds that automatically
221 | // concat, minify and revision files. Creates configurations in memory so
222 | // additional tasks can operate on them
223 | useminPrepare: {
224 | options: {
225 | dest: '<%= config.dist %>'
226 | },
227 | html: '<%= config.app %>/index.html'
228 | },
229 |
230 | // Performs rewrites based on rev and the useminPrepare configuration
231 | usemin: {
232 | options: {
233 | assetsDirs: [
234 | '<%= config.dist %>',
235 | '<%= config.dist %>/images',
236 | '<%= config.dist %>/styles'
237 | ]
238 | },
239 | html: ['<%= config.dist %>/{,*/}*.html'],
240 | css: ['<%= config.dist %>/styles/{,*/}*.css']
241 | },
242 |
243 | // The following *-min tasks produce minified files in the dist folder
244 | imagemin: {
245 | dist: {
246 | files: [{
247 | expand: true,
248 | cwd: '<%= config.app %>/images',
249 | src: '{,*/}*.{gif,jpeg,jpg,png}',
250 | dest: '<%= config.dist %>/images'
251 | }]
252 | }
253 | },
254 |
255 | svgmin: {
256 | dist: {
257 | files: [{
258 | expand: true,
259 | cwd: '<%= config.app %>/images',
260 | src: '{,*/}*.svg',
261 | dest: '<%= config.dist %>/images'
262 | }]
263 | }
264 | },
265 |
266 | htmlmin: {
267 | dist: {
268 | options: {
269 | collapseBooleanAttributes: true,
270 | collapseWhitespace: true,
271 | conservativeCollapse: true,
272 | removeAttributeQuotes: true,
273 | removeCommentsFromCDATA: true,
274 | removeEmptyAttributes: true,
275 | removeOptionalTags: true,
276 | removeRedundantAttributes: true,
277 | useShortDoctype: true
278 | },
279 | files: [{
280 | expand: true,
281 | cwd: '<%= config.dist %>',
282 | src: '{,*/}*.html',
283 | dest: '<%= config.dist %>'
284 | }]
285 | }
286 | },
287 |
288 | // By default, your `index.html`'s will take care
289 | // of minification. These next options are pre-configured if you do not
290 | // wish to use the Usemin blocks.
291 | // cssmin: {
292 | // dist: {
293 | // files: {
294 | // '<%= config.dist %>/styles/main.css': [
295 | // '.tmp/styles/{,*/}*.css',
296 | // '<%= config.app %>/styles/{,*/}*.css'
297 | // ]
298 | // }
299 | // }
300 | // },
301 | // uglify: {
302 | // dist: {
303 | // files: {
304 | // '<%= config.dist %>/scripts/scripts.js': [
305 | // '<%= config.dist %>/scripts/scripts.js'
306 | // ]
307 | // }
308 | // }
309 | // },
310 | // concat: {
311 | // dist: {}
312 | // },
313 |
314 | // Copies remaining files to places other tasks can use
315 | copy: {
316 | dist: {
317 | files: [{
318 | expand: true,
319 | dot: true,
320 | cwd: '<%= config.app %>',
321 | dest: '<%= config.dist %>',
322 | src: [
323 | '*.{ico,png,txt}',
324 | 'images/{,*/}*.webp',
325 | '{,*/}*.html',
326 | 'styles/fonts/{,*/}*.*'
327 | ]
328 | }, {
329 | src: 'node_modules/apache-server-configs/dist/.htaccess',
330 | dest: '<%= config.dist %>/.htaccess'
331 | }, {
332 | expand: true,
333 | dot: true,
334 | cwd: '.',
335 | src: 'bower_components/bootstrap-sass-official/assets/fonts/bootstrap/*',
336 | dest: '<%= config.dist %>'
337 | }]
338 | },
339 | styles: {
340 | expand: true,
341 | dot: true,
342 | cwd: '<%= config.app %>/styles',
343 | dest: '.tmp/styles/',
344 | src: '{,*/}*.css'
345 | }
346 | },
347 |
348 | // Generates a custom Modernizr build that includes only the tests you
349 | // reference in your app
350 | modernizr: {
351 | dist: {
352 | devFile: 'bower_components/modernizr/modernizr.js',
353 | outputFile: '<%= config.dist %>/scripts/vendor/modernizr.js',
354 | files: {
355 | src: [
356 | '<%= config.dist %>/scripts/{,*/}*.js',
357 | '<%= config.dist %>/styles/{,*/}*.css',
358 | '!<%= config.dist %>/scripts/vendor/*'
359 | ]
360 | },
361 | uglify: true
362 | }
363 | },
364 |
365 | // Run some tasks in parallel to speed up build process
366 | concurrent: {
367 | server: [
368 | 'sass:server',
369 | 'copy:styles'
370 | ],
371 | test: [
372 | 'copy:styles'
373 | ],
374 | dist: [
375 | 'sass',
376 | 'copy:styles',
377 | 'imagemin',
378 | 'svgmin'
379 | ]
380 | }
381 | });
382 |
383 |
384 | grunt.registerTask('serve', 'start the server and preview your app, --allow-remote for remote access', function (target) {
385 | if (grunt.option('allow-remote')) {
386 | grunt.config.set('connect.options.hostname', '0.0.0.0');
387 | }
388 | if (target === 'dist') {
389 | return grunt.task.run(['build', 'connect:dist:keepalive']);
390 | }
391 |
392 | grunt.task.run([
393 | 'clean:server',
394 | 'wiredep',
395 | 'concurrent:server',
396 | 'autoprefixer',
397 | 'connect:livereload',
398 | 'watch'
399 | ]);
400 | });
401 |
402 | grunt.registerTask('server', function (target) {
403 | grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
404 | grunt.task.run([target ? ('serve:' + target) : 'serve']);
405 | });
406 |
407 | grunt.registerTask('test', function (target) {
408 | if (target !== 'watch') {
409 | grunt.task.run([
410 | 'clean:server',
411 | 'concurrent:test',
412 | 'autoprefixer'
413 | ]);
414 | }
415 |
416 | grunt.task.run([
417 | 'connect:test',
418 | 'mocha'
419 | ]);
420 | });
421 |
422 | grunt.registerTask('build', [
423 | 'clean:dist',
424 | 'wiredep',
425 | 'useminPrepare',
426 | 'concurrent:dist',
427 | 'autoprefixer',
428 | 'concat',
429 | 'cssmin',
430 | 'uglify',
431 | 'copy:dist',
432 | 'modernizr',
433 | 'rev',
434 | 'usemin',
435 | 'htmlmin'
436 | ]);
437 |
438 | grunt.registerTask('default', [
439 | 'newer:jshint',
440 | 'test',
441 | 'build'
442 | ]);
443 | };
444 |
--------------------------------------------------------------------------------