Last active
June 11, 2024 12:41
-
-
Save versedi/6b216f2135a6d686601066e674752418 to your computer and use it in GitHub Desktop.
Webpack Encore + Sass + MiniCSSExtractPlugin + PurgeCSS + OptimizeCss + Babel + Typescript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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