Skip to content

Instantly share code, notes, and snippets.

@tigt
Created April 17, 2022 03:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tigt/c0f8344690c306f55426b4e2533895dd to your computer and use it in GitHub Desktop.
Save tigt/c0f8344690c306f55426b4e2533895dd to your computer and use it in GitHub Desktop.
Marko + Sass Webpack config
const path = require('path')
const webpack = require('webpack')
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const CopyFilesPlugin = require('copy-webpack-plugin')
const CSSExtractPlugin = require('mini-css-extract-plugin')
const MarkoPlugin = require('@marko/webpack/plugin').default
const nodeExternalsPlugin = require('webpack-node-externals')
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
const WarningsToErrorsPlugin = require('warnings-to-errors-webpack-plugin')
const isDev = process.env.NODE_ENV === 'development'
const isProd = !isDev
if (isDev) {
var BrowserSyncPlugin = require('browser-sync-webpack-plugin')
var SpawnServerPlugin = require('spawn-server-webpack-plugin')
}
const SHARED_DEV_PORT = 8080
const ASSETS_BASE_URL = '/_/'
const markoPlugin = new MarkoPlugin()
const spawnedServer = isDev && new SpawnServerPlugin()
const cssExtractor = new CSSExtractPlugin({
filename: '[name]-[contenthash].css',
ignoreOrder: true // We can’t guarantee that component CSS will concat in the same order across all chunks (nor should we)
})
/**
* BEWARE: WebPack does this _hilarious_ thing where it applies plugins and loaders in the **reverse** order of the array.
*/
const browserBuild = {
name: 'Browser 📱 ',
optimization: {
splitChunks: {
chunks: 'all',
maxInitialRequests: 3
}
},
output: {
filename: '[name]-[contenthash].js', // consider not using [hash]es when isDev: https://webpack.js.org/guides/build-performance/#avoid-production-specific-tooling
path: path.join(__dirname, 'dist/browser')
},
performance: isDev ? undefined : { // Consider always doing this, even in dev?
maxAssetSize: kilobytes(30 * 5), // Multiplying this and the below by 5 because Webpack limits _un_compressed size
maxEntrypointSize: kilobytes(50 * 5),
hints: 'error' // This should go from error to warn though when isDev
},
devServer: isDev ? {
overlay: { warnings: true, errors: true }, // open browser tab when build fails
noInfo: true, // only show warnings/errors
contentBase: false, // I forget why we set this, but I remember it sucks
serveIndex: true, // generate a file list in directories without an index.html
...spawnedServer.devServerConfig,
port: SHARED_DEV_PORT
} : undefined,
plugins: [
new webpack.DefinePlugin({ 'process.browser': true }),
isDev && new BrowserSyncPlugin({
open: 'ui',
ui: { port: 9090 },
host: 'localhost',
notify: false,
proxy: {
target: `http://localhost:${SHARED_DEV_PORT}`,
ws: true // allow WebSockets so webpack-dev-server’s HMR can work
}
},
{
injectCss: true,
reload: false // prevent BrowserSync from reloading the page; let webpack-dev-server do it
}),
isProd && new BundleAnalyzerPlugin({
analyzerMode: 'static', // generate an .html file, don’t start up a server
defaultSizes: 'gzip',
logLevel: 'warn',
reportFilename: '../bundle-analysis.html'
}),
new CopyFilesPlugin([{
from: 'src/static-urls',
to: 'static-urls',
ignore: ['_README.md']
}]),
cssExtractor,
isProd && new OptimizeCssAssetsPlugin(), // Consider always doing this?
markoPlugin.browser
]
}
const serverBuild = {
name: 'Server 🏸 ',
target: 'async-node', // Node.js with async/await support
node: {
__dirname: false // make Webpack stop mocking `__dirname`, since it works fine on the server
},
externals: [nodeExternalsPlugin()],
optimization: { minimize: false }, // no need to minify on the server
output: {
libraryTarget: 'commonjs2',
path: path.join(__dirname, 'dist/server')
},
stats: {
assets: false,
children: false
},
plugins: [
new webpack.DefinePlugin({
'process.browser': undefined, // do we need to explicitly override this?
'process.env.BUNDLE': true
}),
new webpack.BannerPlugin({ // abuse the Webpack banner API to inject JS at the top of files
banner: 'require("source-map-support").install();',
raw: true
}),
cssExtractor,
isDev && spawnedServer,
markoPlugin.server
]
}
// Shared settings for both server and browser compilers
function createWebpackCompiler (config) {
return {
...config,
mode: isProd ? 'production' : 'development',
devtool: isProd ? 'source-map' : 'inline-source-map',
output: {
publicPath: ASSETS_BASE_URL,
hashDigestLength: 7,
...config.output
},
optimization: {
minimizer: [
new TerserPlugin({
cache: true, // cache transformed JS in node_modules/.cache/
sourceMap: true
})
],
noEmitOnErrors: true,
...config.optimization
},
stats: {
builtAt: false,
chunks: false,
entrypoints: false,
excludeAssets: /static-urls|\.map$/, // these files don’t matter for the frontend bundle
hash: false,
modules: false,
version: false,
...config.stats
},
resolve: {
extensions: ['.js', '.json', '.marko']
},
module: {
rules: [
{ test: /\.marko$/, loader: '@marko/webpack/loader' },
{ test: /\.css$/, use: [CSSExtractPlugin.loader, 'css-loader'] },
{ test: /\.scss$/, use: [CSSExtractPlugin.loader, 'css-loader', 'sass-loader'] },
{
test: /\.(?:jpe?g|gif|png|svg)$/,
loader: 'file-loader',
options: {
// Write assets from server & browser compiler output to browser folder
outputPath: '../browser',
publicPath: ASSETS_BASE_URL,
name: '[name]-[contenthash:base62:5].[ext]'
}
}
]
},
plugins: [
...config.plugins,
new WarningsToErrorsPlugin(),
new webpack.DefinePlugin({
__DEV__: isDev,
__PROD__: isProd
}),
isProd && new CleanWebpackPlugin({
cleanStaleWebpackAssets: false // works around https://github.com/webpack-contrib/copy-webpack-plugin/issues/385
})
].filter(Boolean)
}
}
function kilobytes (x) {
return x * 1024
}
module.exports = [browserBuild, serverBuild].map(createWebpackCompiler)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment