Feature: use nuxt bridge to bundle client

This commit is contained in:
Aram Becker 2023-05-04 23:52:36 +02:00
parent 98c117f0b8
commit 228b5203a8
23 changed files with 8484 additions and 19783 deletions

View file

@ -14,6 +14,7 @@ npm-debug.log
dev.js dev.js
test/ test/
/client/.nuxt/ /client/.nuxt/
/client/.output/
/client/dist/ /client/dist/
/dist/ /dist/
/build/ /build/

1
.gitignore vendored
View file

@ -9,6 +9,7 @@
/metadata/ /metadata/
test/ test/
/client/.nuxt/ /client/.nuxt/
/client/.output/
/client/dist/ /client/dist/
/dist/ /dist/
/deploy/ /deploy/

View file

@ -23,16 +23,12 @@ RUN apk update && \
COPY --from=tone /usr/local/bin/tone /usr/local/bin/ COPY --from=tone /usr/local/bin/tone /usr/local/bin/
COPY package* ./ COPY package* ./
COPY --from=build /client/package*.json client/
RUN npm ci --omit=dev RUN npm ci --omit=dev
RUN cd client && npm ci --omit=dev && cd ..
COPY index.js package* ./ COPY index.js package* ./
COPY server server COPY server server
COPY --from=build /client/nuxt.config.js* client/ COPY --from=build /client/.output ./client/.output
COPY --from=build /client/.nuxt client/.nuxt
COPY --from=build /client/modules client/modules
EXPOSE 80 EXPOSE 80
HEALTHCHECK \ HEALTHCHECK \

View file

@ -10,12 +10,12 @@
<div v-show="routeName === route.iod" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" /> <div v-show="routeName === route.iod" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link> </nuxt-link>
<modals-changelog-view-modal v-model="showChangelogModal" :changelog="currentVersionChangelog" :currentVersion="$config.version" /> <modals-changelog-view-modal v-model="showChangelogModal" :changelog="currentVersionChangelog" :currentVersion="$config.public.version" />
</div> </div>
<div class="w-44 h-12 px-4 border-t bg-bg border-black border-opacity-20 fixed left-0 flex flex-col justify-center" :class="wrapperClass" :style="{ bottom: streamLibraryItem ? '160px' : '0px' }"> <div class="w-44 h-12 px-4 border-t bg-bg border-black border-opacity-20 fixed left-0 flex flex-col justify-center" :class="wrapperClass" :style="{ bottom: streamLibraryItem ? '160px' : '0px' }">
<div class="flex justify-between"> <div class="flex justify-between">
<p class="underline font-mono text-sm" @click="clickChangelog">v{{ $config.version }}</p> <p class="underline font-mono text-sm" @click="clickChangelog">v{{ $config.public.version }}</p>
<p class="font-mono text-xs text-gray-300 italic">{{ Source }}</p> <p class="font-mono text-xs text-gray-300 italic">{{ Source }}</p>
</div> </div>

View file

@ -114,12 +114,12 @@
</nuxt-link> </nuxt-link>
<div class="w-full h-12 px-1 py-2 border-t border-black border-opacity-20 absolute left-0" :style="{ bottom: streamLibraryItem ? '240px' : '65px' }"> <div class="w-full h-12 px-1 py-2 border-t border-black border-opacity-20 absolute left-0" :style="{ bottom: streamLibraryItem ? '240px' : '65px' }">
<p class="underline font-mono text-xs text-center text-gray-300 leading-3 mb-1" @click="clickChangelog">v{{ $config.version }}</p> <p class="underline font-mono text-xs text-center text-gray-300 leading-3 mb-1" @click="clickChangelog">v{{ $config.public.version }}</p>
<a v-if="hasUpdate" :href="githubTagUrl" target="_blank" class="text-warning text-xxs text-center block leading-3">Update</a> <a v-if="hasUpdate" :href="githubTagUrl" target="_blank" class="text-warning text-xxs text-center block leading-3">Update</a>
<p v-else class="text-xxs text-gray-400 leading-3 text-center italic">{{ Source }}</p> <p v-else class="text-xxs text-gray-400 leading-3 text-center italic">{{ Source }}</p>
</div> </div>
<modals-changelog-view-modal v-model="showChangelogModal" :changelog="currentVersionChangelog" :currentVersion="$config.version" /> <modals-changelog-view-modal v-model="showChangelogModal" :changelog="currentVersionChangelog" :currentVersion="$config.public.version" />
</div> </div>
</template> </template>

View file

@ -58,7 +58,7 @@ export default {
if (!this.imagePath) return null if (!this.imagePath) return null
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
// Testing // Testing
return `http://localhost:3333${this.$config.routerBasePath}/api/authors/${this.authorId}/image?token=${this.userToken}&ts=${this.updatedAt}` return `http://localhost:3333${this.$config.public.routerBasePath}/api/authors/${this.authorId}/image?token=${this.userToken}&ts=${this.updatedAt}`
} }
return `/api/authors/${this.authorId}/image?token=${this.userToken}&ts=${this.updatedAt}` return `/api/authors/${this.authorId}/image?token=${this.userToken}&ts=${this.updatedAt}`
} }

View file

@ -55,7 +55,7 @@ export default {
return this.item.coverPath return this.item.coverPath
}, },
coverUrl() { coverUrl() {
if (!this.coverPath) return `${this.$config.routerBasePath}/book_placeholder.jpg` if (!this.coverPath) return `${this.$config.public.routerBasePath}/book_placeholder.jpg`
return this.$store.getters['globals/getLibraryItemCoverSrcById'](this.libraryItemId) return this.$store.getters['globals/getLibraryItemCoverSrcById'](this.libraryItemId)
}, },
bookCoverAspectRatio() { bookCoverAspectRatio() {

View file

@ -137,7 +137,7 @@ export default {
slug: this.newFeedSlug, slug: this.newFeedSlug,
metadataDetails: this.metadataDetails metadataDetails: this.metadataDetails
} }
if (this.$isDev) payload.serverAddress = `http://localhost:3333${this.$config.routerBasePath}` if (this.$isDev) payload.serverAddress = `http://localhost:3333${this.$config.public.routerBasePath}`
console.log('Payload', payload) console.log('Payload', payload)
this.$axios this.$axios

View file

@ -1,26 +1,26 @@
<template> <template>
<div class="h-full w-full"> <div class="w-full h-full">
<div class="h-full flex items-center justify-center"> <div class="flex items-center justify-center h-full">
<div style="width: 100px; max-width: 100px" class="h-full hidden sm:flex items-center overflow-x-hidden justify-center"> <div style="width: 100px; max-width: 100px" class="items-center justify-center hidden h-full overflow-x-hidden sm:flex">
<span v-if="hasPrev" class="material-icons text-white text-opacity-50 hover:text-opacity-80 cursor-pointer text-6xl" @mousedown.prevent @click="prev">chevron_left</span> <span v-if="hasPrev" class="text-6xl text-white text-opacity-50 cursor-pointer material-icons hover:text-opacity-80" @mousedown.prevent @click="prev">chevron_left</span>
</div> </div>
<div id="frame" class="w-full" style="height: 80%"> <div id="frame" class="w-full" style="height: 80%">
<div id="viewer"></div> <div id="viewer"></div>
</div> </div>
<div style="width: 100px; max-width: 100px" class="h-full hidden sm:flex items-center justify-center overflow-x-hidden"> <div style="width: 100px; max-width: 100px" class="items-center justify-center hidden h-full overflow-x-hidden sm:flex">
<span v-if="hasNext" class="material-icons text-white text-opacity-50 hover:text-opacity-80 cursor-pointer text-6xl" @mousedown.prevent @click="next">chevron_right</span> <span v-if="hasNext" class="text-6xl text-white text-opacity-50 cursor-pointer material-icons hover:text-opacity-80" @mousedown.prevent @click="next">chevron_right</span>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import ePub from 'epubjs' import { Epub, Rendition, NavItem } from 'epubjs'
/** /**
* @typedef {object} EpubReader * @typedef {object} EpubReader
* @property {ePub.Book} book * @property {Epub} book
* @property {ePub.Rendition} rendition * @property {Rendition} rendition
*/ */
export default { export default {
props: { props: {
@ -33,9 +33,9 @@ export default {
data() { data() {
return { return {
windowWidth: 0, windowWidth: 0,
/** @type {ePub.Book} */ /** @type {Epub} */
book: null, book: null,
/** @type {ePub.Rendition} */ /** @type {Rendition} */
rendition: null rendition: null
} }
}, },
@ -50,7 +50,7 @@ export default {
hasNext() { hasNext() {
return !this.rendition?.location?.atEnd return !this.rendition?.location?.atEnd
}, },
/** @returns {Array<ePub.NavItem>} */ /** @returns {Array<NavItem>} */
chapters() { chapters() {
return this.book ? this.book.navigation.toc : [] return this.book ? this.book.navigation.toc : []
}, },
@ -200,13 +200,13 @@ export default {
/** @type {EpubReader} */ /** @type {EpubReader} */
const reader = this const reader = this
/** @type {ePub.Book} */ /** @type {Epub} */
reader.book = new ePub(reader.url, { reader.book = new ePub(reader.url, {
width: this.readerWidth, width: this.readerWidth,
height: window.innerHeight - 50 height: window.innerHeight - 50
}) })
/** @type {ePub.Rendition} */ /** @type {Rendition} */
reader.rendition = reader.book.renderTo('viewer', { reader.rendition = reader.book.renderTo('viewer', {
width: this.readerWidth, width: this.readerWidth,
height: window.innerHeight * 0.8 height: window.innerHeight * 0.8

View file

@ -368,7 +368,7 @@ export default {
initializeSocket() { initializeSocket() {
this.socket = this.$nuxtSocket({ this.socket = this.$nuxtSocket({
name: process.env.NODE_ENV === 'development' ? 'dev' : 'prod', name: process.env.NODE_ENV === 'development' ? 'dev' : 'prod',
channel: this.$config.routerBasePath, channel: this.$config.public.routerBasePath,
persist: 'main', persist: 'main',
teardown: false, teardown: false,
transports: ['websocket'], transports: ['websocket'],

17
client/nitro.entry.js Normal file
View file

@ -0,0 +1,17 @@
import '#internal/nitro/virtual/polyfill'
const nitroApp = useNitroApp()
const listener = toNodeListener(nitroApp.h3App)
const handler = listener
{
process.on(
"unhandledRejection",
(err) => console.error("[nitro] [dev] [unhandledRejection] " + err)
);
process.on(
"uncaughtException",
(err) => console.error("[nitro] [dev] [uncaughtException] " + err)
);
}
export { useRuntimeConfig, getRouteRules, handler, listener, useNitroApp };

7
client/nitro.preset.js Normal file
View file

@ -0,0 +1,7 @@
import { defineNitroPreset } from 'nitropack'
import { fileURLToPath } from 'node:url'
export default defineNitroPreset({
extends: 'node-server',
entry: fileURLToPath(new URL('./nitro.entry.js', import.meta.url))
})

View file

@ -1,6 +1,8 @@
const { defineNuxtConfig } = require('@nuxt/bridge')
import { fromNodeMiddleware } from 'h3';
const pkg = require('./package.json') const pkg = require('./package.json')
module.exports = ({ command }) => ({ module.exports = defineNuxtConfig({
// Disable server-side rendering: https://go.nuxtjs.dev/ssr-mode // Disable server-side rendering: https://go.nuxtjs.dev/ssr-mode
ssr: false, ssr: false,
target: 'server', target: 'server',
@ -11,9 +13,11 @@ module.exports = ({ command }) => ({
}, },
telemetry: false, telemetry: false,
publicRuntimeConfig: { runtimeConfig: {
version: pkg.version, public: {
routerBasePath: process.env.ROUTER_BASE_PATH || '' version: pkg.version,
routerBasePath: process.env.ROUTER_BASE_PATH || ''
}
}, },
// Global page headers: https://go.nuxtjs.dev/config-head // Global page headers: https://go.nuxtjs.dev/config-head
@ -31,7 +35,7 @@ module.exports = ({ command }) => ({
router: { router: {
// We must specify `./` during build to support dynamic router base paths (https://github.com/nuxt/nuxt/issues/10088) // We must specify `./` during build to support dynamic router base paths (https://github.com/nuxt/nuxt/issues/10088)
base: command == 'build' ? './' : process.env.ROUTER_BASE_PATH || '' base: process.env.ROUTER_BASE_PATH || ''
}, },
// Global CSS: https://go.nuxtjs.dev/config-css // Global CSS: https://go.nuxtjs.dev/config-css
@ -59,31 +63,16 @@ module.exports = ({ command }) => ({
buildModules: [ buildModules: [
// https://go.nuxtjs.dev/tailwindcss // https://go.nuxtjs.dev/tailwindcss
'@nuxtjs/tailwindcss', '@nuxtjs/tailwindcss',
'@nuxtjs/pwa', 'pwa-nuxt-bridge',
'@nuxt/postcss8'
], ],
// Modules: https://go.nuxtjs.dev/config-modules // Modules: https://go.nuxtjs.dev/config-modules
modules: [ modules: [
'nuxt-socket-io', 'nuxt-socket-io',
'@nuxtjs/axios', '@nuxtjs/axios',
'@nuxtjs/proxy', // '@/modules/rewritePwaManifest.js'
'@/modules/rewritePwaManifest.js'
], ],
proxy: {
[`${process.env.ROUTER_BASE_PATH || ''}/dev/`]: {
target: `http://localhost:3333${process.env.ROUTER_BASE_PATH || ''}`,
pathRewrite: { [`^${process.env.ROUTER_BASE_PATH || ''}/dev/`]: process.env.ROUTER_BASE_PATH || '' }
},
[`${process.env.ROUTER_BASE_PATH || ''}/ebook/`]: {
target: (process.env.NODE_ENV !== 'production' ? 'http://localhost:3333' : '') + `${process.env.ROUTER_BASE_PATH || ''}/`
},
[`${process.env.ROUTER_BASE_PATH || ''}/s/`]: {
target: (process.env.NODE_ENV !== 'production' ? 'http://localhost:3333' : '') + `${process.env.ROUTER_BASE_PATH || ''}/`
}
},
io: { io: {
sockets: [{ sockets: [{
name: 'dev', name: 'dev',
@ -138,9 +127,11 @@ module.exports = ({ command }) => ({
// Build Configuration: https://go.nuxtjs.dev/config-build // Build Configuration: https://go.nuxtjs.dev/config-build
build: { build: {
postcss: { postcss: {
plugins: { postcssOptions: {
tailwindcss: {}, plugins: {
autoprefixer: {}, tailwindcss: {},
autoprefixer: {},
},
}, },
} }
}, },
@ -155,6 +146,34 @@ module.exports = ({ command }) => ({
host: '0.0.0.0' host: '0.0.0.0'
}, },
bridge: {
capi: false,
scriptSetup: false
},
nitro: {
preset: './nitro.preset.js',
devProxy: {
[`${process.env.ROUTER_BASE_PATH || ''}/dev/`]: {
target: `http://localhost:3333${process.env.ROUTER_BASE_PATH || ''}`,
pathRewrite: { [`^${process.env.ROUTER_BASE_PATH || ''}/dev/`]: process.env.ROUTER_BASE_PATH || '' }
},
[`${process.env.ROUTER_BASE_PATH || ''}/ebook/`]: {
target: (process.env.NODE_ENV !== 'production' ? 'http://localhost:3333' : '') + `${process.env.ROUTER_BASE_PATH || ''}/`
},
[`${process.env.ROUTER_BASE_PATH || ''}/s/`]: {
target: (process.env.NODE_ENV !== 'production' ? 'http://localhost:3333' : '') + `${process.env.ROUTER_BASE_PATH || ''}/`
}
}
},
hooks: {
// 'nitro:config': (config) => {
// console.log('nitro:config', config)
// process.exit(0)
// },
},
/** /**
* Temporary workaround for @nuxt-community/tailwindcss-module. * Temporary workaround for @nuxt-community/tailwindcss-module.
* *

28061
client/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -4,26 +4,26 @@
"description": "Self-hosted audiobook and podcast client", "description": "Self-hosted audiobook and podcast client",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"dev": "nuxt", "dev": "nuxi dev",
"dev2": "nuxt --hostname localhost --port 1337", "dev2": "nuxi dev --hostname localhost --port 1337",
"build": "nuxt build", "build": "nuxi build",
"start": "nuxt start", "start": "nuxi preview",
"generate": "nuxt generate" "generate": "nuxi generate"
}, },
"author": "advplyr", "author": "advplyr",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@nuxt/bridge": "npm:@nuxt/bridge-edge@^3.0.0-28051151.e88ad64",
"@nuxtjs/axios": "^5.13.6", "@nuxtjs/axios": "^5.13.6",
"@nuxtjs/proxy": "^2.1.0",
"core-js": "^3.16.0", "core-js": "^3.16.0",
"cron-parser": "^4.7.1", "cron-parser": "^4.7.1",
"date-fns": "^2.25.0", "date-fns": "^2.25.0",
"epubjs": "^0.3.88", "epubjs": "^0.5.0-alpha.0",
"glob": "^10.2.2",
"hls.js": "^1.0.7", "hls.js": "^1.0.7",
"libarchive.js": "^1.3.0", "libarchive.js": "^1.3.0",
"nuxt": "^2.15.8", "nuxt": "^2.16.3",
"nuxt-socket-io": "^1.1.18", "nuxt-socket-io": "^1.1.18",
"pwa-nuxt-bridge": "^5.0.1",
"trix": "^1.3.1", "trix": "^1.3.1",
"v-click-outside": "^3.1.2", "v-click-outside": "^3.1.2",
"vue-pdf": "^4.2.0", "vue-pdf": "^4.2.0",
@ -31,10 +31,9 @@
"vuedraggable": "^2.24.3" "vuedraggable": "^2.24.3"
}, },
"devDependencies": { "devDependencies": {
"@nuxt/postcss8": "^1.1.3", "@nuxtjs/tailwindcss": "^6.6.7",
"@nuxtjs/pwa": "^3.3.5",
"@nuxtjs/tailwindcss": "^4.2.1",
"autoprefixer": "^10.4.7", "autoprefixer": "^10.4.7",
"minimatch": "^8.0.4",
"postcss": "^8.3.6", "postcss": "^8.3.6",
"tailwindcss": "^3.1.4" "tailwindcss": "^3.1.4"
} }

View file

@ -415,7 +415,7 @@ export default {
const audioEl = this.audioEl || document.createElement('audio') const audioEl = this.audioEl || document.createElement('audio')
var src = audioTrack.contentUrl + `?token=${this.userToken}` var src = audioTrack.contentUrl + `?token=${this.userToken}`
if (this.$isDev) { if (this.$isDev) {
src = `http://localhost:3333${this.$config.routerBasePath}${src}` src = `http://localhost:3333${this.$config.public.routerBasePath}${src}`
} }
audioEl.src = src audioEl.src = src

View file

@ -18,8 +18,8 @@ export default function ({ $axios, store, $config }) {
} }
console.log($config) console.log($config)
if ($config.routerBasePath) { if ($config.public.routerBasePath) {
config.url = `${$config.routerBasePath}${config.url}` config.url = `${$config.public.routerBasePath}${config.url}`
} }
}) })

View file

@ -4,7 +4,7 @@ export default function ({ $config }) {
const link = document.createElement('link') const link = document.createElement('link')
link.rel = 'icon' link.rel = 'icon'
link.type = 'image/x-icon' link.type = 'image/x-icon'
link.href = `${$config.routerBasePath || ''}${faviconPath}` link.href = `${$config.public.routerBasePath || ''}${faviconPath}`
document.head.appendChild(link) document.head.appendChild(link)
} }

View file

@ -182,5 +182,5 @@ export default ({ app, store }, inject) => {
inject('eventBus', new Vue()) inject('eventBus', new Vue())
inject('isDev', process.env.NODE_ENV !== 'production') inject('isDev', process.env.NODE_ENV !== 'production')
store.commit('setRouterBasePath', app.$config.routerBasePath) store.commit('setRouterBasePath', app.$config.public.routerBasePath)
} }

1
package-lock.json generated
View file

@ -13,6 +13,7 @@
"express": "^4.17.1", "express": "^4.17.1",
"graceful-fs": "^4.2.10", "graceful-fs": "^4.2.10",
"htmlparser2": "^8.0.1", "htmlparser2": "^8.0.1",
"mime-types": "^2.1.35",
"node-tone": "^1.0.1", "node-tone": "^1.0.1",
"socket.io": "^4.5.4", "socket.io": "^4.5.4",
"xml2js": "^0.5.0" "xml2js": "^0.5.0"

View file

@ -39,6 +39,7 @@
"express": "^4.17.1", "express": "^4.17.1",
"graceful-fs": "^4.2.10", "graceful-fs": "^4.2.10",
"htmlparser2": "^8.0.1", "htmlparser2": "^8.0.1",
"mime-types": "^2.1.35",
"node-tone": "^1.0.1", "node-tone": "^1.0.1",
"socket.io": "^4.5.4", "socket.io": "^4.5.4",
"xml2js": "^0.5.0" "xml2js": "^0.5.0"

View file

@ -139,7 +139,7 @@ class Server {
Logger.info('=== Starting Server ===') Logger.info('=== Starting Server ===')
await this.init() await this.init()
if (process.env.NODE_ENV !== 'development') { if (process.env.NODE_ENV !== 'development') {
this.clientRouter.start() await this.clientRouter.start()
} }
const app = express() const app = express()

View file

@ -1,4 +1,5 @@
const express = require('express') const express = require('express')
const mime = require('mime-types')
const Path = require('path') const Path = require('path')
const Logger = require('../Logger') const Logger = require('../Logger')
@ -14,18 +15,15 @@ class ClientRouter {
async init () { async init () {
const clientDir = Path.join(this.appRoot, '/client') const clientDir = Path.join(this.appRoot, '/client')
const { loadNuxt } = require(Path.resolve(clientDir, 'node_modules/nuxt')) const { handler } = await import(Path.join(clientDir, '.output/server/index.mjs'))
this.client = await loadNuxt({ rootDir: clientDir, for: 'start' }) this.client = handler
} }
async start () { async start () {
Logger.info('[Client] Starting') Logger.info('[Client] Starting')
await this.init() await this.init()
this.router.use(this.client.render) this.router.use('/', this.client)
this.client.hook('error', (err) => Logger.error('[Client]', err))
this.client.ready().then(() => Logger.info('[Client] Ready'))
} }
async stop () { async stop () {