Привет!
В данной статье мы рассмотрим пример того как собрать сайт на hugo при помощи webpack с typescript и scss, рассмотрим как использовать хэш для файлов .css, .js, а также рассмотрим как работать с изображениями в scss файлах.
Hugo - очень быстрый инструмент для создания статических сайтов, а также не слишком сложный в освоении, его быстрота сильно подкупает, и поэтому если вы когда-либо использовали генератор статических сайтов раньше, но не использовали hugo, то самое время это сделать.
Данная статья рассчитана на тех, кто уже знаком и знает как работать с hugo. В данной статье не будет рассматриваться процесс создания статического сайта на hugo.
Конфигурацию которую я вам предложу не идеальная, потому что, всегда может понадобиться тот или иной инструмент для внедрения новой фичи или т.п., поэтому данная конфигурация подойдет для базовой настройки, если вы не увидели каких-либо инструментов, то просто добавьте их в свой проект сами, данная конфигурация легко настраивается и расширяется.
Установка необходимых пакетов для работы с webpack:
Начнем с установки webpack и его плагинов:
npm i -D webpack webpack-cli clean-webpack-plugin copy-webpack-plugin terser-webpack-plugin
Далее установим пакеты для работы с js:
npm i -D babel-loader @babel/core @babel/preset-env core-js
Далее установим пакеты для работы с typescript:
npm i -D typescript @babel/preset-typescript ts-loader tsconfig-paths-webpack-plugin
Далее установим пакеты для работы с css и scss:
npm i -D postcss postcss-loader postcss-preset-env sass sass-loader style-loader css-loader css-minimizer-webpack-plugin autoprefixer import-glob-loader group-css-media-queries-loader
Далее установим пакеты для совместной работы hugo server и webpack server:
npm i -D npm-run-all cross-env
Все компоненты в одном запросе:
npm i -D webpack webpack-cli clean-webpack-plugin copy-webpack-plugin terser-webpack-plugin babel-loader @babel/core @babel/preset-env core-js typescript @babel/preset-typescript ts-loader tsconfig-paths-webpack-plugin postcss postcss-loader postcss-preset-env sass sass-loader style-loader css-loader css-minimizer-webpack-plugin autoprefixer import-glob-loader group-css-media-queries-loader npm-run-all cross-env
Настраиваем запуск сервера для разработки hugo + webpack
В файле package.json создадим скрипты для запуска webpack и hugo:
"scripts": {
"webpack-dev": "webpack --env mode=development --watch",
"webpack-build": "webpack --env mode=production",
"hugo-server": "hugo server --disableFastRender -b http://192.168.1.100:3000 --bind 0.0.0.0",
"hugo-build": "hugo --cleanDestinationDir --minify",
"dev": "npm-run-all --parallel webpack-dev hugo-server",
"build": "npm-run-all webpack-build --serial hugo-build"
},
Примечание:
- С помощью npm-run-all –parallel мы запускаем сразу 2 сервера для разработки frontend и hugo
- -b http://192.168.1.100:3000 –bind 0.0.0.0 - Данная настройка необходима для того, чтобы вы могли открыть сервер разработки на любом другом устройстве в сети, например на смартфоне
Настраиваем webpack.config.js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const autoprefixer = require('autoprefixer');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
module.exports = (env) => {
const THEME = 'YOUR_THEME_NAME';
const IS_PRODUCTION = env.mode === 'production';
const MODE = IS_PRODUCTION ? 'production' : 'development';
const BUNDLE_HASH = IS_PRODUCTION ? '' : '';
const SOURCEMAP = !IS_PRODUCTION;
return {
mode: MODE,
entry: [
path.resolve(__dirname, 'themes', THEME, 'src', 'index')
],
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
devtool: SOURCEMAP ? 'source-map' : false,
output: {
filename: `js/bundle${BUNDLE_HASH}.js`,
path: path.resolve(__dirname, 'assets', 'assets'),
assetModuleFilename: 'img/[name]-[hash:8][ext][query]',
},
devServer: {
static: 'static',
compress: true,
port: 3000,
},
watchOptions: {
poll: true,
ignored: /node_modules/
},
module: {
rules: [
{
test: /\.(ts|js)?$/,
exclude: /node_modules/,
use: [
'babel-loader',
'ts-loader'
],
},
{
test: /\.(sa|sc|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {},
},
{
loader: 'css-loader',
options: {
url: true,
sourceMap: SOURCEMAP,
}
},
{
loader: 'postcss-loader',
options: {
sourceMap: SOURCEMAP,
postcssOptions: {
plugins: [
autoprefixer()
]
}
}
},
'group-css-media-queries-loader',
{
loader: 'sass-loader',
options: {
sourceMap: SOURCEMAP,
sassOptions: {
includePaths: ['./node_modules']
},
implementation: require('sass'),
webpackImporter: false,
}
},
'import-glob-loader'
],
},
{
test: /\.(jpe?g|png|gif|svg)$/i,
type: 'asset/resource',
generator: {
filename: 'img/[name]-[hash:8][ext]'
},
},
]
},
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: `css/bundle${BUNDLE_HASH}.css`,
}),
],
optimization: {
minimize: IS_PRODUCTION,
minimizer: [
new CssMinimizerPlugin(),
new TerserPlugin()
],
}
};
}
Структура файлов и работа с ними
Теперь, когда Вы установили все необходимые модули и настроили конфиг файл, самое время разобраться в структуре папок и файлов, т.к. здесь как раз и есть пару нюансов и сейчас самое время о них поговорить.
Здесь логика следующая:
- Через webpack мы разрабатываем frontend часть, компилируем её и кладем в папку ./assets/assets.
- Зачем в папке assets делать еще одну папку assets? Это нужно для того чтобы при очистке папки через webpack, случайно не стереть другие файлы в папке ./assets/, т.к. файлы находящиеся в этой папке используются hugo для различных задач.
- Необходимо понять для чего мы помещаем скомпилированные файлы именно в папку ./assets/, а не в папку ./public/. Итак, для чего это нужно? Это нужно нам, как раз, для того, чтобы создавать хэш для файлов css и js. Т.е. подключать основной bundle.js или bundle.css мы будем через hugo, а не просто html.
Подключаем CSS
{{ $assetsDir := "./assets/assets/" }}
{{ if (and (fileExists $assetsDir) (fileExists (print $assetsDir "css/"))) }}
{{ $cssFilePath := (print "assets/css/" .Site.Params.assets.css) }}
{{ $cssFile := resources.Get $cssFilePath }}
{{ if hugo.IsProduction }}
{{ $cssFile = $cssFile | fingerprint}}
{{ $cssMapPath := (print $cssFilePath ".map") }}
{{ if (fileExists (print $assetsDir "css/" .Site.Params.assets.css ".map")) }}
{{ $cssMap := (resources.Get $cssMapPath).Permalink }}
{{ end }}
{{ end }}
{{ with $cssFile }} <link rel="stylesheet" href="{{ .Permalink }}" type="text/css"> {{ end }}
{{ end }}
Подключаем JS
{{ if (and (fileExists $assetsDir) (fileExists (print $assetsDir "js/"))) }}
{{ $jsFilePath := (print "assets/js/" .Site.Params.assets.js) }}
{{ $jsFile := resources.Get $jsFilePath }}
{{ if hugo.IsProduction }}
{{ $jsFile = $jsFile | fingerprint}}
{{ $jsMapPath := (print $jsFilePath ".map") }}
{{ if (fileExists (print $assetsDir "js/" .Site.Params.assets.js ".map")) }}
{{ $jsMap := (resources.Get $jsMapPath).Permalink }}
{{ end }}
{{ end }}
{{ with $jsFile }} <script src="{{ .Permalink }}"></script> {{ end }}
{{ end }}
resource.Get - указывает на папку ./assets/, после чего производит поиск по файлам и ищет следующий файл “assets/js/bundle.js”. Т.е. в итоге мы получаем следующий путь к файлу: ./assets/assets/js/bundle.js. Если файл не будет найден, то выйдет ошибка, поэтому сначала компилируется webpack и только после этого уже компилируется hugo. Но для того, чтобы ошибки не было и сервер не остановил работу, мы сделали проверку на наличие файлов.
Также мы сделали проверку на наличие .map для css и js, и также включили их для итоговой сборки.
Подключаем изображения
{{ $assetsDir := "./assets/assets/" }}
{{ if (and (fileExists $assetsDir) (fileExists (print $assetsDir "img/"))) }}
{{ range readDir (print $assetsDir "img/") }}
{{ $file := (resources.Get (print "assets/img/" .Name)).Permalink }}
{{ end }}
{{ end }}
Данный код нужно вставить только в index.html, т.к. инициализировать изображения нам нужно всего 1 раз. Также как и со стилями и скриптами, мы делаем проверку на наличие папки и файлов.
Если мы из папки ./themes/YOUR_THEME/src/img переместим изображения в папку static, то у нас возникнет проблема с webpack, т.к. он будет ругаться на неверный путь к файлу, чтобы этого избежать мы просто инициализируем изображения через hugo, т.к. webpack работает как обычно, но для hugo делаем исключение, потому что если этого не сделать, то путь к файлу в css будет неверный.
Итого
- Работаем с webpack как обычно, но кладем всё это в папку ./assets/assets/. Здесь у нас хранятся bundle.css, bundle.css, изображения в папке img которые используются в css и которым мы через webpack назначаем хэш.
- Подключаем bundle.css и bundle.js через hugo, предварительно сделав проверку на наличие файлов, чтобы не получить ошибку, если файлы не успели скомпилироваться через webpack.
- Инициализируем изображения из папки ./assets/assets/img через hugo, для того чтобы он их при сборке положил вместе со всеми остальными файлами, иначе он их просто проигнорирует.
В итоге мы получаем css, js, img вместе с хэшом, поэтому при обновлении проекта не нужно волноваться что все изменения вступят в силу.