Skip to content

Instantly share code, notes, and snippets.

@bymathias
Created June 13, 2020 11:59
Show Gist options
  • Select an option

  • Save bymathias/35d913859054072f259a105d0c5fab50 to your computer and use it in GitHub Desktop.

Select an option

Save bymathias/35d913859054072f259a105d0c5fab50 to your computer and use it in GitHub Desktop.
/*! webpack | Webpack configuration */
import dotenv from 'dotenv'
import { resolve, basename } from 'path'
import { readFileSync } from 'fs'
import { sync } from 'glob'
import { dot } from 'dot-object'
import fmp from 'front-matter-pug'
import webpack from 'webpack'
import { CleanWebpackPlugin } from 'clean-webpack-plugin'
import webpackNodeExternals from 'webpack-node-externals'
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
import StylelintWebpackPlugin from 'stylelint-webpack-plugin'
import TerserWebpackPlugin from 'terser-webpack-plugin'
import OptimizeCssAssetsWebpackPlugin from 'optimize-css-assets-webpack-plugin'
import ImageminWebpackPlugin from 'imagemin-webpack-plugin'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import CopyWebpackPlugin from 'copy-webpack-plugin'
import PurgecssWebpackPlugin from 'purgecss-webpack-plugin'
import CompressionWebpackPlugin from 'compression-webpack-plugin'
import config from './config'
dotenv.config()
const resolvePath = (...args) => resolve(__dirname, ...args)
const allViews = sync(resolvePath('src', 'client', '[^_]*.pug'))
const now = new Date()
// Webpack config: Exporting a Function
module.exports = (env = {}, argv) => {
// Merge environment variables
env = { ...env, ...process.env }
// Set `development` as Webpack default mode
argv.mode = argv.mode || 'development'
// Set `production` mode using `--mode=production`
env.PRODUCTION = argv.mode === 'production'
// Enable debug mode using `--debug`
env.DEBUG = argv.debug || false
// Enable gzip compression using `--gzip`
env.GZIP = argv.gzip || false
const envSuffix = env.PRODUCTION ? `${env.npm_package_version}.min` : 'dev'
const banner = [
now.toISOString().substr(0, 10),
env.npm_package_name,
`@version ${env.npm_package_version}`,
`@license ${env.npm_package_license}`,
`@author ${env.npm_package_author}`,
'Copyright (c) 2020'
].join('\n')
// Exporting multiple Webpack configurations
return([
// -----------------------------------------------
// Back-End: API Configuration
// -----------------------------------------------
{
target: 'node',
name: 'api',
context: resolvePath('src', 'api'),
entry: {
index: ['./index.js']
},
output: {
// This tells the server bundle to use Node-style exports
libraryTarget: 'commonjs2',
path: resolvePath('dist', 'api'),
publicPath: '/',
filename: '[name].js'
},
// Style of source mapping to enhance the debugging process
devtool: '#source-map',
node: {
// If you don't put this is, __dirname
// and __filename return blank or /
__dirname: false,
__filename: false
},
externals: [
// In order to ignore all modules in node_modules folder
webpackNodeExternals()
],
// Options for resolving module requests
resolve: {
extensions: [ '.js' ],
alias: {
'@': resolvePath('src', 'api')
}
},
// Options affecting the normal modules
module: {
rules: [
{
enforce: 'pre',
test: /\.js$/,
exclude: /node_modules/,
loader: 'eslint-loader',
options: {
// eslintPath: '', // `.eslintrc` is used by default
emitError: true,
emitWarning: !env.PRODUCTION,
failOnError: env.PRODUCTION
}
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
},
// Add plugins to the compiler
plugins: [
// Remove output folder(s) before building
new CleanWebpackPlugin({
verbose: env.DEBUG
}),
// Add dynamic banner to output bundle(s)
env.PRODUCTION
? new webpack.BannerPlugin(banner)
: 0
].filter(Boolean),
// Optimizations depending on the chosen mode
optimization: {
minimize: env.PRODUCTION,
minimizer: [
// JavaScript parser, mangler and compressor toolkit for ES6+
new TerserWebpackPlugin({
sourceMap: true,
cache: true,
parallel: true,
extractComments: false,
terserOptions: {
sourceMap: true,
warnings: true,
compress: true,
mangle: true,
ie8: false,
safari10: false
}
})
]
}
},
// -----------------------------------------------
// Front-End: Client Configuration
// -----------------------------------------------
{
target: 'web', // default
name: 'client',
context: resolvePath('src', 'client'),
entry: {
main: [
'./scss/main.scss',
'./js/main.js',
]
},
output: {
path: resolvePath('dist', 'client'),
// chunkFilename: 'js/[name].[id].js',
filename: `js/[name].${envSuffix}.js`
},
// Style of source mapping to enhance the debugging process
devtool: env.PRODUCTION ? '#source-map' : '#cheap-module-eval-source-map',
// How webpack notifies you of assets and entrypoints
// that exceed a specific file limit
performance: {
hints: false
},
// Make watching work properly
watchOptions: {
poll: true
},
// Options for resolving module requests
resolve: {
extensions: ['.js', '.css', '.scss', '.pug'],
modules: ['node_modules']
// alias: {}
},
// Local development server configuration
devServer: {
// clientLogLevel: DEBUG ? 'debug' : 'silent',
stats: env.DEBUG ? 'verbose' : 'minimal',
// turn off all error logging
// required by `friendly-errors-webpack-plugin`
// quiet: true,
contentBase: [ resolvePath('dist', 'client') ],
watchContentBase: true,
writeToDisk: true,
host: 'localhost',
port: env.APP_CLIENT_PORT,
public: env.APP_CLIENT_URL,
publicPath: '/',
allowedHosts: [
env.APP_API_URL
],
// proxy: {
// '/api': {
// target: 'http://localhost:8081',
// changeOrigin: true
// }
// },
inline: true,
hot: true,
disableHostCheck: true,
historyApiFallback: { index: '/404.html' },
// openPage: ['index.html'],
open: true
},
// Options affecting the normal modules
module: {
rules: [
{
enforce: 'pre',
test: /\.js$/,
exclude: /node_modules/,
loader: 'eslint-loader',
options: {
// eslintPath: '', // `.eslintrc` is used by default
emitError: true,
emitWarning: !env.PRODUCTION,
failOnError: env.PRODUCTION
}
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
'useBuiltIns': 'usage',
'corejs': 3
}
]
]
}
},
{
test: /\.s[ac]ss$/i,
exclude: /node_modules/,
use: [
env.PRODUCTION ? MiniCssExtractPlugin.loader : 'style-loader',
{
loader: 'css-loader',
options: {
sourceMap: true,
importLoaders: 2
}
},
{
loader: 'postcss-loader',
options: {
sourceMap: true,
ident: 'postcss',
plugins: (loader) => [
require('postcss-preset-env')()
]
}
},
{
loader: 'sass-loader',
options: {
sourceMap: true
}
}
]
},
{
test: /\.(png|jpe?g|gif|svg)$/,
include: [
resolvePath ('src', 'client', 'img')
],
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'img/'
}
},
{
test: /\.(woff|woff2|ttf|eot|svg)$/,
include: [
/@fortawesome/,
resolvePath ('src', 'client', 'img')
],
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'font/'
}
},
{
test: /\.pug$/,
exclude: /node_modules/,
loader: 'pug-loader',
options: {
self: true,
globals: true,
pretty: true,
root: resolvePath('src', 'client')
// filters: {}
}
}
]
},
// Add plugins to the compiler
plugins: [
// Remove output folder(s) before building
new CleanWebpackPlugin({
verbose: env.DEBUG
}),
// Lint all CSS files with `stylelint`
new StylelintWebpackPlugin({
context: resolvePath('src', 'client', 'scss'),
files: ['**/*.scss'],
emitError: true,
failOnError: env.PRODUCTION
}),
// Move CSS into a separate output file
new MiniCssExtractPlugin({
filename: `css/[name].${envSuffix}.css`
}),
// Generate HTML file(s)
...generateViews(allViews, {
inject: false,
minify: env.PRODUCTION
? {
collapseWhitespace: true,
removeComments: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true,
minifyCSS: true,
minifyJS: true
}
: false,
env,
config
}, env.PRODUCTION),
// Copies individual files or entire directories
new CopyWebpackPlugin({
patterns: [
{
context: resolvePath('src', 'client'),
from: '*.@(txt|xml|webmanifest)',
to: resolvePath('dist', 'client'),
transform (content) { // (content, path)
return replaceContent(content, config)
}
},
{
context: resolvePath('src', 'client', 'img', 'ico'),
from: '*.@(svg|png)',
to: resolvePath('dist', 'client'),
}
]
}),
// Optimize all images for production env only
new ImageminWebpackPlugin({
disable: !env.PRODUCTION,
test: /\.(png|jpe?g|gif|svg)$/i,
pngquant: {
quality: '65-90',
speed: 4
},
optipng: {
interlaced: false,
optimizationLevel: 5
},
jpegtran: {
progressive: true
},
gifsicle: {
interlaced: true,
optimizationLevel: 2
}
}),
// Add dynamic banner to output bundle(s)
env.PRODUCTION
? new webpack.BannerPlugin(banner)
: 0,
// Remove unused CSS with PurgeCSS
env.PRODUCTION
? new PurgecssWebpackPlugin({
paths: sync(`${resolvePath('src', 'client')}/**/*`, { nodir: true }),
})
: 0,
// Prepare compressed versions of assets to serve them with Content-Encoding
env.GZIP
? new CompressionWebpackPlugin({
algorithm: 'gzip',
test: /\.(js|css)$/,
filename: '[path].gz[query]',
threshold: 10240,
minRatio: 0.8,
deleteOriginalAssets: false
})
: 0
].filter(Boolean),
// Optimizations depending on the chosen mode
optimization: {
minimize: env.PRODUCTION,
minimizer: [
// JavaScript parser, mangler and compressor toolkit for ES6+
new TerserWebpackPlugin({
sourceMap: true,
extractComments: false,
terserOptions: {
sourceMap: true,
warnings: true,
ecma: 5,
mangle: false
}
}),
// Plugin to optimize\minimize CSS assets using cssnano
new OptimizeCssAssetsWebpackPlugin({
sourceMap: true,
cssProcessorOptions: {
map: { inline: false },
autoprefixer: false
}
}),
],
// Create a `vendors` chunk, which includes all code
// shared between entrypoints from `node_modules`
splitChunks: {
cacheGroups: {
default: false,
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
minChunks: 2
}
}
}
}
}
])
}
/**
* readContent
*
* @param item {String} Path to the .pug file
* @returns {Object} With these properties (attributes, body, frontmatter)
*/
function readContent (item) {
const data = readFileSync(item, 'utf8')
return fmp(data)
}
/**
* replaceContent
*
* @param content {Object} File content
* @param data {Object} Configuration
* @returns {String} The new file content
*/
function replaceContent (content, data) {
const db = dot(data)
let str = content.toString()
Object.entries(db).forEach(([key, value]) => {
const dataKey = `{{ ${key} }}`
const regexStr = new RegExp(dataKey, 'g')
str = str.replace(regexStr, value)
})
return str
}
/**
* generateViews
*
* @param items {Array} List of paths to the handlebars files
* @param options {Object} html-webpack-plugin options
* @param env {Boolean} the environment mode
* @returns {Array} New instances of HtmlWebpackPlugin
*/
function generateViews(items, options) {
const views = []
items.map(item => {
const data = readContent(item)
const fileName = basename(item, '.pug')
views.push(
new HtmlWebpackPlugin({
template: resolvePath('src', 'client', `${fileName}.pug`),
filename: `${data.attributes.output || fileName}.html`,
chunks: data.attributes.chunks,
page: data.attributes.page,
...options
})
)
})
return views
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment