Webpack. Базове складання проекту

На основі https://www.youtube.com/watch?v=R9DTbDA_ZVE

https://github.com/maxdenaro/maxgraph-youtube-source/tree/master/Webpack

Встановлення webpack

Ініціалізація package.json

npm init

Встановлення webpack

npm i webpack webpack-cli cross-env --save-dev

Встановлення normalize.css

npm i normalize.css

Базова структура

  • app - директорія скомпільованих файлів
  • src - директорія з файлами проекта
    assets/favicon.ico - пустой файл
    fonts
    img
    js
    - js/main.js
    scss
    scss/mixins
    scss/mixins/_breakpoint.scss
    scss/vendor
    scss/_fonts.scss
    scss/_mixins.scss
    scss/_settings.scss
    scss/_vars.scss
    scss/_vendor.scss
    scss/main.scss
    index.html
  • webpack.config.js

​Деякий вміст файлів

Файл scss/mixins/_breakpoint.scss

@mixin laptop {
  @media (max-width: (1025px)) {
    @content;
  }
}

Файл src/scss/_fonts.scss

@include font-face("notosans", "notosans", 400);

Файл src/scss/_vendor.scss

@import "~normalize.css"; 

Файл src/scss/_vars.scss

:root {
    --color-black: #000;
    --color-white: #fff;
  }

Файл src/scss/_settings.scss

html {
    box-sizing: border-box;
}

*,
*::before,
*::after {
    box-sizing: inherit;
}
  
body {
    overflow-anchor: none;
    font-family: "asd", sans-serif;
    background-color: var(--color-white);
    scroll-behavior: smooth;
}
  
img {
    max-width: 100%;
}
  
a {
    text-decoration: none;
    color: inherit;
}

Файл src/scss/_main.scss

@import "mixins";
@import "fonts";
@import "vendor";
@import "vars";
@import "settings";

Файл src/js/main.js

import "../scss/main.scss";
import "../index.html";

Прописуємо скрипти в файлі package.json в розділі "scripts": {...}

"build": "cross-env NODE_ENV=production webpack --mode production",
"dev": "cross-env NODE_ENV=development webpack --mode development",
"start": "cross-env NODE_ENV=development webpack serve --mode development"

Заповнення файлу webpack.config.js

Додаємо головну структуру

module.exports = { }

Вказуємо вхідну та вихідну точку

На початку файла підключаємо модуль

const path = require("path")

В розділ module.exports додаємо

context: path.resolve(__dirname, "src"),
entry: "./js/main.js",
output: {
   filename: "./js/main.js",
   path: path.resolve(__dirname, "app"),
},

Розбиваємо збірку на дев та прод

По за розділом module.exports додаємо

const isDev = process.env.NODE_ENV === "development"
const isProd = !isDev

const filename = (ext) => isDev ? `[name].${ext}` : `[name].[contenthash].${ext}`;

В розділ module.exports додаємо

mode: "development", 

Змінюємо output: {...}

output: {
   filename: `./js/${filename("js")}`,
   path: path.resolve(__dirname, "app"),
},

Додаємо обробку html

Встановлюємо пакет

npm i html-webpack-plugin --save-dev

На початку файла підключаємо модуль

const HTMLWebpackPlugin = require("html-webpack-plugin")

В розділ module.exports додаємо

plugins: [
        new HTMLWebpackPlugin({
            template: path.resolve(__dirname, "src/index.html"),
            filename: "index.html",
            minify: {
                collapseWhitespace: isProd
            }
        }),
   ],

Очищення директорії app

Встановлюємо пакет

npm i clean-webpack-plugin --save-dev

На початку файла підключаємо модуль

const {CleanWebpackPlugin} = require("clean-webpack-plugin")

В розділ plugins додаємо

new CleanWebpackPlugin(), 

Обробка css та scss

Встановлюємо пакет

npm i css-loader mini-css-extract-plugin node-sass sass-loader --save-dev

На початку файла підключаємо модуль

const MiniCssExtractPlugin = require("mini-css-extract-plugin") 

В розділ module.exports додаємо

module: {
  rules: [
   {
      test: /\.css$/i,
      use: [
             {
               loader: MiniCssExtractPlugin.loader,
               options: {
                 hmr: isDev
              }
            },
            "css-loader"
          ]
    },
    {
                test: /\.s[ac]ss$/,
                use: [
                    {
                        loader: MiniCssExtractPlugin.loader,
                        options: {
                            publicPath: (resourcePath, context) => {
                                return path.relative(path.dirname(resourcePath), context) + "/";
                            },
                        }
                    },
                    "css-loader", 
                    "sass-loader"
                ],
     },
  ]
}

В розділ plugins додаємо

new MiniCssExtractPlugin({
    filename: `./css/${filename("css")}`
}),

Встановлення dev-сервера

Встановлюємо пакет

npm i webpack-dev-server --save-dev

В розділ module.exports додаємо

devServer: {
        historyApiFallback: true,
        static: path.resolve(__dirname, "app"),
        open: true,
        compress: true,
        hot: true,
        port: 3000,
    },

В файлі package.json в розділі scripts додаємо

"start": "cross-env NODE_ENV=development webpack serve --mode development"

Встановлюємо html-loader

npm i html-loader --save-dev

В розділі module: { rules: [...] } додаємо

{
   test: /\.html$/,
   loader: "html-loader",
},

Обробка assets

Встановлюємо пакет

npm i copy-webpack-plugin --save-dev

Підключиємо модуль

const CopyWebpackPlugin = require("copy-webpack-plugin")

В розділ plugins додаємо

new CopyWebpackPlugin({
    patterns: [
        {
            from: path.resolve(__dirname, "src/assets"),
            to: path.resolve(__dirname, "app")
        }
    ]
})

Обробка зображень

Встановлюємо пакет

npm i file-loader --save-dev

В розділ module: { rules: [...] } додаємо

{
   test: /\.(?:|gif|png|jpg|jpeg|svg)$/,
   use: [{
       loader: "file-loader",
       options: {
           name: `./img/${filename("[ext]")}`
       }
    }],
},

Обробка шрифтів

Встановлюємо пакет

npm i file-loader --save-dev

В розділ module: { rules: [...] } додаємо

{
    test: /\.(?:|woff2)$/,
    use: [{
              loader: "file-loader",
               options: {
                   name: `./fonts/${filename("[ext]")}`
                 }
           }],
 },

Обробка js-файлів

Встановлюємо пакет

npm i babel-loader @babel/core @babel/preset-env --save-dev

В розділ module: { rules: [...] } додаємо

{
    test: /\.js$/,
    exclude: /node_modules/,
    use: ["babel-loader"],
},

В корні проекта додаємо файл .babelrc з наступним наповненням

{
    "presets": ["@babel/preset-env"],
    "plugins": ["@babel/plugin-proposal-class-properties"]
}

Налаштування SourceMaps

В розділ module.exports додаємо

devtool: isProd ? false : "source-map",

Оптимізація ассетів

Встановлюємо пакет

npm i css-minimizer-webpack-plugin terser-webpack-plugin --save-dev

Підключиємо модуль

const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin")
const TerserWebpackPlugin = require("terser-webpack-plugin")

В розділ module.exports додаємо

optimization: optimization(),

Після підключених модулів додаємо функцію

const optimization = () => {
    const configObj = {
        splitChunks: {
            chunks: "all"
        }
    }

    if(isProd){
        configObj.minimizer = [
            new CssMinimizerWebpackPlugin(),
            new TerserWebpackPlugin()
        ]
    }

    return configObj;
}

Оптимізація зображень

Встановлюємо пакет

npm i image-minimizer-webpack-plugin imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo --save-dev

Підключаємо плагіни

const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const { presetDefault } = require("svgo");

переносимо все з plugins: [...] в функцію plugins() та робимо деякі зміни

const plugins = () => {
    const basePlugins = [
        new HTMLWebpackPlugin({
            template: path.resolve(__dirname, "src/index.html"),
            filename: "index.html",
            minify: {
                collapseWhitespace: isProd
            }
        }),
        new CleanWebpackPlugin(),
        new MiniCssExtractPlugin({
            filename: `./css/${filename("css")}`
        }),
        new CopyWebpackPlugin({
            patterns: [
                {
                    from: path.resolve(__dirname, "src/assets"),
                    to: path.resolve(__dirname, "app")
                }
            ]
        })
    ];

    if(isProd) {
        basePlugins.push(
            new ImageMinimizerPlugin({
                minimizer: {
                implementation: ImageMinimizerPlugin.imageminMinify,
                options: {
                    // Lossless optimization with custom option
                    // Feel free to experiment with options for better result for you
                    plugins: [
                    	["gifsicle", { interlaced: true }],
                    	["jpegtran", { progressive: true }],
                    	["optipng", { optimizationLevel: 5 }],
                    	// Svgo configuration here https://github.com/svg/svgo#configuration
                    	[
                        	"svgo",
                        	{
                        		plugins: [{
                            		name: "preset-default",
      								params: {
        								overrides: {
          									// customize options for plugins included in preset
          									inlineStyles: {
            									onlyMatchedOnce: false,
          									},

		
          									// or disable plugins
          									removeDoctype: false,
        								},
      								},
                        		}],
                        	},
                    	],
                    ],
                  },
           	    },
       		})
      )
    }

    return basePlugins;
}

А в замін визиваємо цю функцію

plugins: plugins(),

Швидке розгортання

Повна команда

npm init
npm i webpack webpack-cli cross-env html-webpack-plugin clean-webpack-plugin css-loader mini-css-extract-plugin node-sass sass-loader webpack-dev-server html-loader copy-webpack-plugin file-loader babel-loader @babel/core @babel/preset-env css-minimizer-webpack-plugin terser-webpack-plugin image-minimizer-webpack-plugin imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo --save-dev
npm i normalize.css

Повний файл webpack.config.js

const path = require("path")
const HTMLWebpackPlugin = require("html-webpack-plugin")
const {CleanWebpackPlugin} = require("clean-webpack-plugin")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const CopyWebpackPlugin = require("copy-webpack-plugin")
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin")
const TerserWebpackPlugin = require("terser-webpack-plugin")
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const { presetDefault } = require("svgo");

const isDev = process.env.NODE_ENV === "development"
const isProd = !isDev

const filename = (ext) => isDev ? `[name].${ext}` : `[name].[contenthash].${ext}`;

const optimization = () => {
    const configObj = {
        splitChunks: {
            chunks: "all"
        }
    }

    if(isProd){
        configObj.minimizer = [
            new CssMinimizerWebpackPlugin(),
            new TerserWebpackPlugin()
        ]
    }

    return configObj;
}

const plugins = () => {
    const basePlugins = [
        new HTMLWebpackPlugin({
            template: path.resolve(__dirname, "src/index.html"),
            filename: "index.html",
            minify: {
                collapseWhitespace: isProd
            }
        }),
        new CleanWebpackPlugin(),
        new MiniCssExtractPlugin({
            filename: `./css/${filename("css")}`
        }),
        new CopyWebpackPlugin({
            patterns: [
                {
                    from: path.resolve(__dirname, "src/assets"),
                    to: path.resolve(__dirname, "app")
                }
            ]
        })
    ];

    if(isProd) {
        basePlugins.push(
            new ImageMinimizerPlugin({
                minimizer: {
                    implementation: ImageMinimizerPlugin.imageminMinify,
                    options: {
                        // Lossless optimization with custom option
                        // Feel free to experiment with options for better result for you
                        plugins: [
                            ["gifsicle", { interlaced: true }],
                            ["jpegtran", { progressive: true }],
                            ["optipng", { optimizationLevel: 5 }],
                            // Svgo configuration here https://github.com/svg/svgo#configuration
                            [
                                "svgo",
                                {
                                    plugins: [{
                                        name: "preset-default",
                                        params: {
                                            overrides: {
                                            // customize options for plugins included in preset
                                            inlineStyles: {
                                                onlyMatchedOnce: false,
                                            },

                                            // or disable plugins
                                            removeDoctype: false,
                                            },
                                        },
                                    }],
                                },
                            ],
                        ],
                    },
                },
            })
        )
    }

    return basePlugins;
}

module.exports = {
    mode: "development",
    context: path.resolve(__dirname, "src"),
    entry: "./js/main.js",
    output: {
        filename: `./js/${filename("js")}`,
        path: path.resolve(__dirname, "app"),
        // publicPath: ""
    },
    devServer: {
        historyApiFallback: true,
        static: path.resolve(__dirname, "app"),
        open: true,
        compress: true,
        hot: true,
        port: 3000,
    },
    optimization: optimization(),
    plugins: plugins(),
    devtool: isProd ? false : "source-map",
    module: {
    rules: [
        {
            test: /\.html$/,
            loader: "html-loader",
        },
        {
            test: /\.css$/i,
            use: [
                {
                    loader: MiniCssExtractPlugin.loader,
                    options: {
                        hmr: isDev
                    }
                },
                "css-loader"
            ]
        },
        {
            test: /\.s[ac]ss$/,
            use: [
                    {
                        loader: MiniCssExtractPlugin.loader,
                        options: {
                            publicPath: (resourcePath, context) => {
                                return path.relative(path.dirname(resourcePath), context) + "/";
                            },
                        }
                    },
                    "css-loader", 
                    "sass-loader"
                ],
       },
       {
            test: /\.(?:|gif|png|jpg|jpeg|svg)$/,
            use: [{
                loader: "file-loader",
                options: {
                    name: `./img/${filename("[ext]")}`
                }
            }],
        },
        {
            test: /\.js$/,
            exclude: /node_modules/,
            use: ["babel-loader"],
        },
        {
            test: /\.(?:|woff2)$/,
            use: [{
                loader: "file-loader",
                options: {
                    name: `./fonts/${filename("[ext]")}`
                }
            }],
        }
    ]
  }
}
2015-2026 © SumyNikNet