mirror of
https://github.com/gedoor/legado.git
synced 2025-08-10 00:52:30 +00:00
web端优化数据加载
This commit is contained in:
3
modules/web/.prettierignore
Normal file
3
modules/web/.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
||||
# d.ts files Generated by unplugin-auto unplugin-vue-components
|
||||
auto-imports.d.ts
|
||||
components.d.ts
|
||||
@@ -1,16 +1,12 @@
|
||||
import type { AxiosResponse } from 'axios'
|
||||
import type { LeagdoApiResponse } from './api'
|
||||
import API, {
|
||||
setWebsocketOnError,
|
||||
legado_http_entry_point,
|
||||
legado_webSocket_entry_point,
|
||||
setApiEntryPoint,
|
||||
} from './api'
|
||||
import API, { setWebsocketOnError, setApiEntryPoint } from './api'
|
||||
import ajax from './axios'
|
||||
import { validatorHttpUrl } from '@/utils/utils'
|
||||
|
||||
const LeagdoApiResponseKeys: string[] = Array.of('isSuccess', 'errorMsg')
|
||||
|
||||
const notification = ElMessage
|
||||
/** Axios.Interceptor: check if resp is LeagaoLeagdoApiResponse*/
|
||||
const responseCheckInterceptor = (resp: AxiosResponse) => {
|
||||
let isLeagdoApiResponse = true
|
||||
@@ -26,21 +22,23 @@ const responseCheckInterceptor = (resp: AxiosResponse) => {
|
||||
if ((data as LeagdoApiResponse<unknown>).isSuccess === true) {
|
||||
if (!('data' in data)) {
|
||||
isLeagdoApiResponse = false
|
||||
LeagdoApiResponseKeys.length = 0
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
isLeagdoApiResponse = false
|
||||
}
|
||||
if (isLeagdoApiResponse === false) {
|
||||
ElNotification.warning('后端返回内容格式错误')
|
||||
notification.warning({ message: '后端返回内容格式错误', grouping: true })
|
||||
throw new Error()
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
const axiosErrorInterceptor = (err: unknown) => {
|
||||
ElNotification.error('后端连接失败,请检查阅读WEB服务或者设置其它可用链接')
|
||||
notification.error({
|
||||
message: '后端连接失败,请检查阅读WEB服务或者设置其它可用链接',
|
||||
grouping: true,
|
||||
})
|
||||
throw err
|
||||
}
|
||||
// http全局
|
||||
@@ -80,8 +78,8 @@ export const parseLeagdoHttpUrlWithDefault = (
|
||||
|
||||
console.info('legado_api_config:')
|
||||
console.table({
|
||||
'http API入口': legado_http_entry_point,
|
||||
'webSocket API入口': legado_webSocket_entry_point,
|
||||
'http API入口': http_entry_point,
|
||||
'webSocket API入口': webSocket_entry_point,
|
||||
})
|
||||
return [http_entry_point, webSocket_entry_point]
|
||||
}
|
||||
|
||||
1
modules/web/src/auto-imports.d.ts
vendored
1
modules/web/src/auto-imports.d.ts
vendored
@@ -9,7 +9,6 @@ declare global {
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const ElMessage: typeof import('element-plus/es')['ElMessage']
|
||||
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
|
||||
const ElNotification: typeof import('element-plus/es')['ElNotification']
|
||||
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
|
||||
const computed: typeof import('vue')['computed']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
|
||||
@@ -76,7 +76,7 @@ const subJustify = computed(() =>
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
.books-wrapper {
|
||||
overflow: auto;
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ const catas = computed(() => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
.selected {
|
||||
color: #eb4259;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import type { webReadConfig } from '@/web'
|
||||
const store = useBookStore()
|
||||
const readWidth = computed(() => store.config.readWidth)
|
||||
const bookUrl = computed(() => store.readingBook.bookUrl)
|
||||
|
||||
const props = defineProps<{
|
||||
chapterIndex: number
|
||||
contents: Array<string>
|
||||
@@ -116,7 +117,7 @@ onUnmounted(() => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
.title {
|
||||
margin-bottom: 57px;
|
||||
font:
|
||||
|
||||
@@ -92,7 +92,7 @@ const gotoChapter = (chapter: BookChapter) => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
.cata-wrapper {
|
||||
margin: -16px;
|
||||
padding: 18px 0 24px 25px;
|
||||
|
||||
@@ -356,7 +356,7 @@ const setInfiniteLoading = (loading: boolean) => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.iconfont) {
|
||||
font-family: iconfont;
|
||||
font-style: normal;
|
||||
|
||||
@@ -60,7 +60,7 @@ const isBookSource = computed(() => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
:deep(#debug-text) {
|
||||
height: calc(100vh - 45px - 36px - 5px);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ import { Link } from '@element-plus/icons-vue'
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
.el-link {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ const isSaveError = computed(() => {
|
||||
return !map.has(sourceUrl.value)
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-checkbox__label) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
|
||||
@@ -34,7 +34,7 @@ watchEffect(async () => {
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-input) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ const outExport = () => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
.tool {
|
||||
display: flex;
|
||||
margin: 4px 0;
|
||||
|
||||
@@ -309,7 +309,7 @@ onMounted(() => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
.flex-space-between {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
5
modules/web/src/config/sourceConfig.d.ts
vendored
5
modules/web/src/config/sourceConfig.d.ts
vendored
@@ -2,11 +2,6 @@ import type { Source } from '@/source'
|
||||
import bookSourceEditConfig from './bookSourceEditConfig'
|
||||
import rssSourceEditConfig from './rssSourceEditConfig'
|
||||
|
||||
type PickAnyValueKey<T> = {
|
||||
[K in keyof T]: T[K] extends { [prop: string]: string } ? K : never
|
||||
}
|
||||
type b = keyof PickAnyValueKey<BookSoure>
|
||||
|
||||
type SourceConfigKey =
|
||||
| keyof typeof bookSourceEditConfig
|
||||
| keyof typeof rssSourceEditConfig
|
||||
|
||||
@@ -19,4 +19,18 @@ const router = createRouter({
|
||||
routes: bookRoutes,
|
||||
})
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import App from '@/App.vue'
|
||||
import store, { useBookStore } from '@/store'
|
||||
|
||||
// init pinia instance
|
||||
createApp(App).use(store)
|
||||
router.beforeEach((to, from, next) => {
|
||||
// 自动加载阅读配置
|
||||
useBookStore()
|
||||
.loadWebConfig()
|
||||
.then(() => next())
|
||||
.catch(() => next())
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
@@ -11,4 +11,19 @@ const router = createRouter({
|
||||
router.afterEach(to => {
|
||||
if (to.name == 'shelf') document.title = '书架'
|
||||
})
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import App from '@/App.vue'
|
||||
import store, { useBookStore } from '@/store'
|
||||
|
||||
// init pinia instance
|
||||
createApp(App).use(store)
|
||||
router.beforeEach((to, from, next) => {
|
||||
// 自动加载阅读配置
|
||||
useBookStore()
|
||||
.loadWebConfig()
|
||||
.then(() => next())
|
||||
.catch(() => next())
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
@@ -8,6 +8,23 @@ import type {
|
||||
SeachBook,
|
||||
} from '@/book'
|
||||
import type { webReadConfig } from '@/web'
|
||||
import { ElMessage } from 'element-plus/es'
|
||||
|
||||
const default_config: webReadConfig = {
|
||||
theme: 0,
|
||||
font: 0,
|
||||
fontSize: 18,
|
||||
readWidth: 800,
|
||||
infiniteLoading: false,
|
||||
customFontName: '',
|
||||
jumpDuration: 1000,
|
||||
spacing: {
|
||||
paragraph: 1,
|
||||
line: 0.8,
|
||||
letter: 0,
|
||||
},
|
||||
}
|
||||
let webReadConfigLoadedDate: Date | undefined
|
||||
|
||||
export const useBookStore = defineStore('book', {
|
||||
state: () => {
|
||||
@@ -18,7 +35,7 @@ export const useBookStore = defineStore('book', {
|
||||
searchBooks: [] as SeachBook[],
|
||||
shelf: [] as Book[],
|
||||
catalog: [] as BookChapter[],
|
||||
readingBook: {} as BaseBook & {
|
||||
readingBook: { chapterPos: 0, chapterIndex: 0 } as BaseBook & {
|
||||
chapterPos: number
|
||||
chapterIndex: number
|
||||
isSeachBook?: boolean
|
||||
@@ -26,20 +43,7 @@ export const useBookStore = defineStore('book', {
|
||||
popCataVisible: false,
|
||||
contentLoading: true,
|
||||
showContent: false,
|
||||
config: {
|
||||
theme: -1,
|
||||
font: 0,
|
||||
fontSize: 18,
|
||||
readWidth: 800,
|
||||
infiniteLoading: false,
|
||||
customFontName: '',
|
||||
jumpDuration: 1000,
|
||||
spacing: {
|
||||
paragraph: 1,
|
||||
line: 0.8,
|
||||
letter: 0,
|
||||
},
|
||||
} as webReadConfig,
|
||||
config: default_config as webReadConfig,
|
||||
miniInterface: false,
|
||||
readSettingsVisible: false,
|
||||
}
|
||||
@@ -62,7 +66,6 @@ export const useBookStore = defineStore('book', {
|
||||
theme: state => {
|
||||
return state.config.theme
|
||||
},
|
||||
configInited: state => state.config.theme !== -1,
|
||||
isNight: state => state.config.theme == 6,
|
||||
},
|
||||
actions: {
|
||||
@@ -75,12 +78,83 @@ export const useBookStore = defineStore('book', {
|
||||
setNewConnect(newConnect: boolean) {
|
||||
this.newConnect = newConnect
|
||||
},
|
||||
addBooks(books: Book[]) {
|
||||
this.shelf = books
|
||||
/** 从后端加载书架书籍,优先返回内存缓存 */
|
||||
async loadBookShelf(): Promise<Book[]> {
|
||||
const fetchBookshellf_promise = API.getBookShelf().then(resp => {
|
||||
console.log('API.getBookShelf数据返回')
|
||||
const { isSuccess, data, errorMsg } = resp.data
|
||||
if (isSuccess === true) {
|
||||
if (
|
||||
this.shelf.length !== data.length &&
|
||||
this.shelf.length > 0 &&
|
||||
data.length > 0
|
||||
) {
|
||||
ElMessage.info(`书架数据已更新`)
|
||||
}
|
||||
this.shelf = data.sort((a, b) => {
|
||||
const x = a['durChapterTime'] || 0
|
||||
const y = b['durChapterTime'] || 0
|
||||
return y - x
|
||||
})
|
||||
} else {
|
||||
if (errorMsg.includes('还没有添加小说') && this.shelf.length > 0) {
|
||||
ElMessage.info('当前书架上的书籍已经被删除')
|
||||
return (this.shelf = [])
|
||||
}
|
||||
ElMessage.error(errorMsg ?? '后端返回格式错误!')
|
||||
}
|
||||
console.log('书架数据已更新')
|
||||
return this.shelf
|
||||
})
|
||||
|
||||
if (this.shelf.length > 0) {
|
||||
// bookshelf data fetched before:do not await
|
||||
console.log('返回缓存书架数据')
|
||||
return this.shelf
|
||||
} else {
|
||||
console.log('从阅读后端获取书架数据...')
|
||||
return await fetchBookshellf_promise
|
||||
}
|
||||
},
|
||||
clearBooks() {
|
||||
this.shelf = []
|
||||
},
|
||||
/** 从后端加载书籍目录,优先返回内存缓存 */
|
||||
async loadWebCatalog(
|
||||
book: typeof this.readingBook,
|
||||
): Promise<BookChapter[]> {
|
||||
const { bookUrl, name, chapterIndex } = book
|
||||
const fetchChapterList_promise = API.getChapterList(
|
||||
book.bookUrl as string,
|
||||
).then(res => {
|
||||
const { isSuccess, data, errorMsg } = res.data
|
||||
if (isSuccess === false) {
|
||||
ElMessage.error(errorMsg)
|
||||
throw new Error()
|
||||
}
|
||||
if (
|
||||
data.length !== this.catalog.length &&
|
||||
data.length > 0 &&
|
||||
this.catalog.length > 0
|
||||
) {
|
||||
ElMessage.info(`书籍${name}: 章节目录已更新`)
|
||||
}
|
||||
this.setCatalog(data)
|
||||
console.log(`书籍${name}: 章节目录已更新`)
|
||||
return this.catalog
|
||||
})
|
||||
if (
|
||||
bookUrl === this.readingBook.bookUrl &&
|
||||
this.catalog.length > 0 &&
|
||||
this.catalog.length - 1 >= chapterIndex
|
||||
) {
|
||||
console.log(`返回书籍《${name}》 缓存的章节目录`)
|
||||
return this.catalog
|
||||
} else {
|
||||
console.log(`从阅读后端获取书籍《${name}》 章节目录数据...`)
|
||||
return await fetchChapterList_promise
|
||||
}
|
||||
},
|
||||
setCatalog(catalog: BookChapter[]) {
|
||||
this.catalog = catalog
|
||||
},
|
||||
@@ -93,8 +167,22 @@ export const useBookStore = defineStore('book', {
|
||||
setReadingBook(readingBook: typeof this.readingBook) {
|
||||
this.readingBook = readingBook
|
||||
},
|
||||
setConfig(config: webReadConfig) {
|
||||
this.config = Object.assign({}, this.config, config)
|
||||
/** 只从从后端加载一次web阅读配置 */
|
||||
async loadWebConfig() {
|
||||
if (webReadConfigLoadedDate === undefined) {
|
||||
const _config = await API.getReadConfig()
|
||||
webReadConfigLoadedDate = new Date()
|
||||
console.log(
|
||||
`${this.$id}.loadWebConfig: ${webReadConfigLoadedDate.toLocaleString()}成功加载阅读配置`,
|
||||
)
|
||||
return this.setConfig(_config)
|
||||
}
|
||||
console.log(
|
||||
`${this.$id}.loadWebConfig: 已于${webReadConfigLoadedDate.toLocaleString()}成功加载`,
|
||||
)
|
||||
},
|
||||
setConfig(config?: webReadConfig) {
|
||||
this.config = Object.assign({}, this.config ?? default_config, config)
|
||||
},
|
||||
setReadSettingsVisible(visible: boolean) {
|
||||
this.readSettingsVisible = visible
|
||||
@@ -118,9 +206,19 @@ export const useBookStore = defineStore('book', {
|
||||
clearSearchBooks() {
|
||||
this.searchBooks = []
|
||||
},
|
||||
//保存进度到app
|
||||
/** 1.保存进度到app 2.修改内存中的数据*/
|
||||
async saveBookProgress() {
|
||||
if (!this.bookProgress) return Promise.resolve()
|
||||
const { bookUrl } = this.readingBook
|
||||
const shelfRaw = toRaw(this.shelf)
|
||||
const findIndex = shelfRaw.findIndex(book => book.bookUrl === bookUrl)
|
||||
if (findIndex > -1) {
|
||||
this.shelf[findIndex] = Object.assign(
|
||||
{},
|
||||
shelfRaw[findIndex],
|
||||
this.bookProgress,
|
||||
)
|
||||
}
|
||||
return API.saveBookProgress(this.bookProgress)
|
||||
},
|
||||
},
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
>
|
||||
<PopCatalog @getContent="getContent" class="popup" />
|
||||
<template #reference>
|
||||
<div class="tool-icon" :class="{ 'no-point': noPoint }">
|
||||
<div class="tool-icon" :class="{ 'no-point': false }">
|
||||
<div class="iconfont"></div>
|
||||
<div class="icon-text">目录</div>
|
||||
</div>
|
||||
@@ -318,7 +318,6 @@ const getContent = (index: number, reloadChapter = true, chapterPos = 0) => {
|
||||
}
|
||||
},
|
||||
err => {
|
||||
ElMessage({ message: '获取章节内容失败', type: 'error' })
|
||||
const content = ['获取章节内容失败!']
|
||||
chapterData.value.push({ index, content, title })
|
||||
store.setShowContent(true)
|
||||
@@ -496,61 +495,25 @@ onMounted(() => {
|
||||
chapterPos,
|
||||
isSeachBook,
|
||||
}
|
||||
/* const bookStr = localStorage.getItem(bookUrl);
|
||||
if (isNullOrBlank(bookStr)) {
|
||||
return setTimeout(toShelf, 500);
|
||||
}
|
||||
book = JSON.parse(bookStr as string);
|
||||
if (
|
||||
book == null ||
|
||||
chapterIndex != book.chapterIndex ||
|
||||
chapterPos != book.chapterPos
|
||||
) {
|
||||
book = {
|
||||
name: bookName!!,
|
||||
author: bookAuthor!!,
|
||||
bookUrl,
|
||||
chapterIndex,
|
||||
chapterPos,
|
||||
isSeachBook
|
||||
};
|
||||
localStorage.setItem(bookUrl, JSON.stringify(book));
|
||||
} */
|
||||
onResize()
|
||||
window.addEventListener('resize', onResize)
|
||||
loadingWrapper(
|
||||
API.getChapterList(bookUrl as string).then(
|
||||
res => {
|
||||
if (!res.data.isSuccess) {
|
||||
ElMessage({ message: res.data.errorMsg, type: 'error' })
|
||||
setTimeout(toShelf, 500)
|
||||
return
|
||||
}
|
||||
const data = res.data.data
|
||||
store.setCatalog(data)
|
||||
store.setReadingBook(book)
|
||||
|
||||
getContent(chapterIndex, true, chapterPos)
|
||||
window.addEventListener('keyup', handleKeyPress)
|
||||
window.addEventListener('keydown', ignoreKeyPress)
|
||||
// 兼容Safari < 14
|
||||
document.addEventListener('visibilitychange', onVisibilityChange)
|
||||
//监听底部加载
|
||||
scrollObserver = new IntersectionObserver(onReachBottom, {
|
||||
rootMargin: '-100% 0% 20% 0%',
|
||||
})
|
||||
if (infiniteLoading.value === true)
|
||||
scrollObserver.observe(loading.value)
|
||||
//第二次点击同一本书 页面标题不会变化
|
||||
document.title = '...'
|
||||
document.title =
|
||||
(name as string) + ' | ' + catalog.value[chapterIndex].title
|
||||
},
|
||||
err => {
|
||||
ElMessage({ message: '获取书籍目录失败', type: 'error' })
|
||||
throw err
|
||||
},
|
||||
),
|
||||
store.loadWebCatalog(book).then(chapters => {
|
||||
store.setReadingBook(book)
|
||||
getContent(chapterIndex, true, chapterPos)
|
||||
window.addEventListener('keyup', handleKeyPress)
|
||||
window.addEventListener('keydown', ignoreKeyPress)
|
||||
// 兼容Safari < 14
|
||||
document.addEventListener('visibilitychange', onVisibilityChange)
|
||||
//监听底部加载
|
||||
scrollObserver = new IntersectionObserver(onReachBottom, {
|
||||
rootMargin: '-100% 0% 20% 0%',
|
||||
})
|
||||
if (infiniteLoading.value === true) scrollObserver.observe(loading.value)
|
||||
//第二次点击同一本书 页面标题不会变化
|
||||
document.title = '...'
|
||||
document.title = (name as string) + ' | ' + chapters[chapterIndex].title
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -288,11 +288,7 @@ const toDetail = (
|
||||
|
||||
const loadShelf = async () => {
|
||||
try {
|
||||
if (store.configInited === false) {
|
||||
const config = await API.getReadConfig()
|
||||
applyReadConfig(config)
|
||||
} else {
|
||||
}
|
||||
//await store.loadWebConfig() called in router.beforeEach
|
||||
await store.saveBookProgress()
|
||||
//确保各种网络情况下同步请求先完成
|
||||
await fetchBookShelfData()
|
||||
@@ -304,35 +300,11 @@ const loadShelf = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const fetchBookShelfData = () => {
|
||||
return API.getBookShelf().then(response => {
|
||||
store.setConnectType('success')
|
||||
if (response.data.isSuccess) {
|
||||
//store.increaseBookNum(response.data.data.length);
|
||||
store.addBooks(
|
||||
response.data.data.sort(function (a, b) {
|
||||
const x = a['durChapterTime'] || 0
|
||||
const y = b['durChapterTime'] || 0
|
||||
return y - x
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
if (
|
||||
response.data.errorMsg.includes('还没有添加小说') &&
|
||||
shelf.value.length > 0
|
||||
) {
|
||||
ElNotification.warning({
|
||||
title: '提示',
|
||||
message: '当前书架上的书籍已经被删除',
|
||||
position: 'bottom-right',
|
||||
})
|
||||
return store.clearBooks()
|
||||
}
|
||||
ElMessage.error(response.data.errorMsg ?? '后端返回格式错误!')
|
||||
}
|
||||
store.setConnectStatus('已连接 ' + legado_http_entry_point)
|
||||
store.setNewConnect(false)
|
||||
})
|
||||
const fetchBookShelfData = async () => {
|
||||
await store.loadBookShelf()
|
||||
store.setConnectType('primary')
|
||||
store.setConnectStatus('已连接 ' + legado_http_entry_point)
|
||||
store.setNewConnect(false)
|
||||
}
|
||||
onMounted(() => {
|
||||
//获取最近阅读书籍
|
||||
@@ -348,7 +320,7 @@ onMounted(() => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
.index-wrapper {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
@@ -52,7 +52,7 @@ export default defineConfig(({ mode }) => {
|
||||
},
|
||||
},
|
||||
esbuild: {
|
||||
drop: ["console"],
|
||||
drop: mode === "development" ? undefined : ["console", "debugger"],
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
|
||||
Reference in New Issue
Block a user