├── .editorconfig ├── .eslintrc.js ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .nvmrc ├── LICENSE ├── README.md ├── cypress.json ├── cypress ├── integration │ ├── css.spec.js │ ├── img.spec.js │ ├── javascript.spec.js │ └── navigation.spec.js ├── plugins │ └── index.js └── support │ ├── commands.js │ └── index.js ├── package-lock.json ├── package.json ├── src ├── css │ └── main.css ├── img │ └── unicorn.jpg ├── js │ ├── bar.es6.js │ └── foo.cjs.js ├── page-about │ ├── main.js │ ├── page.css │ └── tmpl.html ├── page-contacts │ ├── main.js │ ├── page.css │ └── tmpl.html ├── page-index │ ├── main.js │ ├── page.css │ └── tmpl.html └── partials │ ├── footer.html │ ├── images.html │ └── nav.html ├── webpack.dev.js └── webpack.prod.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.html] 14 | indent_style = space 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'standard', 3 | parserOptions: { 4 | ecmaVersion: 6 5 | }, 6 | env: { 7 | browser: true, 8 | node: true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: monthly 7 | open-pull-requests-limit: 10 8 | labels: 9 | - dependencies 10 | ignore: 11 | - dependency-name: cypress 12 | versions: 13 | - 6.4.0 14 | - 6.5.0 15 | - dependency-name: webpack-cli 16 | versions: 17 | - 4.4.0 18 | - dependency-name: "@babel/preset-env" 19 | versions: 20 | - 7.12.11 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | tests: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Checkout 🛎️ 8 | uses: actions/checkout@v2.3.1 9 | - run: npm ci 10 | - run: npm test 11 | - run: npm run build 12 | - name: Cypress run 13 | uses: cypress-io/github-action@v2 14 | env: 15 | CYPRESS_CRASH_REPORTS: 0 16 | with: 17 | start: npm run start:dist 18 | - name: List built files 19 | if: ${{ github.ref == 'refs/heads/master' }} 20 | run: ls dist 21 | - name: Maybe deploy 🚀 22 | if: ${{ github.ref == 'refs/heads/master' }} 23 | uses: JamesIves/github-pages-deploy-action@4.1.5 24 | with: 25 | branch: gh-pages # The branch the action should deploy to. 26 | folder: dist # The folder the action should deploy. 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .tmp 2 | dist/ 3 | node_modules/ 4 | .sass-cache 5 | .idea 6 | *.iml 7 | *.DS_Store 8 | *.log 9 | .project 10 | .elasticbeanstalk/ 11 | cypress/videos/ 12 | cypress/screenshots/ 13 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 12.16 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Aivaras Prudnikovas 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 | Static html pages with Webpack 5 2 | ================================ 3 | 4 | [![CI](https://github.com/ivarprudnikov/webpack-static-html-pages/actions/workflows/ci.yml/badge.svg)](https://github.com/ivarprudnikov/webpack-static-html-pages/actions/workflows/ci.yml) 5 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com) 6 | [![GitHub issues](https://img.shields.io/github/issues/ivarprudnikov/webpack-static-html-pages.svg)](https://github.com/ivarprudnikov/webpack-static-html-pages/issues) 7 | [![GitHub last commit](https://img.shields.io/github/last-commit/ivarprudnikov/webpack-static-html-pages.svg)](https://github.com/ivarprudnikov/webpack-static-html-pages/commits/master) 8 | 9 | [> PREVIEW LIVE](https://ivarprudnikov.github.io/webpack-static-html-pages/) 10 | -------------------------------- 11 | 12 | This is a forkable example of a static website (plain html/css/javascript) 13 | assembled with webpack. You could also use this repository as a template when creating a new one. 14 | 15 | Article explaining how this example was created: https://www.ivarprudnikov.com/static-website-multiple-html-pages-using-webpack-plus-github-example/ 16 | 17 | * Webpack4 implementation can still be found on a separate branch [`webpack4`](https://github.com/ivarprudnikov/webpack-static-html-pages/tree/webpack4) 18 | 19 | ## Prerequisites 20 | 21 | - [Install `node` (comes with `npm`)](https://nodejs.org/). Suggested version expressed in [.nvmrc](./.nvmrc) file. 22 | 23 | ## Development 24 | 25 | - `npm i` - install dependencies 26 | - `npm start` - start development server 27 | - `npm test` - run minimal tests (eg: lint javascript files) 28 | - `npm run cy:run` - run Cypress functional/browser/e2e tests. Works only when running website locally (`npm start` or `npm run preview`) 29 | 30 | ## Production 31 | 32 | - `npm run build` to prepare `html`, `css`, `js` files in `dist/` directory 33 | - `npm run preview` - run build and serve production files locally 34 | 35 | Production build is built on [Travis CI](https://travis-ci.com/ivarprudnikov/webpack-static-html-pages) and saved in [`gh-pages` branch](https://github.com/ivarprudnikov/webpack-static-html-pages/tree/gh-pages) which in turn is hosted through Github pages https://ivarprudnikov.github.io/webpack-static-html-pages/ 36 | 37 | ## Credits 38 | 39 | - @lifenautjoe and his [webpack-starter-basic](https://github.com/lifenautjoe/webpack-starter-basic) 40 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /cypress/integration/css.spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('CSS', () => { 4 | beforeEach(() => { 5 | cy.visit('http://localhost:8080') 6 | }) 7 | 8 | it('homepage colours', () => { 9 | cy.get('body') 10 | .should('have.css', 'background-color', 'rgb(102, 202, 100)') 11 | cy.get('.page-title') 12 | .should('have.css', 'color', 'rgb(173, 255, 47)') 13 | cy.get('nav') 14 | .should('have.css', 'background-color', 'rgb(196, 0, 0)') 15 | cy.get('footer') 16 | .should('have.css', 'background-color', 'rgb(40, 40, 40)') 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /cypress/integration/img.spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Images', () => { 4 | beforeEach(() => { 5 | cy.visit('http://localhost:8080/about.html') 6 | }) 7 | 8 | it('is about page', () => { 9 | cy.get('.page-title').should('contain', 'ABOUT') 10 | }) 11 | 12 | it('loads image 01', () => { 13 | cy.get('.img-src-uses-require') 14 | .should('be.visible') 15 | .and(($img) => { 16 | // "naturalWidth" and "naturalHeight" are set when the image loads 17 | expect($img[0].naturalWidth).to.be.greaterThan(0) 18 | }) 19 | }) 20 | 21 | it('loads image 02', () => { 22 | cy.get('.img-from-html-loader') 23 | .should('be.visible') 24 | .and(($img) => { 25 | // "naturalWidth" and "naturalHeight" are set when the image loads 26 | expect($img[0].naturalWidth).to.be.greaterThan(0) 27 | }) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /cypress/integration/javascript.spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('CSS', () => { 4 | beforeEach(() => { 5 | cy.visit('http://localhost:8080') 6 | }) 7 | 8 | it('Foo and Bar are stored in window object', () => { 9 | cy.window() 10 | .its('Foo.value') 11 | .should('equal', 'foobar') 12 | 13 | cy.window() 14 | .its('Bar') 15 | .should('equal', 'barfoo 1') 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /cypress/integration/navigation.spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Navigation', () => { 4 | beforeEach(() => { 5 | cy.visit('http://localhost:8080') 6 | }) 7 | 8 | it('is homepage', () => { 9 | cy.get('.page-title').should('contain', 'HOME') 10 | cy.location('pathname').should('equal', '/') 11 | }) 12 | 13 | it('opens home page', () => { 14 | cy.get('body > nav > ul').contains('Home').click() 15 | cy.location('pathname', { timeout: 10000 }).should('contain', '/index.html') 16 | cy.get('.page-title').should('contain', 'HOME') 17 | }) 18 | 19 | it('opens about page', () => { 20 | cy.get('body > nav > ul').contains('About').click() 21 | cy.location('pathname', { timeout: 10000 }).should('contain', '/about.html') 22 | cy.get('.page-title').should('contain', 'ABOUT') 23 | }) 24 | 25 | it('opens contacts page', () => { 26 | cy.get('body > nav > ul').contains('Contacts').click() 27 | cy.location('pathname', { timeout: 10000 }).should('equal', '/contacts.html') 28 | cy.get('.page-title').should('contain', 'CONTACTS') 29 | }) 30 | 31 | it('navigation: home > about > home', () => { 32 | cy.get('body > nav > ul').contains('About').click() 33 | cy.location('pathname', { timeout: 10000 }).should('contain', '/about.html') 34 | cy.get('body > nav > ul').contains('Home').click() 35 | cy.location('pathname', { timeout: 10000 }).should('contain', '/index.html') 36 | }) 37 | 38 | it('navigation: home > about > contacts', () => { 39 | cy.get('body > nav > ul').contains('About').click() 40 | cy.location('pathname', { timeout: 10000 }).should('contain', '/about.html') 41 | cy.get('body > nav > ul').contains('Contacts').click() 42 | cy.location('pathname', { timeout: 10000 }).should('contain', '/contacts.html') 43 | }) 44 | 45 | it('navigation: home > contacts > home', () => { 46 | cy.get('body > nav > ul').contains('Contacts').click() 47 | cy.location('pathname', { timeout: 10000 }).should('contain', '/contacts.html') 48 | cy.get('body > nav > ul').contains('Home').click() 49 | cy.location('pathname', { timeout: 10000 }).should('contain', '/index.html') 50 | }) 51 | 52 | it('navigation: home > contacts > about', () => { 53 | cy.get('body > nav > ul').contains('Contacts').click() 54 | cy.location('pathname', { timeout: 10000 }).should('contain', '/contacts.html') 55 | cy.get('body > nav > ul').contains('About').click() 56 | cy.location('pathname', { timeout: 10000 }).should('contain', '/about.html') 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | /** 16 | * @type {Cypress.PluginConfig} 17 | */ 18 | module.exports = (on, config) => { 19 | // `on` is used to hook into various events Cypress emits 20 | // `config` is the resolved Cypress config 21 | } 22 | -------------------------------------------------------------------------------- /cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-static-html-pages", 3 | "version": "2.0.0", 4 | "repository": { 5 | "type": "git", 6 | "url" : "https://github.com/ivarprudnikov/webpack-static-html-pages" 7 | }, 8 | "description": "Example of a static website assembled by using webpack v5", 9 | "keywords": [ 10 | "webpack", 11 | "static", 12 | "starter", 13 | "html", 14 | "pages" 15 | ], 16 | "license": "MIT", 17 | "dependencies": { 18 | "normalize.css": "^8.0.1" 19 | }, 20 | "scripts": { 21 | "start": "webpack serve --config webpack.dev.js", 22 | "start:dist": "http-server dist --port ${PORT:-8080}", 23 | "build": "webpack --config webpack.prod.js", 24 | "preview": "npm run build && npm run start:dist", 25 | "test": "standard", 26 | "cy:run": "cypress run" 27 | }, 28 | "devDependencies": { 29 | "@babel/cli": "^7.17.6", 30 | "@babel/core": "^7.17.7", 31 | "@babel/preset-env": "^7.16.11", 32 | "babel-loader": "^8.2.3", 33 | "css-loader": "^6.7.1", 34 | "css-minimizer-webpack-plugin": "^3.4.1", 35 | "cypress": "^9.5.2", 36 | "html-loader": "^3.1.0", 37 | "html-webpack-plugin": "^5.5.0", 38 | "http-server": "^14.1.0", 39 | "mini-css-extract-plugin": "^2.6.0", 40 | "standard": "^16.0.4", 41 | "style-loader": "^3.3.1", 42 | "terser-webpack-plugin": "^5.3.1", 43 | "webpack": "^5.70.0", 44 | "webpack-cli": "^4.9.2", 45 | "webpack-dev-server": "^4.7.4" 46 | }, 47 | "standard": { 48 | "ignore": [ 49 | "cypress" 50 | ], 51 | "env": [ 52 | "node", 53 | "es6" 54 | ] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/css/main.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100vh; 3 | } 4 | 5 | body { 6 | background-color: #ffffff; 7 | color: #2c302f; 8 | font-weight: 400; 9 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 10 | margin: 0; 11 | padding: 0; 12 | display: flex; 13 | flex-direction: column; 14 | } 15 | 16 | h1, h2, h3 { 17 | font-weight: 300; 18 | } 19 | 20 | nav { 21 | display: block; 22 | background-color: black; 23 | color: white; 24 | padding: 7px; 25 | } 26 | 27 | nav > ul { 28 | list-style: none; 29 | padding: 0; 30 | margin: 0; 31 | display: flex; 32 | justify-content: space-evenly; 33 | } 34 | 35 | nav > ul > li { 36 | display: block; 37 | } 38 | 39 | nav > ul > li > a { 40 | display: block; 41 | color: beige; 42 | font-size: 18px; 43 | padding: 10px 20px; 44 | font-weight: bold; 45 | text-shadow: 0 -1px black; 46 | text-decoration: none; 47 | border: 1px solid rgba(0,0,0,0.1); 48 | border-radius: 3px; 49 | background-color: rgba(0,0,0,0.1); 50 | } 51 | 52 | .page-title { 53 | font-size: 6em; 54 | font-weight: bold; 55 | text-align: center; 56 | word-break: break-all; 57 | line-height: 0.85; 58 | } 59 | 60 | .page-title > i { 61 | display: block; 62 | font-size: 2em; 63 | margin-bottom: 0.3em; 64 | } 65 | 66 | footer { 67 | background: #282828; 68 | color: beige; 69 | flex: 1 0 auto; 70 | padding-top: 20px; 71 | } 72 | 73 | footer a { 74 | color: beige; 75 | } 76 | 77 | footer > ul { 78 | list-style: none; 79 | padding: 0; 80 | margin: 0; 81 | display: flex; 82 | justify-content: space-evenly; 83 | } 84 | 85 | footer > ul > li { 86 | display: block; 87 | } 88 | 89 | footer > ul > li > a { 90 | display: block; 91 | font-size: 21px; 92 | padding: 10px 20px; 93 | } 94 | 95 | .spacer { 96 | flex: 2 1 auto; 97 | } 98 | -------------------------------------------------------------------------------- /src/img/unicorn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivarprudnikov/webpack-static-html-pages/bfaf2618ecdbfa4e29e98dfde714e01b5efe48a8/src/img/unicorn.jpg -------------------------------------------------------------------------------- /src/js/bar.es6.js: -------------------------------------------------------------------------------- 1 | let invocation = 0 2 | 3 | const Bar = () => { 4 | invocation++ 5 | return `barfoo ${invocation}` 6 | } 7 | 8 | export { Bar } 9 | -------------------------------------------------------------------------------- /src/js/foo.cjs.js: -------------------------------------------------------------------------------- 1 | class Foo { 2 | constructor () { 3 | this.value = 'foobar' 4 | } 5 | 6 | static instance () { 7 | return new Foo() 8 | } 9 | 10 | getValue () { 11 | return this.value 12 | } 13 | } 14 | 15 | module.exports = { 16 | Foo 17 | } 18 | -------------------------------------------------------------------------------- /src/page-about/main.js: -------------------------------------------------------------------------------- 1 | require('normalize.css/normalize.css') 2 | require('../css/main.css') 3 | require('./page.css') 4 | const img = require('../img/unicorn.jpg') 5 | 6 | document.addEventListener('DOMContentLoaded', () => { 7 | console.log('DOMContentLoaded', 'page-about') 8 | console.log('Image through require()', img) 9 | }) 10 | -------------------------------------------------------------------------------- /src/page-about/page.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: darkslategrey; 3 | } 4 | nav { 5 | background-color: #8d0085; 6 | } 7 | .page-title { 8 | color: #1c1c2a 9 | } 10 | -------------------------------------------------------------------------------- /src/page-about/tmpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | About 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | <%= require('../partials/nav.html?raw') %> 16 | 17 |

18 | 19 | ABOUT 20 |

21 | 22 |
23 | 24 | 25 | 26 | <%= require('../partials/images.html?template').default %> 27 |
28 | 29 | <%= require('../partials/footer.html?raw') %> 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/page-contacts/main.js: -------------------------------------------------------------------------------- 1 | require('normalize.css/normalize.css') 2 | require('../css/main.css') 3 | require('./page.css') 4 | 5 | document.addEventListener('DOMContentLoaded', () => { 6 | console.log('DOMContentLoaded', 'page-contacts') 7 | }) 8 | -------------------------------------------------------------------------------- /src/page-contacts/page.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #ae223e; 3 | } 4 | nav { 5 | background-color: #006962; 6 | } 7 | .page-title { 8 | color: beige; 9 | } 10 | -------------------------------------------------------------------------------- /src/page-contacts/tmpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Contacts 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | <%= require('../partials/nav.html?raw') %> 16 | 17 |

18 | 19 | CONTACTS 20 |

21 | 22 | <%= require('../partials/footer.html?raw') %> 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/page-index/main.js: -------------------------------------------------------------------------------- 1 | import { Bar } from '../js/bar.es6' 2 | const { Foo } = require('../js/foo.cjs') 3 | require('normalize.css/normalize.css') 4 | require('../css/main.css') 5 | require('./page.css') 6 | 7 | document.addEventListener('DOMContentLoaded', () => { 8 | console.log('DOMContentLoaded', 'page-index') 9 | }) 10 | 11 | window.Foo = Foo.instance() 12 | window.Bar = Bar() 13 | 14 | console.log(window.Foo.getValue()) 15 | console.log(window.Bar) 16 | -------------------------------------------------------------------------------- /src/page-index/page.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #66ca64; 3 | } 4 | nav { 5 | background-color: #c40000; 6 | } 7 | .page-title { 8 | color: greenyellow; 9 | } 10 | -------------------------------------------------------------------------------- /src/page-index/tmpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | My homepage 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | <%= require('../partials/nav.html?raw') %> 17 | 18 |

19 | 20 | HOME 21 |

22 | 23 | <%= require('../partials/footer.html?raw') %> 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/partials/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | -------------------------------------------------------------------------------- /src/partials/images.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/partials/nav.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /webpack.dev.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin') 2 | 3 | module.exports = { 4 | 5 | // https://webpack.js.org/configuration/mode/ 6 | mode: 'development', 7 | 8 | // This option controls if and how source maps are generated. 9 | // https://webpack.js.org/configuration/devtool/ 10 | devtool: 'eval-cheap-module-source-map', 11 | 12 | // https://webpack.js.org/concepts/entry-points/#multi-page-application 13 | entry: { 14 | index: './src/page-index/main.js', 15 | about: './src/page-about/main.js', 16 | contacts: './src/page-contacts/main.js' 17 | }, 18 | 19 | // https://webpack.js.org/configuration/dev-server/ 20 | devServer: { 21 | port: 8080, 22 | writeToDisk: false // https://webpack.js.org/configuration/dev-server/#devserverwritetodisk- 23 | }, 24 | 25 | // https://webpack.js.org/concepts/loaders/ 26 | module: { 27 | rules: [ 28 | { 29 | // https://webpack.js.org/loaders/babel-loader/#root 30 | test: /\.m?js$/i, 31 | exclude: /node_modules/, 32 | use: { 33 | loader: 'babel-loader', 34 | options: { 35 | presets: ['@babel/preset-env'] 36 | } 37 | } 38 | }, 39 | { 40 | // https://webpack.js.org/loaders/css-loader/#root 41 | test: /\.css$/i, 42 | use: ['style-loader', 'css-loader'] 43 | }, 44 | { 45 | // https://webpack.js.org/guides/asset-modules/#resource-assets 46 | test: /\.(png|jpe?g|gif|svg)$/i, 47 | type: 'asset/resource' 48 | }, 49 | { 50 | // https://webpack.js.org/guides/asset-modules/#replacing-inline-loader-syntax 51 | resourceQuery: /raw/, 52 | type: 'asset/source' 53 | }, 54 | { 55 | // https://webpack.js.org/loaders/html-loader/#usage 56 | resourceQuery: /template/, 57 | loader: 'html-loader' 58 | } 59 | ] 60 | }, 61 | 62 | // https://webpack.js.org/concepts/plugins/ 63 | plugins: [ 64 | new HtmlWebpackPlugin({ 65 | template: './src/page-index/tmpl.html', 66 | inject: true, 67 | chunks: ['index'], 68 | filename: 'index.html' 69 | }), 70 | new HtmlWebpackPlugin({ 71 | template: './src/page-about/tmpl.html', 72 | inject: true, 73 | chunks: ['about'], 74 | filename: 'about.html' 75 | }), 76 | new HtmlWebpackPlugin({ 77 | template: './src/page-contacts/tmpl.html', 78 | inject: true, 79 | chunks: ['contacts'], 80 | filename: 'contacts.html' 81 | }) 82 | ] 83 | } 84 | -------------------------------------------------------------------------------- /webpack.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const HtmlWebpackPlugin = require('html-webpack-plugin') 4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 5 | const CssMinimizerPlugin = require('css-minimizer-webpack-plugin') 6 | const TerserPlugin = require('terser-webpack-plugin') 7 | 8 | const buildPath = path.resolve(__dirname, 'dist') 9 | 10 | module.exports = { 11 | 12 | // https://webpack.js.org/configuration/mode/ 13 | mode: 'production', 14 | 15 | // This option controls if and how source maps are generated. 16 | // https://webpack.js.org/configuration/devtool/ 17 | devtool: 'source-map', 18 | 19 | // https://webpack.js.org/concepts/entry-points/#multi-page-application 20 | entry: { 21 | index: './src/page-index/main.js', 22 | about: './src/page-about/main.js', 23 | contacts: './src/page-contacts/main.js' 24 | }, 25 | 26 | // how to write the compiled files to disk 27 | // https://webpack.js.org/concepts/output/ 28 | output: { 29 | filename: '[name].[contenthash].js', 30 | path: buildPath, 31 | clean: true 32 | }, 33 | 34 | // https://webpack.js.org/concepts/loaders/ 35 | module: { 36 | rules: [ 37 | { 38 | // https://webpack.js.org/loaders/babel-loader/#root 39 | test: /\.m?js$/i, 40 | exclude: /node_modules/, 41 | use: { 42 | loader: 'babel-loader', 43 | options: { 44 | presets: ['@babel/preset-env'] 45 | } 46 | } 47 | }, 48 | { 49 | // https://webpack.js.org/loaders/css-loader/#root 50 | test: /\.css$/i, 51 | use: [ 52 | MiniCssExtractPlugin.loader, 53 | 'css-loader' 54 | ] 55 | }, 56 | { 57 | // https://webpack.js.org/guides/asset-modules/#resource-assets 58 | test: /\.(png|jpe?g|gif|svg)$/i, 59 | type: 'asset/resource' 60 | }, 61 | { 62 | // https://webpack.js.org/guides/asset-modules/#replacing-inline-loader-syntax 63 | resourceQuery: /raw/, 64 | type: 'asset/source' 65 | }, 66 | { 67 | // https://webpack.js.org/loaders/html-loader/#usage 68 | resourceQuery: /template/, 69 | loader: 'html-loader' 70 | } 71 | ] 72 | }, 73 | 74 | // https://webpack.js.org/concepts/plugins/ 75 | plugins: [ 76 | new HtmlWebpackPlugin({ 77 | template: './src/page-index/tmpl.html', 78 | inject: true, 79 | chunks: ['index'], 80 | filename: 'index.html' 81 | }), 82 | new HtmlWebpackPlugin({ 83 | template: './src/page-about/tmpl.html', 84 | inject: true, 85 | chunks: ['about'], 86 | filename: 'about.html' 87 | }), 88 | new HtmlWebpackPlugin({ 89 | template: './src/page-contacts/tmpl.html', 90 | inject: true, 91 | chunks: ['contacts'], 92 | filename: 'contacts.html' 93 | }), 94 | new MiniCssExtractPlugin({ 95 | filename: '[name].[contenthash].css', 96 | chunkFilename: '[id].[contenthash].css' 97 | }) 98 | ], 99 | 100 | // https://webpack.js.org/configuration/optimization/ 101 | optimization: { 102 | minimize: true, 103 | minimizer: [ 104 | // https://webpack.js.org/plugins/terser-webpack-plugin/ 105 | new TerserPlugin({ 106 | parallel: true 107 | }), 108 | // https://webpack.js.org/plugins/mini-css-extract-plugin/#minimizing-for-production 109 | new CssMinimizerPlugin() 110 | ] 111 | } 112 | } 113 | --------------------------------------------------------------------------------