├── .gitignore
├── LICENSE
├── README.md
├── Website.py
├── backend.buildspec.yml
├── demo.gif
├── deploy-lambda.sh
├── deploy-s3.sh
├── deploy.buildspec.yml
├── frontend.buildspec.yml
├── index.py
├── package-lambda.sh
├── public
├── .babelrc
├── .editorconfig
├── .eslintrc
├── .gitattributes
├── .gitignore
├── .yo-rc.json
├── README.md
├── gulp
│ ├── tasks
│ │ ├── browserSync.js
│ │ ├── browserify.js
│ │ ├── clean.js
│ │ ├── copy.js
│ │ ├── eslint.js
│ │ ├── handlebars.js
│ │ ├── imagemin.js
│ │ ├── nunjucks.js
│ │ ├── rev.js
│ │ ├── sass.js
│ │ └── watch.js
│ └── utils.js
├── gulpfile.babel.js
├── karma.conf.js
├── package-lock.json
├── package.json
└── src
│ ├── README.md
│ ├── _data
│ └── README.md
│ ├── _images
│ ├── README.md
│ └── floppy.jpg
│ ├── _layouts
│ ├── README.md
│ ├── base.nunjucks
│ └── partials
│ │ ├── carbon-ad.nunjucks
│ │ ├── google-analytics.nunjucks
│ │ └── issue-embed.nunjucks
│ ├── _modules
│ └── README.md
│ ├── _scripts
│ ├── README.md
│ └── main.js
│ ├── _styles
│ ├── README.md
│ ├── _carbon-ad.scss
│ ├── _github-fork.scss
│ ├── _skeleton-alerts.scss
│ └── main.sass
│ ├── _templates
│ ├── helpers.js
│ ├── output.hbs
│ └── partials
│ │ └── _multilineTableRow.hbs
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── apple-touch-icon.png
│ ├── browserconfig.xml
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ ├── index.nunjucks
│ ├── mstile-150x150.png
│ ├── robots.txt
│ ├── safari-pinned-tab.svg
│ └── site.webmanifest
└── serve.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | function.zip
4 | .vscode
5 | __pycache__
6 | v-env
7 | *.pyc
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) [2019] [Brendon Body]
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Fit on a Floppy
2 |
3 | Websites are getting bigger and bigger. The internet is getting faster and faster but not everywhere at the same pace. A floppy is a physical reminder of filesize.
4 |
5 | 
7 |
8 | - [Website](https://fitonafloppy.website/)
9 | - [Blog Post](https://www.brendonbody.com/2019/11/13/fit-on-a-floppy/)
10 | - [Hacker News Discussion](https://news.ycombinator.com/item?id=21609386)
11 |
12 | ## Architecture
13 |
14 | - **CodePipeline** - CI & CD
15 | - **S3** - Webhosting
16 | - **Lambda** - Serverless hosting
17 |
18 | ## Built with
19 |
20 | - Python *3.6* - Serverless function
21 | - NodeJS *12* - Frontend build process
22 | - Gulp - Build process
23 | - Handlebars - In code templating
24 | - Nunjucks - HTML templating
25 |
26 | ## Scripts
27 |
28 | - `package-lambda.sh` - add source to a zip
29 | - `deploy-s3.sh` - deploy frontend to S3
30 | - `deploy-lambda.sh` - deploy function to Lamdba
31 | - `serve.sh` - serve frontend locally
32 |
--------------------------------------------------------------------------------
/Website.py:
--------------------------------------------------------------------------------
1 | from bs4 import BeautifulSoup
2 | import requests
3 | import json
4 | import urllib.parse
5 | import math
6 |
7 | class Website:
8 | def __init__(self, url):
9 | self.url = Website.parseURL(url)
10 | self.friendly_url = Website.parseFriendlyURL(url)
11 | self.js_files = []
12 | self.css_files = []
13 | self.image_files = []
14 | self.favicon = False
15 | self.title = ''
16 | def parseWebsite(self):
17 | page = requests.get(self.url, stream=True)
18 | content = BeautifulSoup(page.content, 'html.parser')
19 |
20 | self.html_sizes = Website.calculate_file_size(page)
21 |
22 | self.setTitle(content)
23 | self.setFavicon(content)
24 | self.setJSFiles(content)
25 | self.setCSSFiles(content)
26 | self.setImageFiles(content)
27 | def setTitle(self, content):
28 | meta_title = content.select('meta[itemprop="name"]')
29 |
30 | if (len(meta_title) == 0):
31 | title = content.title.string
32 | else:
33 | title = meta_title[0]['content']
34 |
35 | self.title = title
36 | def setFavicon(self, content):
37 | favicon_selector = content.select('link[rel="shortcut icon"]')
38 |
39 | if (len(favicon_selector) > 0):
40 | favicon = favicon_selector[0]['href']
41 | else:
42 | favicon = False
43 | self.favicon = favicon
44 | def setJSFiles(self, content):
45 | js_query = content.select('script[src]')
46 | js_files = []
47 |
48 | for js in js_query:
49 | if ((not js.has_attr('defer')) and (not js.has_attr('async'))):
50 | js_files.append(js)
51 |
52 | self.js_files = Website.parse_files('Scripts', self.url, js_files, 'src')
53 | def setCSSFiles(self, content):
54 | css_files = content.select('link[rel="stylesheet"]')
55 | self.css_files = Website.parse_files('Styles and Fonts', self.url, css_files, 'href')
56 | def setImageFiles(self, content):
57 | image_query = content.select('img')
58 | image_files = []
59 |
60 | for image in image_query:
61 | if ((image.has_attr('src')) and (not image['src'].startswith('data:'))):
62 | image_files.append(image)
63 |
64 | self.image_files = Website.parse_files('Images', self.url, image_files, 'src')
65 | def getTotalSize(self):
66 | return self.html_sizes['bytes'] + self.css_files['total_size_bytes'] + self.js_files['total_size_bytes'] + self.image_files['total_size_bytes']
67 | def getWebsiteContent(self):
68 | try:
69 | self.parseWebsite()
70 | totalSize = self.getTotalSize()
71 |
72 | data = {
73 | 'title': self.title,
74 | 'favicon': self.favicon,
75 | 'js_files': self.js_files,
76 | 'css_files': self.css_files,
77 | 'image_files': self.image_files,
78 | 'html_size_bytes': self.html_sizes['bytes'],
79 | 'html_size_kibibytes': self.html_sizes['kibibytes'],
80 | 'html_size_kilobytes': self.html_sizes['kilobytes'],
81 | 'url': self.url,
82 | 'friendly_url': self.friendly_url,
83 | 'total_size_bytes': totalSize,
84 | 'total_size_kibibytes': totalSize/Website.BYTES_PER_KIB,
85 | 'total_size_kilobytes': totalSize/Website.BYTES_PER_KB,
86 | 'floppy_size_bytes': Website.FLOPPY_SIZE,
87 | 'floppy_size_kibibytes': Website.FLOPPY_SIZE/Website.BYTES_PER_KIB,
88 | 'floppy_size_kilobytes': Website.FLOPPY_SIZE/Website.BYTES_PER_KB,
89 | 'floppies': math.ceil(totalSize/Website.FLOPPY_SIZE)
90 | }
91 |
92 | return {
93 | 'statusCode': str(200),
94 | 'body': json.dumps(data),
95 | }
96 | except Exception as e:
97 | print(e)
98 | return {
99 | 'statusCode': str(400),
100 | 'body': {
101 | 'error': 'Error in processing website'
102 | },
103 | }
104 | # Static values and functions
105 | FLOPPY_SIZE = 1474560.0
106 | BYTES_PER_KIB = 1024.0
107 | BYTES_PER_KB = 1000.0
108 | @staticmethod
109 | def calculate_file_size(file_request):
110 | with file_request as response:
111 | size = sum(len(chunk) for chunk in response.iter_content(8))
112 |
113 | return {
114 | 'bytes': size,
115 | 'kibibytes': size/Website.BYTES_PER_KIB,
116 | 'kilobytes': size/Website.BYTES_PER_KB
117 | }
118 | @staticmethod
119 | def get_total_size(files):
120 | totalSize = 0
121 | for f in files:
122 | totalSize += f['bytes']
123 | return totalSize
124 | @staticmethod
125 | def parse_files(title, url, files, attribute):
126 | parsed_files = []
127 | for f in files:
128 | file_name_components = f[attribute].split('/')
129 | filename = file_name_components[len(file_name_components) - 1]
130 | full_filename = f[attribute]
131 |
132 | if (('https://' not in full_filename) and ('http://' not in full_filename)):
133 | full_filename = urllib.parse.urljoin(url, full_filename)
134 |
135 | asset_file = requests.get(full_filename, stream=True)
136 |
137 | file_sizes = Website.calculate_file_size(asset_file)
138 |
139 | parsed_files.append({
140 | 'file_path': full_filename,
141 | 'filename': filename,
142 | 'bytes': file_sizes['bytes'],
143 | 'kibibytes': file_sizes['kibibytes'],
144 | 'kilobytes': file_sizes['kilobytes'],
145 | })
146 | total_size = Website.get_total_size(parsed_files)
147 | return {
148 | 'files': parsed_files,
149 | 'title': title,
150 | 'total_size_bytes': total_size,
151 | 'total_size_kibibytes': total_size / Website.BYTES_PER_KIB,
152 | 'total_size_kilobytes': total_size / Website.BYTES_PER_KB
153 | }
154 | @staticmethod
155 | def parseFriendlyURL(url):
156 | url = url.replace('https://', '')
157 | url = url.replace('http://', '')
158 | return url
159 | @staticmethod
160 | def parseURL(url):
161 | if (('https://' not in url) and ('http://' not in url)):
162 | return 'https://' + url
163 | return url
--------------------------------------------------------------------------------
/backend.buildspec.yml:
--------------------------------------------------------------------------------
1 | # Do not change version. This is the version of aws buildspec, not the version of your buldspec file.
2 | version: 0.2
3 |
4 | phases:
5 | install:
6 | runtime-versions:
7 | python: 3.7
8 | commands:
9 | - echo Installing Python 3.6
10 | - wget https://www.python.org/ftp/python/3.6.0/Python-3.6.0.tar.xz
11 | - tar xJf Python-3.6.0.tar.xz
12 | - cd Python-3.6.0
13 | - ./configure
14 | - make
15 | - make install
16 | - mkdir package
17 | - echo Setup pip
18 | - python3 -m pip install --upgrade pip
19 | - cd ../
20 | - echo Installing Python libraries
21 | - pip install --target ./package beautifulsoup4
22 | - pip install --target ./package urllib3
23 | - pip install --target ./package requests
24 | - cd ./package
25 | - zip -r9 ../function.zip ./
26 | - cd ..
27 | build:
28 | commands:
29 | - echo Zip up application code
30 | - zip -g function.zip index.py
31 | - zip -g function.zip Website.py
32 | # Include only the files required for your application to run.
33 | # Do not use recursively include artifacts from node_modules directory as it will include unnecessary packages
34 | # used only for building and testing.
35 | # ExpressJS apps will need other artifact directories included (bin/*, public/*, routes/*, views/* etc).
36 | artifacts:
37 | files:
38 | - function.zip
39 | - deploy.buildspec.yml
40 | - deploy-lambda.sh
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbody-old/fit-on-a-floppy/7635603f00ed1a130bee9271195dc17b89bc3bf7/demo.gif
--------------------------------------------------------------------------------
/deploy-lambda.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | aws lambda update-function-code --function-name pageSize --region us-west-2 --zip-file fileb://./function.zip
--------------------------------------------------------------------------------
/deploy-s3.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | npm run --prefix ./public/ build
4 | npm run --prefix ./public/ deploy
--------------------------------------------------------------------------------
/deploy.buildspec.yml:
--------------------------------------------------------------------------------
1 | # Do not change version. This is the version of aws buildspec, not the version of your buldspec file.
2 | version: 0.2
3 |
4 | phases:
5 | install:
6 | runtime-versions:
7 | python: 3.7
8 | build:
9 | commands:
10 | - echo Deploy to Lambda
11 | - ./deploy-lambda.sh
--------------------------------------------------------------------------------
/frontend.buildspec.yml:
--------------------------------------------------------------------------------
1 | # Do not change version. This is the version of aws buildspec, not the version of your buldspec file.
2 | version: 0.2
3 |
4 | phases:
5 | install:
6 | runtime-versions:
7 | nodejs: 10
8 | commands:
9 | - echo Installing source NPM dependencies...
10 | - npm --prefix ./public/ install
11 | build:
12 | commands:
13 | - echo Building website
14 | - npm run --prefix ./public/ build
15 | post_build:
16 | commands:
17 | - rm *.sh
18 | - rm .gitignore
19 | - rm *.yml
20 | - rm LICENSE README.md
21 | - rm *.py
22 | - mv public/build/* ./
23 | - rm -r public
24 | artifacts:
25 | files:
26 | - '**/*'
--------------------------------------------------------------------------------
/index.py:
--------------------------------------------------------------------------------
1 | import json
2 | from Website import Website
3 |
4 | origin = "https://fitonafloppy.website"
5 |
6 | def handler(event, context):
7 | url = json.loads(event['body'])['url']
8 | is_https = json.loads(event['body'])['https']
9 |
10 | url = url.replace('https://', '')
11 | url = url.replace('http://', '')
12 |
13 | if (is_https):
14 | url = "https://" + url
15 | else:
16 | url = "http://" + url
17 |
18 | print("Processing " + url)
19 |
20 | website = Website(url)
21 | response = website.getWebsiteContent()
22 |
23 | response['headers'] = {
24 | 'Content-Type': 'application/json',
25 | 'Access-Control-Allow-Origin': origin
26 | }
27 |
28 | return response
29 |
30 | if __name__ == '__main__':
31 | event = {"body": "{\"url\": \"www.brendonbody.com\", \"https\": true}"}
32 | print(json.dumps(handler(event, None), indent = 4, sort_keys=True))
33 |
--------------------------------------------------------------------------------
/package-lambda.sh:
--------------------------------------------------------------------------------
1 | zip function.zip index.py Website.py
--------------------------------------------------------------------------------
/public/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/public/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://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]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/public/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": ["prettier"],
4 | "env": {
5 | "browser": true,
6 | "node": true,
7 | "es6": true
8 | },
9 | "globals": {
10 | "DocumentFragment": true,
11 | "it": true,
12 | "describe": true,
13 | "beforeEach": true,
14 | "runs": true,
15 | "spyOn": true,
16 | "spyOnEvent": true,
17 | "waitsFor": true,
18 | "expect": true,
19 | "afterEach": true
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/public/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
--------------------------------------------------------------------------------
/public/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.log
3 | build
4 | tmp
5 | .grunt
6 | .DS_Store
7 |
--------------------------------------------------------------------------------
/public/.yo-rc.json:
--------------------------------------------------------------------------------
1 | {
2 | "generator-yeogurt": {
3 | "config": {
4 | "projectName": "Fit On A Floppy",
5 | "htmlOption": "nunjucks",
6 | "cssOption": "sass",
7 | "sassSyntax": "sass",
8 | "testFramework": "jasmine"
9 | },
10 | "version": "3.0.1"
11 | }
12 | }
--------------------------------------------------------------------------------
/public/README.md:
--------------------------------------------------------------------------------
1 | # Fit On A Floppy readme
2 |
3 | Generated on 2019-09-19 using
4 | [generator-yeogurt@3.0.1](https://github.com/larsonjj/generator-yeogurt)
5 |
6 | ## Description
7 |
8 | This is an example readme file.
9 | Describe your site/app here.
10 |
11 | ## Technologies used
12 |
13 | JavaScript
14 |
15 | - [Browserify](http://browserify.org/)with ES6/2015 support through [Babel](https://babeljs.io/)
16 | - [Node](https://nodejs.org/)
17 |
18 | Testing
19 |
20 | - [Karma](http://karma-runner.github.io/4.0/index.html)
21 | - [Jasmine](http://jasmine.github.io/)
22 |
23 | Styles
24 |
25 | - [Sass](http://sass-lang.com/) via ([node-sass](https://github.com/sass/node-sass))
26 |
27 | Markup
28 | - [Nunjucks](https://mozilla.github.io/nunjucks/)
29 |
30 | Optimization
31 |
32 | - [Imagemin](https://github.com/imagemin/imagemin)
33 | - [Uglify](https://github.com/mishoo/UglifyJS)
34 |
35 | Server
36 |
37 | - [BrowserSync](http://www.browsersync.io/)
38 |
39 | Linting
40 |
41 | - [ESlint](http://eslint.org/)
42 |
43 | Automation
44 |
45 | - [Gulp](http://gulpjs.com)
46 |
47 | Code Management
48 |
49 | - [Editorconfig](http://editorconfig.org/)
50 | - [Git](https://git-scm.com/)
51 |
52 | ## Automated tasks
53 |
54 | This project uses [Gulp](http://gulpjs.com) and npm scripts (i.e. `npm run...`) to run automated tasks for development and production builds.
55 | The tasks are as follows:
56 |
57 | `npm run build`: Build a production version of your site/app
58 |
59 | `npm run serve`: Compiles preprocessors and boots up development server
60 | `npm run serve -- --open`: Same as `npm run serve` but will also open up site/app in your default browser
61 | `npm run serve:prod`: Same as `npm run serve` but will run all production tasks so you can view the site/app in it's final optimized form
62 |
63 | `npm test`: Lints all `*.js` file in the `source` folder using eslint and runs all `*.test.js` file unit tests through [Karma](http://karma-runner.github.io/0.13/index.html) and Jasmine
64 | `npm test -- --watch`: Same as `npm test` but will constantly watch `*.test.js` files and rerun tests when changes are detected
65 |
66 | **_Adding the `-- --debug` option to any npm script command to display extra debugging information (ex. data being loaded into your templates)_**
67 |
--------------------------------------------------------------------------------
/public/gulp/tasks/browserSync.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import gulp from 'gulp';
4 | import { plugins, args, config, taskTarget, browserSync } from '../utils';
5 |
6 | // BrowserSync
7 | gulp.task('browserSync', () => {
8 | return browserSync.init({
9 | open: args.open ? 'local' : false,
10 | startPath: config.baseUrl,
11 | port: config.port || 3000,
12 | server: {
13 | baseDir: taskTarget,
14 | routes: (() => {
15 | let routes = {};
16 |
17 | // Map base URL to routes
18 | routes[config.baseUrl] = taskTarget;
19 |
20 | return routes;
21 | })()
22 | }
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/public/gulp/tasks/browserify.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import path from 'path';
4 | import glob from 'glob';
5 | import gulp from 'gulp';
6 | import browserify from 'browserify';
7 | import watchify from 'watchify';
8 | import envify from 'envify';
9 | import babelify from 'babelify';
10 | import vsource from 'vinyl-source-stream';
11 | import buffer from 'vinyl-buffer';
12 | import gulpif from 'gulp-if';
13 | import { plugins, args, config, taskTarget, browserSync } from '../utils';
14 |
15 | let dirs = config.directories;
16 | let entries = config.entries;
17 |
18 | let browserifyTask = (files, done) => {
19 | return files.map(entry => {
20 | let dest = path.resolve(taskTarget);
21 |
22 | // Options
23 | let customOpts = {
24 | entries: [entry],
25 | debug: true,
26 | transform: [
27 | babelify, // Enable ES6 features
28 | envify // Sets NODE_ENV for better optimization of npm packages
29 | ]
30 | };
31 |
32 | let bundler = browserify(customOpts);
33 |
34 | if (!args.production) {
35 | // Setup Watchify for faster builds
36 | let opts = Object.assign({}, watchify.args, customOpts);
37 | bundler = watchify(browserify(opts));
38 | }
39 |
40 | let rebundle = function() {
41 | let startTime = new Date().getTime();
42 | bundler
43 | .bundle()
44 | .on('error', function(err) {
45 | plugins.util.log(
46 | plugins.util.colors.red('Browserify compile error:'),
47 | '\n',
48 | err.stack,
49 | '\n'
50 | );
51 | this.emit('end');
52 | })
53 | .pipe(vsource(entry))
54 | .pipe(buffer())
55 | .pipe(
56 | gulpif(!args.production, plugins.sourcemaps.init({ loadMaps: true }))
57 | )
58 | .pipe(gulpif(args.production, plugins.uglify()))
59 | .pipe(
60 | plugins.rename(function(filepath) {
61 | // Remove 'source' directory as well as prefixed folder underscores
62 | // Ex: 'src/_scripts' --> '/scripts'
63 | filepath.dirname = filepath.dirname
64 | .replace(dirs.source, '')
65 | .replace('_', '');
66 | })
67 | )
68 | .pipe(gulpif(!args.production, plugins.sourcemaps.write('./')))
69 | .pipe(gulp.dest(dest))
70 | // Show which file was bundled and how long it took
71 | .on('end', function() {
72 | let time = (new Date().getTime() - startTime) / 1000;
73 | plugins.util.log(
74 | plugins.util.colors.cyan(entry) +
75 | ' was browserified: ' +
76 | plugins.util.colors.magenta(time + 's')
77 | );
78 | done();
79 | return browserSync.reload('*.js');
80 | });
81 | };
82 |
83 | if (!args.production) {
84 | bundler.on('update', rebundle); // on any dep update, runs the bundler
85 | bundler.on('log', plugins.util.log); // output build logs to terminal
86 | }
87 | return rebundle();
88 | });
89 | };
90 |
91 | // Browserify Task
92 | gulp.task('browserify', done => {
93 | glob(`${path.join(dirs.source, dirs.scripts)}/${entries.js}`, function(
94 | err,
95 | files
96 | ) {
97 | if (err) {
98 | throw new Error(err);
99 | }
100 |
101 | return browserifyTask(files, done);
102 | });
103 | });
104 |
--------------------------------------------------------------------------------
/public/gulp/tasks/clean.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import gulp from 'gulp';
4 | import { plugins, args, config, taskTarget, browserSync } from '../utils';
5 | import del from 'del';
6 |
7 | let dirs = config.directories;
8 |
9 | // Clean
10 | gulp.task('clean', () => del([dirs.temporary, dirs.destination]));
11 |
--------------------------------------------------------------------------------
/public/gulp/tasks/copy.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import path from 'path';
4 | import gulp from 'gulp';
5 | import { plugins, args, config, taskTarget, browserSync } from '../utils';
6 |
7 | let dirs = config.directories;
8 | let dest = path.join(taskTarget);
9 |
10 | // Copy
11 | gulp.task('copy', () => {
12 | return gulp.src([
13 | '**/*',
14 | '!{**/\_*,**/\_*/**,*.md}',
15 | '!**/*.nunjucks'
16 | ], { cwd: dirs.source })
17 | .pipe(plugins.changed(dest))
18 | .pipe(gulp.dest(dest));
19 | });
20 |
--------------------------------------------------------------------------------
/public/gulp/tasks/eslint.js:
--------------------------------------------------------------------------------
1 | /*eslint no-process-exit:0 */
2 |
3 | 'use strict';
4 |
5 | import path from 'path';
6 | import gulpif from 'gulp-if';
7 | import gulp from 'gulp';
8 | import { plugins, args, config, taskTarget, browserSync } from '../utils';
9 |
10 | let dirs = config.directories;
11 |
12 | // ESLint
13 | gulp.task('eslint', () => {
14 | return gulp
15 | .src(
16 | [
17 | '../gulpfile.babel.js',
18 | '**/*.js',
19 | // Ignore all vendor folder files
20 | '!**/vendor/**/*'
21 | ],
22 | { cwd: dirs.source }
23 | )
24 | .pipe(browserSync.reload({ stream: true, once: true }))
25 | .pipe(
26 | plugins.eslint({
27 | useEslintrc: true
28 | })
29 | )
30 | .pipe(plugins.eslint.format())
31 | .pipe(gulpif(!browserSync.active, plugins.eslint.failAfterError()))
32 | .on('error', function() {
33 | if (!browserSync.active) {
34 | process.exit(1);
35 | }
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/public/gulp/tasks/handlebars.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var gulp = require('gulp');
3 | var wrap = require('gulp-wrap');
4 | var concat = require('gulp-concat');
5 | var hb = require('handlebars');
6 | var merge = require('merge-stream');
7 | var handlebars = require('../../src/_templates/helpers.js')(hb);
8 |
9 | import { config, taskTarget, browserSync } from '../utils';
10 |
11 | let dirs = config.directories;
12 | let entries = config.entries;
13 |
14 | let dest = path.join(taskTarget, dirs.scripts.replace(/^_/, ''));
15 |
16 | var gulp_handlebars = require('gulp-handlebars');
17 | var wrap = require('gulp-wrap');
18 | var declare = require('gulp-declare');
19 | var concat = require('gulp-concat');
20 | var uglify = require('gulp-uglify');
21 |
22 | gulp.task('handlebars', function(){
23 | var partials = gulp.src(entries.handlebars, { cwd: path.join(dirs.source, dirs.templates, 'partials')})
24 | .pipe(gulp_handlebars({handlebars: handlebars}))
25 | .pipe(wrap('Handlebars.registerPartial(<%= processPartialName(file.relative) %>, Handlebars.template(<%= contents %>));', {}, {
26 | imports: {
27 | processPartialName: function(fileName) {
28 | // Strip the extension and the underscore
29 | // Escape the output with JSON.stringify
30 | return JSON.stringify(path.basename(fileName, '.js').substr(1));
31 | }
32 | }
33 | }));
34 |
35 | var templates = gulp.src(entries.handlebars, { cwd: path.join(dirs.source, dirs.templates)})
36 | .pipe(gulp_handlebars({handlebars: handlebars}))
37 | .pipe(wrap('Handlebars.template(<%= contents %>)'))
38 | .pipe(declare({
39 | namespace: 'foaf',
40 | noRedeclare: true, // Avoid duplicate declarations
41 | }));
42 |
43 | return merge(partials, templates)
44 | .pipe(concat('templates.js'))
45 | .pipe(uglify())
46 | .pipe(gulp.dest(dest))
47 | .pipe(browserSync.stream({ match: 'scripts/templates**.js' }));
48 | });
--------------------------------------------------------------------------------
/public/gulp/tasks/imagemin.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import path from 'path';
4 | import gulpif from 'gulp-if';
5 | import pngquant from 'imagemin-pngquant';
6 | import gulp from 'gulp';
7 | import { plugins, args, config, taskTarget, browserSync } from '../utils';
8 |
9 | let dirs = config.directories;
10 | let dest = path.join(taskTarget, dirs.images.replace(/^_/, ''));
11 |
12 | // Imagemin
13 | gulp.task('imagemin', () => {
14 | return gulp
15 | .src('**/*.{jpg,jpeg,gif,svg,png}', {
16 | cwd: path.join(dirs.source, dirs.images)
17 | })
18 | .pipe(plugins.changed(dest))
19 | .pipe(
20 | gulpif(
21 | args.production,
22 | plugins.imagemin(
23 | [
24 | plugins.imagemin.jpegtran({ progressive: true }),
25 | plugins.imagemin.svgo({ plugins: [{ removeViewBox: false }] })
26 | ],
27 | { use: [pngquant({ speed: 10 })] }
28 | )
29 | )
30 | )
31 | .pipe(gulp.dest(dest));
32 | });
33 |
--------------------------------------------------------------------------------
/public/gulp/tasks/nunjucks.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import fs from 'fs';
4 | import path from 'path';
5 | import foldero from 'foldero';
6 | import nunjucks from 'gulp-nunjucks-html';
7 | import yaml from 'js-yaml';
8 | import gulp from 'gulp';
9 | import { plugins, args, config, taskTarget, browserSync } from '../utils';
10 |
11 | let dirs = config.directories;
12 | let dest = path.join(taskTarget);
13 | let dataPath = path.join(dirs.source, dirs.data);
14 |
15 | // Nunjucks template compile
16 | gulp.task('nunjucks', () => {
17 | let siteData = {};
18 | if (fs.existsSync(dataPath)) {
19 | // Convert directory to JS Object
20 | siteData = foldero(dataPath, {
21 | recurse: true,
22 | whitelist: '(.*/)*.+.(json|ya?ml)$',
23 | loader: function loadAsString(file) {
24 | let json = {};
25 | try {
26 | if (path.extname(file).match(/^.ya?ml$/)) {
27 | json = yaml.safeLoad(fs.readFileSync(file, 'utf8'));
28 | } else {
29 | json = JSON.parse(fs.readFileSync(file, 'utf8'));
30 | }
31 | } catch (e) {
32 | plugins.util.log(`Error Parsing DATA file: ${file}`);
33 | plugins.util.log('==== Details Below ====');
34 | plugins.util.log(e);
35 | }
36 | return json;
37 | }
38 | });
39 | }
40 |
41 | // Add --debug option to your gulp task to view
42 | // what data is being loaded into your templates
43 | if (args.debug) {
44 | plugins.util.log('==== DEBUG: site.data being injected to templates ====');
45 | plugins.util.log(siteData);
46 | plugins.util.log('\n==== DEBUG: package.json config being injected to templates ====');
47 | plugins.util.log(config);
48 | }
49 |
50 | return (
51 | gulp
52 | // Ignore underscore prefix folders/files (ex. _custom-layout.nunjucks)
53 | .src(['**/*.nunjucks', '!{**/_*,**/_*/**}'], { cwd: dirs.source })
54 | .pipe(plugins.changed(dest))
55 | .pipe(plugins.plumber())
56 | .pipe(
57 | plugins.data({
58 | config: config,
59 | debug: !args.production,
60 | site: {
61 | data: siteData
62 | }
63 | })
64 | )
65 | .pipe(
66 | nunjucks({
67 | searchPaths: [path.join(dirs.source)],
68 | ext: '.html'
69 | }).on('error', function(err) {
70 | plugins.util.log(err);
71 | })
72 | )
73 | .on('error', function(err) {
74 | plugins.util.log(err);
75 | })
76 | .pipe(
77 | plugins.htmlmin({
78 | collapseBooleanAttributes: true,
79 | conservativeCollapse: true,
80 | removeCommentsFromCDATA: true,
81 | removeEmptyAttributes: true,
82 | removeRedundantAttributes: true
83 | })
84 | )
85 | .pipe(gulp.dest(dest))
86 | .on('end', browserSync.reload)
87 | );
88 | });
89 |
--------------------------------------------------------------------------------
/public/gulp/tasks/rev.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import path from 'path';
4 | import gulpFilter from 'gulp-filter';
5 | import gulpRev from 'gulp-rev';
6 | import gulpRevDel from 'gulp-rev-delete-original';
7 | import gulpRevRewrite from 'gulp-rev-rewrite';
8 | import gulp from 'gulp';
9 | import { plugins, args, config, taskTarget, browserSync } from '../utils';
10 |
11 | let dirs = config.directories;
12 | let dest = path.join(taskTarget);
13 |
14 | // Copy
15 | gulp.task('rev', () => {
16 | // gulp-rev-rewrite will mangle binary files (images, etc), so ignore them
17 | const binaryAssetFilter = gulpFilter(
18 | ['**', '!**/*.{ico,png,jpg,jpeg,gif,webp}'],
19 | { restore: true }
20 | );
21 | const htmlFilter = gulpFilter(['**', '!**/*.html'], { restore: true });
22 | return gulp
23 | .src(`**/*.{js,css,html}`, { cwd: dirs.destination })
24 | .pipe(htmlFilter)
25 | .pipe(gulpRev())
26 | .pipe(htmlFilter.restore)
27 | .pipe(binaryAssetFilter)
28 | .pipe(gulpRevRewrite())
29 | .pipe(binaryAssetFilter.restore)
30 | .pipe(gulp.dest(dest))
31 | .pipe(gulpRevDel())
32 | .pipe(gulpRev.manifest())
33 | .pipe(gulp.dest(dest));
34 | });
35 |
--------------------------------------------------------------------------------
/public/gulp/tasks/sass.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import path from 'path';
4 | import autoprefixer from 'autoprefixer';
5 | import gulpif from 'gulp-if';
6 | import gulp from 'gulp';
7 | import { plugins, args, config, taskTarget, browserSync } from '../utils';
8 |
9 | let dirs = config.directories;
10 | let entries = config.entries;
11 | let dest = path.join(taskTarget, dirs.styles.replace(/^_/, ''));
12 |
13 | // Sass compilation
14 | gulp.task('sass', () => {
15 | return gulp
16 | .src(entries.css, { cwd: path.join(dirs.source, dirs.styles) })
17 | .pipe(plugins.plumber())
18 | .pipe(gulpif(!args.production, plugins.sourcemaps.init({ loadMaps: true })))
19 | .pipe(
20 | plugins.sass({
21 | outputStyle: 'expanded',
22 | precision: 10,
23 | includePaths: [
24 | path.join(dirs.source, dirs.styles),
25 | path.join(dirs.source, dirs.modules)
26 | ]
27 | })
28 | )
29 | .on('error', function(err) {
30 | plugins.util.log(err);
31 | })
32 | .pipe(plugins.postcss([autoprefixer()]))
33 | .pipe(
34 | plugins.rename(function(path) {
35 | // Remove 'source' directory as well as prefixed folder underscores
36 | // Ex: 'src/_styles' --> '/styles'
37 | path.dirname = path.dirname.replace(dirs.source, '').replace('_', '');
38 | })
39 | )
40 | .pipe(gulpif(args.production, plugins.cssnano({ rebase: false })))
41 | .pipe(gulpif(!args.production, plugins.sourcemaps.write('./')))
42 | .pipe(gulp.dest(dest))
43 | .pipe(browserSync.stream({ match: '**/*.css' }));
44 | });
45 |
--------------------------------------------------------------------------------
/public/gulp/tasks/watch.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import path from 'path';
4 | import gulp from 'gulp';
5 | import { plugins, args, config, taskTarget, browserSync } from '../utils';
6 |
7 | let dirs = config.directories;
8 |
9 | // Watch task
10 | gulp.task('watch', (done) => {
11 | if (!args.production) {
12 | // Styles
13 | gulp.watch([
14 | path.join(dirs.source, dirs.styles) + '/**/*.{scss,sass}',
15 | path.join(dirs.source, dirs.modules) + '/**/*.{scss,sass}'
16 | ], gulp.series('sass'));
17 |
18 | // Nunjucks Templates
19 | gulp.watch([
20 | dirs.source + '/**/*.nunjucks',
21 | path.join(dirs.source, dirs.data) + '/**/*.{json,yaml,yml}'
22 | ], gulp.series('nunjucks'));
23 |
24 |
25 | // Copy
26 | gulp.watch([
27 | dirs.source + '**/*',
28 | '!' + dirs.source + '/{**/\_*,**/\_*/**}',
29 | '!' + dirs.source + '/**/*.nunjucks'
30 | ], gulp.series('copy'));
31 |
32 | // Images
33 | gulp.watch([
34 | path.join(dirs.source, dirs.images) + '/**/*.{jpg,jpeg,gif,svg,png}'
35 | ], gulp.series('imagemin'));
36 |
37 | // Handlebars
38 |
39 | gulp.watch([
40 | path.join(dirs.source, dirs.templates) + '/**/*.hbs'
41 | ], gulp.series('handlebars'));
42 |
43 | // All other files
44 | gulp.watch([
45 | dirs.temporary + '/**/*',
46 | '!' + dirs.temporary + '/**/*.{css,map,html,js}'
47 | ]).on('change', browserSync.reload);
48 | }
49 | done();
50 | });
51 |
--------------------------------------------------------------------------------
/public/gulp/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import gulpLoadPlugins from 'gulp-load-plugins';
4 | import browserSyncLib from 'browser-sync';
5 | import pjson from '../package.json';
6 | import minimist from 'minimist';
7 |
8 | // Load all gulp plugins based on their names
9 | // EX: gulp-copy -> copy
10 | export const plugins = gulpLoadPlugins();
11 |
12 | // Create karma server
13 | export const KarmaServer = require('karma').Server;
14 |
15 | // Get package.json custom configuration
16 | export const config = Object.assign({}, pjson.config);
17 |
18 | // Gather arguments passed to gulp commands
19 | export const args = minimist(process.argv.slice(2));
20 |
21 | // Alias config directories
22 | export const dirs = config.directories;
23 |
24 | // Determine gulp task target destinations
25 | export const taskTarget = args.production ? dirs.destination : dirs.temporary;
26 |
27 | // Create a new browserSync instance
28 | export const browserSync = browserSyncLib.create();
29 |
--------------------------------------------------------------------------------
/public/gulpfile.babel.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import path from 'path';
4 | import gulp from 'gulp';
5 | import glob from 'glob';
6 | import { KarmaServer, args } from './gulp/utils';
7 |
8 | // This will grab all js in the `gulp` directory
9 | // in order to load all gulp tasks
10 | glob.sync('./gulp/tasks/**/*.js').filter(function(file) {
11 | return (/\.(js)$/i).test(file);
12 | }).map(function(file) {
13 | require(file);
14 | });
15 |
16 | // Build production-ready code
17 | gulp.task('build', gulp.series(
18 | gulp.parallel(
19 | 'copy',
20 | 'handlebars',
21 | 'imagemin',
22 | 'nunjucks',
23 | 'sass',
24 | 'browserify'
25 | ),
26 | 'rev'
27 | ));
28 |
29 | // Server tasks with watch
30 | gulp.task('serve', gulp.series(
31 | gulp.parallel(
32 | 'handlebars',
33 | 'imagemin',
34 | 'copy',
35 | 'nunjucks',
36 | 'sass',
37 | 'browserify',
38 | 'browserSync',
39 | 'watch'
40 | )
41 | ));
42 |
43 | // Default task
44 | gulp.task('default', gulp.series('clean', 'build'));
45 |
46 | // Testing
47 | gulp.task('test', gulp.series('eslint', (done) => {
48 | new KarmaServer({
49 | configFile: path.join(__dirname, '/karma.conf.js'),
50 | singleRun: !args.watch,
51 | autoWatch: args.watch
52 | }, done).start();
53 | }));
54 |
--------------------------------------------------------------------------------
/public/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // http://karma-runner.github.io/0.12/config/configuration-file.html
3 | 'use strict';
4 | var path = require('path');
5 | var pjson = require('./package.json');
6 | var config = pjson.config;
7 | var dirs = config.directories;
8 | var testFiles = path.join(__dirname, dirs.source) + '/**/*.test.js';
9 | var preprocessors = {};
10 | preprocessors[testFiles] = ['browserify'];
11 | const puppeteer = require('puppeteer');
12 | process.env.CHROME_BIN = puppeteer.executablePath();
13 |
14 | var karmaConf = function(config) {
15 | config.set({
16 | // base path, that will be used to resolve files and exclude
17 | basePath: '',
18 |
19 | // testing framework to use (jasmine/mocha/qunit/...)
20 | frameworks: ['browserify', 'jasmine'],
21 |
22 | // list of files / patterns to load in the browser
23 | files: [
24 | testFiles
25 | ],
26 |
27 | // list of files to exclude
28 | exclude: [],
29 |
30 | preprocessors: preprocessors,
31 |
32 | browserify: {
33 | debug: true,
34 | transform: [
35 | require('envify'),
36 | require('babelify')
37 | ]
38 | },
39 |
40 | // test results reporter to use
41 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
42 | reporters: ['progress'],
43 |
44 | // Setup to allow external devices to access karma tests
45 | // Change to '127.0.0.1' to disallow external access
46 | host: '0.0.0.0',
47 |
48 | // web server port
49 | port: 3012,
50 |
51 | // enable / disable colors in the output (reporters and logs)
52 | colors: true,
53 |
54 |
55 | // level of logging
56 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
57 | logLevel: config.LOG_INFO,
58 |
59 |
60 | // enable / disable watching file and executing tests whenever any file changes
61 | autoWatch: false,
62 |
63 |
64 | // Start these browsers, currently available:
65 | // - Chrome
66 | // - ChromeHeadless
67 | // - ChromeCanary
68 | // - Firefox
69 | // - Opera
70 | // - Safari (only Mac)
71 | // - PhantomJS
72 | // - IE (only Windows)
73 | browsers: ['ChromeHeadless'],
74 |
75 | // If browser does not capture in given timeout [ms], kill it
76 | captureTimeout: 60000,
77 |
78 | // If browser does not have any activity for given timeout [ms], kill it
79 | browserNoActivityTimeout: 60000,
80 |
81 | // Continuous Integration mode
82 | // if true, it capture browsers, run tests and exit
83 | singleRun: false
84 | });
85 | };
86 |
87 | module.exports = karmaConf;
88 |
--------------------------------------------------------------------------------
/public/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fit-on-a-floppy",
3 | "version": "0.0.1",
4 | "dependencies": {
5 | "jquery": "~3.5.0",
6 | "normalize.css": "~8.0.1"
7 | },
8 | "devDependencies": {
9 | "@babel/core": "~7.0.0",
10 | "@babel/preset-env": "~7.0.0",
11 | "@babel/register": "~7.4.4",
12 | "acorn": "~6.4.1",
13 | "autoprefixer": "~9.6.1",
14 | "babel-eslint": "~9.0.0",
15 | "babelify": "~10.0.0",
16 | "browser-sync": "~2.26.7",
17 | "browserify": "~16.3.0",
18 | "del": "~2.2.2",
19 | "envify": "~4.1.0",
20 | "eslint": "6.1.0",
21 | "eslint-config-prettier": "~6.0.0",
22 | "foldero": "~0.1.1",
23 | "glob": "~7.1.4",
24 | "gulp": "~4.0.2",
25 | "gulp-changed": "~4.0.0",
26 | "gulp-compile-handlebars": "^0.6.1",
27 | "gulp-concat": "^2.6.1",
28 | "gulp-cssnano": "~2.1.3",
29 | "gulp-data": "~1.3.1",
30 | "gulp-declare": "^0.3.0",
31 | "gulp-eslint": "~6.0.0",
32 | "gulp-filter": "6.0.0",
33 | "gulp-handlebars": "^5.0.2",
34 | "gulp-htmlmin": "~5.0.1",
35 | "gulp-if": "~2.0.2",
36 | "gulp-imagemin": "~6.0.0",
37 | "gulp-load-plugins": "~2.0.0",
38 | "gulp-nunjucks-html": "~2.0.0",
39 | "gulp-plumber": "~1.2.1",
40 | "gulp-postcss": "~8.0.0",
41 | "gulp-rename": "~1.4.0",
42 | "gulp-rev": "9.0.0",
43 | "gulp-rev-delete-original": "0.2.3",
44 | "gulp-rev-rewrite": "2.0.0",
45 | "gulp-sass": "~4.0.2",
46 | "gulp-sourcemaps": "~2.6.5",
47 | "gulp-uglify": "^3.0.2",
48 | "gulp-util": "~3.0.8",
49 | "gulp-wrap": "^0.15.0",
50 | "handlebars": "^4.2.0",
51 | "imagemin-pngquant": "~7.0.0",
52 | "imagemin-svgo": "~7.0.0",
53 | "jasmine-core": "3.4.0",
54 | "js-yaml": "~3.13.0",
55 | "karma": "~4.2.0",
56 | "karma-browserify": "~6.1.0",
57 | "karma-chrome-launcher": "~3.0.0",
58 | "karma-jasmine": "~2.0.1",
59 | "lodash": "~4.17.4",
60 | "minimist": "~1.2.0",
61 | "nunjucks": "~3.2.0",
62 | "prettier": "~1.16.4",
63 | "puppeteer": "1.19.0",
64 | "rimraf": "2.6.3",
65 | "vinyl-buffer": "~1.0.1",
66 | "vinyl-source-stream": "~2.0.0",
67 | "watchify": "~3.11.0 "
68 | },
69 | "scripts": {
70 | "clean-deps": "npx rimraf node_modules",
71 | "clean": "npx gulp clean",
72 | "lint": "npx gulp eslint",
73 | "test": "npx gulp test",
74 | "format": "npx prettier --single-quote --write src/**/*.js",
75 | "serve": "NODE_ENV=development npx gulp serve",
76 | "serve:prod": "NODE_ENV=production npx gulp serve --production",
77 | "build": "gulp handlebars && NODE_ENV=production npx gulp --production",
78 | "deploy": "s3-deploy './build/**' --cwd './build/' --region=us-west-2 --bucket=fitonafloppy.website --deleteRemoved --gzip=xml,html,htm,js,css,ttf,otf,svg,txt"
79 | },
80 | "engines": {
81 | "node": ">=8.16.0"
82 | },
83 | "browserslist": [
84 | "last 2 version",
85 | "> 5%",
86 | "safari 5",
87 | "ios 6",
88 | "android 4"
89 | ],
90 | "//": "CUSTOM CONFIGURATION",
91 | "config": {
92 | "//": "Entry files",
93 | "host": "127.0.0.1",
94 | "port": "3000",
95 | "baseUrl": "./",
96 | "directories": {
97 | "source": "src",
98 | "destination": "build",
99 | "temporary": "tmp",
100 | "//": "Directories relative to `source` directory",
101 | "modules": "_modules",
102 | "templates": "_templates",
103 | "layouts": "_layouts",
104 | "images": "_images",
105 | "styles": "_styles",
106 | "scripts": "_scripts",
107 | "data": "_data"
108 | },
109 | "entries": {
110 | "js": "main**.js",
111 | "css": "main**.{sass,scss}",
112 | "handlebars": "**.hbs"
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/public/src/README.md:
--------------------------------------------------------------------------------
1 | # Source
2 |
3 | This "Source" folder is where all of your files associated with this site will go
4 | and is considered the root ('/') of your site.
5 | This is also where all of your pages will be generated when using the [page subgenerator](#Subgenerator).
6 |
7 | ## Pages
8 |
9 | Pages are the main driver for static sites and also determine your site's routes.
10 | All page templates (except index.{pug,nunjucks}) should be placed in a folder named by your desired route.
11 | For example, a contact page would most likely be loaded at the `/contact` route.
12 | You would acheive this by creating the following structure:
13 |
14 | ```
15 | └── src
16 | └── contact
17 | └── index.{pug,nunjucks}
18 | ```
19 |
20 | ### Subgenerator
21 |
22 | You can easily create new pages using the built-in sub-generator like so:
23 |
24 | ```
25 | yo yeogurt:page about
26 | ```
27 |
28 | This will create the structure you saw above:
29 |
30 | ```
31 | └── src
32 | └── about
33 | └── index.{pug,nunjucks}
34 | ```
35 |
36 | So when you boot up your site and go to `/about` you will see your new page.
37 |
38 | ### Specifying a layout
39 |
40 | You can also create a new page that extends from a different layout file than `base.{pug,nunjucks}`.
41 |
42 | ```
43 | yo yeogurt:page about --layout=two-col
44 | ```
45 |
--------------------------------------------------------------------------------
/public/src/_data/README.md:
--------------------------------------------------------------------------------
1 | # Data
2 |
3 | This "Data" folder is the designated location for `json` and `yaml` file data
4 | that will be injected into your templates under the `site.data` property.
5 |
6 | ## Example
7 |
8 | If you have two data files in this data folder with the following contents:
9 |
10 | ```
11 | └── _data
12 | ├── global.yml
13 | └── menu.json
14 | ```
15 |
16 | ***global.yml***
17 |
18 | ```yml
19 | siteName: Sample
20 | ```
21 |
22 | ***menu.json***
23 |
24 | ```json
25 | [{
26 | "name": "Home"
27 | },{
28 | "name": "About"
29 | }]
30 | ```
31 |
32 | They would be converted to the following object:
33 |
34 | ```js
35 | {
36 | menu: [{
37 | name: "Home"
38 | }, {
39 | name: "About"
40 | }],
41 | global: {
42 | siteName: "Sample"
43 | }
44 | }
45 | ```
46 |
47 | And would then be injected into your template within the `site.data` property
48 | so you could access your data like so:
49 |
50 | ```nunjucks
51 |
{{site.data.global.siteName}}
52 |
53 | {% for val in site.data.menu %}
54 |
{{ val.name }}
55 | {% endfor %}
56 |
57 | ```
58 |
--------------------------------------------------------------------------------
/public/src/_images/README.md:
--------------------------------------------------------------------------------
1 | # Images
2 |
3 | This "Images" folder is designated for all image files (jpg, jpeg, png, gif, svg).
4 | During development and production builds, images will be compressed using [imagemin](https://github.com/imagemin/imagemin).
5 |
--------------------------------------------------------------------------------
/public/src/_images/floppy.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbody-old/fit-on-a-floppy/7635603f00ed1a130bee9271195dc17b89bc3bf7/public/src/_images/floppy.jpg
--------------------------------------------------------------------------------
/public/src/_layouts/README.md:
--------------------------------------------------------------------------------
1 | # Layouts
2 |
3 | This "Layouts" folder is designated for all page layouts.
4 |
5 | ## Example
6 |
7 | An example layout:
8 |
9 | ```nunjucks
10 | {% extends "base.nunjucks" %}
11 |
12 | //- Add extra stylesheets
13 | {% block stylesheets %}{% endblock %}
14 |
15 | {% block content %}
16 | //- Provides layout level markup
17 |
18 | {% block first %}
19 | //- Add first column content here
20 | {% endblock %}
21 | {% block second %}
22 | //- Add second column content here
23 | {% endblock %}
24 |
25 | {% endblock %}
26 |
27 | //- Add extra scripts
28 | {% block scripts %}{% endblock }
29 | ```
30 |
31 | > NOTE: The `append stylesheets` and `append scripts` blocks allow you to add on any layout-specific scripts or stylesheets.
32 | > The `content` block is overriding the parent `base.pug` file's block by the same name since we are extending from it.
33 | > The `first` and `second` blocks can contain default markup, but also allow you to extend from this layout and overwrite them.
34 | > You can read more about extensions and blocks on the [Pug website](https://pugjs.org/api/reference.html)
35 |
36 | ## Sub-generator
37 |
38 | You can easily create new layouts using the built-in sub-generator like so:
39 |
40 | ````sh
41 |
42 | yo yeogurt:layout two-col
43 |
44 | ```
45 |
46 | ### Extend from a layout other than `base`
47 |
48 | You can also create a new layout that extends from a different layout file than `base.pug`.
49 |
50 | ```sh
51 |
52 | yo yeogurt:layout three-col --layout=two-col
53 |
54 | ````
55 |
56 | This new layout will look something like this:
57 |
58 | ```nunjucks
59 | {% extends "two-col.nunjucks" %}
60 |
61 | //- Add extra stylesheets
62 | {% block stylesheets %}{% endblock %}
63 |
64 | {% block content %}
65 | //- Provides layout level markup
66 |
13 | Webpages are getting bigger and bigger. The internet is getting faster and faster but not everywhere at the same pace. A floppy is a physical reminder of filesize.
14 |
15 | Assets Audited (On page load):
16 |