const path = require('path'); const fs = require('fs'); const webpack = require('webpack'); const CopyWebpackPlugin = require('copy-webpack-plugin'); const SymlinkWebpackPlugin = require('symlink-webpack-plugin'); const { VueLoaderPlugin } = require('vue-loader'); const ROOT = path.resolve(__dirname, '..'); const SRC = path.resolve(ROOT, 'src'); const DIST = path.resolve(ROOT, 'dist'); const LIB = path.resolve(ROOT, 'lib'); 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: DIST, filename: '[name].js', chunkFilename: '[name].js', publicPath: './', clean: { keep: /^(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: /\.html$/i, loader: 'html-loader' }, { 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)$/i, type: 'asset', parser: { dataUrlCondition: { maxSize: 1 * 1024 } }, generator: { filename: 'images/[name][ext]' } }] }, plugins: [ new CopyWebpackPlugin({ patterns: [ { from: path.resolve(LIB, 'index.php'), to: 'index.php' }, { from: path.resolve(SRC, 'images', 'logo_black.png'), to: 'images' }, { from: path.resolve(SRC, 'images', 'spot-logo-only.svg'), to: 'images' } ] }), new SymlinkWebpackPlugin([ { origin: '../files/', symlink: 'files' }, { origin: '../geo/', symlink: 'geo' }, { origin: '../src/images/icons/', symlink: '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.writeFileSync(path.resolve(DIST, 'entrypoints.json'), JSON.stringify(manifest, null, '\t')); }); } }, new VueLoaderPlugin() ], resolve: { extensions: ['.vue', '.scss', '...'], alias: { '@components': path.resolve(SRC, 'components'), '@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')) ]) ); }