├── .npmrc ├── .yarnrc ├── .forestry ├── snippets │ ├── link.snippet │ └── table.snippet └── settings.yml ├── .gitignore ├── src ├── index.md ├── scripts │ ├── main.js │ ├── .babelrc │ └── .eslintrc ├── robots.txt ├── styles │ └── main.css ├── 404.md ├── _layouts │ └── default.njk ├── CNAME.11ty.js └── _shortcodes │ └── image.js ├── .eslintrc ├── .stylelintrc ├── .babelrc ├── lib ├── filters.js ├── constants.js └── nunjucks │ └── tags │ └── link.js ├── .postcssrc ├── .parcelrc ├── tailwind.config.js ├── LICENSE.txt ├── eleventy.config.js ├── package.json └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | node-options="--require @babel/register" 2 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | env: 2 | NODE_OPTIONS "--require @babel/register" 3 | -------------------------------------------------------------------------------- /.forestry/snippets/link.snippet: -------------------------------------------------------------------------------- 1 | [Link Title]({% link "index.md" %}) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .parcel-cache/ 2 | dist/ 3 | node_modules/ 4 | tmp/ 5 | -------------------------------------------------------------------------------- /src/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Home 4 | --- 5 | Welcome. 6 | -------------------------------------------------------------------------------- /src/scripts/main.js: -------------------------------------------------------------------------------- 1 | console.log(process.env.NODE_ENV); // eslint-disable-line no-console 2 | -------------------------------------------------------------------------------- /src/robots.txt: -------------------------------------------------------------------------------- 1 | # www.robotstxt.org/ 2 | 3 | # Allow crawling of all content 4 | User-agent: * 5 | Disallow: 6 | -------------------------------------------------------------------------------- /src/styles/main.css: -------------------------------------------------------------------------------- 1 | /* Package modules. */ 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "airbnb-base", 4 | "parser": "babel-eslint", 5 | "reportUnusedDisableDirectives": true, 6 | "rules": {} 7 | } 8 | -------------------------------------------------------------------------------- /.forestry/snippets/table.snippet: -------------------------------------------------------------------------------- 1 | | Header | Header | Header | 2 | | ------ | ------ | ------ | 3 | | Cell | Cell | Cell | 4 | | Cell | Cell | Cell | 5 | | Cell | Cell | Cell | -------------------------------------------------------------------------------- /src/scripts/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["@babel/plugin-transform-runtime", { corejs: 3 }] 4 | ], 5 | "presets": [ 6 | ["@babel/preset-env", { loose: true, modules: false }] 7 | ] 8 | } -------------------------------------------------------------------------------- /src/404.md: -------------------------------------------------------------------------------- 1 | --- 2 | eleventyExcludeFromCollections: true 3 | layout: default 4 | permalink: 404.html 5 | title: Page Not Found 6 | --- 7 | Page not found. Go back [home]. 8 | 9 | [home]: {% link "index.md" %} 10 | -------------------------------------------------------------------------------- /src/scripts/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": false 5 | }, 6 | "globals": { 7 | "process": "readonly" 8 | }, 9 | "rules": { 10 | "no-console": "error" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-recommended", 3 | "plugins": [ 4 | "stylelint-no-unsupported-browser-features" 5 | ], 6 | "rules": { 7 | "plugin/no-unsupported-browser-features": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@babel/plugin-proposal-class-properties", 4 | "@babel/plugin-proposal-private-methods" 5 | ], 6 | "presets": [ 7 | ["@babel/preset-env", { 8 | "targets": { "node": "current" } 9 | }] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /lib/filters.js: -------------------------------------------------------------------------------- 1 | // @see https://www.11ty.io/docs/filters/ 2 | 3 | // Standard lib. 4 | import { inspect } from 'util'; 5 | 6 | // Exports. 7 | export default (value) => { 8 | console.debug(inspect(value, { colors: true })); // eslint-disable-line no-console 9 | return inspect(value); 10 | }; 11 | -------------------------------------------------------------------------------- /.forestry/settings.yml: -------------------------------------------------------------------------------- 1 | --- 2 | build: 3 | preview_env: 4 | - TARGET_ENV=staging 5 | preview_output_directory: dist/ 6 | install_dependencies_command: npm install 7 | preview_docker_image: node:10 8 | mount_path: "/srv" 9 | working_dir: "/srv" 10 | instant_preview_command: npm run watch 11 | -------------------------------------------------------------------------------- /.postcssrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": { 3 | "stylelint": {}, 4 | "postcss-preset-env": { 5 | "features": { 6 | "custom-properties": { "preserve": true }, 7 | "nesting-rules": true 8 | } 9 | }, 10 | "tailwindcss": {}, 11 | "postcss-reporter": { "clearReportedMessages": true } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.parcelrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@parcel/config-default", 3 | "namers": ["parcel-namer-custom", "..."], 4 | "optimizers": { 5 | "*.{gif,jpeg,jpg,png,svg,webp}": ["parcel-optimizer-imagemin", "..."], 6 | "*.html": ["parcel-optimizer-friendly-urls", "..."] 7 | }, 8 | "transformers": { 9 | "*.{gif,jpeg,jpg,png,svg,webp}": ["@parcel/transformer-raw"], 10 | "*.txt": ["@parcel/transformer-raw"] 11 | }, 12 | "validators": { 13 | "*.js": ["@parcel/validator-eslint"] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | // Local modules. 2 | import { config } from '~/package.json'; 3 | 4 | // Exports. 5 | export const { 6 | intermediate: INTERMEDIATE_DIRECTORY, 7 | input: INPUT_DIRECTORY, 8 | output: OUTPUT_DIRECTORY, 9 | } = config; 10 | 11 | // Don't rely on NODE_ENV as it is always set to production: 12 | // - https://github.com/parcel-bundler/parcel/issues/4550 13 | // - https://github.com/parcel-bundler/parcel/issues/5029 14 | export const PRODUCTION = process.env.BUILD_ENV === 'production'; 15 | -------------------------------------------------------------------------------- /src/_layouts/default.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ title }} 7 | 8 | 9 | 10 | {% if not eleventyExcludeFromCollections %} 11 | 12 | {% endif %} 13 | 14 | 15 | {{ content | safe }} 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/CNAME.11ty.js: -------------------------------------------------------------------------------- 1 | // @see https://www.11ty.io/docs/languages/javascript/ 2 | 3 | // Standard lib. 4 | import { join as joinPath } from 'path'; 5 | import { URL as NodeURL } from 'url'; 6 | 7 | // Package modules. 8 | import { 9 | OUTPUT_DIRECTORY, 10 | PRODUCTION, 11 | } from '~/lib/constants'; 12 | import { homepage } from '~/package.json'; 13 | 14 | // Constants. 15 | const CNAME_FILE = joinPath(OUTPUT_DIRECTORY, 'CNAME'); 16 | 17 | // Exports. 18 | module.exports = class CNameRecord { 19 | #hostname = null; 20 | 21 | data = { 22 | permalink: PRODUCTION && CNAME_FILE, // Enable only in production. 23 | permalinkBypassOutputDir: true, 24 | } 25 | 26 | constructor() { 27 | // Extract hostname from homepage. 28 | // If not a valid URL, disable CNAME generation altogether. 29 | try { 30 | this.#hostname = new NodeURL(homepage).hostname; 31 | } catch (e) { 32 | this.data.permalink = false; 33 | } 34 | } 35 | 36 | render() { 37 | return this.#hostname; 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | // @see https://tailwindcss.com/docs/configuration 2 | 3 | // Standard lib. 4 | import { join as joinPath } from 'path'; 5 | 6 | // Local modules. 7 | import { 8 | INPUT_DIRECTORY, 9 | PRODUCTION, 10 | } from './lib/constants'; 11 | 12 | // Constants. 13 | // @see https://www.11ty.io/docs/languages/ 14 | const ELEVENTY_TEMPLATE_LANGUAGES = [ 15 | 'html', 'md', '11ty.js', 'liquid', 'njk', 'hbs', 'mustache', 'ejs', 'haml', 'pug', 'jstl', 16 | ]; 17 | 18 | // Exports. 19 | module.exports = { 20 | // @see https://tailwindcss.com/docs/optimizing-for-production#writing-purgeable-html 21 | purge: { 22 | content: [joinPath(INPUT_DIRECTORY, `**/*.{${ELEVENTY_TEMPLATE_LANGUAGES}}`)], 23 | enabled: PRODUCTION, 24 | mode: 'all', // Remove all unused styles, not just Tailwinds'. 25 | options: { 26 | fontFace: true, 27 | keyframes: true, 28 | safelist: [], 29 | variables: true, 30 | }, 31 | }, 32 | 33 | darkMode: false, // or 'media' or 'class' 34 | theme: { 35 | extend: {}, 36 | }, 37 | variants: { 38 | extend: {}, 39 | }, 40 | plugins: [], 41 | }; 42 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Mark van Seventer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/_shortcodes/image.js: -------------------------------------------------------------------------------- 1 | // Package modules. 2 | import render from 'posthtml-render'; 3 | 4 | // Helpers. 5 | const generateSrcSet = (entries) => ( 6 | entries 7 | .map((entry) => entry.srcset) 8 | .join(', ') 9 | ); 10 | 11 | // Exports. 12 | export default (stats, options = {}) => { 13 | const { 14 | alt = '', 15 | sizes = '100vw', 16 | } = options; 17 | 18 | const formats = Object.keys(stats); 19 | const [firstFormat] = formats; 20 | 21 | // Use lowest-quality src as base img element. 22 | const [{ url, width, height }] = stats[firstFormat]; 23 | const lowestSrc = { 24 | tag: 'img', 25 | attrs: { 26 | src: url, 27 | width, 28 | height, 29 | alt, 30 | }, 31 | }; 32 | 33 | // Use single img element if there's only one format. 34 | if (formats.length === 1) { 35 | const entries = stats[firstFormat]; 36 | 37 | // Append srcset only if there's more than one entry. 38 | if (entries.length > 1) { 39 | lowestSrc.attrs = { 40 | ...lowestSrc.attrs, 41 | sizes, 42 | srcset: generateSrcSet(entries), 43 | }; 44 | } 45 | 46 | return render(lowestSrc); 47 | } 48 | 49 | // Use picture element otherwise. 50 | return render({ 51 | tag: 'picture', 52 | content: [ 53 | ...formats.map((format) => ({ 54 | tag: 'source', 55 | attrs: { 56 | sizes, 57 | srcset: generateSrcSet(stats[format]), 58 | type: `image/${format}`, 59 | }, 60 | })), 61 | lowestSrc, 62 | ], 63 | }); 64 | }; 65 | -------------------------------------------------------------------------------- /lib/nunjucks/tags/link.js: -------------------------------------------------------------------------------- 1 | // @see https://mozilla.github.io/nunjucks/api.html#custom-tags 2 | 3 | // Standard lib. 4 | import { resolve as resolvePath } from 'path'; 5 | import { inspect } from 'util'; 6 | 7 | // Local modules. 8 | import { INPUT_DIRECTORY } from '~/lib/constants'; 9 | 10 | // Exports. 11 | export default class LinkExtension { 12 | #memo = { }; 13 | 14 | #nunjucksEngine; 15 | 16 | tags = ['link']; 17 | 18 | static #instance = null; 19 | 20 | constructor(nunjucksEngine) { 21 | this.#nunjucksEngine = nunjucksEngine; 22 | } 23 | 24 | parse(parser, nodes /* , lexer */) { 25 | // Get the tag token. 26 | const tok = parser.nextToken(); 27 | 28 | // Parse the args and move after the block end. 29 | const args = parser.parseSignature(null, true); 30 | parser.advanceAfterBlockEnd(tok.value); 31 | 32 | return new nodes.CallExtension(this, 'run', args); 33 | } 34 | 35 | run({ ctx }, rawInputPath) { 36 | // Memoize result. 37 | const search = resolvePath(INPUT_DIRECTORY, rawInputPath); 38 | if (!Object.prototype.hasOwnProperty.call(this.#memo, search)) { 39 | const page = ctx.collections.all.find(({ inputPath }) => resolvePath(inputPath) === search); 40 | this.#memo[search] = page ? page.url : null; 41 | } 42 | 43 | // Return the result, or fail if no such page was found. 44 | const result = this.#memo[search]; 45 | if (result === null) { 46 | throw new Error(`Invalid link: no page for ${inspect(rawInputPath)}`); 47 | } 48 | return new this.#nunjucksEngine.runtime.SafeString(result); 49 | } 50 | 51 | static singleton(nunjucksEngine) { 52 | if (LinkExtension.#instance === null) { 53 | LinkExtension.#instance = new LinkExtension(nunjucksEngine); 54 | } 55 | return LinkExtension.#instance; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /eleventy.config.js: -------------------------------------------------------------------------------- 1 | // @see https://www.11ty.io/docs/config/ 2 | 3 | // Standard lib. 4 | import { 5 | basename, 6 | extname, 7 | join as joinPath, 8 | relative as relativePath, 9 | } from 'path'; 10 | 11 | // Package modules. 12 | import EleventyImage from '@11ty/eleventy-img'; 13 | 14 | // Local modules. 15 | import { 16 | INPUT_DIRECTORY, 17 | INTERMEDIATE_DIRECTORY, 18 | } from './lib/constants'; 19 | import inspect from './lib/filters'; 20 | import NunjucksLinkExtension from './lib/nunjucks/tags/link'; 21 | import imageShortcode from './src/_shortcodes/image'; 22 | 23 | // Constants. 24 | const ELEVENTY_IMAGE_DEFAULT_URL_PATH = '/images/'; 25 | 26 | // Helpers. 27 | const formatImageFilename = (id, src, width, format /* , options */) => { 28 | const filename = basename(src, extname(src)); 29 | return `${filename}.${width}w.${format}`; 30 | }; 31 | 32 | // Exports. 33 | module.exports = (eleventyConfig) => { 34 | // Add universal filters. 35 | // @see https://www.11ty.io/docs/filters/ 36 | eleventyConfig.addFilter('debug', inspect); 37 | eleventyConfig.addFilter('pageURL', ({ outputPath, url }) => { 38 | if (outputPath) { 39 | return joinPath('/', outputPath); 40 | } 41 | return eleventyConfig.getFilter('url')(url); 42 | }); 43 | 44 | // Add utility to perform build-time image manipulation. 45 | eleventyConfig.addNunjucksAsyncShortcode('image', async (src, options) => { 46 | const urlPath = options?.urlPath || ELEVENTY_IMAGE_DEFAULT_URL_PATH; 47 | const stats = await EleventyImage(src, { 48 | filenameFormat: formatImageFilename, 49 | formats: [extname(src).substring(1)], // Use input format. 50 | ...options, 51 | outputDir: joinPath(INTERMEDIATE_DIRECTORY, urlPath), 52 | urlPath: joinPath('~', INTERMEDIATE_DIRECTORY, urlPath), 53 | }); 54 | return imageShortcode(stats, options); 55 | }); 56 | 57 | // Add custom tags. 58 | // @see https://www.11ty.io/docs/shortcodes/ 59 | eleventyConfig.addNunjucksTag('link', NunjucksLinkExtension.singleton); 60 | 61 | // Copy static assets. 62 | eleventyConfig.addPassthroughCopy(joinPath(INPUT_DIRECTORY, '*.txt')); 63 | 64 | // Return configuration options. 65 | // @see https://www.11ty.io/docs/config/ 66 | return { 67 | // @see https://www.11ty.io/docs/config/#input-directory 68 | dir: { 69 | layouts: relativePath(INPUT_DIRECTORY, joinPath(INPUT_DIRECTORY, '_layouts/')), 70 | }, 71 | 72 | // @see https://www.11ty.io/docs/config/#default-template-engine-for-markdown-files 73 | markdownTemplateEngine: 'njk', 74 | }; 75 | }; 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eleventy-parcel-boilerplate", 3 | "version": "0.1.0", 4 | "description": "Starter kit for using Eleventy with Parcel, backed by Forestry.", 5 | "author": "Mark van Seventer ", 6 | "license": "MIT", 7 | "browserslist": "last 2 years and > 0.5%, ie 11", 8 | "homepage": "http://localhost:8080", 9 | "config": { 10 | "input": "src/", 11 | "intermediate": "tmp/", 12 | "output": "dist/" 13 | }, 14 | "sideEffects": [ 15 | "src/scripts/main.js" 16 | ], 17 | "targets": { 18 | "browser": {} 19 | }, 20 | "scripts": { 21 | "lint": "cross-env eslint '*.js' lib/ $npm_package_config_input --ignore-pattern $npm_package_config_input'scripts/'", 22 | 23 | "11ty": "cross-env eleventy --config=./eleventy.config.js --input=$npm_package_config_input --output=$npm_package_config_intermediate", 24 | "parcel:build": "cross-env parcel build $npm_package_config_intermediate'*.*' --dist-dir $npm_package_config_output --no-cache --no-source-maps --public-url $npm_package_homepage", 25 | "parcel:watch": "cross-env parcel serve $npm_package_config_intermediate'*.*' --no-autoinstall --no-content-hash", 26 | 27 | "clean": "cross-env rimraf $npm_package_config_intermediate $npm_package_config_output", 28 | 29 | "watch:11ty": "run-s '11ty -- --watch'", 30 | "watch:parcel": "run-s parcel:watch", 31 | "prewatch": "run-p clean lint", 32 | "watch": "run-p watch:*", 33 | 34 | "prebuild": "run-p clean lint", 35 | "build": "BUILD_ENV=production run-s 11ty parcel:build", 36 | 37 | "start": "run-s watch" 38 | }, 39 | "dependencies": { 40 | "@11ty/eleventy-img": "^0.5", 41 | "postcss-preset-env": "^6.7", 42 | "postcss-reporter": "^7", 43 | "posthtml-render": "^1.4", 44 | "stylelint": "^13.8", 45 | "tailwindcss": "^2", 46 | "~": "file:." 47 | }, 48 | "devDependencies": { 49 | "@11ty/eleventy": "^0.11", 50 | "@babel/core": "^7.12", 51 | "@babel/plugin-proposal-class-properties": "^7.12", 52 | "@babel/plugin-proposal-private-methods": "^7.12", 53 | "@babel/plugin-transform-runtime": "^7.12", 54 | "@babel/preset-env": "^7.12", 55 | "@babel/register": "^7.12", 56 | "@babel/runtime-corejs3": "^7.12", 57 | "@parcel/validator-eslint": "2.0.0-nightly.464", 58 | "babel-eslint": "^10", 59 | "cross-env": "^7", 60 | "eslint": "^7.12", 61 | "eslint-config-airbnb-base": "^14.2", 62 | "eslint-plugin-import": "^2.20", 63 | "npm-run-all": "^4.1", 64 | "parcel": "2.0.0-nightly.462", 65 | "parcel-namer-custom": "^0.2", 66 | "parcel-optimizer-friendly-urls": "^0.2", 67 | "parcel-optimizer-imagemin": "^1", 68 | "postcss": "^8.1", 69 | "rimraf": "^3", 70 | "sharp": "^0.26", 71 | "stylelint-config-recommended": "^3", 72 | "stylelint-no-unsupported-browser-features": "^4.1" 73 | }, 74 | "engines": { 75 | "node": ">=12.13" 76 | }, 77 | "parcel-namer-custom": { 78 | ".css$": "[folder]/[name].[hash].[type]", 79 | ".jsx?$": "[folder]/[name].[hash].[type]", 80 | ".txt$": "[base]", 81 | "(tmp/)(.*).(gif|jpe?g|png|svg|webp)$": "[2].[hash].[type]" 82 | }, 83 | "parcel-optimizer-imagemin": { 84 | "imagemin-pngquant": { 85 | "quality": [ 86 | 0.6, 87 | 0.8 88 | ], 89 | "speed": 1, 90 | "strip": true 91 | } 92 | }, 93 | "private": true 94 | } 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eleventy-parcel-boilerplate 2 | > Starter kit for using [Eleventy] with [Parcel], backed by [Forestry]. 3 | 4 | [Eleventy] is _a simpler static site generator_, which does a beautiful job of scaffolding your static site. However, a web application is so much more; what about images, stylesheets, or scripts? This is where [Parcel], a _zero configuration web application bundler_, comes in. By combining [Eleventy] with [Parcel], you can take your static site to the next level with minimal effort. 5 | 6 | As a bonus, this project is preconfigured to work out of the box with [Forestry], in case you use [Forestry] to edit your site content. 7 | 8 | ## Installation 9 | **Recommended** 10 | 11 | This project is set-up as a [Template Repository][1]. Click the "Use this template" button to create your new static site from this repository. 12 | 13 | **Others** 14 | 15 | 1. Clone the repository using `git clone https://github.com/vseventer/eleventy-parcel-boilerplate`. 16 | 2. Navigate to your project directory using `cd eleventy-parcel-boilerplate`. 17 | 3. Install the dependencies using `npm install`. 18 | 19 | _This project supports both `npm` and `yarn`, feel free to use whichever package manager you're most comfortable with._ 20 | 21 | ## Getting Started 22 | Please familiarize yourself with [Eleventy] and [Parcel], and you will recognize the source directory contains all you need to get started with your new static site. 23 | 24 | By default, [Parcel] will mark all files in your top-level source directory as entry points of your static site. Typically, these are your `index.html`, `404.html`, `robots.txt`, or `CNAME`. This project assumes all other pages of your static site are referenced by any of these entry points, and Parcel will pick them up automatically. 25 | 26 | ## Development 27 | * To start the development server, run `npm start` or `npm run watch` and navigate to `http://localhost:8080`. 28 | * To build your site just once (for production), run `npm run build`. 29 | 30 | _The development server, [browser-sync], is provided by [Eleventy] and set-up to work in sync with [Parcel]._ 31 | 32 | ## Configuration 33 | This project predefines a set of configuration files, which can be tweaked depending on your preferences. 34 | 35 | ### `package.json` 36 | The `browserslist` property reflects the browsers your static website supports, per [browserslist]. 37 | 38 | The `homepage` property should reflect the URL of your production site. If you prefer to use absolute URLs, remove the `--public-url $npm_package_homepage` flag from the `parcel:build` npm script. 39 | 40 | The `config` block in `package.json` enumerates three directories: 41 | * `input`: the source of your web application. 42 | * `intermediate`: the output directory for [Eleventy], and input directory for [Parcel]. You should never directly modify contents in this directory. 43 | * `output`: the final build of your web application. 44 | 45 | ### `.babelrc` 46 | The [Babel] smart preset is used allowing you to use the latest JavaScript. Two separate plugins supporting (private) class methods and properties are added by default as well. 47 | 48 | ### `.eslintrc` and `src/.eslintrc` 49 | This project follows [Airbnb] configuration for [ESLint]. The source directory extends the base configuration, and makes sure you can use `process` in your JavaScript, as this is [supported][2] by [Parcel]. 50 | 51 | Linting is ran on your configuration files, as well as the scripts in the source directory of your static site. 52 | 53 | ### `.stylelintrc` 54 | This project follows the recommended configuration for [stylelint], with support for SCSS-syntax. Linting is ran as part of [PostCSS] as explained below. 55 | 56 | ### `eleventy.config.js` 57 | The [Eleventy] configuration file adds support for running a staging environment, useful for [Forestry] integration. The development server is also updated to redirect 404 routes to your `404.html` page (if present in your project). 58 | 59 | In addition, it sets some sane defaults, as well as provide a boilerplate for how to add custom filters and tags. This project comes with two, a `debug` filter, and `link` custom Nunjucks tag. 60 | 61 | ### `postcss.config.js` 62 | The [PostCSS] configuration adds a number of plugins. Your stylesheets are linted with [stylelint], before being optimized with [PurgeCSS] (production only), and [autoprefixer]. 63 | 64 | ### `posthtml.config.js` 65 | The [PostHTML] configuration adds a custom plugin to your pipeline, required to make [Eleventy] and [Parcel] play nice together. Do not remove this plugin unless you know what you are doing, or want your build to break. 66 | 67 | ### Parcel 68 | [Parcel] does not have a separate configuration file, but does pick-up on packages named `parcel-plugin-*`. Included with this project are: 69 | * `parcel-plugin-eslint`: required to run linting before building with [Parcel]. 70 | * `parcel-plugin-keep-asset-folders`: recommended if you want to keep your assets source directory structure rather than storing them all in the top-level output folder. 71 | * `parcel-plugin-remove-index-html`: highly recommended if you want your final build to have nice URLs (`https://example.com/` vs `https://example.com/index.html`). 72 | 73 | ## Content Management 74 | Content of your site lives in the `src/` directory by default. 75 | 76 | If you are using [Forestry] to manage your content, import your site by following the steps in the [Forestry] Dashboard. This project is set-up so that the [Instant Preview][3] functionality of Forestry will work out of the box. 77 | 78 | ## Alternatives 79 | * [parceleventy] (not actively maintained) 80 | 81 | ## License 82 | The MIT License (MIT) 83 | 84 | Copyright (c) 2019 Mark van Seventer 85 | 86 | Permission is hereby granted, free of charge, to any person obtaining a copy of 87 | this software and associated documentation files (the "Software"), to deal in 88 | the Software without restriction, including without limitation the rights to 89 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 90 | the Software, and to permit persons to whom the Software is furnished to do so, 91 | subject to the following conditions: 92 | 93 | The above copyright notice and this permission notice shall be included in all 94 | copies or substantial portions of the Software. 95 | 96 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 97 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 98 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 99 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 100 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 101 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 102 | 103 | [eleventy]: https://www.11ty.io/ 104 | [airbnb]: https://github.com/airbnb/javascript 105 | [autoprefixer]: https://github.com/postcss/autoprefixer 106 | [babel]: https://babeljs.io/ 107 | [browser-sync]: https://www.browsersync.io/ 108 | [browserslist]: https://github.com/browserslist/browserslist 109 | [eslint]: https://eslint.org/ 110 | [forestry]: https://forestry.io/ 111 | [parcel]: https://parceljs.org/ 112 | [parceleventy]: https://github.com/chrisdmacrae/parceleventy 113 | [postcss]: https://postcss.org/ 114 | [posthtml]: https://github.com/posthtml/posthtml 115 | [purgecss]: https://www.purgecss.com/ 116 | [stylelint]: https://stylelint.io/ 117 | [1]: https://help.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-from-a-template 118 | [2]: https://parceljs.org/env.html 119 | [3]: https://forestry.io/docs/previews/instant-previews/ 120 | --------------------------------------------------------------------------------