444 lines
15 KiB
TypeScript
444 lines
15 KiB
TypeScript
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;
|