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]")}`
}
}],
}
]
}
}