@@ -1,172 +0,0 @@
|
|||||||
const path = require('path');
|
|
||||||
const fs = require('fs');
|
|
||||||
const webpack = require('webpack');
|
|
||||||
const SymlinkWebpackPlugin = require('symlink-webpack-plugin');
|
|
||||||
const { VueLoaderPlugin } = require('vue-loader');
|
|
||||||
const CopyPlugin = require("copy-webpack-plugin");
|
|
||||||
|
|
||||||
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: isDev ? false : {
|
|
||||||
keep: /^(index\.php|files|geo|assets\/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: /\.mjs$/,
|
|
||||||
include: /node_modules\/maplibre-gl/,
|
|
||||||
parser: {
|
|
||||||
exprContextCritical: false, // Silences expression context warnings [2]
|
|
||||||
unknownContextCritical: false // Silences unknown context warnings
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
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: isDev ? 'assets/images/[name][ext]' : '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 CopyPlugin({
|
|
||||||
patterns: [{
|
|
||||||
from: "node_modules/maplibre-gl/dist/maplibre-gl-(worker|shared).mjs",
|
|
||||||
to: "maplibre/[name][ext]"
|
|
||||||
}]
|
|
||||||
}),
|
|
||||||
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)
|
|
||||||
};
|
|
||||||
|
|
||||||
const manifestPath = path.resolve(PUBLIC, 'assets', 'entrypoints.json');
|
|
||||||
const tmpManifestPath = `${manifestPath}.tmp`;
|
|
||||||
|
|
||||||
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
||||||
fs.writeFileSync(tmpManifestPath, JSON.stringify(manifest, null, '\t'));
|
|
||||||
fs.renameSync(tmpManifestPath, manifestPath);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stats: {
|
|
||||||
children: true,
|
|
||||||
errorDetails: true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
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'))
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
+47
-4
@@ -44,6 +44,7 @@ class Spot extends Main
|
|||||||
const PROJECT_NAME = 'LiveTrail';
|
const PROJECT_NAME = 'LiveTrail';
|
||||||
|
|
||||||
const MAIN_PAGE = 'index';
|
const MAIN_PAGE = 'index';
|
||||||
|
const VITE_APP = 'src/app.js';
|
||||||
|
|
||||||
private Project $oProject;
|
private Project $oProject;
|
||||||
private Media $oMedia;
|
private Media $oMedia;
|
||||||
@@ -165,6 +166,8 @@ class Spot extends Main
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getAppMainPage(string $sCsrfToken='') {
|
public function getAppMainPage(string $sCsrfToken='') {
|
||||||
|
$asViteAssets = $this->getViteAssets();
|
||||||
|
|
||||||
return parent::getMainPage(
|
return parent::getMainPage(
|
||||||
array(
|
array(
|
||||||
'projects' => $this->oProject->getProjects(),
|
'projects' => $this->oProject->getProjects(),
|
||||||
@@ -186,18 +189,58 @@ class Spot extends Main
|
|||||||
'tags' => [
|
'tags' => [
|
||||||
'language' => $this->oLang->getLanguage(),
|
'language' => $this->oLang->getLanguage(),
|
||||||
'title' => self::PROJECT_NAME,
|
'title' => self::PROJECT_NAME,
|
||||||
|
'app_entry' => $asViteAssets['app']
|
||||||
],
|
],
|
||||||
'instances' => [
|
'instances' => [
|
||||||
'entrypoint' => $this->getAppEntryPoints()
|
'css' => $asViteAssets['css'],
|
||||||
|
'module' => $asViteAssets['module']
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getAppEntryPoints() {
|
private function getViteAssets() {
|
||||||
|
$asManifest = json_decode(file_get_contents(__DIR__.'/../public/.vite/manifest.json'), true);
|
||||||
|
$asAppImport = $asManifest[self::VITE_APP];
|
||||||
|
|
||||||
|
//Recursive search for chunk imports
|
||||||
|
$asImports = array();
|
||||||
|
$asSeenImports = array(self::VITE_APP => true);
|
||||||
|
$this->appendViteImportedChunks($asManifest, $asAppImport, $asSeenImports, $asImports);
|
||||||
|
|
||||||
|
//CSS
|
||||||
|
$asCssFiles = array();
|
||||||
|
foreach(array_merge(array($asAppImport), $asImports) as $asChunk) {
|
||||||
|
foreach($asChunk['css'] ?? array() as $sCssFile) $asCssFiles[] = $sCssFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Modules
|
||||||
|
$asModuleFiles = array();
|
||||||
|
foreach($asImports as $asImport) {
|
||||||
|
if(str_ends_with($asImport['file'] ?? '', '.js')) $asModuleFiles[] = $asImport['file'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'app' => $asAppImport['file'],
|
||||||
|
'css' => $this->getViteAssetInstances($asCssFiles),
|
||||||
|
'module' => $this->getViteAssetInstances($asModuleFiles)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function appendViteImportedChunks($asManifest, $asChunk, &$asSeenImports, &$asImports) {
|
||||||
|
foreach($asChunk['imports'] ?? array() as $sImport) {
|
||||||
|
if(isset($asSeenImports[$sImport]) || !isset($asManifest[$sImport])) continue;
|
||||||
|
|
||||||
|
$asSeenImports[$sImport] = true;
|
||||||
|
$this->appendViteImportedChunks($asManifest, $asManifest[$sImport], $asSeenImports, $asImports);
|
||||||
|
$asImports[] = $asManifest[$sImport];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getViteAssetInstances($asFilePaths) {
|
||||||
return array_map(
|
return array_map(
|
||||||
function($sFileName) {return ['filename' => self::addTimestampToFilePath($sFileName)];},
|
function($sFilePath) { return array('filename' => $sFilePath); },
|
||||||
json_decode(file_get_contents('assets/entrypoints.json'), true)['entrypoints']['app']
|
$asFilePaths
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Generated
+746
-3609
File diff suppressed because it is too large
Load Diff
+5
-14
@@ -1,13 +1,7 @@
|
|||||||
{
|
{
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^8.0.1",
|
"@vitejs/plugin-vue": "^6.0.8",
|
||||||
"@babel/preset-env": "^8.0.2",
|
"vite": "^8.1.5"
|
||||||
"babel-loader": "^10.0.0",
|
|
||||||
"copy-webpack-plugin": "^14.0.0",
|
|
||||||
"symlink-webpack-plugin": "^1.1.0",
|
|
||||||
"vue-loader": "^17.4.2",
|
|
||||||
"webpack": "^5.99.7",
|
|
||||||
"webpack-cli": "^7.0.2"
|
|
||||||
},
|
},
|
||||||
"name": "spot",
|
"name": "spot",
|
||||||
"description": "FindMeSpot & GPX integration",
|
"description": "FindMeSpot & GPX integration",
|
||||||
@@ -15,8 +9,8 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "flock -n node_modules/.webpack-build.lock webpack --config build/webpack.config.js --mode development",
|
"dev": "vite build --mode development --watch",
|
||||||
"prod": "flock -n node_modules/.webpack-build.lock webpack --config build/webpack.config.js --mode production"
|
"prod": "vite build"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "Franzz",
|
"author": "Franzz",
|
||||||
@@ -27,13 +21,10 @@
|
|||||||
"@uppy/core": "^5.2.0",
|
"@uppy/core": "^5.2.0",
|
||||||
"@uppy/xhr-upload": "^5.2.0",
|
"@uppy/xhr-upload": "^5.2.0",
|
||||||
"autosize": "^6.0.1",
|
"autosize": "^6.0.1",
|
||||||
"css-loader": "^7.1.2",
|
|
||||||
"maplibre-gl": "^6.0.0",
|
"maplibre-gl": "^6.0.0",
|
||||||
"sass": "^1.97.2",
|
"sass": "^1.97.2",
|
||||||
"sass-loader": "^17.0.0",
|
|
||||||
"simplebar-vue": "^2.3.3",
|
"simplebar-vue": "^2.3.3",
|
||||||
"vue": "^3.5.40",
|
"vue": "^3.5.40"
|
||||||
"vue-style-loader": "^4.1.3"
|
|
||||||
},
|
},
|
||||||
"allowScripts": {
|
"allowScripts": {
|
||||||
"@parcel/watcher@2.6.0": true,
|
"@parcel/watcher@2.6.0": true,
|
||||||
|
|||||||
@@ -34,12 +34,14 @@
|
|||||||
|
|
||||||
## Web Root
|
## Web Root
|
||||||
|
|
||||||
The web server should serve `public/` as the application document root. PHP source, configuration, Composer dependencies, uploaded files, and GPX data stay outside the public tree; `public/index.php` is the front controller and webpack writes generated frontend assets to `public/assets/`.
|
The web server should serve `public/` as the application document root. PHP source, configuration, Composer dependencies, uploaded files, and GPX data stay outside the public tree; `public/index.php` is the front controller and Vite writes generated frontend assets to `public/assets/`.
|
||||||
|
|
||||||
Runtime data is exposed through symlinks only: `public/files -> ../files` and `public/geo -> ../resources/geo`. The build must not copy uploaded media or GPX data into `public/`.
|
Runtime data is exposed through symlinks only: `public/files -> ../files` and `public/geo -> ../resources/geo`. The build must not copy uploaded media or GPX data into `public/`.
|
||||||
|
|
||||||
## Local Development
|
## Local Development
|
||||||
|
|
||||||
|
`npm run dev` watches and builds development assets into `public/assets/`. `npm run prod` builds hashed production assets into `public/assets/`. PHP reads `public/.vite/manifest.json` and populates the CSS, module, and preload parts defined in `resources/masks/index.html` while rendering the request.
|
||||||
|
|
||||||
When developing Spot and the sibling `objects` library together, install dependencies through the local Composer manifest:
|
When developing Spot and the sibling `objects` library together, install dependencies through the local Composer manifest:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -19,11 +19,15 @@
|
|||||||
<meta name="theme-color" content="#081B19">
|
<meta name="theme-color" content="#081B19">
|
||||||
<script id="app-config" type="application/json">[#]app_config[#]</script>
|
<script id="app-config" type="application/json">[#]app_config[#]</script>
|
||||||
<title>[#]title[#]</title>
|
<title>[#]title[#]</title>
|
||||||
|
<!-- [PART] css [START] -->
|
||||||
|
<link rel="stylesheet" href="[#]filename[#]" />
|
||||||
|
<!-- [PART] css [END] -->
|
||||||
|
<!-- [PART] module [START] -->
|
||||||
|
<link rel="modulepreload" href="[#]filename[#]" />
|
||||||
|
<!-- [PART] module [END] -->
|
||||||
|
<script type="module" src="[#]app_entry[#]"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="container"></div>
|
<div id="container"></div>
|
||||||
<!-- [PART] entrypoint [START] -->
|
|
||||||
<script defer src="[#]filename[#]"></script>
|
|
||||||
<!-- [PART] entrypoint [END] -->
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+2
-2
@@ -4,8 +4,8 @@ import Project from '@components/project';
|
|||||||
|
|
||||||
const aoRoutes = {
|
const aoRoutes = {
|
||||||
'project': Project, //Merge app.js and project.js calls to avoid extra http request on inital page
|
'project': Project, //Merge app.js and project.js calls to avoid extra http request on inital page
|
||||||
'admin': defineAsyncComponent(() => import(/* webpackChunkName: "admin" */ '@components/admin')),
|
'admin': defineAsyncComponent(() => import('@components/admin')),
|
||||||
'upload': defineAsyncComponent(() => import(/* webpackChunkName: "upload" */ '@components/upload'))
|
'upload': defineAsyncComponent(() => import('@components/upload'))
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
+3
-2
@@ -1,4 +1,5 @@
|
|||||||
//Librairies
|
//Librairies
|
||||||
|
import 'vite/modulepreload-polyfill';
|
||||||
import Api from '@scripts/api';
|
import Api from '@scripts/api';
|
||||||
import Lang from '@scripts/lang';
|
import Lang from '@scripts/lang';
|
||||||
import Projects from '@scripts/projects';
|
import Projects from '@scripts/projects';
|
||||||
@@ -6,10 +7,10 @@ import User from '@scripts/user';
|
|||||||
import { createApp, reactive } from 'vue';
|
import { createApp, reactive } from 'vue';
|
||||||
|
|
||||||
//Main template
|
//Main template
|
||||||
import App from './App';
|
import App from './App.vue';
|
||||||
|
|
||||||
//Style
|
//Style
|
||||||
import Css from '@styles/spot';
|
import '@styles/spot.scss';
|
||||||
|
|
||||||
//App Configuration from PHP
|
//App Configuration from PHP
|
||||||
const appConfig = JSON.parse(document.getElementById('app-config').textContent);
|
const appConfig = JSON.parse(document.getElementById('app-config').textContent);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { Map, Marker, LngLatBounds, LngLat, Popup, ScaleControl, NavigationControl, setWorkerUrl } from 'maplibre-gl';
|
import { Map, Marker, LngLatBounds, LngLat, Popup, ScaleControl, NavigationControl, setWorkerUrl } from 'maplibre-gl';
|
||||||
|
import maplibreWorkerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url';
|
||||||
import 'maplibre-gl/dist/maplibre-gl.css';
|
import 'maplibre-gl/dist/maplibre-gl.css';
|
||||||
|
|
||||||
import { createApp } from 'vue';
|
import { createApp } from 'vue';
|
||||||
@@ -12,7 +13,7 @@ import ProjectPopup from '@components/projectPopup';
|
|||||||
import ProjectFeed from '@components/projectFeed';
|
import ProjectFeed from '@components/projectFeed';
|
||||||
import ProjectSettings from '@components/projectSettings';
|
import ProjectSettings from '@components/projectSettings';
|
||||||
|
|
||||||
setWorkerUrl('maplibre/maplibre-gl-worker.mjs');
|
setWorkerUrl(maplibreWorkerUrl);
|
||||||
|
|
||||||
class GroupedScaleControl {
|
class GroupedScaleControl {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
@@ -486,7 +487,7 @@ export default {
|
|||||||
|
|
||||||
this.map.jumpTo({
|
this.map.jumpTo({
|
||||||
center: new LngLat(oDefaultProject.longitude, oDefaultProject.latitude),
|
center: new LngLat(oDefaultProject.longitude, oDefaultProject.latitude),
|
||||||
zoom: Math.log2(iWorldSize / this.map.transform.tileSize),
|
zoom: Math.log2(iWorldSize / 512),
|
||||||
pitch: 0,
|
pitch: 0,
|
||||||
bearing: 0
|
bearing: 0
|
||||||
});
|
});
|
||||||
|
|||||||
+127
@@ -0,0 +1,127 @@
|
|||||||
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
const ROOT = fileURLToPath(new URL('.', import.meta.url));
|
||||||
|
const SRC = path.resolve(ROOT, 'src');
|
||||||
|
const PUBLIC = path.resolve(ROOT, 'public');
|
||||||
|
|
||||||
|
export default defineConfig(({ mode }) => {
|
||||||
|
const isDev = (mode === 'development');
|
||||||
|
|
||||||
|
return {
|
||||||
|
base: './',
|
||||||
|
publicDir: false,
|
||||||
|
plugins: [
|
||||||
|
spotPublicAssets(),
|
||||||
|
vue()
|
||||||
|
],
|
||||||
|
build: {
|
||||||
|
outDir: PUBLIC,
|
||||||
|
assetsDir: 'assets',
|
||||||
|
emptyOutDir: false,
|
||||||
|
manifest: true,
|
||||||
|
sourcemap: isDev,
|
||||||
|
minify: !isDev,
|
||||||
|
cssMinify: !isDev,
|
||||||
|
assetsInlineLimit: 1 * 1024,
|
||||||
|
chunkSizeWarningLimit: 1500,
|
||||||
|
rolldownOptions: {
|
||||||
|
input: {
|
||||||
|
app: path.resolve(SRC, 'app.js')
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
entryFileNames: isDev ? 'assets/[name].js' : 'assets/[name].[hash].js',
|
||||||
|
chunkFileNames: isDev ? 'assets/[name].js' : 'assets/[name].[hash].js',
|
||||||
|
assetFileNames: (assetInfo) => {
|
||||||
|
const sourceName = assetInfo.names?.[0] || assetInfo.name || 'asset';
|
||||||
|
const extension = path.extname(sourceName).toLowerCase();
|
||||||
|
const isImage = ['.png', '.svg', '.jpg', '.jpeg', '.gif', '.webp'].includes(extension);
|
||||||
|
const folder = isImage ? 'assets/images' : 'assets';
|
||||||
|
return isDev ? `${folder}/[name][extname]` : `${folder}/[name].[hash][extname]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.mjs', '.js', '.vue', '.scss', '.json'],
|
||||||
|
alias: {
|
||||||
|
'@components': path.resolve(SRC, 'components'),
|
||||||
|
'@images': path.resolve(SRC, 'images'),
|
||||||
|
'@scripts': path.resolve(SRC, 'scripts'),
|
||||||
|
'@styles': path.resolve(SRC, 'styles')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
define: {
|
||||||
|
__VUE_OPTIONS_API__: 'true',
|
||||||
|
__VUE_PROD_DEVTOOLS__: 'false',
|
||||||
|
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
function spotPublicAssets() {
|
||||||
|
return {
|
||||||
|
name: 'spot-public-assets',
|
||||||
|
apply: 'build',
|
||||||
|
buildStart() {
|
||||||
|
cleanGeneratedAssets();
|
||||||
|
ensurePublicSymlinks();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanGeneratedAssets() {
|
||||||
|
fs.rmSync(path.resolve(PUBLIC, '.vite'), { recursive: true, force: true });
|
||||||
|
fs.rmSync(path.resolve(PUBLIC, 'maplibre'), { recursive: true, force: true });
|
||||||
|
|
||||||
|
const assetsDir = path.resolve(PUBLIC, 'assets');
|
||||||
|
if (fs.existsSync(assetsDir)) {
|
||||||
|
for (const entry of fs.readdirSync(assetsDir, { withFileTypes: true })) {
|
||||||
|
const entryPath = path.resolve(assetsDir, entry.name);
|
||||||
|
if (entry.name === 'images' && entry.isDirectory()) {
|
||||||
|
cleanImagesDir(entryPath);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fs.rmSync(entryPath, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.mkdirSync(path.resolve(PUBLIC, 'assets', 'images'), { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanImagesDir(imagesDir) {
|
||||||
|
for (const entry of fs.readdirSync(imagesDir, { withFileTypes: true })) {
|
||||||
|
if (entry.name === 'icons') continue;
|
||||||
|
fs.rmSync(path.resolve(imagesDir, entry.name), { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensurePublicSymlinks() {
|
||||||
|
ensureSymlink('../files', path.resolve(PUBLIC, 'files'));
|
||||||
|
ensureSymlink('../resources/geo', path.resolve(PUBLIC, 'geo'));
|
||||||
|
ensureSymlink('../../../src/images/icons', path.resolve(PUBLIC, 'assets', 'images', 'icons'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureSymlink(target, linkPath) {
|
||||||
|
fs.mkdirSync(path.dirname(linkPath), { recursive: true });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stats = fs.lstatSync(linkPath);
|
||||||
|
if (stats.isSymbolicLink()) {
|
||||||
|
if (fs.readlinkSync(linkPath) === target) return;
|
||||||
|
fs.unlinkSync(linkPath);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new Error(`Refusing to replace non-symlink public path: ${linkPath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if (error.code !== 'ENOENT') throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.symlinkSync(target, linkPath);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user