@glossarist/concept-browser 0.7.21 → 0.7.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.html +2 -1
- package/package.json +2 -2
- package/scripts/build-edges.js +50 -5
- package/scripts/generate-data.mjs +33 -6
- package/src/App.vue +10 -12
- package/src/__tests__/concept-view.test.ts +7 -1
- package/src/__tests__/dataset-adapter.test.ts +87 -0
- package/src/__tests__/dataset-view.test.ts +1 -0
- package/src/__tests__/factory-lazy.test.ts +183 -0
- package/src/__tests__/graph-engine-fixes.test.ts +104 -0
- package/src/__tests__/ontology-registry.test.ts +1 -1
- package/src/__tests__/performance-v2.test.ts +77 -0
- package/src/__tests__/performance.test.ts +95 -0
- package/src/__tests__/relationship-categories.test.ts +3 -3
- package/src/__tests__/search-utils.test.ts +59 -0
- package/src/__tests__/test-helpers.ts +4 -0
- package/src/__tests__/utils-barrel.test.ts +15 -0
- package/src/__tests__/vocabulary-layered.test.ts +291 -0
- package/src/adapters/DatasetAdapter.ts +41 -1
- package/src/adapters/factory.ts +35 -4
- package/src/adapters/ontology-registry.ts +1 -1
- package/src/adapters/types.ts +12 -0
- package/src/components/AppSidebar.vue +17 -343
- package/src/components/ConceptDetail.vue +124 -55
- package/src/components/GraphPanel.vue +14 -6
- package/src/components/OntologySidebarSection.vue +338 -0
- package/src/config/use-site-config.ts +20 -9
- package/src/data/taxonomies.json +246 -18
- package/src/directives/v-math.ts +2 -3
- package/src/graph/GraphEngine.ts +22 -5
- package/src/i18n/index.ts +1 -1
- package/src/stores/vocabulary.ts +65 -105
- package/src/utils/index.ts +1 -0
- package/src/utils/relationship-categories.ts +15 -6
- package/src/utils/search.ts +15 -0
- package/src/views/ConceptView.vue +0 -2
- package/src/views/DatasetView.vue +64 -39
- package/src/views/HomeView.vue +0 -1
- package/vite.config.ts +94 -6
|
@@ -20,7 +20,7 @@ export const RELATIONSHIP_CATEGORIES: RelationshipCategory[] = [
|
|
|
20
20
|
{
|
|
21
21
|
id: 'mapping',
|
|
22
22
|
label: 'Equivalence',
|
|
23
|
-
types: ['equivalent', 'close_match', 'broad_match', 'narrow_match', 'related_match'],
|
|
23
|
+
types: ['equivalent', 'exact_match', 'close_match', 'broad_match', 'narrow_match', 'related_match'],
|
|
24
24
|
color: 'text-emerald-600 bg-emerald-50',
|
|
25
25
|
},
|
|
26
26
|
{
|
|
@@ -73,7 +73,7 @@ export const RELATIONSHIP_CATEGORIES: RelationshipCategory[] = [
|
|
|
73
73
|
|
|
74
74
|
|
|
75
75
|
export const INVERSE_RELATIONSHIPS: Record<string, string> = {
|
|
76
|
-
// Lifecycle
|
|
76
|
+
// Lifecycle (ISO 10241-1)
|
|
77
77
|
supersedes: 'superseded_by',
|
|
78
78
|
superseded_by: 'supersedes',
|
|
79
79
|
deprecates: 'deprecated_by',
|
|
@@ -81,41 +81,50 @@ export const INVERSE_RELATIONSHIPS: Record<string, string> = {
|
|
|
81
81
|
replaces: 'replaced_by',
|
|
82
82
|
replaced_by: 'replaces',
|
|
83
83
|
invalidates: 'invalidated_by',
|
|
84
|
+
invalidated_by: 'invalidates',
|
|
84
85
|
retires: 'retired_by',
|
|
86
|
+
retired_by: 'retires',
|
|
85
87
|
|
|
86
|
-
// Hierarchical (generic)
|
|
88
|
+
// Hierarchical (generic — SKOS)
|
|
87
89
|
broader: 'narrower',
|
|
88
90
|
narrower: 'broader',
|
|
89
91
|
broader_generic: 'narrower_generic',
|
|
90
92
|
narrower_generic: 'broader_generic',
|
|
91
93
|
|
|
92
|
-
// Hierarchical (partitive)
|
|
94
|
+
// Hierarchical (partitive — ISO 25964)
|
|
93
95
|
broader_partitive: 'narrower_partitive',
|
|
94
96
|
narrower_partitive: 'broader_partitive',
|
|
95
97
|
has_part: 'is_part_of',
|
|
96
98
|
is_part_of: 'has_part',
|
|
97
99
|
|
|
98
|
-
// Hierarchical (instantial)
|
|
100
|
+
// Hierarchical (instantial — ISO 25964)
|
|
99
101
|
broader_instantial: 'narrower_instantial',
|
|
100
102
|
narrower_instantial: 'broader_instantial',
|
|
101
103
|
instance_of: 'has_instance',
|
|
102
104
|
has_instance: 'instance_of',
|
|
103
105
|
|
|
104
|
-
// ISO 19135
|
|
106
|
+
// ISO 19135 concept-to-concept
|
|
105
107
|
has_concept: 'is_concept_of',
|
|
106
108
|
is_concept_of: 'has_concept',
|
|
107
109
|
inherits: 'inherited_by',
|
|
108
110
|
inherited_by: 'inherits',
|
|
109
111
|
has_definition: 'definition_of',
|
|
112
|
+
definition_of: 'has_definition',
|
|
113
|
+
|
|
114
|
+
// ISO 19135 versioning
|
|
110
115
|
has_version: 'version_of',
|
|
116
|
+
version_of: 'has_version',
|
|
111
117
|
current_version: 'current_version_of',
|
|
118
|
+
current_version_of: 'current_version',
|
|
112
119
|
|
|
113
120
|
// Symmetric (self-inverse)
|
|
114
121
|
equivalent: 'equivalent',
|
|
122
|
+
exact_match: 'exact_match',
|
|
115
123
|
compare: 'compare',
|
|
116
124
|
contrast: 'contrast',
|
|
117
125
|
close_match: 'close_match',
|
|
118
126
|
related_match: 'related_match',
|
|
127
|
+
related_concept: 'related_concept',
|
|
119
128
|
};
|
|
120
129
|
const CATEGORY_MAP = new Map<string, RelationshipCategory>();
|
|
121
130
|
for (const cat of RELATIONSHIP_CATEGORIES) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SearchHit } from '../adapters/types';
|
|
2
|
+
|
|
3
|
+
export function deduplicateSearchHits(hits: SearchHit[]): SearchHit[] {
|
|
4
|
+
const best = new Map<string, SearchHit>();
|
|
5
|
+
for (const hit of hits) {
|
|
6
|
+
const key = `${hit.registerId}:${hit.conceptId}`;
|
|
7
|
+
const existing = best.get(key);
|
|
8
|
+
if (!existing) {
|
|
9
|
+
best.set(key, hit);
|
|
10
|
+
} else if (hit.matchField === 'designation' && existing.matchField === 'id') {
|
|
11
|
+
best.set(key, hit);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return [...best.values()];
|
|
15
|
+
}
|
|
@@ -61,8 +61,6 @@ async function loadAdjacent() {
|
|
|
61
61
|
adjacent.value = adapter.getAdjacentConcepts(props.conceptId);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
watch(() => props.conceptId, () => { loadAdjacent(); });
|
|
65
|
-
|
|
66
64
|
function goAdjacent(id: string) {
|
|
67
65
|
router.push({ name: 'concept', params: { registerId: props.registerId, conceptId: id } });
|
|
68
66
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed, ref, watch, onMounted, onUnmounted } from 'vue';
|
|
2
|
+
import { computed, ref, watch, onMounted, onUnmounted, onBeforeUnmount } from 'vue';
|
|
3
3
|
import { useRoute, useRouter } from 'vue-router';
|
|
4
4
|
import { useVocabularyStore } from '../stores/vocabulary';
|
|
5
5
|
import { useDsStyle } from '../utils/dataset-style';
|
|
@@ -9,7 +9,7 @@ import { langName, langLabel, sortLanguages } from '../utils/lang';
|
|
|
9
9
|
import ConceptCard from '../components/ConceptCard.vue';
|
|
10
10
|
import { useI18n, locale } from '../i18n';
|
|
11
11
|
import { useSiteConfig } from '../config/use-site-config';
|
|
12
|
-
import type { SectionNode
|
|
12
|
+
import type { SectionNode } from '../adapters/types';
|
|
13
13
|
|
|
14
14
|
const props = defineProps<{ registerId: string }>();
|
|
15
15
|
|
|
@@ -27,6 +27,41 @@ const localizedDescription = computed(() => localizedDatasetField(props.register
|
|
|
27
27
|
const adapter = computed(() => store.datasets.get(props.registerId));
|
|
28
28
|
const chunkLoading = ref(false);
|
|
29
29
|
|
|
30
|
+
// Background chunk preloading via requestIdleCallback
|
|
31
|
+
let idlePreloadHandle: ReturnType<typeof requestIdleCallback> | ReturnType<typeof setTimeout> | null = null;
|
|
32
|
+
|
|
33
|
+
watch(adapter, (a) => {
|
|
34
|
+
if (idlePreloadHandle !== null) return;
|
|
35
|
+
if (!a || !a.index) return;
|
|
36
|
+
|
|
37
|
+
const schedule = typeof requestIdleCallback !== 'undefined'
|
|
38
|
+
? requestIdleCallback
|
|
39
|
+
: (cb: () => void) => setTimeout(cb, 0);
|
|
40
|
+
|
|
41
|
+
idlePreloadHandle = schedule(() => {
|
|
42
|
+
if (allChunksLoaded.value || !a.index) {
|
|
43
|
+
idlePreloadHandle = null;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const count = a.getConceptCount();
|
|
47
|
+
if (count <= 200) {
|
|
48
|
+
a.ensureAllChunksLoaded().then(() => {
|
|
49
|
+
allChunksLoaded.value = true;
|
|
50
|
+
}).catch(() => {});
|
|
51
|
+
} else {
|
|
52
|
+
a.ensureChunksForRange(0, 100).catch(() => {});
|
|
53
|
+
}
|
|
54
|
+
idlePreloadHandle = null;
|
|
55
|
+
}, { timeout: 2000 } as any);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
onBeforeUnmount(() => {
|
|
59
|
+
if (idlePreloadHandle !== null) {
|
|
60
|
+
(typeof cancelIdleCallback !== 'undefined' ? cancelIdleCallback : clearTimeout)(idlePreloadHandle as any);
|
|
61
|
+
idlePreloadHandle = null;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
30
65
|
function formatSize(bytes: number): string {
|
|
31
66
|
if (bytes < 1024) return `${bytes} B`;
|
|
32
67
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
@@ -87,37 +122,26 @@ function onGlobalKeydown(e: KeyboardEvent) {
|
|
|
87
122
|
onMounted(() => window.addEventListener('keydown', onGlobalKeydown));
|
|
88
123
|
onUnmounted(() => window.removeEventListener('keydown', onGlobalKeydown));
|
|
89
124
|
|
|
90
|
-
|
|
91
|
-
watch(filter, async (q) => {
|
|
125
|
+
async function ensureAllChunksForFilter(needsLoad: boolean) {
|
|
92
126
|
page.value = 1;
|
|
93
|
-
if (
|
|
127
|
+
if (needsLoad && !allChunksLoaded.value && adapter.value) {
|
|
94
128
|
chunkLoading.value = true;
|
|
95
129
|
await adapter.value.ensureAllChunksLoaded();
|
|
96
130
|
allChunksLoaded.value = true;
|
|
97
131
|
chunkLoading.value = false;
|
|
98
132
|
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
watch(filter, async (q) => {
|
|
136
|
+
await ensureAllChunksForFilter(q.trim().length >= 2);
|
|
99
137
|
});
|
|
100
138
|
|
|
101
|
-
// When section filter changes, reset page and load all chunks
|
|
102
139
|
watch(sectionQuery, async () => {
|
|
103
|
-
|
|
104
|
-
if (sectionQuery.value && !allChunksLoaded.value && adapter.value) {
|
|
105
|
-
chunkLoading.value = true;
|
|
106
|
-
await adapter.value.ensureAllChunksLoaded();
|
|
107
|
-
allChunksLoaded.value = true;
|
|
108
|
-
chunkLoading.value = false;
|
|
109
|
-
}
|
|
140
|
+
await ensureAllChunksForFilter(!!sectionQuery.value);
|
|
110
141
|
});
|
|
111
142
|
|
|
112
|
-
// When language filter changes, reset page and load all chunks
|
|
113
143
|
watch(selectedLang, async (lang) => {
|
|
114
|
-
|
|
115
|
-
if (lang && !allChunksLoaded.value && adapter.value) {
|
|
116
|
-
chunkLoading.value = true;
|
|
117
|
-
await adapter.value.ensureAllChunksLoaded();
|
|
118
|
-
allChunksLoaded.value = true;
|
|
119
|
-
chunkLoading.value = false;
|
|
120
|
-
}
|
|
144
|
+
await ensureAllChunksForFilter(!!lang);
|
|
121
145
|
});
|
|
122
146
|
|
|
123
147
|
// Dense array: only loaded (non-undefined) entries
|
|
@@ -133,38 +157,39 @@ const filtered = computed(() => {
|
|
|
133
157
|
const sec = sectionQuery.value;
|
|
134
158
|
return loadedConcepts.value.filter(c => {
|
|
135
159
|
if (lang && !(lang in (c.designations ?? {}))) return false;
|
|
136
|
-
if (sec && !conceptMatchesSection(c
|
|
160
|
+
if (sec && !conceptMatchesSection(c, sec)) return false;
|
|
137
161
|
if (!q) return true;
|
|
138
162
|
return (c.eng || '').toLowerCase().includes(q) || c.id.toLowerCase().includes(q);
|
|
139
163
|
});
|
|
140
164
|
});
|
|
141
165
|
|
|
142
|
-
function conceptMatchesSection(
|
|
143
|
-
// section-X matches concept IDs starting with X.
|
|
144
|
-
// e.g. section-1 matches 1.1, 1.2, etc.
|
|
145
|
-
// section-103-01 matches 103-01-01, 103-01-02, etc.
|
|
166
|
+
function conceptMatchesSection(concept: import('../adapters/types').ConceptSummary, sectionPrefix: string): boolean {
|
|
146
167
|
const prefix = sectionPrefix.replace(/^section-/, '');
|
|
147
|
-
|
|
168
|
+
// Check explicit groups (e.g. G18 sections derived from domains)
|
|
169
|
+
if (concept.groups?.length && concept.groups.includes(prefix)) return true;
|
|
170
|
+
// Check concept ID prefix matching (e.g. VIML/VIM numbered sections)
|
|
171
|
+
return concept.id.startsWith(prefix + '.') || concept.id.startsWith(prefix + '-');
|
|
148
172
|
}
|
|
149
173
|
|
|
150
174
|
function getSections(): SectionNode[] {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return m.sections.map(s => enrichSection(s));
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function enrichSection(s: ManifestSection): SectionNode {
|
|
157
|
-
const node: SectionNode = { id: s.id, names: s.names || {}, conceptCount: 0 };
|
|
158
|
-
if (s.children && s.children.length > 0) {
|
|
159
|
-
node.children = s.children.map(c => enrichSection(c));
|
|
160
|
-
}
|
|
161
|
-
return node;
|
|
175
|
+
if (!adapter.value) return [];
|
|
176
|
+
return adapter.value.getSectionTree();
|
|
162
177
|
}
|
|
163
178
|
|
|
164
179
|
function sectionName(section: SectionNode): string {
|
|
165
180
|
return section.names[locale.value] || section.names.eng || section.id;
|
|
166
181
|
}
|
|
167
182
|
|
|
183
|
+
const sectionDisplayName = computed(() => {
|
|
184
|
+
if (!sectionQuery.value) return '';
|
|
185
|
+
const prefix = sectionQuery.value.replace(/^section-/, '');
|
|
186
|
+
const sections = getSections();
|
|
187
|
+
const found = sections.find(s => s.id === prefix);
|
|
188
|
+
if (!found) return prefix;
|
|
189
|
+
const name = sectionName(found);
|
|
190
|
+
return name !== found.id ? `${found.id} — ${name}` : name;
|
|
191
|
+
});
|
|
192
|
+
|
|
168
193
|
// Alphabetical grouping
|
|
169
194
|
const alphabetGroups = computed(() => {
|
|
170
195
|
if (viewMode.value !== 'alphabetical') return [];
|
|
@@ -367,7 +392,7 @@ function clearSection() {
|
|
|
367
392
|
<div v-if="sectionQuery" class="flex items-center gap-2 mb-4">
|
|
368
393
|
<span class="text-sm text-ink-500">{{ t('dataset.sectionFilter') }}:</span>
|
|
369
394
|
<span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-lg bg-amber-50 text-amber-700 text-sm font-medium">
|
|
370
|
-
{{
|
|
395
|
+
{{ sectionDisplayName }}
|
|
371
396
|
<button @click="clearSection" class="text-amber-400 hover:text-amber-600 transition-colors">
|
|
372
397
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
|
373
398
|
</button>
|
package/src/views/HomeView.vue
CHANGED
|
@@ -24,7 +24,6 @@ async function exploreRandom() {
|
|
|
24
24
|
}
|
|
25
25
|
const result = await store.getRandomConcept();
|
|
26
26
|
if (result) {
|
|
27
|
-
await store.viewConcept(result.registerId, result.conceptId);
|
|
28
27
|
router.push({ name: 'concept', params: { registerId: result.registerId, conceptId: result.conceptId } });
|
|
29
28
|
}
|
|
30
29
|
} finally {
|
package/vite.config.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { defineConfig } from 'vite'
|
|
1
|
+
import { defineConfig, type Plugin } from 'vite'
|
|
2
2
|
import vue from '@vitejs/plugin-vue'
|
|
3
|
-
import { resolve, dirname } from 'path'
|
|
3
|
+
import { resolve, dirname, extname, join } from 'path'
|
|
4
|
+
import { readFileSync, existsSync, statSync, createReadStream } from 'fs'
|
|
4
5
|
import { fileURLToPath } from 'url'
|
|
5
6
|
import yaml from 'js-yaml'
|
|
6
7
|
|
|
@@ -41,6 +42,40 @@ function faviconPlugin() {
|
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
function inlineDataPlugin() {
|
|
46
|
+
let publicDir: string | undefined
|
|
47
|
+
const cache: Map<string, string> = new Map()
|
|
48
|
+
return {
|
|
49
|
+
name: 'inline-data',
|
|
50
|
+
configResolved(config: any) {
|
|
51
|
+
publicDir = config.publicDir
|
|
52
|
+
},
|
|
53
|
+
transformIndexHtml(html: string) {
|
|
54
|
+
if (!publicDir) return html
|
|
55
|
+
const tags: any[] = []
|
|
56
|
+
for (const [id, path] of [
|
|
57
|
+
['datasets-json', 'datasets.json'],
|
|
58
|
+
['site-config-json', 'site-config.json'],
|
|
59
|
+
] as const) {
|
|
60
|
+
try {
|
|
61
|
+
let data = cache.get(path)
|
|
62
|
+
if (!data) {
|
|
63
|
+
data = readFileSync(resolve(publicDir!, path), 'utf-8')
|
|
64
|
+
cache.set(path, data)
|
|
65
|
+
}
|
|
66
|
+
tags.push({
|
|
67
|
+
tag: 'script',
|
|
68
|
+
attrs: { type: 'application/json', id },
|
|
69
|
+
children: data,
|
|
70
|
+
injectTo: 'body' as const,
|
|
71
|
+
})
|
|
72
|
+
} catch { /* file may not exist during first build */ }
|
|
73
|
+
}
|
|
74
|
+
return { html, tags }
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
44
79
|
function brandingPlugin() {
|
|
45
80
|
return {
|
|
46
81
|
name: 'branding-inject',
|
|
@@ -53,8 +88,8 @@ function brandingPlugin() {
|
|
|
53
88
|
const fontsUrl = process.env.SITE_FONTS_URL
|
|
54
89
|
if (fontsUrl) {
|
|
55
90
|
result = result.replace(
|
|
56
|
-
/<link[^>]*href="https:\/\/fonts\.googleapis\.com\/css2\?[^"]*"[^>]
|
|
57
|
-
`<link href="${fontsUrl}" rel="stylesheet">`
|
|
91
|
+
/<link[^>]*href="https:\/\/fonts\.googleapis\.com\/css2\?[^"]*"[^>]*>(?:\s*<noscript>[^<]*<\/noscript>)?/,
|
|
92
|
+
`<link rel="preload" as="style" href="${fontsUrl}" onload="this.rel='stylesheet'"><noscript><link href="${fontsUrl}" rel="stylesheet"></noscript>`
|
|
58
93
|
)
|
|
59
94
|
}
|
|
60
95
|
return result
|
|
@@ -62,15 +97,68 @@ function brandingPlugin() {
|
|
|
62
97
|
}
|
|
63
98
|
}
|
|
64
99
|
|
|
100
|
+
const dataDir = resolve(cwd, 'public/data')
|
|
101
|
+
const publicDir = resolve(cwd, 'public')
|
|
102
|
+
|
|
103
|
+
const mimeTypes: Record<string, string> = {
|
|
104
|
+
'.json': 'application/json',
|
|
105
|
+
'.yaml': 'text/yaml',
|
|
106
|
+
'.yml': 'text/yaml',
|
|
107
|
+
'.ttl': 'text/turtle',
|
|
108
|
+
'.jsonld': 'application/ld+json',
|
|
109
|
+
'.tbx': 'application/xml',
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Serves /data/ files via middleware so the dev server doesn't need to watch
|
|
113
|
+
// the 15,000+ files in public/data/ (which causes fsevents to consume ~400% CPU).
|
|
114
|
+
function dataServePlugin(): Plugin {
|
|
115
|
+
return {
|
|
116
|
+
name: 'data-serve',
|
|
117
|
+
configureServer(server) {
|
|
118
|
+
server.middlewares.use((req, res, next) => {
|
|
119
|
+
if (!req.url?.startsWith('/data/')) return next()
|
|
120
|
+
const filePath = join(dataDir, req.url.slice('/data/'.length))
|
|
121
|
+
if (!filePath.startsWith(dataDir + '/') && filePath !== dataDir) return next()
|
|
122
|
+
if (!existsSync(filePath)) return next()
|
|
123
|
+
try {
|
|
124
|
+
const stat = statSync(filePath)
|
|
125
|
+
if (!stat.isFile()) return next()
|
|
126
|
+
const ext = extname(filePath)
|
|
127
|
+
res.setHeader('Content-Type', mimeTypes[ext] || 'application/octet-stream')
|
|
128
|
+
res.setHeader('Content-Length', stat.size)
|
|
129
|
+
createReadStream(filePath).pipe(res)
|
|
130
|
+
} catch { next() }
|
|
131
|
+
})
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
65
136
|
export default defineConfig({
|
|
66
137
|
base: process.env.BASE_PATH || '/',
|
|
67
138
|
root: __dirname,
|
|
68
|
-
publicDir
|
|
139
|
+
publicDir,
|
|
69
140
|
build: {
|
|
70
141
|
outDir: resolve(cwd, 'dist'),
|
|
71
142
|
emptyOutDir: true,
|
|
72
143
|
},
|
|
73
|
-
|
|
144
|
+
server: {
|
|
145
|
+
watch: {
|
|
146
|
+
ignored: [
|
|
147
|
+
// concept-browser's own non-source dirs (121K+ files in dist/public)
|
|
148
|
+
resolve(__dirname, 'dist') + '/**',
|
|
149
|
+
resolve(__dirname, 'public') + '/**',
|
|
150
|
+
resolve(__dirname, '.datasets') + '/**',
|
|
151
|
+
resolve(__dirname, '.gcr') + '/**',
|
|
152
|
+
resolve(__dirname, '.gcr-staging') + '/**',
|
|
153
|
+
// oiml-vocab's data dir (15K+ files)
|
|
154
|
+
resolve(cwd, 'public/data') + '/**',
|
|
155
|
+
],
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
optimizeDeps: {
|
|
159
|
+
exclude: ['@plurimath/plurimath'],
|
|
160
|
+
},
|
|
161
|
+
plugins: [yamlPlugin(), faviconPlugin(), brandingPlugin(), dataServePlugin(), inlineDataPlugin(), vue()],
|
|
74
162
|
resolve: {
|
|
75
163
|
alias: {
|
|
76
164
|
'@': resolve(__dirname, 'src'),
|