Skip to content

Instantly share code, notes, and snippets.

@versedi
Last active January 18, 2024 13:21
Show Gist options
  • Star 20 You must be signed in to star a gist
  • Fork 13 You must be signed in to fork a gist
  • Save versedi/6b216f2135a6d686601066e674752418 to your computer and use it in GitHub Desktop.
Save versedi/6b216f2135a6d686601066e674752418 to your computer and use it in GitHub Desktop.
Webpack Encore + Sass + MiniCSSExtractPlugin + PurgeCSS + OptimizeCss + Babel + Typescript
/* eslint-disable no-useless-escape */
const Encore = require('@symfony/webpack-encore');
const TerserPlugin = require('terser-webpack-plugin');
const CircularDependencyPlugin = require('circular-dependency-plugin');
const HtmlCriticalWebpackPlugin = require('html-critical-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const PurgeCssPlugin = require('purgecss-webpack-plugin');
const WebpackBar = require('webpackbar');
const path = require('path');
const glob = require('glob-all');
const cssWhitelist = require('./purge-css-whitelist');
const sassOptions = {
includePaths: ['node_modules', './resources/assets/scss/'],
};
// add node modules to be transformed by babel here. anything uses new JS features (eg arrow function) has to be transpiled to work in older browsers
const includedNodeModules = [
'jquery',
'swiper',
];
if (!Encore.isRuntimeEnvironmentConfigured()) {
Encore.configureRuntimeEnvironment(Encore.isProduction() ? 'production' : 'dev-server');
}
Encore.addPlugin(new WebpackBar());
Encore.addLoader({
test: /\.ts$/,
enforce: 'pre',
loader: 'eslint-loader',
exclude: path.resolve(__dirname, 'node_modules/'),
options: {
fix: true,
},
});
if (!Encore.isProduction()) {
Encore.addLoader({
test: [/\.ts$/],
enforce: 'pre',
loader: 'prettier-loader',
exclude: path.resolve(__dirname, 'node_modules/'),
options: {
parser: 'prettierx-typescript',
semi: true,
trailingComma: 'all',
singleQuote: true,
printWidth: 120,
tabWidth: 4,
spaceBeforeFunctionParen: true,
},
});
Encore.addLoader({
test: [/\.js$/],
enforce: 'pre',
loader: 'prettier-loader',
exclude: path.resolve(__dirname, 'node_modules/'),
options: {
parser: 'prettierx-babel',
semi: true,
trailingComma: 'all',
singleQuote: true,
printWidth: 120,
tabWidth: 4,
spaceBeforeFunctionParen: true,
},
});
}
if (!Encore.isProduction()) {
Encore.configureFilenames({
js: '[name].js',
css: '[name].css',
images: 'images/[name].[ext]',
fonts: 'fonts/[name].[ext]',
});
Encore.addLoader({
test: /\.(scss|css)$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
sourceMap: !Encore.isProduction(),
},
},
{
loader: 'sass-loader',
options: {
sourceMap: !Encore.isProduction(),
},
},
],
});
} else {
const postcssLoaderOptions = {
autoprefixer: {
browsers: ['last 2 versions'],
},
plugins: () => [autoprefixer],
sourceMap: !Encore.isProduction,
};
Encore.addLoader({
test: /\.(scss|css)$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
sourceMap: !Encore.isProduction(),
},
},
{
loader: 'postcss-loader',
options: postcssLoaderOptions,
},
{
loader: 'sass-loader',
options: {
sourceMap: !Encore.isProduction(),
},
},
],
});
// Encore.enablePostCssLoader();
}
if (!Encore.isProduction()) {
Encore.addPlugin(
new CircularDependencyPlugin({
// exclude detection of files based on a RegExp
exclude: /a\.js|node_modules/,
// add errors to webpack instead of warnings
failOnError: true,
// set the current working directory for displaying module paths
cwd: process.cwd(),
}),
);
}
// Uncomment only when generating static site for above the fold CSS (critical CSS).
// if (!Encore.isProduction()) {
// Encore.addPlugin(
// new HtmlCriticalWebpackPlugin({
// base: path.resolve(__dirname, 'public'),
// src: 'static_home.html',
// dest: 'optimized_home.html',
// inline: true,
// minify: true,
// extract: true,
// width: 375,
// height: 565,
// penthouse: {
// blockJSRequests: false,
// timeout: 99999,
// },
// }),
// );
// }
Encore.setOutputPath('public/assets/') // directory where compiled assets will be stored
.setPublicPath('/assets'); // public path used by the web server to access the output path
if (Encore.isProduction()) {
Encore.cleanupOutputBeforeBuild();
}
Encore.copyFiles({
from: './resources/assets/images',
to: 'images/[path][name].[ext]',
pattern: /\.(png|jpg|jpeg|gif|svg)/,
});
Encore.addLoader({
test: /\.scss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
// you can specify a publicPath here
// by default it uses publicPath in webpackOptions.output
publicPath: '../',
hmr: process.env.NODE_ENV === 'development',
},
},
'css-loader',
'sass-loader',
],
});
Encore.addPlugin(
new MiniCssExtractPlugin({
filename: Encore.isProduction() ? '[name].[contenthash].css' : '[name].css',
}),
);
if (Encore.isProduction()) {
Encore.addPlugin(
new OptimizeCSSAssetsPlugin({
assetNameRegExp: /\.(c|s[ac])ss$/,
cssProcessorPluginOptions: {
preset: [
'default',
{
discardComments: {
removeAll: true, // remove any comments?
},
},
],
},
canPrint: true,
}),
);
}
Encore.addPlugin(
new TerserPlugin({
terserOptions: {
sourceMap: !Encore.isProduction(),
cache: !Encore.isProduction(),
parallel: true,
output: {
// comments: false,
},
},
}),
);
if(Encore.isProduction()) {
Encore.addPlugin(
new PurgeCssPlugin({
// folders: ['resources/views/**/*', 'resources/assets/scss/'],
paths: glob.sync([path.join(__dirname, 'resources/views/**/*/*.blade.php')]),
whitelist: cssWhitelist,
// This is a tough task - any classes that aren't present in the view files (eg. added by JS) need to be whitelisted here
// It's not an easy or quick thing to do - requires THOROUGH testing for any missing CSS classes.
// I recommend to turn it off and only use it manually after major updates are done in styling
whitelistPatterns: [
/icon$/,
/primary$/,
/info$/,
/success$/,
/danger$/,
/swiper\-w+/,
/slide$/,
/popover\-\w+/,
/tooltip\-\w+/,
/lb\-\w+/,
/ui\-\w+/,
],
}),
);
}
Encore.configureOptimizeCssPlugin().enableSourceMaps(!Encore.isProduction());
Encore.enableSourceMaps(true);
Encore.configureBabel(
babelConfig => {
// add additional presets (preset-env is added by default)
babelConfig.presets.push('@babel/preset-flow');
// IE11/Edge requires below plugins
babelConfig.plugins.push('@babel/plugin-transform-object-assign');
babelConfig.plugins.push('@babel/plugin-transform-spread');
babelConfig.plugins.push('@babel/plugin-transform-exponentiation-operator');
babelConfig.plugins.push('@babel/plugin-transform-arrow-functions');
babelConfig.plugins.push('@babel/plugin-proposal-object-rest-spread');
babelConfig.plugins.push('@babel/plugin-proposal-class-properties');
// no plugins are added by default, but you can add some
// babelConfig.plugins.push('styled-jsx/babel');
// if (Encore.isProduction()) {
// babelConfig.plugins.push("transform-remove-console");
// }
},
{
// node_modules is not processed through Babel by default
// but you can whitelist specific modules to process
includeNodeModules: includedNodeModules,
useBuiltIns: 'usage',
corejs: {
version: 3.2,
proposals: true,
},
// or completely control the exclude rule (note that you
// can't use both "include_node_modules" and "exclude" at
// the same time)
// exclude: /bower_components/
},
);
/*
* ENTRY CONFIG
*
* Add 1 entry for each "page" of your app
* (including one that's included on every page - e.g. "app")
*
* Each entry will result in one JavaScript file (e.g. app.js)
* and one CSS file (e.g. app.css) if you JavaScript imports CSS.
*/
Encore.splitEntryChunks()
.addEntry('app', './resources/assets/js/entries/app.ts')
.addEntry('blogPosts', './resources/assets/js/entries/blogPosts.ts')
.addEntry('profile', './resources/assets/js/entries/profile.ts')
.enableVersioning(Encore.isProduction())
.enableIntegrityHashes(Encore.isProduction())
.enableSingleRuntimeChunk()
.configureSplitChunks(() => ({
name: 'vendor_app',
chunks: 'all',
minChunks: 2,
}))
.autoProvidejQuery() // uncomment if you're having problems with a jQuery plugin
.configureFriendlyErrorsPlugin()
// uncomment if you use TypeScript
.enableTypeScriptLoader();
// .enableHandlebarsLoader()
// .enableForkedTypeScriptTypesChecking()
// .configureFilenames({
// images: '[path][name].[ext]',
// })
// Retrieve the config
const config = Encore.getWebpackConfig();
if (Encore.isProduction()) {
config.devtool = 'source-map';
} else {
Encore.addLoader({
test: [/\.ts$/, /\.scss/],
use: ['cache-loader', 'babel-loader'],
include: [path.resolve('resources/assets/**/*'), path.resolve('node_modules')],
});
// Change the kind of source map generated in development mode
config.devtool = 'inline-source-map';
// USE cheap for Debugging in Chrome - awesome feature - you can put breakpoint in your PhpStorm/Vscode/whatever and browser will stop executing on it! Just like in PHP
// config.devtool = 'cheap-module-eval-source-map';
//
config.optimization.minimize = false;
Encore.configureWatchOptions(function(watchOptions) {
watchOptions.poll = 1000;
watchOptions.aggregateTimeout = 2000;
watchOptions.ignored = /node_modules/;
});
//Use below options when you need to debug webpack build
config.stats = {
// assets: true,
// builtAt: true,
cachedAssets: true,
errors: true,
errorDetails: true,
// reasons: true,
timings: true,
// warnings: true,
};
config.devServer = {
host: 'app.local',
port: 9000,
hot: true,
index: 'public/index.php',
liveReload: true,
historyApiFallback: true,
disableHostCheck: true,
public: 'app.local',
allowedHosts: ['app.local'],
contentBase: path.join(__dirname, 'public'),
watchContentBase: true,
watchOptions: {
aggregateTimeout: 3500,
},
proxy: {
'*': {
target: 'http://app.local:80',
},
},
};
config.node = {
global: true,
fs: 'empty',
// Fix: "Uncaught ReferenceError: global is not defined", and "Can't resolve 'fs'".
// output: {
// libraryTarget: 'umd', // Fix: "Uncaught ReferenceError: exports is not defined".
// },
};
}
// Export the config (be careful not to call
// getWebpackConfig() again)
module.exports = config;
@versedi
Copy link
Author

versedi commented Jul 28, 2020

Update for eslint + prettier, changed dev server. For live reload access your webpage by http://app.local:9000

compatible with "@symfony/webpack-encore": "^0.29.1",

addew few important warnings and comments

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment