From bae6b368aed4c2f2fe5343081b7f57f55d4e0a7f Mon Sep 17 00:00:00 2001 From: meteorlxy Date: Fri, 16 Aug 2024 10:55:49 +0800 Subject: [PATCH] chore: updates --- e2e/playwright.config.ts | 16 +- e2e/tests/hmr.spec.ts | 4 +- e2e/tests/imports/conditional-exports.spec.ts | 4 +- e2e/tests/router/navigate-by-link.spec.ts | 4 +- e2e/tests/router/navigate-by-router.spec.ts | 2 +- e2e/utils/env.ts | 6 +- packages/bundler-vite/package.json | 3 + packages/bundler-vite/src/build/renderPage.ts | 1 - .../src/plugins/vuepressMainPlugin.ts | 178 +++++++++--------- .../src/plugins/vuepressVuePlugin.ts | 25 ++- packages/bundler-vite/src/viteBundler.ts | 4 +- .../src/build/renderPagePrefetchLinks.ts | 2 +- .../src/build/renderPagePreloadLinks.ts | 2 +- .../src/build/resolveClientManifestMeta.ts | 20 +- .../src/build/resolvePageClientFilesMeta.ts | 1 + .../src/build/ssr/createClientPlugin.ts | 8 +- packages/client/src/router/createVueRouter.ts | 2 +- packages/client/src/router/resolveRoute.ts | 1 + .../client/src/router/resolveRoutePath.ts | 2 + packages/client/src/setupUpdateHead.ts | 136 ++++++------- packages/client/src/types/clientData.ts | 7 +- packages/core/src/page/createPage.ts | 2 +- .../core/src/page/resolvePageComponentInfo.ts | 6 +- packages/core/src/page/resolvePageDate.ts | 2 + .../pluginApi/createPluginApiRegisterHooks.ts | 5 +- .../src/pluginApi/normalizeAliasDefineHook.ts | 1 - packages/core/src/types/pluginApi/hooks.ts | 3 +- .../page/resolvePageComponentInfo.spec.ts | 4 +- packages/shared/src/types/page.ts | 18 +- .../shared/src/utils/ensureEndingSlash.ts | 2 +- .../shared/src/utils/ensureLeadingSlash.ts | 2 +- .../shared/src/utils/removeEndingSlash.ts | 2 +- .../shared/src/utils/removeLeadingSlash.ts | 2 +- pnpm-lock.yaml | 12 +- 34 files changed, 255 insertions(+), 234 deletions(-) diff --git a/e2e/playwright.config.ts b/e2e/playwright.config.ts index a8b19d25c1..8bfcec75b8 100644 --- a/e2e/playwright.config.ts +++ b/e2e/playwright.config.ts @@ -1,16 +1,16 @@ import { defineConfig, devices } from '@playwright/test' -import { BASE, BUNDLER, isCI, isDev } from './utils/env' +import { BASE, BUNDLER, IS_CI, IS_DEV } from './utils/env' -const COMMAND_PART1 = isDev ? 'docs:dev' : 'docs:build' +const COMMAND_PART1 = IS_DEV ? 'docs:dev' : 'docs:build' const COMMAND_PART2 = BUNDLER === 'vite' ? '' : `-${BUNDLER}` -const COMMAND_PART3 = isDev ? '' : ' && pnpm docs:serve' +const COMMAND_PART3 = IS_DEV ? '' : ' && pnpm docs:serve' export default defineConfig({ testDir: 'tests', - forbidOnly: isCI, - reporter: isCI ? 'github' : 'line', - retries: isCI ? 2 : 0, - workers: isDev ? 1 : undefined, + forbidOnly: IS_CI, + reporter: IS_CI ? 'github' : 'line', + retries: IS_CI ? 2 : 0, + workers: IS_DEV ? 1 : undefined, projects: [ { name: 'chromium', @@ -24,6 +24,6 @@ export default defineConfig({ webServer: { command: `pnpm docs:clean && pnpm ${COMMAND_PART1}${COMMAND_PART2}${COMMAND_PART3}`, url: 'http://127.0.0.1:9080', - reuseExistingServer: !isCI, + reuseExistingServer: !IS_CI, }, }) diff --git a/e2e/tests/hmr.spec.ts b/e2e/tests/hmr.spec.ts index 37336026ca..f1040d9c02 100644 --- a/e2e/tests/hmr.spec.ts +++ b/e2e/tests/hmr.spec.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test' -import { isDev } from '../utils/env' +import { IS_DEV } from '../utils/env' import { readSourceMarkdown, writeSourceMarkdown } from '../utils/source' const hmrUpdateTitle = async (): Promise => { @@ -33,7 +33,7 @@ const hmrRestore = async (): Promise => { ) } -if (isDev) { +if (IS_DEV) { test.beforeEach(async () => { await hmrRestore() }) diff --git a/e2e/tests/imports/conditional-exports.spec.ts b/e2e/tests/imports/conditional-exports.spec.ts index daec6fc941..6f43cb25b8 100644 --- a/e2e/tests/imports/conditional-exports.spec.ts +++ b/e2e/tests/imports/conditional-exports.spec.ts @@ -8,8 +8,8 @@ test('should load different files correctly', async ({ page }) => { if (COMMAND === 'build') { expect( - await page.evaluate(() => - fetch('./conditional-exports.html').then((res) => res.text()), + await page.evaluate(async () => + fetch('./conditional-exports.html').then(async (res) => res.text()), ), ).toContain('

node-mjs

') } diff --git a/e2e/tests/router/navigate-by-link.spec.ts b/e2e/tests/router/navigate-by-link.spec.ts index 4b7560a732..7c6f974992 100644 --- a/e2e/tests/router/navigate-by-link.spec.ts +++ b/e2e/tests/router/navigate-by-link.spec.ts @@ -8,7 +8,7 @@ test.beforeEach(async ({ page }) => { test.describe('markdown links', () => { test('should navigate to home correctly', async ({ page }) => { await page.locator('#markdown-links + ul > li > a').nth(0).click() - await expect(page).toHaveURL(`${BASE}`) + await expect(page).toHaveURL(BASE) await expect(page.locator('#home-h2')).toHaveText('Home H2') }) @@ -46,7 +46,7 @@ test.describe('markdown links', () => { test.describe('html links', () => { test('should navigate to home correctly', async ({ page }) => { await page.locator('#html-links + p > a').nth(0).click() - await expect(page).toHaveURL(`${BASE}`) + await expect(page).toHaveURL(BASE) await expect(page.locator('#home-h2')).toHaveText('Home H2') }) diff --git a/e2e/tests/router/navigate-by-router.spec.ts b/e2e/tests/router/navigate-by-router.spec.ts index 327c53be57..b847a839a2 100644 --- a/e2e/tests/router/navigate-by-router.spec.ts +++ b/e2e/tests/router/navigate-by-router.spec.ts @@ -7,7 +7,7 @@ test.beforeEach(async ({ page }) => { test('should navigate to home correctly', async ({ page }) => { await page.locator('#home').click() - await expect(page).toHaveURL(`${BASE}`) + await expect(page).toHaveURL(BASE) await expect(page.locator('#home-h2')).toHaveText('Home H2') }) diff --git a/e2e/utils/env.ts b/e2e/utils/env.ts index c1999dd3e2..17781ce31e 100644 --- a/e2e/utils/env.ts +++ b/e2e/utils/env.ts @@ -2,6 +2,6 @@ export const BASE = process.env.E2E_BASE ?? '/' export const BUNDLER = process.env.E2E_BUNDLER ?? 'vite' export const COMMAND = process.env.E2E_COMMAND ?? 'dev' -export const isDev = COMMAND === 'dev' -export const isProd = COMMAND === 'build' -export const isCI = !!process.env.CI +export const IS_DEV = COMMAND === 'dev' +export const IS_PROD = COMMAND === 'build' +export const IS_CI = !!process.env.CI diff --git a/packages/bundler-vite/package.json b/packages/bundler-vite/package.json index ae4be4aabf..9862ee3c25 100644 --- a/packages/bundler-vite/package.json +++ b/packages/bundler-vite/package.json @@ -49,6 +49,9 @@ "vue": "^3.4.37", "vue-router": "^4.4.3" }, + "devDependencies": { + "@types/connect-history-api-fallback": "^1.5.4" + }, "publishConfig": { "access": "public" }, diff --git a/packages/bundler-vite/src/build/renderPage.ts b/packages/bundler-vite/src/build/renderPage.ts index 7601c1dcfa..a0af5b2374 100644 --- a/packages/bundler-vite/src/build/renderPage.ts +++ b/packages/bundler-vite/src/build/renderPage.ts @@ -38,7 +38,6 @@ export const renderPage = async ({ await vueRouter.isReady() // create vue ssr context with default values - // eslint-disable-next-line @typescript-eslint/no-dynamic-delete, no-underscore-dangle delete vueApp._context.provides[ssrContextKey] const ssrContext: VuepressSSRContext = { lang: 'en', diff --git a/packages/bundler-vite/src/plugins/vuepressMainPlugin.ts b/packages/bundler-vite/src/plugins/vuepressMainPlugin.ts index 9e811ada18..da2ce652fe 100644 --- a/packages/bundler-vite/src/plugins/vuepressMainPlugin.ts +++ b/packages/bundler-vite/src/plugins/vuepressMainPlugin.ts @@ -6,6 +6,92 @@ import type { AcceptedPlugin } from 'postcss' import postcssrc from 'postcss-load-config' import type { AliasOptions, Connect, Plugin, UserConfig } from 'vite' +/** + * Resolve vite config `resolve.alias` + */ +const resolveAlias = async ({ + app, + isServer, +}: { + app: App + isServer: boolean +}): Promise => { + const alias: AliasOptions = { + '@internal': app.dir.temp('internal'), + '@temp': app.dir.temp(), + '@source': app.dir.source(), + } + + // plugin hook: alias + const aliasResult = await app.pluginApi.hooks.alias.process(app, isServer) + + aliasResult.forEach((aliasObject) => { + Object.entries(aliasObject).forEach(([key, value]) => { + alias[key] = value as string + }) + }) + + return [ + ...Object.keys(alias).map((item) => ({ + find: item, + replacement: alias[item], + })), + ...(isServer + ? [] + : [ + { + find: /^vue$/, + replacement: 'vue/dist/vue.runtime.esm-bundler.js', + }, + { + find: /^vue-router$/, + replacement: 'vue-router/dist/vue-router.esm-bundler.js', + }, + ]), + ] +} + +/** + * Resolve vite config `define` + */ +const resolveDefine = async ({ + app, + isBuild, + isServer, +}: { + app: App + isBuild: boolean + isServer: boolean +}): Promise => { + const define: UserConfig['define'] = { + __VUEPRESS_VERSION__: JSON.stringify(app.version), + __VUEPRESS_BASE__: JSON.stringify(app.options.base), + __VUEPRESS_DEV__: JSON.stringify(!isBuild), + __VUEPRESS_SSR__: JSON.stringify(isServer), + // @see http://link.vuejs.org/feature-flags + // enable options API by default + __VUE_OPTIONS_API__: JSON.stringify(true), + __VUE_PROD_DEVTOOLS__: JSON.stringify(app.env.isDebug), + __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: JSON.stringify(app.env.isDebug), + } + + // override vite built-in define config in debug mode + if (app.env.isDebug) { + define['process.env.NODE_ENV'] = JSON.stringify('development') + } + + // plugin hook: define + const defineResult = await app.pluginApi.hooks.define.process(app, isServer) + + defineResult.forEach((defineObject) => { + Object.entries(defineObject).forEach(([key, value]) => { + define[key] = JSON.stringify(value) + }) + }) + + return define +} + /** * The main plugin to compat vuepress with vite */ @@ -92,7 +178,11 @@ import 'vuepress/client-app' cssCodeSplit: false, rollupOptions: { input: app.dir.client( - fs.readJsonSync(app.dir.client('package.json')).exports['./app'], + ( + fs.readJsonSync(app.dir.client('package.json')) as { + exports: { './app': string } + } + ).exports['./app'], ), output: { sanitizeFileName, @@ -144,89 +234,3 @@ import 'vuepress/client-app' } }, }) - -/** - * Resolve vite config `resolve.alias` - */ -const resolveAlias = async ({ - app, - isServer, -}: { - app: App - isServer: boolean -}): Promise => { - const alias: AliasOptions = { - '@internal': app.dir.temp('internal'), - '@temp': app.dir.temp(), - '@source': app.dir.source(), - } - - // plugin hook: alias - const aliasResult = await app.pluginApi.hooks.alias.process(app, isServer) - - aliasResult.forEach((aliasObject) => { - Object.entries(aliasObject).forEach(([key, value]) => { - alias[key] = value - }) - }) - - return [ - ...Object.keys(alias).map((item) => ({ - find: item, - replacement: alias[item], - })), - ...(isServer - ? [] - : [ - { - find: /^vue$/, - replacement: 'vue/dist/vue.runtime.esm-bundler.js', - }, - { - find: /^vue-router$/, - replacement: 'vue-router/dist/vue-router.esm-bundler.js', - }, - ]), - ] -} - -/** - * Resolve vite config `define` - */ -const resolveDefine = async ({ - app, - isBuild, - isServer, -}: { - app: App - isBuild: boolean - isServer: boolean -}): Promise => { - const define: UserConfig['define'] = { - __VUEPRESS_VERSION__: JSON.stringify(app.version), - __VUEPRESS_BASE__: JSON.stringify(app.options.base), - __VUEPRESS_DEV__: JSON.stringify(!isBuild), - __VUEPRESS_SSR__: JSON.stringify(isServer), - // @see http://link.vuejs.org/feature-flags - // enable options API by default - __VUE_OPTIONS_API__: JSON.stringify(true), - __VUE_PROD_DEVTOOLS__: JSON.stringify(app.env.isDebug), - __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: JSON.stringify(app.env.isDebug), - } - - // override vite built-in define config in debug mode - if (app.env.isDebug) { - define['process.env.NODE_ENV'] = JSON.stringify('development') - } - - // plugin hook: define - const defineResult = await app.pluginApi.hooks.define.process(app, isServer) - - defineResult.forEach((defineObject) => { - Object.entries(defineObject).forEach(([key, value]) => { - define[key] = JSON.stringify(value) - }) - }) - - return define -} diff --git a/packages/bundler-vite/src/plugins/vuepressVuePlugin.ts b/packages/bundler-vite/src/plugins/vuepressVuePlugin.ts index 35d72951de..96b11b514a 100644 --- a/packages/bundler-vite/src/plugins/vuepressVuePlugin.ts +++ b/packages/bundler-vite/src/plugins/vuepressVuePlugin.ts @@ -3,19 +3,6 @@ import type { Plugin } from 'vite' import type { AssetURLOptions, AssetURLTagConfig } from 'vue/compiler-sfc' import type { ViteBundlerOptions } from '../types.js' -/** - * Wrapper of official vue plugin - */ -export const vuepressVuePlugin = (options: ViteBundlerOptions): Plugin => { - return vuePlugin({ - ...options.vuePluginOptions, - template: { - ...options.vuePluginOptions?.template, - transformAssetUrls: resolveTransformAssetUrls(options), - }, - }) -} - /** * Determine if the given `transformAssetUrls` option is `AssetURLTagConfig` */ @@ -56,3 +43,15 @@ const resolveTransformAssetUrls = ( ...userTransformAssetUrls, } } + +/** + * Wrapper of official vue plugin + */ +export const vuepressVuePlugin = (options: ViteBundlerOptions): Plugin => + vuePlugin({ + ...options.vuePluginOptions, + template: { + ...options.vuePluginOptions?.template, + transformAssetUrls: resolveTransformAssetUrls(options), + }, + }) diff --git a/packages/bundler-vite/src/viteBundler.ts b/packages/bundler-vite/src/viteBundler.ts index d8e85898a1..6764c4aea1 100644 --- a/packages/bundler-vite/src/viteBundler.ts +++ b/packages/bundler-vite/src/viteBundler.ts @@ -5,6 +5,6 @@ import type { ViteBundlerOptions } from './types.js' export const viteBundler = (options: ViteBundlerOptions = {}): Bundler => ({ name: '@vuepress/bundler-vite', - dev: (app) => dev(options, app), - build: (app) => build(options, app), + dev: async (app) => dev(options, app), + build: async (app) => build(options, app), }) diff --git a/packages/bundler-webpack/src/build/renderPagePrefetchLinks.ts b/packages/bundler-webpack/src/build/renderPagePrefetchLinks.ts index aeb60089cf..875013c598 100644 --- a/packages/bundler-webpack/src/build/renderPagePrefetchLinks.ts +++ b/packages/bundler-webpack/src/build/renderPagePrefetchLinks.ts @@ -14,7 +14,7 @@ export const renderPagePrefetchLinks = ({ pageClientFilesMeta: FileMeta[] }): string => { // shouldPrefetch option - const shouldPrefetch = app.options.shouldPrefetch + const { shouldPrefetch } = app.options // do not render prefetch links if (shouldPrefetch === false) { diff --git a/packages/bundler-webpack/src/build/renderPagePreloadLinks.ts b/packages/bundler-webpack/src/build/renderPagePreloadLinks.ts index 3321fbabd1..5dbec08f0c 100644 --- a/packages/bundler-webpack/src/build/renderPagePreloadLinks.ts +++ b/packages/bundler-webpack/src/build/renderPagePreloadLinks.ts @@ -14,7 +14,7 @@ export const renderPagePreloadLinks = ({ pageClientFilesMeta: FileMeta[] }): string => { // shouldPreload option - const shouldPreload = app.options.shouldPreload + const { shouldPreload } = app.options // do not render preload links if (shouldPreload === false) { diff --git a/packages/bundler-webpack/src/build/resolveClientManifestMeta.ts b/packages/bundler-webpack/src/build/resolveClientManifestMeta.ts index 38f4dfe625..837c4b1efb 100644 --- a/packages/bundler-webpack/src/build/resolveClientManifestMeta.ts +++ b/packages/bundler-webpack/src/build/resolveClientManifestMeta.ts @@ -27,17 +27,15 @@ export const resolveClientManifestMeta = ({ // module to files meta map const moduleFilesMetaMap = Object.fromEntries( - Object.entries(modules).map(([moduleRequest, assetFilesIndex]) => { - return [ - moduleRequest, - assetFilesIndex - .map((fileIndex) => allFilesMeta[fileIndex]) - .filter( - ({ file, type }) => - async.includes(file) || (type !== 'style' && type !== 'script'), - ), - ] - }), + Object.entries(modules).map(([moduleRequest, assetFilesIndex]) => [ + moduleRequest, + assetFilesIndex + .map((fileIndex) => allFilesMeta[fileIndex]) + .filter( + ({ file, type }) => + async.includes(file) || (type !== 'style' && type !== 'script'), + ), + ]), ) return { diff --git a/packages/bundler-webpack/src/build/resolvePageClientFilesMeta.ts b/packages/bundler-webpack/src/build/resolvePageClientFilesMeta.ts index 05d19a47de..17a3c57963 100644 --- a/packages/bundler-webpack/src/build/resolvePageClientFilesMeta.ts +++ b/packages/bundler-webpack/src/build/resolvePageClientFilesMeta.ts @@ -12,6 +12,7 @@ export const resolvePageClientFilesMeta = ({ }): FileMeta[] => { const files = new Set() moduleRequests.forEach((request) => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- unsafe indexed access moduleFilesMetaMap[request]?.forEach((file) => files.add(file)) }) return Array.from(files) diff --git a/packages/bundler-webpack/src/build/ssr/createClientPlugin.ts b/packages/bundler-webpack/src/build/ssr/createClientPlugin.ts index 25a778f6ae..39d50f7164 100644 --- a/packages/bundler-webpack/src/build/ssr/createClientPlugin.ts +++ b/packages/bundler-webpack/src/build/ssr/createClientPlugin.ts @@ -50,7 +50,7 @@ export const createClientPlugin = ( // get asset modules const assetModules = modules.filter( - (m): m is StatsModule & Required> => + (m): m is Required> & StatsModule => Boolean(m.assets?.length), ) @@ -81,10 +81,10 @@ export const createClientPlugin = ( const files = [...chunk.files.map(fileToIndex)] // find all asset modules associated with the same chunk - assetModules.forEach((m) => { - if (m.chunks?.some((id) => id === cid)) { + assetModules.forEach((item) => { + if (item.chunks?.some((id) => id === cid)) { // get asset files - files.push(...m.assets.map(fileToIndex)) + files.push(...item.assets.map(fileToIndex)) } }) diff --git a/packages/client/src/router/createVueRouter.ts b/packages/client/src/router/createVueRouter.ts index a9dd605096..0c8221b570 100644 --- a/packages/client/src/router/createVueRouter.ts +++ b/packages/client/src/router/createVueRouter.ts @@ -1,10 +1,10 @@ import { removeEndingSlash } from '@vuepress/shared' import type { Router } from 'vue-router' import { + START_LOCATION, createMemoryHistory, createRouter, createWebHistory, - START_LOCATION, } from 'vue-router' import type { PageChunk } from '../types/index.js' import { resolveRoute } from './resolveRoute.js' diff --git a/packages/client/src/router/resolveRoute.ts b/packages/client/src/router/resolveRoute.ts index c8a7f4b8a8..8f848a56a3 100644 --- a/packages/client/src/router/resolveRoute.ts +++ b/packages/client/src/router/resolveRoute.ts @@ -24,6 +24,7 @@ export const resolveRoute = ( const routeFullPath = routePath + hashAndQueries // the route not found + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- unsafe indexed access if (!routes.value[routePath]) { return { ...routes.value['/404.html'], diff --git a/packages/client/src/router/resolveRoutePath.ts b/packages/client/src/router/resolveRoutePath.ts index 4f815e4c17..74a2620955 100644 --- a/packages/client/src/router/resolveRoutePath.ts +++ b/packages/client/src/router/resolveRoutePath.ts @@ -12,11 +12,13 @@ export const resolveRoutePath = ( const normalizedRoutePath = normalizeRoutePath(pathname, currentPath) // check if the normalized path is in routes + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- unsafe indexed access if (routes.value[normalizedRoutePath]) return normalizedRoutePath // check encoded path const encodedRoutePath = encodeURI(normalizedRoutePath) + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- unsafe indexed access if (routes.value[encodedRoutePath]) { return encodedRoutePath } diff --git a/packages/client/src/setupUpdateHead.ts b/packages/client/src/setupUpdateHead.ts index 5757426c82..d3f61e414b 100644 --- a/packages/client/src/setupUpdateHead.ts +++ b/packages/client/src/setupUpdateHead.ts @@ -1,12 +1,76 @@ -import { isPlainObject, isString } from '@vuepress/shared' import type { HeadConfig, VuepressSSRContext } from '@vuepress/shared' +import { isPlainObject, isString } from '@vuepress/shared' import { onMounted, provide, useSSRContext, watch } from 'vue' +import type { UpdateHead } from './composables/index.js' import { updateHeadSymbol, usePageHead, usePageLang, } from './composables/index.js' -import type { UpdateHead } from './composables/index.js' + +/** + * Query the matched head element of head config + */ +export const queryHeadElement = ([ + tagName, + attrs, + content = '', +]: HeadConfig): HTMLElement | null => { + const attrsSelector = Object.entries(attrs) + .map(([key, value]) => { + if (isString(value)) { + return `[${key}=${JSON.stringify(value)}]` + } + if (value) { + return `[${key}]` + } + return '' + }) + .join('') + + const selector = `head > ${tagName}${attrsSelector}` + const headElements = Array.from( + document.querySelectorAll(selector), + ) + const matchedHeadElement = headElements.find( + (item) => item.innerText === content, + ) + return matchedHeadElement ?? null +} + +/** + * Create head element from head config + */ +export const createHeadElement = ([ + tagName, + attrs, + content, +]: HeadConfig): HTMLElement | null => { + if (!isString(tagName)) { + return null + } + + // create element + const headElement = document.createElement(tagName) + + // set attributes + if (isPlainObject(attrs)) { + Object.entries(attrs).forEach(([key, value]) => { + if (isString(value)) { + headElement.setAttribute(key, value) + } else if (value) { + headElement.setAttribute(key, '') + } + }) + } + + // set content + if (isString(content)) { + headElement.appendChild(document.createTextNode(content)) + } + + return headElement +} /** * Auto update head and provide as global util @@ -71,7 +135,7 @@ export const setupUpdateHead = (): void => { // remove the non-intersection from old elements if (matchedIndex === -1) { oldEl.remove() - // use delete to make the index consistent + // eslint-disable-next-line @typescript-eslint/no-array-delete -- use delete to make the index consistent delete managedHeadElements[oldIndex] } // keep the intersection in old elements, and remove it from new elements @@ -85,7 +149,7 @@ export const setupUpdateHead = (): void => { // update managed head elements managedHeadElements = [ // filter out empty deleted items - ...managedHeadElements.filter((item) => !!item), + ...managedHeadElements.filter((item: HTMLElement | undefined) => !!item), ...newHeadElements, ] } @@ -100,67 +164,3 @@ export const setupUpdateHead = (): void => { watch(head, updateHead, { immediate: __VUEPRESS_DEV__ }) }) } - -/** - * Query the matched head element of head config - */ -export const queryHeadElement = ([ - tagName, - attrs, - content = '', -]: HeadConfig): HTMLElement | null => { - const attrsSelector = Object.entries(attrs) - .map(([key, value]) => { - if (isString(value)) { - return `[${key}=${JSON.stringify(value)}]` - } - if (value === true) { - return `[${key}]` - } - return '' - }) - .join('') - - const selector = `head > ${tagName}${attrsSelector}` - const headElements = Array.from( - document.querySelectorAll(selector), - ) - const matchedHeadElement = headElements.find( - (item) => item.innerText === content, - ) - return matchedHeadElement || null -} - -/** - * Create head element from head config - */ -export const createHeadElement = ([ - tagName, - attrs, - content, -]: HeadConfig): HTMLElement | null => { - if (!isString(tagName)) { - return null - } - - // create element - const headElement = document.createElement(tagName) - - // set attributes - if (isPlainObject(attrs)) { - Object.entries(attrs).forEach(([key, value]) => { - if (isString(value)) { - headElement.setAttribute(key, value) - } else if (value === true) { - headElement.setAttribute(key, '') - } - }) - } - - // set content - if (isString(content)) { - headElement.appendChild(document.createTextNode(content)) - } - - return headElement -} diff --git a/packages/client/src/types/clientData.ts b/packages/client/src/types/clientData.ts index d5af2c224f..5107ac3067 100644 --- a/packages/client/src/types/clientData.ts +++ b/packages/client/src/types/clientData.ts @@ -37,10 +37,11 @@ export type RouteLocale = string export type LayoutsRef = ComputedRef export type PageComponentRef = ComputedRef -export type PageDataRef = Record> = - ComputedRef> +export type PageDataRef< + T extends Record = Record, +> = ComputedRef> export type PageFrontmatterRef< - T extends Record = Record, + T extends Record = Record, > = ComputedRef> export type PageHeadRef = ComputedRef export type PageHeadTitleRef = ComputedRef diff --git a/packages/core/src/page/createPage.ts b/packages/core/src/page/createPage.ts index ab9b05deb6..c0b5e74d26 100644 --- a/packages/core/src/page/createPage.ts +++ b/packages/core/src/page/createPage.ts @@ -83,7 +83,7 @@ export const createPage = async ( // resolve page component and extract headers & links const { componentFilePath, componentFilePathRelative } = - await resolvePageComponentInfo({ + resolvePageComponentInfo({ app, htmlFilePathRelative, }) diff --git a/packages/core/src/page/resolvePageComponentInfo.ts b/packages/core/src/page/resolvePageComponentInfo.ts index d27aa1933c..b1ee312cc4 100644 --- a/packages/core/src/page/resolvePageComponentInfo.ts +++ b/packages/core/src/page/resolvePageComponentInfo.ts @@ -4,16 +4,16 @@ import type { App } from '../types/index.js' /** * Resolve page component and related info */ -export const resolvePageComponentInfo = async ({ +export const resolvePageComponentInfo = ({ app, htmlFilePathRelative, }: { app: App htmlFilePathRelative: string -}): Promise<{ +}): { componentFilePath: string componentFilePathRelative: string -}> => { +} => { // resolve component file path const componentFilePathRelative = path.join( 'pages', diff --git a/packages/core/src/page/resolvePageDate.ts b/packages/core/src/page/resolvePageDate.ts index ab92bcc01a..d371062f68 100644 --- a/packages/core/src/page/resolvePageDate.ts +++ b/packages/core/src/page/resolvePageDate.ts @@ -44,6 +44,7 @@ export const resolvePageDate = ({ const matches = filename.match(FILENAME_DATE_RE) if (matches) { return formatDateString( + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- unsafe indexed access `${matches[1]}-${matches[2]}-${matches[3] ?? '01'}`, DEFAULT_DATE, ) @@ -56,6 +57,7 @@ export const resolvePageDate = ({ const matches = dirname.match(DIRNAME_DATE_RE) if (matches) { return formatDateString( + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- unsafe indexed access `${matches[1]}-${matches[2]}-${matches[3] ?? '01'}`, DEFAULT_DATE, ) diff --git a/packages/core/src/pluginApi/createPluginApiRegisterHooks.ts b/packages/core/src/pluginApi/createPluginApiRegisterHooks.ts index 22ac07b49b..79ee344016 100644 --- a/packages/core/src/pluginApi/createPluginApiRegisterHooks.ts +++ b/packages/core/src/pluginApi/createPluginApiRegisterHooks.ts @@ -48,9 +48,10 @@ export const createPluginApiRegisterHooks = */ Object.keys(commonHooks).forEach((key) => { if (hooks[key] && commonHooks[key]) { - hooks[key].add({ + hooks[key as keyof typeof hooks].add({ pluginName, - hook: commonHooks[key], + // @ts-expect-error: the type could not be narrowed correctly + hook: commonHooks[key as keyof typeof commonHooks], }) } }) diff --git a/packages/core/src/pluginApi/normalizeAliasDefineHook.ts b/packages/core/src/pluginApi/normalizeAliasDefineHook.ts index 6801b04cb6..6c3da1a21b 100644 --- a/packages/core/src/pluginApi/normalizeAliasDefineHook.ts +++ b/packages/core/src/pluginApi/normalizeAliasDefineHook.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/require-await, @typescript-eslint/no-unsafe-return */ import { isFunction } from '@vuepress/shared' import type { AliasDefineHook } from '../types/index.js' diff --git a/packages/core/src/types/pluginApi/hooks.ts b/packages/core/src/types/pluginApi/hooks.ts index f2b2388f87..5d5271111a 100644 --- a/packages/core/src/types/pluginApi/hooks.ts +++ b/packages/core/src/types/pluginApi/hooks.ts @@ -12,7 +12,8 @@ interface Closable { export interface Hook< Exposed, Normalized = Exposed, - Result = Normalized extends (...args: unknown[]) => infer U + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- `any` type is required to infer the result type correctly + Result = Normalized extends (...args: any) => infer U ? U extends Promise ? V : U diff --git a/packages/core/tests/page/resolvePageComponentInfo.spec.ts b/packages/core/tests/page/resolvePageComponentInfo.spec.ts index a1b9c6d997..7feabdd495 100644 --- a/packages/core/tests/page/resolvePageComponentInfo.spec.ts +++ b/packages/core/tests/page/resolvePageComponentInfo.spec.ts @@ -9,8 +9,8 @@ const app = createBaseApp({ bundler: {} as Bundler, }) -it('should resolve page component info correctly', async () => { - const resolved = await resolvePageComponentInfo({ +it('should resolve page component info correctly', () => { + const resolved = resolvePageComponentInfo({ app, htmlFilePathRelative: 'foo.html', }) diff --git a/packages/shared/src/types/page.ts b/packages/shared/src/types/page.ts index 404d00d826..a219ea773b 100644 --- a/packages/shared/src/types/page.ts +++ b/packages/shared/src/types/page.ts @@ -5,7 +5,10 @@ import type { HeadConfig } from './head.js' * Base type of vuepress page */ export interface PageBase< - ExtraPageFrontmatter extends Record = Record, + ExtraPageFrontmatter extends Record = Record< + string, + unknown + >, > { /** * Route path of the page @@ -44,9 +47,12 @@ export interface PageBase< * Vuepress page data */ export type PageData< - ExtraPageData extends Record = Record, - ExtraPageFrontmatter extends Record = Record, -> = PageBase & ExtraPageData + ExtraPageData extends Record = Record, + ExtraPageFrontmatter extends Record = Record< + string, + unknown + >, +> = ExtraPageData & PageBase /** * Vuepress page frontmatter @@ -55,9 +61,9 @@ export type PageData< * so we cannot guarantee the type safety */ export type PageFrontmatter< - T extends Record = Record, + T extends Record = Record, > = Partial & { - date?: string | Date + date?: Date | string description?: string head?: HeadConfig[] lang?: string diff --git a/packages/shared/src/utils/ensureEndingSlash.ts b/packages/shared/src/utils/ensureEndingSlash.ts index 678f3eeefc..4b03ba6a58 100644 --- a/packages/shared/src/utils/ensureEndingSlash.ts +++ b/packages/shared/src/utils/ensureEndingSlash.ts @@ -2,4 +2,4 @@ * Ensure a url string to have ending slash / */ export const ensureEndingSlash = (str: string): string => - str[str.length - 1] === '/' || str.endsWith('.html') ? str : `${str}/` + str.endsWith('/') || str.endsWith('.html') ? str : `${str}/` diff --git a/packages/shared/src/utils/ensureLeadingSlash.ts b/packages/shared/src/utils/ensureLeadingSlash.ts index 1d574a86b4..700ccd1f22 100644 --- a/packages/shared/src/utils/ensureLeadingSlash.ts +++ b/packages/shared/src/utils/ensureLeadingSlash.ts @@ -2,4 +2,4 @@ * Ensure a url string to have leading slash / */ export const ensureLeadingSlash = (str: string): string => - str[0] === '/' ? str : `/${str}` + str.startsWith('/') ? str : `/${str}` diff --git a/packages/shared/src/utils/removeEndingSlash.ts b/packages/shared/src/utils/removeEndingSlash.ts index 38c12f8fe4..a23a1dacbc 100644 --- a/packages/shared/src/utils/removeEndingSlash.ts +++ b/packages/shared/src/utils/removeEndingSlash.ts @@ -2,4 +2,4 @@ * Remove ending slash / from a string */ export const removeEndingSlash = (str: string): string => - str[str.length - 1] === '/' ? str.slice(0, -1) : str + str.endsWith('/') ? str.slice(0, -1) : str diff --git a/packages/shared/src/utils/removeLeadingSlash.ts b/packages/shared/src/utils/removeLeadingSlash.ts index 227c1dff5f..b4da3234a8 100644 --- a/packages/shared/src/utils/removeLeadingSlash.ts +++ b/packages/shared/src/utils/removeLeadingSlash.ts @@ -2,4 +2,4 @@ * Remove leading slash / from a string */ export const removeLeadingSlash = (str: string): string => - str[0] === '/' ? str.slice(1) : str + str.startsWith('/') ? str.slice(1) : str diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aa445a21ea..e4dc5e8d88 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -156,6 +156,10 @@ importers: vue-router: specifier: ^4.4.3 version: 4.4.3(vue@3.4.38(typescript@5.5.4)) + devDependencies: + '@types/connect-history-api-fallback': + specifier: ^1.5.4 + version: 1.5.4 packages/bundler-webpack: dependencies: @@ -993,8 +997,8 @@ packages: '@mdit-vue/types@2.1.0': resolution: {integrity: sha512-TMBB/BQWVvwtpBdWD75rkZx4ZphQ6MN0O4QB2Bc0oI5PC2uE57QerhNxdRZ7cvBHE2iY2C+BUNUziCfJbjIRRA==} - '@meteorlxy/eslint-config@4.2.1': - resolution: {integrity: sha512-uiqEsyMVlbtIJj8oDHs+J51kKqPLijp46PWzkeRjcqHo/4ERtuTKYVvWtjRkmYkfE9XdL5EJvTE+NQsnSormrQ==} + '@meteorlxy/eslint-config@4.2.2': + resolution: {integrity: sha512-kVEigfYiptY3feIfyIr8JIYubLPkqeL3W+lqUvgyE6lk8ab2oZUW0mUgNQW6wDcMbdzL0KSWiTjnn+D79JZNSw==} peerDependencies: eslint-plugin-react: ^7.35.0 eslint-plugin-react-hooks: ^4.6.2 @@ -5748,7 +5752,7 @@ snapshots: '@mdit-vue/types@2.1.0': {} - '@meteorlxy/eslint-config@4.2.1(eslint-plugin-vue@9.27.0(eslint@9.9.0(jiti@1.21.6)))(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4)(vue-eslint-parser@9.4.3(eslint@9.9.0(jiti@1.21.6)))': + '@meteorlxy/eslint-config@4.2.2(eslint-plugin-vue@9.27.0(eslint@9.9.0(jiti@1.21.6)))(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4)(vue-eslint-parser@9.4.3(eslint@9.9.0(jiti@1.21.6)))': dependencies: '@typescript-eslint/eslint-plugin': 8.1.0(@typescript-eslint/parser@8.1.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4))(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4) '@typescript-eslint/parser': 8.1.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4) @@ -7305,7 +7309,7 @@ snapshots: eslint-config-vuepress@5.1.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4)(vue-eslint-parser@9.4.3(eslint@9.9.0(jiti@1.21.6))): dependencies: - '@meteorlxy/eslint-config': 4.2.1(eslint-plugin-vue@9.27.0(eslint@9.9.0(jiti@1.21.6)))(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4)(vue-eslint-parser@9.4.3(eslint@9.9.0(jiti@1.21.6))) + '@meteorlxy/eslint-config': 4.2.2(eslint-plugin-vue@9.27.0(eslint@9.9.0(jiti@1.21.6)))(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4)(vue-eslint-parser@9.4.3(eslint@9.9.0(jiti@1.21.6))) '@typescript-eslint/utils': 8.1.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4) eslint-plugin-vue: 9.27.0(eslint@9.9.0(jiti@1.21.6)) transitivePeerDependencies: