const Encore = require('@symfony/webpack-encore'); const crypto = require('crypto'); const path = require('path'); if (!Encore.isRuntimeEnvironmentConfigured()) { Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev'); } // TYPO3 publishes Resources/Public of a Composer package as /_assets/, // e.g. md5('/vendor/ep/ep-theme/'). The hash depends on the package name only, so it is the // same locally and in production. const publicPath = '/_assets/' + crypto.createHash('md5').update('/vendor/ep/ep-theme/').digest('hex') + '/'; // Create webpack config for ext:ep_theme Encore .setOutputPath('./packages/ep_theme/Resources/Public/') .setPublicPath(publicPath) // manifest.json keys without a prefix ("js/main.js"); output and public path differ .setManifestKeyPrefix('') .addEntry('main', './packages/ep_theme/Resources/Private/Assets/js/main.js') .addStyleEntry('rte', './packages/ep_theme/Resources/Private/Assets/scss/rte.scss') .splitEntryChunks() // There is only one JS entry, so a shared runtime chunk buys nothing and costs a separate // 1.6KB request that every other bundle hard-depends on. Inlining it into main.js removes // both, and stops the `rte` style entry from dragging a JS file in behind it. .disableSingleRuntimeChunk() .enableSassLoader(options => { // Opt into the modern Sass JS API (sass-loader defaults to the legacy one, // which Dart Sass 2.0 removes). Encore still passes `outputStyle`, which // the modern API ignores in favour of `style`. options.api = 'modern' delete options.sassOptions.outputStyle options.sassOptions.style = 'expanded' // sass-loader's webpack importer declines `.css` requests, so plain CSS // pulled in with meta.load-css() has to be resolvable by Dart Sass itself. options.sassOptions.loadPaths = [path.join(__dirname, 'node_modules')] }) .enableVersioning(Encore.isProduction()) .enableSourceMaps(!Encore.isProduction()) .cleanupOutputBeforeBuild() .enablePostCssLoader(options => { options.postcssOptions = { plugins: [ require('tailwindcss'), require('autoprefixer'), ], } }) .copyFiles([ { from: './packages/ep_theme/Resources/Private/Assets/images', to: 'images/[name].[ext]' }, { from: './packages/ep_theme/Resources/Private/Assets/images/icons', to: 'images/icons/[name].[ext]' }, // TYPO3 takes the extension icon from this fixed path { from: './packages/ep_theme/Resources/Private/Assets/icons', to: 'Icons/[name].[ext]' }, ]) // The content hash is what lets .htaccess serve these as `immutable` for a year: a new // build is a new URL, so a browser never has to revalidate one. It also replaces TYPO3's // `?` query buster, which changed on every deploy even when the bytes did not. // `rte` stays unhashed - Configuration/RTE/*.yaml reference rte.css by literal path. .configureFilenames({ js: Encore.isProduction() ? 'js/[name].[contenthash:8].js' : 'js/[name].js', css: (pathData) => Encore.isProduction() && pathData.chunk.name !== 'rte' ? 'css/[name].[contenthash:8].css' : 'css/[name].css', }) .configureImageRule({ filename: 'images/[name].[contenthash:8][ext]', }) .configureFontRule({ filename: 'fonts/[name].[contenthash:8][ext]', }) .configureBabelPresetEnv((config) => { config.shippedProposals = true }); const EpThemeConfig = Encore.getWebpackConfig(); EpThemeConfig.name = 'EpThemeConfig'; EpThemeConfig.output.chunkFilename = 'js/[name].[contenthash:8].js'; module.exports = EpThemeConfig;