const path = require('path'); const fs = require('fs'); const webpack = require('webpack'); const SymlinkWebpackPlugin = require('symlink-webpack-plugin'); const { VueLoaderPlugin } = require('vue-loader'); const ROOT = path.resolve(__dirname, '..'); const SRC = path.resolve(ROOT, 'src'); const PUBLIC = path.resolve(ROOT, 'public'); module.exports = (env, argv) => { const mode = argv.mode || 'production'; const isDev = (mode === 'development'); return { mode, devtool: isDev ? 'inline-source-map' : false, watch: isDev, entry: { app: path.resolve(SRC, 'app.js') }, output: { path: PUBLIC, filename: isDev ? 'assets/[name].js' : 'assets/[name].[contenthash:8].js', chunkFilename: isDev ? 'assets/[name].js' : 'assets/[name].[contenthash:8].js', publicPath: './', clean: { keep: /^(index\.php|files|geo|images\/icons)(\/.*)?$/ } }, optimization: { splitChunks: { chunks: 'all', cacheGroups: { maplibre: { test: /[\\/]node_modules[\\/]maplibre-gl[\\/]/, name: 'maplibre', chunks: 'all', priority: 30, enforce: true }, uppy: { test: /[\\/]node_modules[\\/](@uppy|@transloadit|namespace-emitter|nanoid)[\\/]/, name: 'uppy', chunks: 'async', priority: 20, enforce: true } } } }, performance: { hints: isDev ? false : 'warning', maxEntrypointSize: 1500 * 1024, maxAssetSize: 1100 * 1024 }, module: { rules: [{ test: /\.vue$/, loader: 'vue-loader' }, { test: /\.js$/, exclude: file => (/node_modules/.test(file) && !/\.vue\.js/.test(file)), use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } }, { test: /\.s[ac]ss$/i, use: [ 'vue-style-loader', 'css-loader', { loader: 'sass-loader', options: { implementation: require('sass'), sourceMap: isDev } } ] }, { test: /\.css$/i, use: ['vue-style-loader', 'css-loader'] }, { test: /\.(png|svg|jpg|jpeg|gif|webp)$/i, type: 'asset', parser: { dataUrlCondition: { maxSize: 1 * 1024 } }, generator: { filename: 'assets/images/[name].[contenthash:8][ext]' } }] }, plugins: [ new SymlinkWebpackPlugin([ { origin: '../files/', symlink: 'files' }, { origin: '../resources/geo/', symlink: 'geo' }, { origin: '../src/images/icons/', symlink: 'assets/images/icons' } ]), new webpack.DefinePlugin({ __VUE_OPTIONS_API__: 'true', __VUE_PROD_DEVTOOLS__: 'false', __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false' }), { apply(compiler) { compiler.hooks.done.tap('EntryPointManifestPlugin', (stats) => { const manifest = { entrypoints: mapChunkGroups(stats.compilation.entrypoints), chunkGroups: mapChunkGroups(stats.compilation.chunkGroups) }; fs.mkdirSync(path.resolve(PUBLIC, 'assets'), { recursive: true }); fs.writeFileSync(path.resolve(PUBLIC, 'assets', 'entrypoints.json'), JSON.stringify(manifest, null, '\t')); }); } }, new VueLoaderPlugin() ], resolve: { extensions: ['.vue', '.scss', '...'], alias: { '@components': path.resolve(SRC, 'components'), '@images': path.resolve(SRC, 'images'), '@scripts': path.resolve(SRC, 'scripts'), '@styles': path.resolve(SRC, 'styles') } } }; }; function mapChunkGroups(chunkGroups = {}) { const chunkGroupEntries = (chunkGroups instanceof Map)?Array.from(chunkGroups.entries()):Array.from(chunkGroups).map((chunkGroup) => [chunkGroup.name, chunkGroup]); return Object.fromEntries( chunkGroupEntries .filter(([name]) => name) .map(([name, chunkGroup]) => [ name, Array.from((typeof chunkGroup.getFiles === 'function')?chunkGroup.getFiles():chunkGroup.assets) .map((asset) => (typeof asset === 'string')?asset:asset.name) .filter((file) => file.endsWith('.js')) ]) ); }