import Encore from '@symfony/webpack-encore'; console.log(`Webpack configuration - development: ${Encore.isDev()}, production: ${Encore.isProduction()}, devServer: ${Encore.isDevServer()}`); import WpjResolver from './WpjResolver'; import { IComponents } from './WpjConfig'; const path = require('path'); const fs = require('fs'); const process = require('process'); const handlebars = require('handlebars'); const CompressionPlugin = require('compression-webpack-plugin'); const zlib = require('zlib') const WpjComponentLoader = require('./WpjComponentLoader'); const sass = require("sass"); const { fullConfig, components, modulesWithValues, modules , hasComponents, entrypoints} = require('./WpjConfig'); const sassUtils = require('node-sass-utils')(sass); const { sentryWebpackPlugin } = require("@sentry/webpack-plugin"); WpjComponentLoader.setComponents(fullConfig.components); const module_template = handlebars.compile(fs.readFileSync(__dirname + '/scss_modules.hbs', 'utf8')); const scss_modules = module_template(fullConfig); const rootDir = process.cwd(); if (!Encore.isRuntimeEnvironmentConfigured()) { Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev'); } // token for uploading webpack sourcemaps into sentry const sentryUploadToken = Encore.isProduction() && hasComponents && (process.env.SENTRY_UPLOAD_TOKEN || false); Encore // directory where compiled assets will be stored .setOutputPath('web/build/') // public path used by the web server to access the output path .setPublicPath('/web/build') // only needed for CDN's or sub-directory deploy //.setManifestKeyPrefix('build/') // Every entrypoint here is NEEDED in BOTH component (for fallback) a non-component shops // Otherwise move entrypoint to list downward .addEntry('icons', '@assets/entrypoints/icons.js') .addEntry('app', '@assets/entrypoints/app.js') .addEntry('htmlpage', '@assets/entrypoints/htmlpage.js') // TODO: předělat 404, pak může dolů .addEntry('cart', '@assets/entrypoints/cart.js') .addEntry('shopping_list', '@assets/entrypoints/shopping-list.js') // TODO: předělat nákupní seznamy, pak může dolů .addEntry('wpj_toolbar', '@assets/entrypoints/wpj_toolbar.js') // TODO: předělat WpjToolbar, pak může dolů .addEntry('user', '@assets/entrypoints/user.js') // TODO: předělat, pak může dolů //.addEntry('page2', './assets/js/page2.js') // will require an extra script tag for runtime.js // but, you probably want this, unless you're building a single-page app .enableSingleRuntimeChunk() .splitEntryChunks() .enableSourceMaps(!Encore.isProduction() || !!sentryUploadToken) // enables hashed filenames (e.g. app.abc123.css) .enableVersioning(Encore.isProduction()) // uncomment if you use Sass/SCSS files .enableSassLoader(options => { // https://github.com/sass/node-sass#options options.sassOptions.includePaths = [path.join(rootDir, 'engine/web/common/static/compass/'), path.join(rootDir, 'common')]; options.implementation = require('sass'); options.sassOptions.data = (loaderContext) => { // More information about available properties https://webpack.js.org/api/loaders/ const { resourcePath, rootContext } = loaderContext; return scss_modules; }; options.sassOptions.functions = { "component($component, $value)": function(name, value) { name = name.getValue(); value = value.getValue(); const component = fullConfig.components[name] ?? {class: 'c-unknown-component'}; let result = component[value]; if (value === 'class') { result = 'c-' + result; } return sassUtils.castToSass(result); } } }) .enablePostCssLoader(options => { options.postcssOptions = { // the directory where the postcss.config.js file is stored config: path.join(rootDir, 'engine/web/common/webpack/') }; }) .configureDefinePlugin(options => { options.ADMIN = false; options.MODULES = modules; options.COMPONENTS = Object.fromEntries(Object.entries(components as IComponents).map(([name, component]) => { return [name, {version: component.version, selector: component.selector}]; })); }) .configureDevServerOptions(options => { options.server = { type: 'https', options: { cert: '/etc/ssl/cert.pem', key: '/etc/ssl/key.pem', }, }; options.hot = true; options.allowedHosts = 'all'; options.host = '0.0.0.0'; options.client = { overlay: true, progress: true, }; options.proxy = { secure: false, context: () => true, target: 'https://localhost:443' }; }) .addAliases({ '@webpack': path.join(rootDir, 'engine/web/common/webpack/assets/'), }) if (hasComponents) { Encore .addEntry('base', '@assets/components/base.ts') .addEntry('sentry', '@assets/entrypoints/sentry.js'); for (const entrypoint of Array.from(entrypoints.keys())) { Encore.addEntry(`c-${entrypoint}`, `WpjComponentLoader.ts!?entrypoint=${entrypoint}`); } const CopyWebpackPlugin = require('copy-webpack-plugin'); Encore.addPlugin( new CopyWebpackPlugin({ patterns: [ { from: path.resolve(rootDir, 'engine/web/common/webpack/assets/icons'), to: 'icons', priority: 5 }, { from: 'assets/icons', to: 'icons', force: true, priority: 10 } ], }) ) Encore.addPlugin( new CopyWebpackPlugin({ patterns: [ { from: path.resolve(rootDir, 'engine/web/common/webpack/assets/images'), to: 'images/[name].[hash][ext]', priority: 5 }, { from: 'assets/images', to: 'images/[name].[hash][ext]', force: true, priority: 10 } ], }) ) } else { Encore .addEntry('product', '@assets/entrypoints/product.js') .addEntry('category', '@assets/entrypoints/category.js') .addEntry('home', '@assets/entrypoints/home.js') .addEntry('articles', '@assets/entrypoints/articles.js') .addEntry('last_visited_products', '@assets/entrypoints/last_visited_products.js') } if (Encore.isDevServer()) { Encore.disableCssExtraction(); } Encore.cleanupOutputBeforeBuild(['**/*'], (options) => { options.cleanOnceBeforeBuildPatterns.push(...[ '!icons/**', '!icons-preview.*', '!app.icons.*', ]); // options.verbose = true; }) if (!Encore.isProduction()) { Encore.enableBuildCache({ // object of "buildDependencies" // https://webpack.js.org/configuration/other-options/#cachebuilddependencies // __filename means that changes to webpack.config.ts should invalidate the cache config: [__filename], }); } /* Při lokálním buildu přes prepareWeb chceme pokaždé unikátní cestu k souboru kvůli cache busting */ if (Encore.isDev() && !Encore.isDevServer()) { Encore.configureFilenames({ 'js': '[name].js?id=[chunkhash]', 'css': '[name].css?id=[chunkhash]' }); } if (Encore.isProduction()) { Encore.addPlugin( new CompressionPlugin({ algorithm: 'gzip', }) ).addPlugin( new CompressionPlugin({ filename: '[path][base].br', algorithm: 'brotliCompress', compressionOptions: { params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 11, }, }, }) ) } if (modules['PRODUCTS_COMPARE'] === 'true') { Encore.addEntry('product-compare', '@assets/entrypoints/product-compare.js'); } if (modules['RETURNS'] === 'true' || modules['RECLAMATIONS'] === 'true') { Encore.addEntry('returns', '@assets/entrypoints/returns.js'); } if (modules['B2B_PREORDERS'] === 'true') { Encore.addEntry('preorders', '@assets/entrypoints/preorders.js'); } let enableTypeScript = true; let enableReact = false; if (modules['JS_SHOP'] === 'true' && !hasComponents) { Encore.addEntry('js-shop', '@assets/entrypoints/js-shop.js'); enableTypeScript = true; enableReact = true; } if (modules['JS_MENU'] === 'true' && !hasComponents) { Encore.addEntry('js-menu', '@assets/entrypoints/js-menu.js'); enableTypeScript = true; enableReact = true; } if (modulesWithValues['PRODUCTS__SETS'] === 'js_multisets' && !hasComponents) { Encore.addEntry('js-multisets', '@assets/entrypoints/js-multisets.js'); enableTypeScript = true; enableReact = true; } if (hasComponents) { enableTypeScript = true; enableReact = true; Encore.addRule({ resourceQuery: /raw/, type: 'asset/source', }) .enableStimulusBridge(path.resolve(rootDir, 'engine/web/common/webpack/assets/components/controllers.json')); } if (enableTypeScript) { Encore.enableTypeScriptLoader(options => { options.transpileOnly = false; options.onlyCompileBundledFiles = true; // engine/tsconfig.json options.configFile = path.resolve(__dirname, '../../../', 'tsconfig.json'); }) } if(sentryUploadToken) { Encore.addPlugin(sentryWebpackPlugin({ authToken: sentryUploadToken, org: 'wpj', project: 'wpjshop', sourcemaps: { filesToDeleteAfterUpload: "web/build/**/*.map*", // clean source maps from prod build after they are uploaded to sentry }, url: 'https://sentry.wpj.cz/"' }) ) } if (enableReact) { Encore.enableReactPreset(); } // .configureLoaderRule('eslint', loaderRule => { // loaderRule.test = /\.(jsx?|vue)$/ // }); // uncomment if you're having problems with a jQuery plugin //.autoProvidejQuery() const EncoreProxy = new Proxy(Encore, { get: (target, prop) => { if (prop === 'getWebpackConfig') { return (...parameters) => { const config = target[prop](...parameters); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const loaders = []; if (!Encore.isDevServer()) { loaders.push(MiniCssExtractPlugin.loader); } else { loaders.push('style-loader'); } // config.stats = 'verbose'; config.resolve.modules = [path.resolve(rootDir, 'engine/node_modules'), path.resolve(rootDir, 'node_modules'), path.resolve(rootDir)]; config.resolveLoader = config.resolve; config.module.rules.push({ test: /\.font\.js/, use: [ ...loaders, { loader: 'css-loader', }, { loader: 'webfonts-loader', options: { publicPath: '/web/build/' } } ] }); const WpjResolver = require('./WpjResolver.js'); function getBundlesPaths(lastPath) { let paths = {}; let index = 0; for (const bundle in fullConfig['frontend_bundles']) { let resourcePath = path.join(fullConfig['frontend_bundles'][bundle], lastPath); if (fs.existsSync(resourcePath)) { paths['bundle' + (index ? index.toString() : '')] = resourcePath; ++index; } } return paths; } config.resolve.plugins = [ new WpjResolver({ rule: /^@assets/, paths: { shop: path.join(rootDir, '/assets/'), ...getBundlesPaths('assets/'), shared: (modules['COMPONENTS'] === 'true') ? path.join(rootDir, 'engine/web/templates/x/static/') : path.join(rootDir, '/static/'), common: path.join(rootDir, '/engine/web/common/webpack/assets/') } }) ]; // Zatím nemůžu tohle zakázat, protože jsou tu fallback views a fallback entrypointy, který potřebujou tyhle resolvery if (true /*!hasComponents*/) { config.resolve.plugins.push( new WpjResolver({ rule: /^@css/, scss: true, paths: { shop: path.join(rootDir, '/templates/css/'), ...(hasComponents ? {twig_compat: path.join(rootDir, '/engine/web/templates/twig_compat/scss/')} : {}), ...getBundlesPaths('scss/'), shared: hasComponents ? path.join(rootDir, 'engine/web/templates/x/static/scss/') : path.join(rootDir, '/static/scss/'), common: path.join(rootDir, '/engine/web/common/static/scss/') } }), new WpjResolver({ rule: /^@static/, paths: { shop: path.join(rootDir, '/assets/'), ...getBundlesPaths('/'), shared: path.join(rootDir, '/static/'), common: path.join(rootDir, '/engine/web/common/static/') } }), new WpjResolver({ rule: /^@shop/, paths: { shop: rootDir } }), ); } if (hasComponents) { config.resolve.plugins.push(new WpjResolver({ rule: /^@twig/, paths: { shop: path.join(rootDir, '/twig/'), ...getBundlesPaths('twig/'), shared: path.join(rootDir, '/engine/web/common/twig/'), } })); for (const [key, bundlePath] of Object.entries(fullConfig['twig_bundles'])) { config.resolve.plugins.push(new WpjResolver({ rule: new RegExp(`^@${key.replace("Bundle", '')}`), paths: { shop: path.join(rootDir, `twig/bundles/${key}/`), shared: path.join(rootDir, bundlePath), } })); } } config.resolve.cacheWithContext = false; // config.resolve.symlinks = false; config.resolveLoader = { modules: [__dirname, path.resolve(rootDir, 'engine/node_modules'), path.resolve(rootDir, 'node_modules')], } config.watchOptions = { ignored: /node_modules/, }; if (!Encore.isDevServer()) { // Exit on first error config.bail = true; } console.log('Config ready, starting build ...'); // const util = require('util'); // console.log(util.inspect(config, false, null, true /* enable colors */)); return config; }; } else if (prop === 'default') { return EncoreProxy; } return (...parameters) => { const res = target[prop](...parameters); return (res === target) ? EncoreProxy : res; }; } }); module.exports = EncoreProxy; export default EncoreProxy;