├── .browserslistrc ├── .editorconfig ├── .eleventy.js ├── .eleventyignore ├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── babel.config.json ├── package-lock.json ├── package.json ├── postcss.config.js ├── src ├── _data │ └── cacheBust.js ├── _layouts │ └── default.ejs ├── _pages │ ├── _pages.json │ └── index.md ├── assets │ ├── css │ │ └── index.scss │ └── js │ │ └── index.js └── images │ ├── favicon.png │ └── logo.svg ├── tutorial.md ├── webpack.config.common.js ├── webpack.config.dev.js └── webpack.config.prod.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{ejs,js,scss}] 2 | charset = utf-8 3 | end_of_line = lf 4 | insert_final_newline = true 5 | indent_size = 2 6 | indent_style = space 7 | trim_trailing_whitespace = true 8 | 9 | [*.json] 10 | end_of_line = lf 11 | insert_final_newline = true 12 | indent_size = 2 13 | indent_style = space 14 | trim_trailing_whitespace = true 15 | 16 | [*.md] 17 | insert_final_newline = true 18 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.eleventy.js: -------------------------------------------------------------------------------- 1 | const htmlmin = require('html-minifier'); 2 | 3 | module.exports = function(eleventyConfig) { 4 | eleventyConfig.setUseGitIgnore(false); 5 | 6 | // Watch our compiled assets for changes 7 | eleventyConfig.addWatchTarget('./src/compiled-assets/main.css'); 8 | eleventyConfig.addWatchTarget('./src/compiled-assets/main.js'); 9 | eleventyConfig.addWatchTarget('./src/compiled-assets/vendor.js'); 10 | 11 | // Copy src/compiled-assets to /assets 12 | eleventyConfig.addPassthroughCopy({ 'src/compiled-assets': 'assets' }); 13 | // Copy all images 14 | eleventyConfig.addPassthroughCopy('src/images'); 15 | 16 | if (process.env.ELEVENTY_ENV === 'production') { 17 | eleventyConfig.addTransform('htmlmin', (content, outputPath) => { 18 | if (outputPath.endsWith('.html')) { 19 | const minified = htmlmin.minify(content, { 20 | collapseInlineTagWhitespace: false, 21 | collapseWhitespace: true, 22 | removeComments: true, 23 | sortClassName: true, 24 | useShortDoctype: true, 25 | }); 26 | 27 | return minified; 28 | } 29 | 30 | return content; 31 | }); 32 | } 33 | 34 | return { 35 | dir: { 36 | includes: '_components', 37 | input: 'src', 38 | layouts: '_layouts', 39 | output: 'dist', 40 | }, 41 | markdownTemplateEngine: 'ejs', 42 | templateFormats: [ 43 | 'ejs', 44 | 'md', 45 | ], 46 | }; 47 | }; 48 | -------------------------------------------------------------------------------- /.eleventyignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | /node_modules/ 3 | 4 | .DS_Store 5 | Thumbs.db -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "airbnb-base" 8 | ], 9 | "parserOptions": { 10 | "ecmaVersion": 12, 11 | "sourceType": "module" 12 | }, 13 | "rules": { 14 | }, 15 | "overrides": [{ 16 | "files": ["./*.js", "./src/_data/**/*.js"], 17 | "rules": { 18 | "import/no-extraneous-dependencies": ["error", { 19 | "devDependencies": true 20 | }] 21 | } 22 | }] 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | /node_modules/ 3 | /src/compiled-assets/ 4 | 5 | .DS_Store 6 | Thumbs.db -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Matt Stow 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | Elf logo 7 | 8 | # Elf 9 | 10 |
11 | 12 | Elf is a simple, magical [Eleventy](https://www.11ty.dev/) starter kit to help you create a project using standard technologies like webpack, Babel and Sass, while also considering ease of use, performance and browser compatibility. 13 | 14 | If you'd like to know why Elf exists and how best to take advantage of it, read [Creating a production-ready Eleventy project with webpack, Babel and Sass](https://dev.to/stowball/creating-a-production-ready-eleventy-project-with-webpack-babel-and-sass-35ep). 15 | 16 | ## Getting started 17 | 18 | 1. Clone or fork this repo: `git clone https://github.com/stowball/elf` 19 | 2. `cd` into the project directory and run `npm install` 20 | 21 | ## Running and serving a dev build 22 | 23 | ```sh 24 | npm run dev 25 | ``` 26 | 27 | Browse to [http://localhost:8080](http://localhost:8080). 28 | 29 | ## Running and serving a prod build 30 | 31 | ```sh 32 | npm run prod 33 | npm run serve:prod 34 | ``` 35 | 36 | Browse to [http://localhost:5000](http://localhost:5000). 37 | 38 | ## Technologies used 39 | 40 | * [Eleventy](https://www.11ty.dev/)… obviously 41 | * [EJS](https://ejs.co/) as the templating language 42 | * [Sass](https://sass-lang.com/) for writing CSS 43 | * [Babel](https://babeljs.io/) for transpiling and polyfilling JavaScript 44 | * [Autoprefixer](https://github.com/postcss/autoprefixer) for vendor prefixing CSS 45 | * [Webpack](https://webpack.js.org/) for compiling the Sass and JavaScript assets 46 | * [ESLint](https://eslint.org/) and [Airbnb's base configuration](https://www.npmjs.com/package/eslint-config-airbnb-base) for linting 47 | 48 | ## Project structure 49 | 50 | ``` 51 | src/ 52 | _components/ 53 | All UI partials 54 | _data/ 55 | Eleventy data files 56 | _layouts/ 57 | Base page layouts 58 | _pages/ 59 | Each individual page template 60 | assets/ 61 | css/ 62 | index.scss 63 | All other scss files 64 | js/ 65 | index.js 66 | All other js files 67 | images/ 68 | All images used 69 | Configuration and build files 70 | ``` 71 | 72 | Files in `assets` will be handled by webpack, Eleventy will transform all of the directories with a leading `_`, and will copy across any `images`. 73 | 74 | Eleventy’s output will be to a `dist` directory at the root level. 75 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "corejs": 3, 7 | "useBuiltIns": "usage" 8 | } 9 | ] 10 | ] 11 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elf", 3 | "version": "1.0.0", 4 | "description": "An Eleventy starter kit", 5 | "main": ".eleventy.js", 6 | "scripts": { 7 | "build:assets": "webpack --config webpack.config.prod.js", 8 | "build:site": "ELEVENTY_ENV=production npx eleventy", 9 | "del:assets": "rimraf ./src/compiled-assets", 10 | "del:dist": "rimraf ./dist", 11 | "dev": "npm run dev:assets & npm run dev:site", 12 | "dev:assets": "webpack --config webpack.config.dev.js", 13 | "dev:site": "ELEVENTY_ENV=development npx eleventy --serve", 14 | "prod": "npm-run-all del:dist del:assets build:assets build:site", 15 | "serve:prod": "serve ./dist/" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+ssh://git@github.com/stowball/elf.git" 20 | }, 21 | "author": "Matt Stow", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/stowball/elf/issues" 25 | }, 26 | "homepage": "https://github.com/stowball/elf#readme", 27 | "devDependencies": { 28 | "@11ty/eleventy": "0.11.0", 29 | "@babel/core": "7.11.6", 30 | "@babel/preset-env": "7.11.5", 31 | "autoprefixer": "9.8.6", 32 | "babel-loader": "8.1.0", 33 | "core-js": "3.6.5", 34 | "css-loader": "4.2.2", 35 | "eslint": "7.8.1", 36 | "eslint-config-airbnb-base": "14.2.0", 37 | "eslint-plugin-import": "2.22.0", 38 | "fibers": "5.0.0", 39 | "html-minifier": "4.0.0", 40 | "md5-file": "5.0.0", 41 | "mini-css-extract-plugin": "0.11.0", 42 | "npm-run-all": "4.1.5", 43 | "optimize-css-assets-webpack-plugin": "5.0.4", 44 | "postcss-loader": "4.0.1", 45 | "rimraf": "3.0.2", 46 | "sass": "1.26.10", 47 | "sass-loader": "10.0.2", 48 | "serve": "11.3.2", 49 | "terser-webpack-plugin": "4.1.0", 50 | "webpack": "4.44.1", 51 | "webpack-cli": "3.3.12", 52 | "webpack-merge": "5.1.3" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | // eslint-disable-next-line global-require 4 | require('autoprefixer'), 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /src/_data/cacheBust.js: -------------------------------------------------------------------------------- 1 | const md5File = require('md5-file'); 2 | 3 | const cacheBust = () => { 4 | // A "map" of files to cache bust 5 | const files = { 6 | mainCss: './src/compiled-assets/main.css', 7 | mainJs: './src/compiled-assets/main.js', 8 | vendorJs: './src/compiled-assets/vendor.js', 9 | }; 10 | 11 | return Object.entries(files).reduce((acc, [key, path]) => { 12 | const now = Date.now(); 13 | const bust = process.env.ELEVENTY_ENV === 'production' ? md5File.sync(path, (_err, hash) => hash) : now; 14 | 15 | acc[key] = bust; 16 | 17 | return acc; 18 | }, {}); 19 | }; 20 | 21 | module.exports = cacheBust; 22 | -------------------------------------------------------------------------------- /src/_layouts/default.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%= locals.title %> 8 | 9 | 10 | 11 | 12 | 13 |
14 | <%- content -%> 15 |
16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/_pages/_pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "permalink": "<%- page.filePathStem.replace('/_pages', '').replace('/index', '') %>/index.html" 3 | } 4 | -------------------------------------------------------------------------------- /src/_pages/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: My cool website 3 | layout: default.ejs 4 | --- 5 | 6 | # Hello, world 7 | 8 | Welcome to my website. 9 | 10 | A random number is <%- Math.random() %> 11 | 12 | This project was built with **[Elf](https://github.com/stowball/elf)**, a simple & magical **[Eleventy](https://www.11ty.dev/)** starter project. 13 | 14 | ![Elf logo](/images/logo.svg) 15 | 16 | Created by [Matt Stow](https://twitter.com/stowball). 17 | -------------------------------------------------------------------------------- /src/assets/css/index.scss: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: sans-serif; 3 | background: #cbe3f5; 4 | } 5 | -------------------------------------------------------------------------------- /src/assets/js/index.js: -------------------------------------------------------------------------------- 1 | import '../css/index.scss'; 2 | 3 | // eslint-disable-next-line no-console 4 | console.log('Hello again'); 5 | 6 | Array.from(document.getElementsByTagName('p')).forEach((p, index) => { 7 | // eslint-disable-next-line no-console 8 | console.log(`p ${index}, startsWith('W')`, p, p.innerHTML.startsWith('W')); 9 | }); 10 | -------------------------------------------------------------------------------- /src/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stowball/elf/d19cac28adfbaa04bb3fffaa07414177619f0079/src/images/favicon.png -------------------------------------------------------------------------------- /src/images/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tutorial.md: -------------------------------------------------------------------------------- 1 | # Creating a production-ready Eleventy project with webpack, Babel and Sass 2 | 3 | While [Eleventy](https://www.11ty.dev/) is a magnificent, and increasingly popular static site generator (SSG), I’ve found it hard to find good references on starting a project using standard technologies like [webpack](https://webpack.js.org/), [Babel](https://babeljs.io/) and [Sass](https://sass-lang.com/), so decided to write this tutorial. 4 | 5 | Before we start, it’s important to know that I’m not going to describe any technology in great detail, so I’ll assume you have a basic understanding of the “front-end” stack, and have [Node.js](https://nodejs.org/en/) installed (both v10.15 and v14.5 have been tested and work perfectly). 6 | 7 | It’s also not a comprehensive guide to Eleventy. There are other resources for that, with [Learn Eleventy From Scratch](https://piccalil.li/course/learn-eleventy-from-scratch/) seemingly a great option. While I have only read the free, first lesson, I see many positive comments from the wider community so feel free to check it out. 8 | 9 | Let’s take a look at what we’ll cover: 10 | 11 | * [Setup](#setup) 12 | * [Configuring Eleventy](#configuring-eleventy) 13 | * [Creating a layout](#creating-a-layout) 14 | * [Creating our first page](#creating-our-first-page) 15 | * [Serving our site](#serving-our-site) 16 | * [Re-writing URLs](#re-writing-urls) 17 | * [Setting up webpack](#setting-up-webpack) 18 | * [Creating our assets](#creating-our-assets) 19 | * [Installing our dependencies](#installing-our-dependencies) 20 | * [Creating our configs](#creating-our-configs) 21 | * [Adding the assets to our Eleventy site](#adding-the-assets-to-our-eleventy-site) 22 | * [Updating our Eleventy config](#updating-our-eleventy-config) 23 | * [Improving our cache busting](#improving-our-cache-busting) 24 | * [Serving our prod build](#serving-our-prod-build) 25 | * [Cleaning our prod build](#cleaning-our-prod-build) 26 | * [Minifying our HTML for prod](#minifying-our-html-for-prod) 27 | * [Implementing Babel for polyfilling and transpilation](#implementing-babel-for-polyfilling-and-transpilation) 28 | * [Vendor prefixing CSS with Autoprefixer](#vendor-prefixing-css-with-autoprefixer) 29 | * [A Git gotcha - my assets aren’t updating!](#a-git-gotcha---my-assets-arent-updating) 30 | * [Wrapping up](#wrapping-up) 31 | 32 | ## Setup 33 | 34 | Open a terminal in a new project directory, and run: 35 | 36 | ```sh 37 | npm init -y 38 | ``` 39 | 40 | Next up, install Eleventy: 41 | 42 | ```sh 43 | npm install @11ty/eleventy --save-dev --save-exact 44 | ``` 45 | 46 | ## Configuring Eleventy 47 | 48 | I prefer to keep all source files in a root `src` directory, with the full folder structure looking like this: 49 | 50 | ``` 51 | src/ 52 | _components/ 53 | All UI partials 54 | _data/ 55 | Eleventy data files 56 | _layouts/ 57 | Base page layouts 58 | _pages/ 59 | Each individual page template 60 | assets/ 61 | css/ 62 | index.scss 63 | All other scss files 64 | js/ 65 | index.js 66 | All other js files 67 | images/ 68 | All images used 69 | Configuration and build files 70 | ``` 71 | 72 | Files in `assets` will be handled by webpack, Eleventy will transform all of the directories with a leading `_`, and will copy across any `images`. 73 | 74 | When the site is built, we’ll configure Eleventy to output it to a `dist` directory at the root level. 75 | 76 | I prefer to use [EJS](https://ejs.co/) as my templating language because it’s the closest to being “just JavaScript” while also providing a simple developer experience for writing standard HTML. 77 | 78 | OK, so let’s configure Eleventy to support the above structure. 79 | 80 | First, create an `.eleventy.js` file in the project root with the following: 81 | 82 | ```js 83 | module.exports = function(eleventyConfig) { 84 | return { 85 | dir: { 86 | includes: '_components', 87 | input: 'src', 88 | layouts: '_layouts', 89 | output: 'dist', 90 | }, 91 | // Allows using markup and EJS features in markdown 92 | markdownTemplateEngine: 'ejs', 93 | templateFormats: [ 94 | 'ejs', 95 | 'md', 96 | ], 97 | }; 98 | }; 99 | ``` 100 | 101 | The above assumes that you’ll be using `.ejs` or `.md` files for your templating, so I recommend you install an appropriate syntax highlighter for your editor. 102 | 103 | ## Creating a layout 104 | 105 | [Layouts](https://www.11ty.dev/docs/layouts/) are special templates that can be used to wrap other content, which in our case will be the base, page-level HTML markup. 106 | 107 | In `src/_layouts`, let’s create a `default.ejs` file with the following: 108 | 109 | ```html 110 | 111 | 112 | 113 | 114 | 115 | 116 | <%= locals.title %> 117 | 118 | 119 | <%- content -%> 120 | 121 | 122 | ``` 123 | 124 | For the most part, this is a standard HTML file, but we have 2 uses of EJS syntax: 125 | 126 | ``` 127 | <%= locals.title %> 128 | ``` 129 | 130 | Will output an escaped page `title` here. In EJS, it’s safer to always prefix your variables with `locals.`, that way you can easily support `undefined` variables in your templates. 131 | 132 | ``` 133 | <%- content -%> 134 | ``` 135 | 136 | Eleventy provides a `content` variable, so this will render any page content (unescaped) that uses this layout within this block. 137 | 138 | ## Creating our first page 139 | 140 | In `src/_pages`, let’s create an `index.md` with the following: 141 | 142 | ```md 143 | --- 144 | title: My cool website 145 | layout: default.ejs 146 | --- 147 | 148 | # Hello, world 149 | 150 | Welcome to my website. 151 | 152 | A random number is <%- Math.random() %> 153 | ``` 154 | 155 | The part between `---` is called [front matter](https://www.11ty.dev/docs/data-frontmatter/), where we can define whatever variables we like, as well as make use of some built-in ones provided by Eleventy. 156 | 157 | We’ve defined what our page `title` will be (which is consumed within our `default` layout), and the `layout` to use. 158 | 159 | We’re using markdown which will be converted to an `

` and `

`s, and demonstrating that you can also use EJS features (and JavaScript) within markdown itself. 160 | 161 | ## Serving our site 162 | 163 | With our page created, how do we serve it up locally and see changes as we update the content and create new pages? 164 | 165 | Let’s head over to `package.json`, and update our `"scripts"` with this: 166 | 167 | ``` 168 | "scripts": { 169 | "build:site": "ELEVENTY_ENV=production npx eleventy", 170 | "dev:site": "ELEVENTY_ENV=development npx eleventy --serve" 171 | }, 172 | ``` 173 | 174 | Now, from a terminal, you can run `npm run dev:site` and browse to [http://localhost:8080/_pages/](http://localhost:8080/_pages/) to see your HTML page fully rendered. 175 | 176 | We’ve also added a command to perform a production build, but we won’t need to use that just yet. Also note the `ELEVENTY_ENV=production|development`. This provides us the ability to do different things with our Eleventy process, like minifying HTML, depending on the build type. 177 | 178 | ## Re-writing URLs 179 | 180 | But wait up, we don’t want users (or us) to have to browse to `/_pages` in every URL; that home page should be available at the root domain! 181 | 182 | Thankfully, Eleventy has a feature called [permalinks](https://www.11ty.dev/docs/permalinks/), which allows you to set what the URL for each page will be. Now, while we can manually add this to every page’s front matter to remove `_pages`, we can go one better by automating that. 183 | 184 | Let’s create a `_pages.json` in `src/_pages`, with the following: 185 | 186 | ```json 187 | { 188 | "permalink": "<%- page.filePathStem.replace('/_pages', '').replace('/index', '') %>/index.html" 189 | } 190 | ``` 191 | 192 | “What is this madness?”, I hear you cry. Well, since we can use JavaScript within EJS, and use EJS within JSON, we can use the `page.filePathStem` which Eleventy provides to construct a new, better permalink path. 193 | 194 | As an example, for the following files: 195 | 196 | ``` 197 | /_pages/index.md 198 | /_pages/foo/index.ejs 199 | /_pages/foo/bar.md 200 | /_pages/foo/baz/index.ejs 201 | /_pages/foo/baz/qux.md 202 | ``` 203 | 204 | Eleventy will provide the following `filePathStem`s: 205 | 206 | ``` 207 | /_pages/index 208 | /_pages/foo/index 209 | /_pages/foo/bar 210 | /_pages/foo/baz/index 211 | /_pages/foo/baz/qux 212 | ``` 213 | 214 | So, this “script” first removes the `/_pages` from the `filePathStem` string, then removes any trailing `/index` so every page is “equal”, and finally appends `/index.html`, which, for the above path examples, results in: 215 | 216 | ``` 217 | /index.html 218 | /foo/index.html 219 | /foo/bar/index.html 220 | /foo/baz/index.html 221 | /foo/baz/qux/index.html 222 | ``` 223 | 224 | Now, you should be able to browse directly to [http://localhost:8080/](http://localhost:8080/) to see your “Hello, world” file, and the correct path for any other file you create later on. 225 | 226 | While this is kinda cool, it’s completely unstyled, so let’s see how we can set up webpack to compile CSS using Sass. 227 | 228 | ## Setting up webpack 229 | 230 | ### Creating our assets 231 | 232 | Before we do any webpack configuration, let’s first scaffold our assets so we have something to configure. 233 | 234 | We’re going to use Sass, which, while it may be going out of favour in some circles, still does an excellent job at allowing us to write more maintainable CSS. 235 | 236 | *As a side note, I still like Sass so much that I’ve used it to create an atomic CSS library called [Hucssley](https://github.com/stowball/hucssley), which, in my humble opinion, is excellent!* 237 | 238 | Anyway, back to this project… 239 | 240 | Create `src/assets/css/index.scss` with the following code: 241 | 242 | ```css 243 | html { 244 | font-family: sans-serif; 245 | background: #cbe3f5; 246 | } 247 | ``` 248 | 249 | In this directory, you would add all of your project’s Sass partials and `@import` them from `index.scss`. 250 | 251 | *If you prefer to co-locate your CSS and components, you could just as easily store them in specific template folders within `_components` and `@import` from there as well.* 252 | 253 | For webpack to handle our CSS, it must be `import`ed into a JavaScript file, so let’s create `src/assets/js/index.js` with the following: 254 | 255 | ```js 256 | import '../css/index.scss'; 257 | 258 | console.log('Hello again'); 259 | ``` 260 | 261 | Although we `import` the CSS within in JavaScript, this is not CSS-in-JS; it’s purely so webpack can do its thing™. 262 | 263 | ### Installing our dependencies 264 | 265 | Now that we have our asset files, let’s begin with the setup. 266 | 267 | First, we’re going to need to install quite a few dependencies now, so kill your `dev:site` process, and run this in the terminal. 268 | 269 | ```sh 270 | npm install css-loader fibers mini-css-extract-plugin optimize-css-assets-webpack-plugin sass sass-loader terser-webpack-plugin webpack webpack-cli webpack-merge --save-dev --save-exact 271 | ``` 272 | 273 | ### Creating our configs 274 | 275 | In the previous step, we installed a dependency called [webpack-merge](https://www.npmjs.com/package/webpack-merge). This will allow us to have separate development and production configurations which share the same, common configuration. 276 | 277 | In the project root, create `webpack.config.common.js` with the following: 278 | 279 | ```js 280 | // Makes Sass faster! 281 | const Fiber = require('fibers'); 282 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 283 | const path = require('path'); 284 | 285 | module.exports = { 286 | // Our "entry" point 287 | entry: './src/assets/js/index.js', 288 | output: { 289 | // The global variable name any `exports` from `index.js` will be available at 290 | library: 'SITE', 291 | // Where webpack will compile the assets 292 | path: path.resolve(__dirname, 'src/compiled-assets'), 293 | }, 294 | module: { 295 | rules: [ 296 | { 297 | // Setting up compiling our Sass 298 | test: /\.scss$/, 299 | use: [ 300 | { 301 | loader: MiniCssExtractPlugin.loader, 302 | }, 303 | { 304 | loader: 'css-loader', 305 | options: { 306 | url: false, 307 | }, 308 | }, 309 | { 310 | loader: 'sass-loader', 311 | options: { 312 | implementation: require('sass'), 313 | sassOptions: { 314 | fiber: Fiber, 315 | outputStyle: 'expanded', 316 | }, 317 | }, 318 | }, 319 | ], 320 | }, 321 | ], 322 | }, 323 | // Any `import`s from `node_modules` will compiled in to a `vendor.js` file. 324 | optimization: { 325 | splitChunks: { 326 | cacheGroups: { 327 | commons: { 328 | test: /[\\/]node_modules[\\/]/, 329 | name: 'vendor', 330 | chunks: 'all', 331 | }, 332 | }, 333 | }, 334 | }, 335 | plugins: [ 336 | new MiniCssExtractPlugin({ 337 | filename: '[name].css', 338 | }), 339 | ], 340 | }; 341 | ``` 342 | 343 | Now, let’s create our development config, at `webpack.config.dev.js`: 344 | 345 | ```js 346 | const { merge } = require('webpack-merge'); 347 | const common = require('./webpack.config.common.js'); 348 | 349 | module.exports = merge(common, { 350 | mode: 'development', 351 | // Allow watching and live reloading of assets 352 | watch: true, 353 | }); 354 | ``` 355 | 356 | And finally, our production config, at `webpack.config.prod.js`: 357 | 358 | ```js 359 | const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); 360 | const TerserPlugin = require('terser-webpack-plugin'); 361 | const { merge } = require('webpack-merge'); 362 | const common = require('./webpack.config.common.js'); 363 | 364 | module.exports = merge(common, { 365 | // Enable minification and tree-shaking 366 | mode: 'production', 367 | optimization: { 368 | minimizer: [ 369 | new OptimizeCssAssetsPlugin({}), 370 | new TerserPlugin({ 371 | extractComments: false, 372 | }), 373 | ], 374 | }, 375 | }); 376 | ``` 377 | 378 | Now we have our configs, we need scripts to run them. Head over to `package.json`, and add 2 new scripts: 379 | 380 | ``` 381 | "build:assets": "webpack --config webpack.config.prod.js", 382 | "dev:assets": "webpack --config webpack.config.dev.js", 383 | ``` 384 | 385 | so it should look like this: 386 | 387 | ```json 388 | "scripts": { 389 | "build:assets": "webpack --config webpack.config.prod.js", 390 | "build:site": "ELEVENTY_ENV=production npx eleventy", 391 | "dev:assets": "webpack --config webpack.config.dev.js", 392 | "dev:site": "ELEVENTY_ENV=development npx eleventy --serve" 393 | }, 394 | ``` 395 | 396 | *Note: JSON doesn’t allow trailing commas on the last line of an object, so all of the updates I suggest are correct if adding them alphabetically.* 397 | 398 | If you run `npm run build:assets` in your terminal, you should now have 2, minified files generated at: 399 | 400 | ``` 401 | src/compiled-assets/main.css 402 | src/compiled-assets/main.js 403 | ``` 404 | 405 | ### Adding the assets to our Eleventy site 406 | 407 | Let’s open up our `src/_layouts/default.ejs`, and in the ``, add a reference to the stylesheet, and before the closing ``, add a reference to our JavaScript files. 408 | 409 | ```html 410 | 411 | … existing tags 412 | 413 | 414 | ``` 415 | 416 | ```html 417 | 418 | 419 | 420 | ``` 421 | 422 | We’ve also added some simplistic cache busting using the current timestamp (but we’ll improve that for production later on). 423 | 424 | If you’re wondering why we have a commented out `vendor.js` `