├── .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 | [](https://github.com/ivarprudnikov/webpack-static-html-pages/actions/workflows/ci.yml)
5 | [](http://standardjs.com)
6 | [](https://github.com/ivarprudnikov/webpack-static-html-pages/issues)
7 | [](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 |
--------------------------------------------------------------------------------