├── .eleventyignore ├── .gitignore ├── src └── site │ ├── _includes │ ├── components │ │ ├── Wrapper.js │ │ ├── Card.js │ │ ├── Image.js │ │ └── Button.js │ └── layouts │ │ └── master.njk │ ├── about │ └── index.njk │ └── index.njk ├── .eleventy.js ├── package.json ├── LICENSE └── README.md /.eleventyignore: -------------------------------------------------------------------------------- 1 | README.md -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /src/site/_includes/components/Wrapper.js: -------------------------------------------------------------------------------- 1 | module.exports = (content = '') => (` 2 |
3 | ${ content } 4 |
5 | `); -------------------------------------------------------------------------------- /src/site/_includes/layouts/master.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Eleventy Shortcomps 6 | 7 | 8 | 9 | {{ content | safe }} 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/site/_includes/components/Card.js: -------------------------------------------------------------------------------- 1 | const Button = require('./Button.js'); 2 | 3 | module.exports = (name, bio, url) => (` 4 |
5 |

${ name }

6 |

${ bio }

7 | 8 | ${ Button('Visit site', url) } 9 |
10 | `); -------------------------------------------------------------------------------- /src/site/_includes/components/Image.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ src, altText = '', caption = '' } = {}) => (` 2 |
3 | ${ altText } 4 | ${ caption && ` 5 |
${ caption }
6 | `} 7 |
8 | `); -------------------------------------------------------------------------------- /src/site/about/index.njk: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layouts/master.njk 3 | --- 4 | 5 |
6 | 7 |

About

8 |

To see more, visit 9 | {% Button 10 | 'eleventy-shortcomps repo ', 11 | 'https://github.com/adamduncan/eleventy-shortcomps' 12 | %} 13 |

14 | 15 |
-------------------------------------------------------------------------------- /src/site/_includes/components/Button.js: -------------------------------------------------------------------------------- 1 | module.exports = (text, href, primary) => { 2 | 3 | // rendering logic, classnames etc.? 4 | const primaryClass = primary ? 'button--primary' : ''; 5 | 6 | if (href) { 7 | return ` 8 | 9 | ${ text } 10 | 11 | `; 12 | } 13 | 14 | return ` 15 | 18 | `; 19 | }; -------------------------------------------------------------------------------- /.eleventy.js: -------------------------------------------------------------------------------- 1 | const inputDir = 'src/site'; 2 | const componentsDir = `${inputDir}/_includes/components`; 3 | 4 | // Components 5 | const Wrapper = require(`./${componentsDir}/Wrapper.js`); 6 | const Image = require(`./${componentsDir}/Image.js`); 7 | const Button = require(`./${componentsDir}/Button.js`); 8 | const Card = require(`./${componentsDir}/Card.js`); 9 | 10 | module.exports = function (config) { 11 | 12 | // Paired shortcodes 13 | config.addPairedShortcode('Wrapper', Wrapper); 14 | 15 | // Shortcodes 16 | config.addShortcode('Image', Image); 17 | config.addShortcode('Button', Button); 18 | config.addShortcode('Card', Card); 19 | 20 | return { 21 | dir: { 22 | input: inputDir, 23 | output: 'dist' 24 | } 25 | }; 26 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eleventy-shortcomps", 3 | "version": "1.0.0", 4 | "description": "Starter project for static site, using Eleventy and shortcode components.", 5 | "scripts": { 6 | "start": "eleventy --serve", 7 | "build": "eleventy", 8 | "debug": "DEBUG=Eleventy* eleventy" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/adamduncan/eleventy-shortcomps" 13 | }, 14 | "author": "Adam Duncan (https://adamduncandesigns.com)", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/adamduncan/eleventy-shortcomps/issues" 18 | }, 19 | "homepage": "https://github.com/adamduncan/eleventy-shortcomps#readme", 20 | "devDependencies": { 21 | "@11ty/eleventy": "^0.11.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/site/index.njk: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layouts/master.njk 3 | --- 4 | 5 |
6 | 7 |

Hello, Eleventy Shortcomps

8 | 9 |

Some Shortcode components

10 | {% Button 'This is a button' %} 11 | {% Button 'This is a button link', '/about' %} 12 | {% Button 'This is a primary button link', '/about', true %} 13 | 14 |

Some Paired Shortcode components

15 | {% Wrapper %} 16 | {% Card 'Zach Leatherman', 'Bio text goes here.', 'https://zachleat.com' %} 17 | {% Card 'Adam Duncan', 'Other card bio text goes here.', 'https://adamduncandesigns.com' %} 18 | {% endWrapper %} 19 | 20 |

Props variation (see docs)

21 | {% Image { 22 | src: 'https://www.11ty.io/img/possum-sm.png', 23 | altText: 'Possum suspended in mid-air by red balloon', 24 | caption: '@jameswillweb\'s Eleventy logo (source)' 25 | } %} 26 | 27 |
-------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Adam Duncan 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 | # eleventy-shortcomps 2 | 3 | Starter project for static site, using [Eleventy](https://11ty.io) and shortcode components (AKA _shortcomps_) pattern. 4 | 5 | ## Goal 6 | 7 | The ability to create and maintain reusable, framework-agnostic [functional stateless components](https://javascriptplayground.com/functional-stateless-components-react/). 8 | 9 | These can be used throughout static sites and/or in environments already utilising frameworks (e.g. React, Vue). They are composable, and serve as the single source of truth across all applications. 10 | 11 | Benefit from the advantages of the component model, without needing to reach for a framework right away. 12 | 13 | ## Concept 14 | 15 | As in many frameworks, components can be written as functions that return JavaScript Template Literals. These receive and interpolate any values passed as arguments, and can contain rendering logic: 16 | 17 | ```JavaScript 18 | // Button.js 19 | module.exports = (text, href, primary) => { 20 | 21 | // rendering logic, classnames etc.? 22 | const primaryClass = primary ? 'button--primary' : ''; 23 | 24 | return ` 25 | 26 | ${ text } 27 | 28 | `; 29 | }; 30 | ``` 31 | 32 | Import and define components using Eleventy’s `addShortcode` and `addPairedShortcode` config methods, as needed: 33 | 34 | ```JavaScript 35 | // .eleventy.js 36 | const componentsDir = `./_includes/components`; 37 | 38 | const Wrapper = require(`${ componentsDir }/Wrapper.js`); 39 | const Card = require(`${ componentsDir }/Card.js`); 40 | const Button = require(`${ componentsDir }/Button.js`); 41 | 42 | module.exports = function (config) { 43 | 44 | config.addPairedShortcode('Wrapper', Wrapper); 45 | config.addShortcode('Card', Card); 46 | config.addShortcode('Button', Button); 47 | 48 | }; 49 | ``` 50 | 51 | They’ll then be available throughout templates, using the include syntax (i.e. Nunjucks): 52 | 53 | ```HTML 54 | {% Button 'This is a link to Eleventy', 'http://11ty.io' %} 55 | ``` 56 | 57 | And can be nested within other components: 58 | 59 | ```JavaScript 60 | // Card.js 61 | const Button = require('./Button.js'); 62 | 63 | module.exports = (name, bio, url) => (` 64 |
65 |

${ name }

66 |

${ bio }

67 | 68 | ${ Button('Visit site', url) } 69 |
70 | `); 71 | ``` 72 | 73 | ## Props variation 74 | 75 | Developers coming from (or possibly heading towards) a framework-based component model might be used to passing and receiving their component parameters in a single `props` object. 76 | 77 | It’s an elegant way of saying, “Hey, component, here’s everything you’ll need in one tasty little package.” 78 | 79 | This commonly results in a functional component that looks more like: 80 | 81 | ```JavaScript 82 | // Image.js 83 | module.exports = ({ src, altText = '', caption = '' }) => (` 84 |
85 | ${ altText } 86 | ${ caption && ` 87 |
${ caption }
88 | `} 89 |
90 | `); 91 | ``` 92 | 93 | (See React’s [Functional and class components](https://reactjs.org/docs/components-and-props.html#functional-and-class-components) documentation) 94 | 95 | This single `props` argument can also be [destructured](https://davidwalsh.name/destructuring-function-arguments) and assigned [default parameter values](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters). Awesome. 96 | 97 | _Note:_ The example above uses the [logical AND operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators#Description) to add conditional rendering logic for the `
`. Ensure props have default values set to avoid this rendering a value of `false` in compiled templates. 98 | 99 | With this approach, we still declare our shortcodes in `.eleventy.js` as we did previously. But instead of passing multiple parameters to them in our templates, we pass a single object containing all of the properties. In a templating language like Nunjucks, that might look like: 100 | 101 | ```HTML 102 | {% Image { 103 | src: '/path/to/image.jpg', 104 | altText: 'The Beatles on stage at Shea Stadium', 105 | caption: 'Where’s Ringo?' 106 | } %} 107 | ``` 108 | 109 | Or, if you’re using a functional component inside another component, that could start to look a whole lot like those React’y components: 110 | 111 | ```JavaScript 112 | // SomeComponent.js 113 | const Image = require('./Image.js'); 114 | 115 | module.exports = ({ title, image = {} } = {}) => { 116 | const { src, altText = '', caption } = image; 117 | 118 | return ` 119 |
120 | ${ title && ` 121 |

${ title }

122 | `} 123 | ${ Image({ 124 | src, 125 | altText, 126 | caption 127 | }) } 128 |
129 | `; 130 | }; 131 | ``` 132 | 133 | Here we can further leverage modern JavaScript features, such as [Object property value shorthand](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#New_notations_in_ECMAScript_2015) — along with destructuring — to give us a clear, terse syntax for using components. 134 | 135 | It seems advantageous to use this `props` approach in favour of the multiple parameter approach outlined first. Our components will benefit from having the same functional signatures as their React (and to some degree, Vue) counterparts, should we need to take them there in the future. 136 | 137 | ## Demo 138 | 139 | This repo contains just enough to demonstrate how one _could_ utilise this pattern (config, functional stateless components, props, shortcodes, paired shortcodes, layouts). 140 | 141 | Site can be viewed at: [eleventy-shortcomps.netlify.com](https://eleventy-shortcomps.netlify.com) 142 | 143 | Feedback welcome 🙌 144 | --------------------------------------------------------------------------------