diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 744240c870..0000000000 --- a/.eslintignore +++ /dev/null @@ -1,7 +0,0 @@ -!.vuepress/ -!.*.js -.cache/ -.temp/ -node_modules/ -lib/ -dist/ diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index 7f763acda8..0000000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,26 +0,0 @@ -module.exports = { - root: true, - extends: 'vuepress', - overrides: [ - { - files: ['*.ts', '*.vue', '*.cts'], - extends: 'vuepress-typescript', - parserOptions: { - project: ['tsconfig.json'], - }, - rules: { - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', - 'vue/multi-word-component-names': 'off', - }, - }, - { - files: ['**/tests/**/*.ts', 'e2e/**/*.ts', 'tsup.config.ts'], - rules: { - '@typescript-eslint/explicit-function-return-type': 'off', - 'import/no-extraneous-dependencies': 'off', - 'vue/one-component-per-file': 'off', - }, - }, - ], -} diff --git a/commitlint.config.ts b/commitlint.config.ts index 6ee3b18b7a..859187f3de 100644 --- a/commitlint.config.ts +++ b/commitlint.config.ts @@ -6,12 +6,13 @@ const getSubDirectories = (dir: string): string[] => fs .readdirSync(dir) .filter((item) => fs.statSync(path.join(dir, item)).isDirectory()) -const packages = getSubDirectories(path.resolve(__dirname, 'packages')) + +const PACKAGES = getSubDirectories(path.resolve(__dirname, 'packages')) export default { extends: ['@commitlint/config-conventional'], rules: { - 'scope-enum': [2, 'always', [...packages, 'e2e']], + 'scope-enum': [2, 'always', [...PACKAGES, 'e2e']], 'footer-max-line-length': [0], }, } satisfies UserConfig diff --git a/e2e/docs/.vuepress/plugins/foo/fooPlugin.ts b/e2e/docs/.vuepress/plugins/foo/fooPlugin.ts index 93ef0c8cff..ac2c07663f 100644 --- a/e2e/docs/.vuepress/plugins/foo/fooPlugin.ts +++ b/e2e/docs/.vuepress/plugins/foo/fooPlugin.ts @@ -1,11 +1,8 @@ import { getDirname, path } from 'vuepress/utils' -const __dirname = getDirname(import.meta.url) +const DIRNAME = getDirname(import.meta.url) export const fooPlugin = { name: 'test-plugin', - clientConfigFile: path.resolve( - __dirname, - './nonDefaultExportClientConfig.js', - ), + clientConfigFile: path.resolve(DIRNAME, './nonDefaultExportClientConfig.js'), } diff --git a/e2e/docs/.vuepress/theme/client/config.ts b/e2e/docs/.vuepress/theme/client/config.ts index 8d35748afe..c263a6f6e7 100644 --- a/e2e/docs/.vuepress/theme/client/config.ts +++ b/e2e/docs/.vuepress/theme/client/config.ts @@ -8,7 +8,7 @@ import NotFound from './layouts/NotFound.vue' import './styles/index.scss' export default defineClientConfig({ - enhance({ app, router }) { + enhance() { // ... }, @@ -16,12 +16,14 @@ export default defineClientConfig({ // ... }, + /* eslint-disable @typescript-eslint/no-unsafe-assignment -- vue sfc type info is not available in eslint scope */ layouts: { CssModulesLayout, CustomLayout, Layout, NotFound, }, + /* eslint-enable @typescript-eslint/no-unsafe-assignment */ rootComponents: [RootComponentFromTheme], }) diff --git a/e2e/docs/.vuepress/theme/client/layouts/Layout.vue b/e2e/docs/.vuepress/theme/client/layouts/Layout.vue index 4c4d2d83d2..07519db48d 100644 --- a/e2e/docs/.vuepress/theme/client/layouts/Layout.vue +++ b/e2e/docs/.vuepress/theme/client/layouts/Layout.vue @@ -21,4 +21,6 @@ const siteData = useSiteData() - + diff --git a/e2e/docs/.vuepress/theme/node/e2eTheme.ts b/e2e/docs/.vuepress/theme/node/e2eTheme.ts index 7c2a5f2e6c..7d2f5d1288 100644 --- a/e2e/docs/.vuepress/theme/node/e2eTheme.ts +++ b/e2e/docs/.vuepress/theme/node/e2eTheme.ts @@ -1,26 +1,24 @@ -import type { Page, Theme } from 'vuepress/core' +import type { Theme } from 'vuepress/core' import { getDirname, path } from 'vuepress/utils' -const __dirname = getDirname(import.meta.url) +const DIRNAME = getDirname(import.meta.url) -export const e2eTheme = (): Theme => { - return { - name: '@vuepress/theme-e2e', +export const e2eTheme = (): Theme => ({ + name: '@vuepress/theme-e2e', - alias: { - // ... - }, + alias: { + // ... + }, - define: { - // ... - }, + define: { + // ... + }, - clientConfigFile: path.resolve(__dirname, '../client/config.ts'), + clientConfigFile: path.resolve(DIRNAME, '../client/config.ts'), - extendsPage: (page: Page) => { - // ... - }, + extendsPage: () => { + // ... + }, - plugins: [], - } -} + plugins: [], +}) diff --git a/e2e/modules/conditional-exports/types.d.ts b/e2e/modules/conditional-exports/types.d.ts index 657d2a74ff..a1d1d5f6c2 100644 --- a/e2e/modules/conditional-exports/types.d.ts +++ b/e2e/modules/conditional-exports/types.d.ts @@ -1,2 +1,2 @@ -declare const str: string -export default str +declare const STR: string +export default STR diff --git a/e2e/playwright.config.ts b/e2e/playwright.config.ts index 13ff0ad7f3..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 commandPart1 = isDev ? 'docs:dev' : 'docs:build' -const commandPart2 = BUNDLER === 'vite' ? '' : `-${BUNDLER}` -const commandPart3 = isDev ? '' : ' && pnpm docs:serve' +const COMMAND_PART1 = IS_DEV ? 'docs:dev' : 'docs:build' +const COMMAND_PART2 = BUNDLER === 'vite' ? '' : `-${BUNDLER}` +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', @@ -22,8 +22,8 @@ export default defineConfig({ trace: 'on-first-retry', }, webServer: { - command: `pnpm docs:clean && pnpm ${commandPart1}${commandPart2}${commandPart3}`, + 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/tests/router/resolve-route-query-hash.spec.ts b/e2e/tests/router/resolve-route-query-hash.spec.ts index d7554c6425..1afa86d501 100644 --- a/e2e/tests/router/resolve-route-query-hash.spec.ts +++ b/e2e/tests/router/resolve-route-query-hash.spec.ts @@ -1,6 +1,6 @@ import { expect, test } from '@playwright/test' -const testCases = [ +const TEST_CASES = [ { path: '/?query=1', notFound: false, @@ -28,8 +28,11 @@ test('should resolve routes when including both the query and hash', async ({ for (const [index, li] of listItemsLocator.entries()) { const textContent = await li.textContent() - const resolvedRoute = JSON.parse(/: (\{.*\})\s*$/.exec(textContent!)![1]) - expect(resolvedRoute.path).toEqual(testCases[index].path) - expect(resolvedRoute.notFound).toEqual(testCases[index].notFound) + const resolvedRoute = JSON.parse( + /: (\{.*\})\s*$/.exec(textContent!)![1], + ) as { path: string; notFound: boolean } + + expect(resolvedRoute.path).toEqual(TEST_CASES[index].path) + expect(resolvedRoute.notFound).toEqual(TEST_CASES[index].notFound) } }) diff --git a/e2e/tests/router/resolve-route.spec.ts b/e2e/tests/router/resolve-route.spec.ts index 10cf9799c1..bad4350843 100644 --- a/e2e/tests/router/resolve-route.spec.ts +++ b/e2e/tests/router/resolve-route.spec.ts @@ -1,6 +1,6 @@ import { expect, test } from '@playwright/test' -const testCases = [ +const TEST_CASES = [ { selector: '#index', expected: { @@ -54,13 +54,19 @@ const testCases = [ test('should resolve routes correctly', async ({ page }) => { await page.goto('router/resolve-route.html') - for (const { selector, expected } of testCases) { + for (const { selector, expected } of TEST_CASES) { const listItemsLocator = await page .locator(`.e2e-theme-content ${selector} + ul > li`) .all() for (const li of listItemsLocator) { const textContent = await li.textContent() - const resolvedRoute = JSON.parse(/: (\{.*\})\s*$/.exec(textContent!)![1]) + const resolvedRoute = JSON.parse( + /: (\{.*\})\s*$/.exec(textContent!)![1], + ) as { + path: string + meta: Record + notFound: boolean + } expect(resolvedRoute.path).toEqual(expected.path) expect(resolvedRoute.meta).toStrictEqual(expected.meta) expect(resolvedRoute.notFound).toEqual(expected.notFound) 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/e2e/utils/source.ts b/e2e/utils/source.ts index 1db8f57011..6f07073446 100644 --- a/e2e/utils/source.ts +++ b/e2e/utils/source.ts @@ -1,17 +1,14 @@ import { fs, getDirname, path } from 'vuepress/utils' -const __dirname = getDirname(import.meta.url) +const DIRNAME = getDirname(import.meta.url) const resolveSourceMarkdownPath = (...args: string[]): string => - path.resolve(__dirname, '../docs', ...args) + path.resolve(DIRNAME, '../docs', ...args) -export const readSourceMarkdown = async (filePath: string): Promise => { - return fs.readFile(resolveSourceMarkdownPath(filePath), 'utf-8') -} +export const readSourceMarkdown = async (filePath: string): Promise => + fs.readFile(resolveSourceMarkdownPath(filePath), 'utf-8') export const writeSourceMarkdown = async ( filePath: string, content: string, -): Promise => { - return fs.writeFile(resolveSourceMarkdownPath(filePath), content) -} +): Promise => fs.writeFile(resolveSourceMarkdownPath(filePath), content) diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000000..955989f874 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,41 @@ +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { vuepress } from 'eslint-config-vuepress' + +const ROOT = path.resolve(fileURLToPath(import.meta.url), '..') +const E2E_DIR = path.resolve(ROOT, 'e2e') +const PACKAGES_DIRS = fs + .readdirSync(path.resolve(ROOT, 'packages')) + .map((item) => `./packages/${item}`) + +export default vuepress( + { + imports: { + packageDir: [ROOT, E2E_DIR, ...PACKAGES_DIRS], + }, + typescript: { + overrides: { + '@typescript-eslint/no-dynamic-delete': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + 'no-underscore-dangle': [ + 'warn', + { allow: ['_context', '_pageChunk', '_registeredComponents'] }, + ], + }, + }, + vue: { + overrides: { + 'no-useless-assignment': 'off', // TODO: false positive in vue sfc + }, + }, + }, + { + files: ['**/tests/**'], + rules: { + 'import/no-unresolved': 'off', + 'no-console': 'off', + 'prefer-template': 'off', + }, + }, +) diff --git a/package.json b/package.json index c5fd77e1f4..81672f1251 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,8 @@ "check-types": "vue-tsc --noEmit", "clean": "pnpm --parallel --stream clean", "format": "prettier --write .", - "lint": "eslint --ext .cjs,.js,.ts,.vue . && prettier --check .", - "lint:fix": "eslint --fix --ext .cjs,.js,.ts,.vue . && prettier --write .", + "lint": "eslint . && prettier --check .", + "lint:fix": "eslint --fix . && prettier --write .", "prepare": "husky", "release": "pnpm release:check && pnpm release:version && pnpm release:publish", "release:changelog": "conventional-changelog -p angular -i CHANGELOG.md -s", @@ -39,16 +39,15 @@ "@vitest/coverage-istanbul": "^2.0.5", "bumpp": "^9.5.1", "conventional-changelog-cli": "^5.0.0", - "eslint": "^8.57.0", - "eslint-config-vuepress": "^4.10.1", - "eslint-config-vuepress-typescript": "^4.10.1", + "eslint": "^9.9.0", + "eslint-config-vuepress": "^5.1.2", "husky": "^9.1.4", "lint-staged": "^15.2.9", "prettier": "^3.3.3", - "prettier-config-vuepress": "^4.4.0", + "prettier-config-vuepress": "^5.0.0", "rimraf": "^6.0.1", "sort-package-json": "^2.10.0", - "tsconfig-vuepress": "^4.5.0", + "tsconfig-vuepress": "^5.0.0", "tsup": "^8.2.4", "typescript": "^5.5.4", "vite": "~5.4.0", 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/build.ts b/packages/bundler-vite/src/build/build.ts index 4b90aa5137..33466ea6b0 100644 --- a/packages/bundler-vite/src/build/build.ts +++ b/packages/bundler-vite/src/build/build.ts @@ -22,13 +22,13 @@ export const build = async ( let serverOutput!: RollupOutput await withSpinner('Compiling with vite')(async () => { // create vite config - const clientConfig = await resolveViteConfig({ + const clientConfig = resolveViteConfig({ app, options, isBuild: true, isServer: false, }) - const serverConfig = await resolveViteConfig({ + const serverConfig = resolveViteConfig({ app, options, isBuild: true, diff --git a/packages/bundler-vite/src/build/renderPage.ts b/packages/bundler-vite/src/build/renderPage.ts index 6e8af6ed88..a0af5b2374 100644 --- a/packages/bundler-vite/src/build/renderPage.ts +++ b/packages/bundler-vite/src/build/renderPage.ts @@ -1,8 +1,9 @@ import type { App, Page } from '@vuepress/core' +import type { VuepressSSRContext } from '@vuepress/shared' import { fs, renderHead } from '@vuepress/utils' import type { OutputAsset, OutputChunk, RollupOutput } from 'rollup' -import { ssrContextKey } from 'vue' import type { App as VueApp } from 'vue' +import { ssrContextKey } from 'vue' import type { SSRContext } from 'vue/server-renderer' import type { Router } from 'vue-router' import { renderPagePrefetchLinks } from './renderPagePrefetchLinks.js' @@ -38,7 +39,7 @@ export const renderPage = async ({ // create vue ssr context with default values delete vueApp._context.provides[ssrContextKey] - const ssrContext: SSRContext = { + const ssrContext: VuepressSSRContext = { lang: 'en', head: [], } diff --git a/packages/bundler-vite/src/build/renderPagePrefetchLinks.ts b/packages/bundler-vite/src/build/renderPagePrefetchLinks.ts index a0bd189560..e4a81b24df 100644 --- a/packages/bundler-vite/src/build/renderPagePrefetchLinks.ts +++ b/packages/bundler-vite/src/build/renderPagePrefetchLinks.ts @@ -14,7 +14,7 @@ export const renderPagePrefetchLinks = ({ pageChunkFiles: string[] }): 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-vite/src/build/renderPagePreloadLinks.ts b/packages/bundler-vite/src/build/renderPagePreloadLinks.ts index 8e198ec445..3f035d699e 100644 --- a/packages/bundler-vite/src/build/renderPagePreloadLinks.ts +++ b/packages/bundler-vite/src/build/renderPagePreloadLinks.ts @@ -14,7 +14,7 @@ export const renderPagePreloadLinks = ({ pageChunkFiles: string[] }): 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-vite/src/dev.ts b/packages/bundler-vite/src/dev.ts index da2c69bb4f..c389d08131 100644 --- a/packages/bundler-vite/src/dev.ts +++ b/packages/bundler-vite/src/dev.ts @@ -14,7 +14,7 @@ export const dev = async ( // plugin hook: extendsBundlerOptions await app.pluginApi.hooks.extendsBundlerOptions.process(options, app) - const viteConfig = await resolveViteConfig({ + const viteConfig = resolveViteConfig({ app, options, isBuild: false, @@ -24,8 +24,8 @@ export const dev = async ( const server = await createServer(viteConfig) await server.listen() - const viteVersion = fs.readJsonSync( - require.resolve('vite/package.json'), + const viteVersion = ( + fs.readJsonSync(require.resolve('vite/package.json')) as { version: string } ).version server.config.logger.info( colors.cyan(`\n vite v${viteVersion}`) + diff --git a/packages/bundler-vite/src/plugins/vuepressMainPlugin.ts b/packages/bundler-vite/src/plugins/vuepressMainPlugin.ts index 05a53d3b0d..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 */ @@ -56,7 +142,7 @@ import 'vuepress/client-app' try { const postcssConfigResult = await postcssrc() postcssPlugins = postcssConfigResult.plugins - } catch (error) { + } catch { postcssPlugins = [autoprefixer] } @@ -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/resolveViteConfig.ts b/packages/bundler-vite/src/resolveViteConfig.ts index ee18480c1e..2c381386c7 100644 --- a/packages/bundler-vite/src/resolveViteConfig.ts +++ b/packages/bundler-vite/src/resolveViteConfig.ts @@ -8,7 +8,7 @@ import { } from './plugins/index.js' import type { ViteBundlerOptions } from './types.js' -export const resolveViteConfig = async ({ +export const resolveViteConfig = ({ app, options, isBuild, @@ -18,8 +18,8 @@ export const resolveViteConfig = async ({ options: ViteBundlerOptions isBuild: boolean isServer: boolean -}): Promise => { - return mergeConfig( +}): InlineConfig => + mergeConfig( { clearScreen: false, configFile: false, @@ -36,4 +36,3 @@ export const resolveViteConfig = async ({ // some vite options would not take effect inside a plugin, so we still need to merge them here in addition to userConfigPlugin options.viteOptions ?? {}, ) -} diff --git a/packages/bundler-vite/src/types.ts b/packages/bundler-vite/src/types.ts index c6f968edf1..dde557c177 100644 --- a/packages/bundler-vite/src/types.ts +++ b/packages/bundler-vite/src/types.ts @@ -1,10 +1,11 @@ import type { Options as VuePluginOptions } from '@vitejs/plugin-vue' +import type { BundlerOptions } from '@vuepress/core' import type { InlineConfig } from 'vite' /** * Options for bundler-vite */ -export interface ViteBundlerOptions { +export interface ViteBundlerOptions extends BundlerOptions { viteOptions?: InlineConfig vuePluginOptions?: VuePluginOptions } 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/build.ts b/packages/bundler-webpack/src/build/build.ts index c6ead7feab..72ddb08331 100644 --- a/packages/bundler-webpack/src/build/build.ts +++ b/packages/bundler-webpack/src/build/build.ts @@ -5,13 +5,14 @@ import { debug, fs, importFileDefault, + logger, withSpinner, } from '@vuepress/utils' import webpack from 'webpack' import { resolveWebpackConfig } from '../resolveWebpackConfig.js' import type { WebpackBundlerOptions } from '../types.js' import { - clientManifestFilename, + CLIENT_MANIFEST_FILENAME, createClientConfig, } from './createClientConfig.js' import { createServerConfig } from './createServerConfig.js' @@ -50,14 +51,14 @@ export const build = async ( if (err) { reject(err) } else if (stats?.hasErrors()) { - stats.toJson().errors?.forEach((err) => { - console.error(err) + stats.toJson().errors?.forEach((item) => { + logger.error(item) }) reject(new Error('Failed to compile with errors')) } else { if (stats?.hasWarnings()) { stats.toJson().warnings?.forEach((warning) => { - console.warn(warning) + logger.warn(warning) }) } resolve() @@ -70,8 +71,10 @@ export const build = async ( // render pages await withSpinner(`Rendering ${app.pages.length} pages`)(async (spinner) => { // load the client manifest file - const clientManifestPath = app.dir.temp(clientManifestFilename) - const clientManifest: ClientManifest = await fs.readJson(clientManifestPath) + const clientManifestPath = app.dir.temp(CLIENT_MANIFEST_FILENAME) + const clientManifest = (await fs.readJson( + clientManifestPath, + )) as ClientManifest // resolve client files meta const { initialFilesMeta, asyncFilesMeta, moduleFilesMetaMap } = diff --git a/packages/bundler-webpack/src/build/createClientConfig.ts b/packages/bundler-webpack/src/build/createClientConfig.ts index b64ea26770..e61f048c77 100644 --- a/packages/bundler-webpack/src/build/createClientConfig.ts +++ b/packages/bundler-webpack/src/build/createClientConfig.ts @@ -15,7 +15,7 @@ const require = createRequire(import.meta.url) /** * Filename of the client manifest file that generated by client plugin */ -export const clientManifestFilename = '.server/client-manifest.json' +export const CLIENT_MANIFEST_FILENAME = '.server/client-manifest.json' export const createClientConfig = async ( app: App, @@ -39,7 +39,7 @@ export const createClientConfig = async ( // vuepress client plugin, handle client assets info for ssr config .plugin('vuepress-client') - .use(createClientPlugin(app.dir.temp(clientManifestFilename))) + .use(createClientPlugin(app.dir.temp(CLIENT_MANIFEST_FILENAME))) // copy files from public dir to dest dir if (fs.pathExistsSync(app.dir.public())) { @@ -66,7 +66,7 @@ export const createClientConfig = async ( styles: { idHint: 'styles', // necessary to ensure async chunks are also extracted - test: (m: CssModule) => /css\/mini-extract/.test(m.type), + test: (m: CssModule) => m.type.includes('css/mini-extract'), chunks: 'all', enforce: true, reuseExistingChunk: true, diff --git a/packages/bundler-webpack/src/build/renderPage.ts b/packages/bundler-webpack/src/build/renderPage.ts index ac7deb7378..009841dffb 100644 --- a/packages/bundler-webpack/src/build/renderPage.ts +++ b/packages/bundler-webpack/src/build/renderPage.ts @@ -1,8 +1,8 @@ import type { App, Page } from '@vuepress/core' import type { VuepressSSRContext } from '@vuepress/shared' import { fs, renderHead } from '@vuepress/utils' -import { ssrContextKey } from 'vue' import type { App as VueApp } from 'vue' +import { ssrContextKey } from 'vue' import type { SSRContext } from 'vue/server-renderer' import type { Router } from 'vue-router' import { renderPagePrefetchLinks } from './renderPagePrefetchLinks.js' 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/bundler-webpack/src/build/types.ts b/packages/bundler-webpack/src/build/types.ts index 5cf926c25c..112392f4cf 100644 --- a/packages/bundler-webpack/src/build/types.ts +++ b/packages/bundler-webpack/src/build/types.ts @@ -21,7 +21,7 @@ export interface FileMeta { /** * Client file meta type, mainly used for */ -export type FileMetaType = 'script' | 'style' | 'image' | 'font' | '' +export type FileMetaType = '' | 'font' | 'image' | 'script' | 'style' /** * A "module request" to "client files meta" key-value map diff --git a/packages/bundler-webpack/src/config/createBaseConfig.ts b/packages/bundler-webpack/src/config/createBaseConfig.ts index b6d8bec3b0..0b6e0accc3 100644 --- a/packages/bundler-webpack/src/config/createBaseConfig.ts +++ b/packages/bundler-webpack/src/config/createBaseConfig.ts @@ -52,7 +52,7 @@ export const createBaseConfig = async ({ /** * module */ - handleModule({ app, options, config, isBuild, isServer }) + handleModule({ options, config, isBuild, isServer }) /** * plugins diff --git a/packages/bundler-webpack/src/config/handleEntry.ts b/packages/bundler-webpack/src/config/handleEntry.ts index 1c2c592b7d..0600895275 100644 --- a/packages/bundler-webpack/src/config/handleEntry.ts +++ b/packages/bundler-webpack/src/config/handleEntry.ts @@ -13,11 +13,13 @@ export const handleEntry = ({ config: Config }): void => { // set client app as entry point - config - .entry('app') - .add( - app.dir.client( - fs.readJsonSync(app.dir.client('package.json')).exports['./app'], - ), - ) + config.entry('app').add( + app.dir.client( + ( + fs.readJsonSync(app.dir.client('package.json')) as { + exports: { './app': string } + } + ).exports['./app'], + ), + ) } diff --git a/packages/bundler-webpack/src/config/handleModule.ts b/packages/bundler-webpack/src/config/handleModule.ts index fe857c7f11..be33ca6a73 100644 --- a/packages/bundler-webpack/src/config/handleModule.ts +++ b/packages/bundler-webpack/src/config/handleModule.ts @@ -1,4 +1,3 @@ -import type { App } from '@vuepress/core' import type Config from 'webpack-5-chain' import type { WebpackBundlerOptions } from '../types.js' import { handleModuleAssets } from './handleModuleAssets.js' @@ -12,13 +11,11 @@ import { handleModuleVue } from './handleModuleVue.js' * Set webpack module */ export const handleModule = ({ - app, options, config, isBuild, isServer, }: { - app: App options: WebpackBundlerOptions config: Config isBuild: boolean @@ -30,19 +27,19 @@ export const handleModule = ({ ) // vue files - handleModuleVue({ app, options, config, isServer }) + handleModuleVue({ options, config, isServer }) // pug files, for templates handleModulePug({ config }) // images & media & fonts - handleModuleAssets({ app, config }) + handleModuleAssets({ config }) // js files handleModuleJs({ options, config, isBuild, isServer }) // ts files - handleModuleTs({ app, config }) + handleModuleTs({ config }) // styles files handleModuleStyles({ options, config, isBuild, isServer }) diff --git a/packages/bundler-webpack/src/config/handleModuleAssets.ts b/packages/bundler-webpack/src/config/handleModuleAssets.ts index 0ccd291938..03f66337ae 100644 --- a/packages/bundler-webpack/src/config/handleModuleAssets.ts +++ b/packages/bundler-webpack/src/config/handleModuleAssets.ts @@ -1,16 +1,9 @@ -import type { App } from '@vuepress/core' import type Config from 'webpack-5-chain' /** * Set webpack config to handle assets files */ -export const handleModuleAssets = ({ - app, - config, -}: { - app: App - config: Config -}): void => { +export const handleModuleAssets = ({ config }: { config: Config }): void => { // images config.module .rule('images') diff --git a/packages/bundler-webpack/src/config/handleModuleJs.ts b/packages/bundler-webpack/src/config/handleModuleJs.ts index d0e3efb386..89a51d698d 100644 --- a/packages/bundler-webpack/src/config/handleModuleJs.ts +++ b/packages/bundler-webpack/src/config/handleModuleJs.ts @@ -45,7 +45,7 @@ export const handleModuleJs = ({ return false } // don't transpile node_modules - return /node_modules/.test(filePath) + return filePath.includes('node_modules') }) .end() // use esbuild-loader diff --git a/packages/bundler-webpack/src/config/handleModuleStyles.ts b/packages/bundler-webpack/src/config/handleModuleStyles.ts index 45b5bede25..6a99b02206 100644 --- a/packages/bundler-webpack/src/config/handleModuleStyles.ts +++ b/packages/bundler-webpack/src/config/handleModuleStyles.ts @@ -3,10 +3,7 @@ import autoprefixer from 'autoprefixer' import MiniCssExtractPlugin from 'mini-css-extract-plugin' import type Config from 'webpack-5-chain' import type { - LessLoaderOptions, - SassLoaderOptions, StylePreprocessorLoaderOptions, - StylusLoaderOptions, WebpackBundlerOptions, } from '../types.js' @@ -26,9 +23,7 @@ export const handleModuleStyles = ({ isBuild: boolean isServer: boolean }): void => { - const handleStyle = < - T extends StylePreprocessorLoaderOptions = StylePreprocessorLoaderOptions, - >({ + const handleStyle = ({ lang, test, loaderName, @@ -37,7 +32,7 @@ export const handleModuleStyles = ({ lang: string test: RegExp loaderName?: string - loaderOptions?: T + loaderOptions?: StylePreprocessorLoaderOptions }): void => { const rule = config.module.rule(lang).test(test) @@ -94,28 +89,28 @@ export const handleModuleStyles = ({ test: /\.p(ost)?css$/, }) - handleStyle({ + handleStyle({ lang: 'scss', test: /\.scss$/, loaderName: 'sass-loader', loaderOptions: options.scss, }) - handleStyle({ + handleStyle({ lang: 'sass', test: /\.sass$/, loaderName: 'sass-loader', loaderOptions: options.sass, }) - handleStyle({ + handleStyle({ lang: 'less', test: /\.less$/, loaderName: 'less-loader', loaderOptions: options.less, }) - handleStyle({ + handleStyle({ lang: 'stylus', test: /\.styl(us)?$/, loaderName: 'stylus-loader', diff --git a/packages/bundler-webpack/src/config/handleModuleTs.ts b/packages/bundler-webpack/src/config/handleModuleTs.ts index 74e045a372..db254558d5 100644 --- a/packages/bundler-webpack/src/config/handleModuleTs.ts +++ b/packages/bundler-webpack/src/config/handleModuleTs.ts @@ -1,5 +1,4 @@ import { createRequire } from 'node:module' -import type { App } from '@vuepress/core' import type Config from 'webpack-5-chain' import { resolveEsbuildJsxOptions } from './resolveEsbuildJsxOptions.js' @@ -8,13 +7,7 @@ const require = createRequire(import.meta.url) /** * Set webpack module to handle ts files */ -export const handleModuleTs = ({ - app, - config, -}: { - app: App - config: Config -}): void => { +export const handleModuleTs = ({ config }: { config: Config }): void => { config.module .rule('ts') .test(/\.tsx?/) diff --git a/packages/bundler-webpack/src/config/handleModuleVue.ts b/packages/bundler-webpack/src/config/handleModuleVue.ts index ecfdbc7069..335159cc1b 100644 --- a/packages/bundler-webpack/src/config/handleModuleVue.ts +++ b/packages/bundler-webpack/src/config/handleModuleVue.ts @@ -1,7 +1,6 @@ import { createRequire } from 'node:module' -import type { App } from '@vuepress/core' -import { VueLoaderPlugin } from 'vue-loader' import type { VueLoaderOptions } from 'vue-loader' +import { VueLoaderPlugin } from 'vue-loader' import type Config from 'webpack-5-chain' import type { WebpackBundlerOptions } from '../types.js' @@ -11,12 +10,10 @@ const require = createRequire(import.meta.url) * Set webpack module to handle vue files */ export const handleModuleVue = ({ - app, options, config, isServer, }: { - app: App options: WebpackBundlerOptions config: Config isServer: boolean diff --git a/packages/bundler-webpack/src/config/handleOtherOptions.ts b/packages/bundler-webpack/src/config/handleOtherOptions.ts index ef8e1d8816..57c9b67392 100644 --- a/packages/bundler-webpack/src/config/handleOtherOptions.ts +++ b/packages/bundler-webpack/src/config/handleOtherOptions.ts @@ -35,9 +35,13 @@ export const handleOtherOptions = ({ isServer, 'version': app.version, // dependencies - 'esbuild-loader': require('esbuild-loader/package.json').version, - 'vue-loader': require('vue-loader/package.json').version, - 'webpack': require('webpack/package.json').version, + 'esbuild-loader': ( + require('esbuild-loader/package.json') as { version: string } + ).version, + 'vue-loader': (require('vue-loader/package.json') as { version: string }) + .version, + 'webpack': (require('webpack/package.json') as { version: string }) + .version, }), }) } diff --git a/packages/bundler-webpack/src/config/handlePluginDefine.ts b/packages/bundler-webpack/src/config/handlePluginDefine.ts index d118844d48..8fdf06b1ac 100644 --- a/packages/bundler-webpack/src/config/handlePluginDefine.ts +++ b/packages/bundler-webpack/src/config/handlePluginDefine.ts @@ -36,12 +36,14 @@ export const handlePluginDefine = async ({ // tap the arguments of DefinePlugin config.plugin('define').tap(([options]) => { - defineResult.forEach((defineObject) => + defineResult.forEach((defineObject) => { Object.entries(defineObject).forEach(([key, value]) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access options[key] = JSON.stringify(value) - }), - ) + }) + }) + // eslint-disable-next-line @typescript-eslint/no-unsafe-return return [options] }) } diff --git a/packages/bundler-webpack/src/config/handleResolve.ts b/packages/bundler-webpack/src/config/handleResolve.ts index 6081614be2..b20704b52a 100644 --- a/packages/bundler-webpack/src/config/handleResolve.ts +++ b/packages/bundler-webpack/src/config/handleResolve.ts @@ -44,9 +44,9 @@ export const handleResolve = async ({ const aliasResult = await app.pluginApi.hooks.alias.process(app, isServer) // set aliases - aliasResult.forEach((aliasObject) => + aliasResult.forEach((aliasObject) => { Object.entries(aliasObject).forEach(([key, value]) => { - config.resolve.alias.set(key, value) - }), - ) + config.resolve.alias.set(key, value as string) + }) + }) } diff --git a/packages/bundler-webpack/src/dev/createDevServerConfig.ts b/packages/bundler-webpack/src/dev/createDevServerConfig.ts index de6ce8c7c1..742e7ae82c 100644 --- a/packages/bundler-webpack/src/dev/createDevServerConfig.ts +++ b/packages/bundler-webpack/src/dev/createDevServerConfig.ts @@ -5,10 +5,10 @@ import type WebpackDevServer from 'webpack-dev-server' import type { WebpackBundlerOptions } from '../types.js' import { trailingSlashMiddleware } from './trailingSlashMiddleware.js' -export const createDevServerConfig = async ( +export const createDevServerConfig = ( app: App, options: WebpackBundlerOptions, -): Promise => ({ +): WebpackDevServer.Configuration => ({ allowedHosts: 'all', compress: true, devMiddleware: { diff --git a/packages/bundler-webpack/src/dev/dev.ts b/packages/bundler-webpack/src/dev/dev.ts index 96b5f5428e..c96a31c530 100644 --- a/packages/bundler-webpack/src/dev/dev.ts +++ b/packages/bundler-webpack/src/dev/dev.ts @@ -29,7 +29,7 @@ export const dev = async ( const compiler = webpack(webpackConfig) // create webpack-dev-server - const serverConfig = await createDevServerConfig(app, options) + const serverConfig = createDevServerConfig(app, options) const server = new WebpackDevServer(serverConfig, compiler) const [, close] = await Promise.all([ @@ -56,7 +56,7 @@ export const dev = async ( if (hasFinished) return hasFinished = true - spinner.succeed(`Compilation finished in ${endTime! - startTime!}ms`) + spinner.succeed(`Compilation finished in ${endTime - startTime}ms`) // replace `0.0.0.0` with `localhost` as `0.0.0.0` is not available on windows const url = `http://${ @@ -67,7 +67,7 @@ export const dev = async ( ) // resolve the close function - resolve((): Promise => server.stop()) + resolve(async (): Promise => server.stop()) }) // stop spinner and reject error if the first compilation is failed diff --git a/packages/bundler-webpack/src/dev/trailingSlashMiddleware.ts b/packages/bundler-webpack/src/dev/trailingSlashMiddleware.ts index 17133dd16a..34d34e0717 100644 --- a/packages/bundler-webpack/src/dev/trailingSlashMiddleware.ts +++ b/packages/bundler-webpack/src/dev/trailingSlashMiddleware.ts @@ -15,7 +15,8 @@ export const trailingSlashMiddleware: RequestHandler = (req, res, next) => { // if the path already has trailing slash req.path.endsWith('/') ) { - return next() + next() + return } // add trailing slash and retain query diff --git a/packages/bundler-webpack/src/types.ts b/packages/bundler-webpack/src/types.ts index 9ce835dba1..162d8aa238 100644 --- a/packages/bundler-webpack/src/types.ts +++ b/packages/bundler-webpack/src/types.ts @@ -1,3 +1,4 @@ +import type { BundlerOptions } from '@vuepress/core' import type { VueLoaderOptions } from 'vue-loader' import type { LoaderContext, @@ -16,7 +17,7 @@ export type { /** * Options for bundler-webpack */ -export interface WebpackBundlerOptions { +export interface WebpackBundlerOptions extends BundlerOptions { /** * use webpack-merge to set webpack config */ @@ -84,7 +85,7 @@ export interface StylePreprocessorLoaderOptions { | string | (( content: string, - loaderContext: LoaderContext>, + loaderContext: LoaderContext>, ) => string) sourceMap?: boolean webpackImporter?: boolean @@ -94,7 +95,7 @@ export interface StylePreprocessorLoaderOptions { * Common type for style pre-processor options */ export type StylePreprocessorOptions< - T extends Record = Record, + T extends Record = Record, > = T | ((loaderContext: LoaderContext) => TextDecodeOptions) /** @@ -105,7 +106,7 @@ export type StylePreprocessorOptions< export interface PostcssLoaderOptions extends Pick { execute?: boolean - implementation?: ((...args: any) => any) | string + implementation?: string | ((...args: unknown[]) => unknown) postcssOptions?: StylePreprocessorOptions } @@ -115,7 +116,7 @@ export interface PostcssLoaderOptions * @see https://github.com/webpack-contrib/stylus-loader#options */ export interface StylusLoaderOptions extends StylePreprocessorLoaderOptions { - implementation?: ((...args: any) => any) | string + implementation?: string | ((...args: unknown[]) => unknown) stylusOptions?: StylePreprocessorOptions } @@ -125,8 +126,8 @@ export interface StylusLoaderOptions extends StylePreprocessorLoaderOptions { * @see https://github.com/webpack-contrib/sass-loader#options */ export interface SassLoaderOptions extends StylePreprocessorLoaderOptions { - api?: 'legacy' | 'modern' | 'modern-compiler' - implementation?: Record | string + api?: 'legacy' | 'modern-compiler' | 'modern' + implementation?: Record | string sassOptions?: StylePreprocessorOptions warnRuleAsWarning?: boolean } @@ -137,7 +138,7 @@ export interface SassLoaderOptions extends StylePreprocessorLoaderOptions { * @see https://github.com/webpack-contrib/less-loader#options */ export interface LessLoaderOptions extends StylePreprocessorLoaderOptions { - implementation?: Record | string + implementation?: Record | string lessLogAsWarnOrErr?: boolean lessOptions?: StylePreprocessorOptions } diff --git a/packages/bundler-webpack/src/webpackBundler.ts b/packages/bundler-webpack/src/webpackBundler.ts index 6a219eea14..1cfb1f9ba2 100644 --- a/packages/bundler-webpack/src/webpackBundler.ts +++ b/packages/bundler-webpack/src/webpackBundler.ts @@ -7,6 +7,6 @@ export const webpackBundler = ( options: WebpackBundlerOptions = {}, ): Bundler => ({ name: '@vuepress/bundler-webpack', - 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/cli/src/cli.ts b/packages/cli/src/cli.ts index 4561cd04e2..1ec277bd8f 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -1,7 +1,7 @@ import { createRequire } from 'node:module' import process from 'node:process' import type { AppConfig } from '@vuepress/core' -import { colors } from '@vuepress/utils' +import { colors, logger } from '@vuepress/utils' import { cac } from 'cac' import { createBuild, createDev, info } from './commands/index.js' @@ -15,8 +15,10 @@ export const cli = (defaultAppConfig: Partial = {}): void => { const program = cac('vuepress') // display core version and cli version - const versionCli = require('../package.json').version - const versionCore = require('@vuepress/core/package.json').version + const versionCli = (require('../package.json') as { version: string }).version + const versionCore = ( + require('@vuepress/core/package.json') as { version: string } + ).version program.version(`core@${versionCore} vuepress/cli@${versionCli}`) // display help message @@ -59,8 +61,8 @@ export const cli = (defaultAppConfig: Partial = {}): void => { // run command or fallback to help messages if (program.matchedCommand) { - program.runMatchedCommand().catch((err) => { - console.error(colors.red(err.stack)) + ;(program.runMatchedCommand() as Promise).catch((err: unknown) => { + logger.error(err instanceof Error ? colors.red(err.stack) : err) process.exit(1) }) } else { diff --git a/packages/cli/src/commands/build/createBuild.ts b/packages/cli/src/commands/build/createBuild.ts index 5828bdc5c8..ad001bd536 100644 --- a/packages/cli/src/commands/build/createBuild.ts +++ b/packages/cli/src/commands/build/createBuild.ts @@ -1,6 +1,6 @@ import process from 'node:process' -import { createBuildApp } from '@vuepress/core' import type { AppConfig } from '@vuepress/core' +import { createBuildApp } from '@vuepress/core' import { debug, formatMs, fs, logger, withSpinner } from '@vuepress/utils' import { loadUserConfig, @@ -53,13 +53,13 @@ export const createBuild = // clean temp and cache if (commandOptions.cleanTemp === true) { - await withSpinner('Cleaning temp')(() => { - return fs.remove(app.dir.temp()) + await withSpinner('Cleaning temp')(async () => { + await fs.remove(app.dir.temp()) }) } if (commandOptions.cleanCache === true) { - await withSpinner('Cleaning cache')(() => { - return fs.remove(app.dir.cache()) + await withSpinner('Cleaning cache')(async () => { + await fs.remove(app.dir.cache()) }) } diff --git a/packages/cli/src/commands/dev/createDev.ts b/packages/cli/src/commands/dev/createDev.ts index d34a7b7f82..b340e00a82 100644 --- a/packages/cli/src/commands/dev/createDev.ts +++ b/packages/cli/src/commands/dev/createDev.ts @@ -1,6 +1,6 @@ import process from 'node:process' -import { createDevApp } from '@vuepress/core' import type { AppConfig } from '@vuepress/core' +import { createDevApp } from '@vuepress/core' import { debug, fs, logger, withSpinner } from '@vuepress/utils' import type { FSWatcher } from 'chokidar' import { @@ -57,14 +57,12 @@ export const createDev = (defaultAppConfig: Partial): DevCommand => { // clean temp and cache if (commandOptions.cleanTemp === true) { - await withSpinner('Cleaning temp')(() => { - return fs.remove(app.dir.temp()) - }) + await withSpinner('Cleaning temp')(async () => fs.remove(app.dir.temp())) } if (commandOptions.cleanCache === true) { - await withSpinner('Cleaning cache')(() => { - return fs.remove(app.dir.cache()) - }) + await withSpinner('Cleaning cache')(async () => + fs.remove(app.dir.cache()), + ) } // initialize and prepare @@ -88,7 +86,7 @@ export const createDev = (defaultAppConfig: Partial): DevCommand => { const restart = async (): Promise => { await Promise.all([ // close all watchers - ...watchers.map((item) => item.close()), + ...watchers.map(async (item) => item.close()), // close current dev server close(), ]) diff --git a/packages/cli/src/commands/dev/handlePageAdd.ts b/packages/cli/src/commands/dev/handlePageAdd.ts index 8f4b367975..bcf5415a97 100644 --- a/packages/cli/src/commands/dev/handlePageAdd.ts +++ b/packages/cli/src/commands/dev/handlePageAdd.ts @@ -1,10 +1,10 @@ +import type { App, Page } from '@vuepress/core' import { createPage, preparePageChunk, preparePageComponent, prepareRoutes, } from '@vuepress/core' -import type { App, Page } from '@vuepress/core' /** * Event handler for page add event diff --git a/packages/cli/src/commands/dev/handlePageChange.ts b/packages/cli/src/commands/dev/handlePageChange.ts index 6eeff179aa..c2255096f0 100644 --- a/packages/cli/src/commands/dev/handlePageChange.ts +++ b/packages/cli/src/commands/dev/handlePageChange.ts @@ -1,10 +1,10 @@ +import type { App, Page } from '@vuepress/core' import { createPage, preparePageChunk, preparePageComponent, prepareRoutes, } from '@vuepress/core' -import type { App, Page } from '@vuepress/core' /** * Event handler for page change event diff --git a/packages/cli/src/commands/dev/handlePageUnlink.ts b/packages/cli/src/commands/dev/handlePageUnlink.ts index 1260480292..ad4d01a2c4 100644 --- a/packages/cli/src/commands/dev/handlePageUnlink.ts +++ b/packages/cli/src/commands/dev/handlePageUnlink.ts @@ -1,5 +1,5 @@ -import { prepareRoutes } from '@vuepress/core' import type { App, Page } from '@vuepress/core' +import { prepareRoutes } from '@vuepress/core' /** * Event handler for page unlink event diff --git a/packages/cli/src/commands/dev/watchPageFiles.ts b/packages/cli/src/commands/dev/watchPageFiles.ts index 48dcee2060..d9b884a2ee 100644 --- a/packages/cli/src/commands/dev/watchPageFiles.ts +++ b/packages/cli/src/commands/dev/watchPageFiles.ts @@ -1,7 +1,8 @@ +/* eslint-disable @typescript-eslint/no-misused-promises */ import type { App, Page } from '@vuepress/core' import { colors, logger } from '@vuepress/utils' -import chokidar from 'chokidar' import type { FSWatcher } from 'chokidar' +import chokidar from 'chokidar' import { handlePageAdd } from './handlePageAdd.js' import { handlePageChange } from './handlePageChange.js' import { handlePageUnlink } from './handlePageUnlink.js' @@ -27,7 +28,6 @@ export const watchPageFiles = (app: App): FSWatcher[] => { } const depsListener = async (dep: string): Promise => { const pagePaths = depsHelper.get(dep) - if (!pagePaths) return for (const filePathRelative of pagePaths) { logger.info( `dependency of page ${colors.magenta(filePathRelative)} is modified`, @@ -38,7 +38,9 @@ export const watchPageFiles = (app: App): FSWatcher[] => { depsWatcher.on('add', depsListener) depsWatcher.on('change', depsListener) depsWatcher.on('unlink', depsListener) - app.pages.forEach((page) => addDeps(page)) + app.pages.forEach((page) => { + addDeps(page) + }) // watch page files const pagesWatcher = chokidar.watch(app.options.pagePatterns, { diff --git a/packages/cli/src/commands/dev/watchUserConfigFile.ts b/packages/cli/src/commands/dev/watchUserConfigFile.ts index 6b89ac9e89..fa11200698 100644 --- a/packages/cli/src/commands/dev/watchUserConfigFile.ts +++ b/packages/cli/src/commands/dev/watchUserConfigFile.ts @@ -1,7 +1,7 @@ import process from 'node:process' import { colors, logger } from '@vuepress/utils' -import chokidar from 'chokidar' import type { FSWatcher } from 'chokidar' +import chokidar from 'chokidar' export const watchUserConfigFile = ({ userConfigPath, @@ -20,7 +20,7 @@ export const watchUserConfigFile = ({ }) configWatcher.on('change', (configFile) => { logger.info(`config ${colors.magenta(configFile)} is modified`) - restart() + void restart() }) const depsWatcher = chokidar.watch(userConfigDependencies, { @@ -29,7 +29,7 @@ export const watchUserConfigFile = ({ }) depsWatcher.on('change', (depFile) => { logger.info(`config dependency ${colors.magenta(depFile)} is modified`) - restart() + void restart() }) return [configWatcher, depsWatcher] diff --git a/packages/cli/src/commands/info.ts b/packages/cli/src/commands/info.ts index 3b09851a3b..ee2906e4b3 100644 --- a/packages/cli/src/commands/info.ts +++ b/packages/cli/src/commands/info.ts @@ -1,4 +1,4 @@ -import { ora } from '@vuepress/utils' +import { logger, ora } from '@vuepress/utils' import envinfo from 'envinfo' export const info = async (): Promise => { @@ -33,5 +33,5 @@ export const info = async (): Promise => { ) spinner.stop() - console.info(result) + logger.info(result) } diff --git a/packages/cli/src/config/loadUserConfig.ts b/packages/cli/src/config/loadUserConfig.ts index ff796865fa..5f42bbf1a5 100644 --- a/packages/cli/src/config/loadUserConfig.ts +++ b/packages/cli/src/config/loadUserConfig.ts @@ -42,10 +42,10 @@ export const loadUserConfig = async ( plugins: [ { name: 'externalize-deps', - setup(build) { - build.onResolve({ filter: /.*/ }, ({ path: id }) => { + setup(pluginBuild) { + pluginBuild.onResolve({ filter: /.*/ }, ({ path: id }) => { // externalize bare imports - if (id[0] !== '.' && !path.isAbsolute(id)) { + if (!id.startsWith('.') && !path.isAbsolute(id)) { return { external: true, } @@ -56,8 +56,8 @@ export const loadUserConfig = async ( }, { name: 'inject-file-scope-variables', - setup(build) { - build.onLoad({ filter: /\.[cm]?[jt]s$/ }, async (args) => { + setup(pluginBuild) { + pluginBuild.onLoad({ filter: /\.[cm]?[jt]s$/ }, async (args) => { const contents = await fs.readFile(args.path, 'utf8') const injectValues = `const ${dirnameVarName} = ${JSON.stringify( @@ -92,6 +92,6 @@ export const loadUserConfig = async ( } return { userConfig, - userConfigDependencies: Object.keys(result.metafile?.inputs ?? {}), + userConfigDependencies: Object.keys(result.metafile.inputs), } } diff --git a/packages/cli/src/config/resolveAppConfig.ts b/packages/cli/src/config/resolveAppConfig.ts index ba52cde94e..0c8263cbdb 100644 --- a/packages/cli/src/config/resolveAppConfig.ts +++ b/packages/cli/src/config/resolveAppConfig.ts @@ -25,7 +25,12 @@ export const resolveAppConfig = ({ ...userConfig, // allow cli options to override config file ...cliAppConfig, - } as AppConfig + } + + if (appConfig.source === undefined) { + logger.error(`The ${colors.magenta('source')} option is missing.`) + return null + } if (appConfig.bundler === undefined || appConfig.theme === undefined) { logger.error( @@ -57,5 +62,5 @@ export const resolveAppConfig = ({ delete appConfig.dest } - return appConfig + return appConfig as AppConfig } diff --git a/packages/cli/src/config/types.ts b/packages/cli/src/config/types.ts index 570c825dcc..ec2da48e4a 100644 --- a/packages/cli/src/config/types.ts +++ b/packages/cli/src/config/types.ts @@ -5,6 +5,5 @@ import type { AppConfig, PluginObject } from '@vuepress/core' * * It will be transformed to `AppConfig` by cli */ -export type UserConfig = Partial & - // user config can be used as a plugin - Omit +export type UserConfig = Omit & + Partial diff --git a/packages/cli/tests/config/loadUserConfig.spec.ts b/packages/cli/tests/config/loadUserConfig.spec.ts index 74ccbd1c3b..3327d9bd05 100644 --- a/packages/cli/tests/config/loadUserConfig.spec.ts +++ b/packages/cli/tests/config/loadUserConfig.spec.ts @@ -2,7 +2,7 @@ import { path } from '@vuepress/utils' import { describe, expect, it } from 'vitest' import { loadUserConfig } from '../../src/index.js' -const tsCases: [string, any][] = [ +const TS_CASES: [string, unknown][] = [ [ path.resolve(__dirname, '../__fixtures__/config/ts/.vuepress/config.ts'), { @@ -17,7 +17,7 @@ const tsCases: [string, any][] = [ ], ] -const jsCases: [string, any][] = [ +const JS_CASES: [string, unknown][] = [ [ path.resolve(__dirname, '../__fixtures__/config/js/.vuepress/config.js'), { @@ -32,7 +32,7 @@ const jsCases: [string, any][] = [ ], ] -const mjsCases: [string, any][] = [ +const MJS_CASES: [string, unknown][] = [ [ path.resolve(__dirname, '../__fixtures__/config/mjs/.vuepress/config.mjs'), { @@ -49,7 +49,7 @@ const mjsCases: [string, any][] = [ describe('cli > config > loadUserConfig', () => { describe('should load ts config file correctly', () => { - tsCases.forEach(([source, expected]) => { + TS_CASES.forEach(([source, expected]) => { it(JSON.stringify(source), async () => { const { userConfig } = await loadUserConfig(source) expect(userConfig).toEqual(expected) @@ -58,7 +58,7 @@ describe('cli > config > loadUserConfig', () => { }) describe('should load js config file correctly', () => { - jsCases.forEach(([source, expected]) => { + JS_CASES.forEach(([source, expected]) => { it(JSON.stringify(source), async () => { const { userConfig } = await loadUserConfig(source) expect(userConfig).toEqual(expected) @@ -67,7 +67,7 @@ describe('cli > config > loadUserConfig', () => { }) describe('should load mjs config file correctly', () => { - mjsCases.forEach(([source, expected]) => { + MJS_CASES.forEach(([source, expected]) => { it(JSON.stringify(source), async () => { const { userConfig } = await loadUserConfig(source) expect(userConfig).toEqual(expected) diff --git a/packages/cli/tests/config/resolveUserConfigConventionalPath.spec.ts b/packages/cli/tests/config/resolveUserConfigConventionalPath.spec.ts index f74b1f1d20..808ac4ec19 100644 --- a/packages/cli/tests/config/resolveUserConfigConventionalPath.spec.ts +++ b/packages/cli/tests/config/resolveUserConfigConventionalPath.spec.ts @@ -5,7 +5,7 @@ import { resolveUserConfigConventionalPath } from '../../src/index.js' const resolveFixtures = (str: string): string => path.resolve(__dirname, '../__fixtures__/config/convention', str) -const testCases: [string, string][] = [ +const TEST_CASES: [string, string][] = [ [resolveFixtures('case1'), 'vuepress.config.ts'], [resolveFixtures('case2'), '.vuepress/config.ts'], [resolveFixtures('case3'), 'vuepress.config.js'], @@ -16,7 +16,7 @@ const testCases: [string, string][] = [ describe('cli > config > resolveUserConfigConventionalPath', () => { describe('should resolve conventional config file correctly', () => { - testCases.forEach(([source, expected]) => { + TEST_CASES.forEach(([source, expected]) => { it(expected, () => { const configFile = resolveUserConfigConventionalPath(source, source) expect(configFile).toEqual(path.resolve(source, expected)) diff --git a/packages/client/src/app.ts b/packages/client/src/app.ts index 4c85269963..dfab9f56d2 100644 --- a/packages/client/src/app.ts +++ b/packages/client/src/app.ts @@ -29,15 +29,16 @@ export const createVueApp: CreateVueAppFunction = async () => { } // get all root components - const rootComponents = clientConfigs.flatMap(({ rootComponents = [] }) => - rootComponents.map((component) => h(component)), + const clientRootComponents = clientConfigs.flatMap( + ({ rootComponents = [] }) => + rootComponents.map((component) => h(component)), ) // get page layout const pageLayout = usePageLayout() // render layout and root components - return () => [h(pageLayout.value), rootComponents] + return () => [h(pageLayout.value), clientRootComponents] }, }) @@ -74,8 +75,8 @@ export const createVueApp: CreateVueAppFunction = async () => { // mount app in client bundle if (!__VUEPRESS_SSR__) { - createVueApp().then(({ app, router }) => { - router.isReady().then(() => { + void createVueApp().then(({ app, router }) => { + void router.isReady().then(() => { app.mount('#app') }) }) diff --git a/packages/client/src/components/AutoLink.ts b/packages/client/src/components/AutoLink.ts index f570dcd49d..948a39e066 100644 --- a/packages/client/src/components/AutoLink.ts +++ b/packages/client/src/components/AutoLink.ts @@ -9,7 +9,7 @@ export interface AutoLinkConfig { /** * Pattern to determine if the link should be active, which has higher priority than `exact` */ - activeMatch?: string | RegExp + activeMatch?: RegExp | string /** * The `aria-label` attribute @@ -59,9 +59,9 @@ export const AutoLink = defineComponent({ }, slots: Object as SlotsType<{ - default?: (config: AutoLinkConfig) => VNode[] | VNode - before?: (config: AutoLinkConfig) => VNode[] | VNode | null - after?: (config: AutoLinkConfig) => VNode[] | VNode | null + default?: (config: AutoLinkConfig) => VNode | VNode[] + before?: (config: AutoLinkConfig) => VNode | VNode[] | null + after?: (config: AutoLinkConfig) => VNode | VNode[] | null }>, setup(props, { slots }) { @@ -134,7 +134,7 @@ export const AutoLink = defineComponent({ return () => { const { before, after, default: defaultSlot } = slots - const content = defaultSlot?.(config.value) || [ + const content = defaultSlot?.(config.value) ?? [ before?.(config.value), config.value.text, after?.(config.value), diff --git a/packages/client/src/components/Content.ts b/packages/client/src/components/Content.ts index 41204daeb6..e58b46214d 100644 --- a/packages/client/src/components/Content.ts +++ b/packages/client/src/components/Content.ts @@ -6,7 +6,6 @@ import { resolveRoute } from '../router/index.js' * Markdown rendered content */ export const Content = defineComponent({ - // eslint-disable-next-line vue/no-reserved-component-names name: 'Content', props: { @@ -22,7 +21,9 @@ export const Content = defineComponent({ const ContentComponent = computed(() => { if (!props.path) return pageComponent.value const route = resolveRoute(props.path) - return defineAsyncComponent(() => route.loader().then(({ comp }) => comp)) + return defineAsyncComponent(async () => + route.loader().then(({ comp }) => comp), + ) }) return () => h(ContentComponent.value) diff --git a/packages/client/src/components/RouteLink.ts b/packages/client/src/components/RouteLink.ts index 1b1e62b157..ffff10e430 100644 --- a/packages/client/src/components/RouteLink.ts +++ b/packages/client/src/components/RouteLink.ts @@ -1,5 +1,5 @@ -import { computed, defineComponent, h } from 'vue' import type { SlotsType, VNode } from 'vue' +import { computed, defineComponent, h } from 'vue' import { useRoute, useRouter } from 'vue-router' import { resolveRouteFullPath } from '../router/index.js' @@ -12,6 +12,7 @@ const guardEvent = (event: MouseEvent): boolean | void => { // don't redirect when preventDefault called if (event.defaultPrevented) return // don't redirect on right click + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (event.button !== undefined && event.button !== 0) return // don't redirect if `target="_blank"` if (event.currentTarget) { @@ -81,7 +82,7 @@ export const RouteLink = defineComponent({ }, slots: Object as SlotsType<{ - default: () => string | VNode | (string | VNode)[] + default: () => (VNode | string)[] | VNode | string }>, setup(props, { slots }) { @@ -102,11 +103,11 @@ export const RouteLink = defineComponent({ href: path.value, onClick: (event: MouseEvent = {} as MouseEvent) => { if (guardEvent(event)) { - router.push(props.to).catch() + void router.push(props.to).catch() } }, }, - slots.default?.(), + slots.default(), ) }, }) diff --git a/packages/client/src/composables/clientDataUtils.ts b/packages/client/src/composables/clientDataUtils.ts index c9338284f8..3dc3109670 100644 --- a/packages/client/src/composables/clientDataUtils.ts +++ b/packages/client/src/composables/clientDataUtils.ts @@ -21,11 +21,11 @@ export const usePageComponent = (): PageComponentRef => useClientData().pageComponent export const usePageData = < - T extends Record = Record, + T extends Record = Record, >(): PageDataRef => useClientData().pageData as PageDataRef export const usePageFrontmatter = < - T extends Record = Record, + T extends Record = Record, >(): PageFrontmatterRef => useClientData().pageFrontmatter as PageFrontmatterRef diff --git a/packages/client/src/composables/updateHead.ts b/packages/client/src/composables/updateHead.ts index 630c378b4a..6fa83074ec 100644 --- a/packages/client/src/composables/updateHead.ts +++ b/packages/client/src/composables/updateHead.ts @@ -1,5 +1,5 @@ -import { inject } from 'vue' import type { InjectionKey } from 'vue' +import { inject } from 'vue' /** * A util function to force update `` of current page diff --git a/packages/client/src/internal/routes.ts b/packages/client/src/internal/routes.ts index 796e34bebf..3d395e53f9 100644 --- a/packages/client/src/internal/routes.ts +++ b/packages/client/src/internal/routes.ts @@ -2,8 +2,8 @@ import { redirects as redirectsRaw, routes as routesRaw, } from '@internal/routes' -import { shallowRef } from 'vue' import type { Ref } from 'vue' +import { shallowRef } from 'vue' import type { Redirects, Routes } from '../types/index.js' /** diff --git a/packages/client/src/resolvers.ts b/packages/client/src/resolvers.ts index 7b1fc0f18f..46e85c8da3 100644 --- a/packages/client/src/resolvers.ts +++ b/packages/client/src/resolvers.ts @@ -85,6 +85,7 @@ export const resolvers = reactive({ const layoutName = isString(pageData.frontmatter.layout) ? pageData.frontmatter.layout : LAYOUT_NAME_DEFAULT + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- unsafe indexed access if (!layouts[layoutName]) { throw new Error(`[vuepress] Cannot resolve layout: ${layoutName}`) } @@ -113,8 +114,9 @@ export const resolvers = reactive({ head: [ // when merging head, the locales head should be placed before root head // to get higher priority + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- unsafe indexed access ...(locales[routeLocale]?.head ?? []), - ...(siteData.head ?? []), + ...siteData.head, ], }), }) 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/setupDevtools.ts b/packages/client/src/setupDevtools.ts index ab57f1e43e..a22a731140 100644 --- a/packages/client/src/setupDevtools.ts +++ b/packages/client/src/setupDevtools.ts @@ -1,6 +1,6 @@ import { setupDevtoolsPlugin } from '@vue/devtools-api' -import { watch } from 'vue' import type { App } from 'vue' +import { watch } from 'vue' import type { ClientData } from './types/index.js' const PLUGIN_ID = 'org.vuejs.vuepress' @@ -12,11 +12,14 @@ const INSPECTOR_LABEL = PLUGIN_LABEL const INSPECTOR_CLIENT_DATA_ID = 'client-data' const INSPECTOR_CLIENT_DATA_LABEL = 'Client Data' +type ClientDataKey = keyof ClientData +type ClientDataValue = ClientData[ClientDataKey] + export const setupDevtools = (app: App, clientData: ClientData): void => { setupDevtoolsPlugin( { // fix recursive reference - app: app as any, + app: app as never, id: PLUGIN_ID, label: PLUGIN_LABEL, packageName: '@vuepress/client', @@ -25,9 +28,12 @@ export const setupDevtools = (app: App, clientData: ClientData): void => { componentStateTypes: [PLUGIN_COMPONENT_STATE_TYPE], }, (api) => { - const clientDataEntries = Object.entries(clientData) - const clientDataKeys = Object.keys(clientData) - const clientDataValues = Object.values(clientData) + const clientDataEntries = Object.entries(clientData) as [ + ClientDataKey, + ClientDataValue, + ][] + const clientDataKeys = Object.keys(clientData) as ClientDataKey[] + const clientDataValues = Object.values(clientData) as ClientDataValue[] // setup component state api.on.inspectComponent((payload) => { @@ -72,12 +78,12 @@ export const setupDevtools = (app: App, clientData: ClientData): void => { ), } } - if (clientDataKeys.includes(payload.nodeId)) { + if (clientDataKeys.includes(payload.nodeId as ClientDataKey)) { payload.state = { [INSPECTOR_CLIENT_DATA_LABEL]: [ { key: payload.nodeId, - value: clientData[payload.nodeId].value, + value: clientData[payload.nodeId as ClientDataKey].value, }, ], } diff --git a/packages/client/src/setupGlobalComponents.ts b/packages/client/src/setupGlobalComponents.ts index 6c513cc98a..684f9d5b8a 100644 --- a/packages/client/src/setupGlobalComponents.ts +++ b/packages/client/src/setupGlobalComponents.ts @@ -5,9 +5,7 @@ import { ClientOnly, Content, RouteLink } from './components/index.js' * Register global built-in components */ export const setupGlobalComponents = (app: App): void => { - /* eslint-disable vue/match-component-file-name, vue/no-reserved-component-names */ app.component('ClientOnly', ClientOnly) app.component('Content', Content) app.component('RouteLink', RouteLink) - /* eslint-enable vue/match-component-file-name, vue/no-reserved-component-names */ } diff --git a/packages/client/src/setupGlobalComputed.ts b/packages/client/src/setupGlobalComputed.ts index 57197148a6..e258cb7673 100644 --- a/packages/client/src/setupGlobalComputed.ts +++ b/packages/client/src/setupGlobalComputed.ts @@ -48,7 +48,7 @@ export const setupGlobalComputed = ( __VUE_HMR_RUNTIME__.updatePageData = async (newPageData: PageData) => { const oldPageChunk = await routes.value[newPageData.path].loader() const newPageChunk = { comp: oldPageChunk.comp, data: newPageData } - routes.value[newPageData.path].loader = () => + routes.value[newPageData.path].loader = async () => Promise.resolve(newPageChunk) if ( newPageData.path === 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/clientConfig.ts b/packages/client/src/types/clientConfig.ts index 9b0a490bd9..ad2a73eca9 100644 --- a/packages/client/src/types/clientConfig.ts +++ b/packages/client/src/types/clientConfig.ts @@ -14,7 +14,7 @@ export interface ClientConfig { app: App router: Router siteData: SiteDataRef - }) => void | Promise + }) => Promise | void /** * A function to be called inside the setup function of vue app 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/client/types.d.ts b/packages/client/types.d.ts index 2d55050eb3..f694351554 100644 --- a/packages/client/types.d.ts +++ b/packages/client/types.d.ts @@ -1,7 +1,8 @@ +/* eslint-disable @typescript-eslint/naming-convention, no-underscore-dangle */ declare const __VUEPRESS_VERSION__: string declare const __VUEPRESS_BASE__: string declare const __VUEPRESS_DEV__: boolean declare const __VUEPRESS_SSR__: boolean -declare const __VUE_HMR_RUNTIME__: Record +declare const __VUE_HMR_RUNTIME__: Record declare const __VUE_OPTIONS_API__: boolean declare const __VUE_PROD_DEVTOOLS__: boolean diff --git a/packages/core/src/app/createBaseApp.ts b/packages/core/src/app/createBaseApp.ts index b596ac2031..3e91a42eff 100644 --- a/packages/core/src/app/createBaseApp.ts +++ b/packages/core/src/app/createBaseApp.ts @@ -36,8 +36,8 @@ export const createBaseApp = (config: AppConfig, isBuild = false): App => { // methods use: (plugin: Plugin) => appUse(app, plugin), - init: () => appInit(app), - prepare: () => appPrepare(app), + init: async () => appInit(app), + prepare: async () => appPrepare(app), } as App // setup theme and plugins diff --git a/packages/core/src/app/createBuildApp.ts b/packages/core/src/app/createBuildApp.ts index 3be1e3e8e9..36830cbdf8 100644 --- a/packages/core/src/app/createBuildApp.ts +++ b/packages/core/src/app/createBuildApp.ts @@ -6,6 +6,6 @@ import { createBaseApp } from './createBaseApp.js' */ export const createBuildApp = (config: AppConfig): BuildApp => { const app = createBaseApp(config, true) as BuildApp - app.build = () => app.options.bundler.build(app) + app.build = async () => app.options.bundler.build(app) return app } diff --git a/packages/core/src/app/createDevApp.ts b/packages/core/src/app/createDevApp.ts index c611560e3e..59fe0f2ea0 100644 --- a/packages/core/src/app/createDevApp.ts +++ b/packages/core/src/app/createDevApp.ts @@ -6,6 +6,6 @@ import { createBaseApp } from './createBaseApp.js' */ export const createDevApp = (config: AppConfig): DevApp => { const app = createBaseApp(config, false) as DevApp - app.dev = () => app.options.bundler.dev(app) + app.dev = async () => app.options.bundler.dev(app) return app } diff --git a/packages/core/src/app/resolveAppDir.ts b/packages/core/src/app/resolveAppDir.ts index 675c30ee5e..5e78b9d344 100644 --- a/packages/core/src/app/resolveAppDir.ts +++ b/packages/core/src/app/resolveAppDir.ts @@ -7,9 +7,10 @@ const require = createRequire(import.meta.url) /** * Create directory util function */ -export const createAppDirFunction = (baseDir: string): AppDirFunction => { - return (...args: string[]): string => path.resolve(baseDir, ...args) -} +export const createAppDirFunction = + (baseDir: string): AppDirFunction => + (...args) => + path.resolve(baseDir, ...args) /** * Resolve directory utils for vuepress app diff --git a/packages/core/src/app/resolveAppMarkdown.ts b/packages/core/src/app/resolveAppMarkdown.ts index 0875dceaf6..84ce7d0a1a 100644 --- a/packages/core/src/app/resolveAppMarkdown.ts +++ b/packages/core/src/app/resolveAppMarkdown.ts @@ -1,5 +1,5 @@ -import { createMarkdown } from '@vuepress/markdown' import type { Markdown } from '@vuepress/markdown' +import { createMarkdown } from '@vuepress/markdown' import type { App } from '../types/index.js' /** diff --git a/packages/core/src/app/resolveAppPages.ts b/packages/core/src/app/resolveAppPages.ts index 2f721fa7fd..1f7d083dc0 100644 --- a/packages/core/src/app/resolveAppPages.ts +++ b/packages/core/src/app/resolveAppPages.ts @@ -18,7 +18,7 @@ export const resolveAppPages = async (app: App): Promise => { // create pages from files const pages = await Promise.all( - pageFilePaths.map((filePath) => createPage(app, { filePath })), + pageFilePaths.map(async (filePath) => createPage(app, { filePath })), ) // find the 404 page diff --git a/packages/core/src/app/resolveAppVersion.ts b/packages/core/src/app/resolveAppVersion.ts index 0990da5f7e..590340ffc4 100644 --- a/packages/core/src/app/resolveAppVersion.ts +++ b/packages/core/src/app/resolveAppVersion.ts @@ -9,6 +9,6 @@ const require = createRequire(import.meta.url) export const resolveAppVersion = (): string => { const pkgJson = fs.readJsonSync( require.resolve('@vuepress/core/package.json'), - ) + ) as { version: string } return pkgJson.version } 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/renderPageContent.ts b/packages/core/src/page/renderPageContent.ts index ea430de1f2..3702cd037b 100644 --- a/packages/core/src/page/renderPageContent.ts +++ b/packages/core/src/page/renderPageContent.ts @@ -74,6 +74,7 @@ export const renderPageContent = ({ 'frontmatter', ), sfcBlocks, + // eslint-disable-next-line @typescript-eslint/no-unnecessary-template-expression -- the title from frontmatter is not guaranteed to be a string title: frontmatter.title ? `${frontmatter.title}` : title, } } 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/page/resolvePageHtmlInfo.ts b/packages/core/src/page/resolvePageHtmlInfo.ts index 58d1c63b4b..677921ed04 100644 --- a/packages/core/src/page/resolvePageHtmlInfo.ts +++ b/packages/core/src/page/resolvePageHtmlInfo.ts @@ -20,10 +20,10 @@ export const resolvePageHtmlInfo = ({ // /foo/ -> foo/index.html const htmlFilePathRelative = removeLeadingSlash( path.endsWith('/') - ? path + 'index.html' + ? `${path}index.html` : path.endsWith('.html') ? path - : path + '.html', + : `${path}.html`, ) const htmlFilePath = app.dir.dest(htmlFilePathRelative) diff --git a/packages/core/src/page/resolvePageLang.ts b/packages/core/src/page/resolvePageLang.ts index 1f3f49136a..3f2f243256 100644 --- a/packages/core/src/page/resolvePageLang.ts +++ b/packages/core/src/page/resolvePageLang.ts @@ -16,5 +16,6 @@ export const resolvePageLang = ({ if (isString(frontmatter.lang) && frontmatter.lang) { return frontmatter.lang } + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- unsafe indexed access return app.siteData.locales[pathLocale]?.lang ?? app.siteData.lang } diff --git a/packages/core/src/page/resolvePagePermalink.ts b/packages/core/src/page/resolvePagePermalink.ts index 6a549085f2..15a421dec1 100644 --- a/packages/core/src/page/resolvePagePermalink.ts +++ b/packages/core/src/page/resolvePagePermalink.ts @@ -45,9 +45,9 @@ export const resolvePagePermalink = ({ const link = path.join( pathLocale, permalinkPattern - .replace(/:year/, year!) - .replace(/:month/, month!) - .replace(/:day/, day!) + .replace(/:year/, year) + .replace(/:month/, month) + .replace(/:day/, day) .replace(/:slug/, slug) .replace(/:raw/, pathInferred?.replace(/^\//, '') ?? ''), ) diff --git a/packages/core/src/page/resolvePageSlug.ts b/packages/core/src/page/resolvePageSlug.ts index e538526e47..a7329ffbaa 100644 --- a/packages/core/src/page/resolvePageSlug.ts +++ b/packages/core/src/page/resolvePageSlug.ts @@ -15,5 +15,5 @@ export const resolvePageSlug = ({ } const filename = path.parse(filePathRelative).name const match = filename.match(DATE_RE) - return match ? match[3]! : filename + return match ? match[3] : filename } 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/types/app/options.ts b/packages/core/src/types/app/options.ts index 93271fd4bf..4cbcabbc37 100644 --- a/packages/core/src/types/app/options.ts +++ b/packages/core/src/types/app/options.ts @@ -67,7 +67,7 @@ export interface AppConfigBuild { * * @default true */ - shouldPreload?: ((file: string, type: string) => boolean) | boolean + shouldPreload?: boolean | ((file: string, type: string) => boolean) /** * Determine what resource files should be prefetched. Use boolean value to @@ -75,7 +75,7 @@ export interface AppConfigBuild { * * @default true */ - shouldPrefetch?: ((file: string, type: string) => boolean) | boolean + shouldPrefetch?: boolean | ((file: string, type: string) => boolean) /** * Specify the path of the HTML template to be used for build @@ -95,7 +95,7 @@ export interface AppConfigBuild { /** * Vuepress app config */ -export type AppConfig = AppConfigCommon & AppConfigDev & AppConfigBuild +export type AppConfig = AppConfigBuild & AppConfigCommon & AppConfigDev /** * Vuepress app options diff --git a/packages/core/src/types/bundler.ts b/packages/core/src/types/bundler.ts index d69f273ec3..cd3b8dc2a7 100644 --- a/packages/core/src/types/bundler.ts +++ b/packages/core/src/types/bundler.ts @@ -12,3 +12,5 @@ export interface Bundler { dev: (app: App) => Promise<() => Promise> build: (app: App) => Promise } + +export type BundlerOptions = Record diff --git a/packages/core/src/types/page.ts b/packages/core/src/types/page.ts index 5aadd8d7ae..178fec9782 100644 --- a/packages/core/src/types/page.ts +++ b/packages/core/src/types/page.ts @@ -5,11 +5,14 @@ import type { PageBase, PageData, PageFrontmatter } from '@vuepress/shared' * Vuepress Page */ export type Page< - ExtraPageData extends Record = Record, - ExtraPageFrontmatter extends Record = Record, - ExtraPageFields extends Record = Record, -> = PageBase & - ExtraPageFields & { + ExtraPageData extends Record = Record, + ExtraPageFrontmatter extends Record = Record< + string, + unknown + >, + ExtraPageFields extends Record = Record, +> = ExtraPageFields & + PageBase & { /** * Data of the page, which will be available in client code */ diff --git a/packages/core/src/types/plugin.ts b/packages/core/src/types/plugin.ts index c7f6bc39ca..ae52a6b334 100644 --- a/packages/core/src/types/plugin.ts +++ b/packages/core/src/types/plugin.ts @@ -11,8 +11,8 @@ import type { HooksExposed } from './pluginApi/index.js' * A plugin package should have a `Plugin` as the default export */ export type Plugin = - | T | PluginFunction + | T /** * Vuepress plugin function diff --git a/packages/core/src/types/pluginApi/hooks.ts b/packages/core/src/types/pluginApi/hooks.ts index 4e88851256..cab24a8a13 100644 --- a/packages/core/src/types/pluginApi/hooks.ts +++ b/packages/core/src/types/pluginApi/hooks.ts @@ -1,5 +1,6 @@ import type { Markdown, MarkdownOptions } from '@vuepress/markdown' import type { App } from '../app/index.js' +import type { BundlerOptions } from '../bundler.js' import type { Page, PageOptions } from '../page.js' // util type @@ -12,11 +13,12 @@ interface Closable { export interface Hook< Exposed, Normalized = Exposed, + // 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 - : void, + : never, > { exposed: Exposed normalized: Normalized @@ -41,9 +43,9 @@ export type ClientConfigFileHook = Hook< // alias and define hook export type AliasDefineHook = Hook< - | Record - | ((app: App, isServer: boolean) => PromiseOrNot>), - (app: App, isServer: boolean) => Promise> + | Record + | ((app: App, isServer: boolean) => PromiseOrNot>), + (app: App, isServer: boolean) => Promise> > /** @@ -58,7 +60,7 @@ export interface Hooks { extendsMarkdown: ExtendsHook extendsPageOptions: ExtendsHook extendsPage: ExtendsHook - extendsBundlerOptions: ExtendsHook + extendsBundlerOptions: ExtendsHook clientConfigFile: ClientConfigFileHook alias: AliasDefineHook define: AliasDefineHook diff --git a/packages/core/tests/app/resolveAppEnv.spec.ts b/packages/core/tests/app/resolveAppEnv.spec.ts index bcdb45210d..6ba685baa6 100644 --- a/packages/core/tests/app/resolveAppEnv.spec.ts +++ b/packages/core/tests/app/resolveAppEnv.spec.ts @@ -1,18 +1,17 @@ import { describe, expect, it } from 'vitest' +import type { Bundler } from '../../src/index.js' import { resolveAppEnv, resolveAppOptions } from '../../src/index.js' -const source = '/foo' - -const testCases: [ +const TEST_CASES: [ Parameters, ReturnType, ][] = [ [ [ resolveAppOptions({ - source, + source: '/foo', theme: { name: 'test' }, - bundler: {} as any, + bundler: {} as Bundler, }), false, ], @@ -25,9 +24,9 @@ const testCases: [ [ [ resolveAppOptions({ - source, + source: '/foo', theme: { name: 'test' }, - bundler: {} as any, + bundler: {} as Bundler, debug: true, }), false, @@ -41,9 +40,9 @@ const testCases: [ [ [ resolveAppOptions({ - source, + source: '/foo', theme: { name: 'test' }, - bundler: {} as any, + bundler: {} as Bundler, }), true, ], @@ -57,7 +56,7 @@ const testCases: [ describe('core > app > resolveAppEnv', () => { describe('should create app env correctly', () => { - testCases.forEach(([params, expected], i) => { + TEST_CASES.forEach(([params, expected], i) => { it(`case ${i}`, () => { expect(resolveAppEnv(...params)).toEqual(expected) }) diff --git a/packages/core/tests/app/resolveAppOptions.spec.ts b/packages/core/tests/app/resolveAppOptions.spec.ts index a7f3ff125c..442737587d 100644 --- a/packages/core/tests/app/resolveAppOptions.spec.ts +++ b/packages/core/tests/app/resolveAppOptions.spec.ts @@ -1,5 +1,6 @@ import { path, templateRenderer } from '@vuepress/utils' import { describe, expect, it } from 'vitest' +import type { Bundler } from '../../src/index.js' import { resolveAppOptions } from '../../src/index.js' describe('core > app > resolveAppOptions', () => { @@ -9,8 +10,8 @@ describe('core > app > resolveAppOptions', () => { expect( resolveAppOptions({ source, - theme: { name: 'theme' } as any, - bundler: { name: 'bundler' } as any, + theme: { name: 'theme' }, + bundler: { name: 'bundler' } as Bundler, }), ).toEqual({ base: '/', diff --git a/packages/core/tests/app/resolveAppPages.spec.ts b/packages/core/tests/app/resolveAppPages.spec.ts index 8d47875eb2..f394888238 100644 --- a/packages/core/tests/app/resolveAppPages.spec.ts +++ b/packages/core/tests/app/resolveAppPages.spec.ts @@ -1,6 +1,7 @@ import { createMarkdown } from '@vuepress/markdown' import { path } from '@vuepress/utils' import { describe, expect, it } from 'vitest' +import type { Bundler } from '../../src/index.js' import { createBaseApp, resolveAppPages } from '../../src/index.js' describe('core > app > resolveAppPages', () => { @@ -8,7 +9,7 @@ describe('core > app > resolveAppPages', () => { const app = createBaseApp({ source: path.resolve(__dirname, '../__fixtures__/pages'), theme: { name: 'test' }, - bundler: {} as any, + bundler: {} as Bundler, }) app.markdown = createMarkdown() @@ -28,7 +29,7 @@ describe('core > app > resolveAppPages', () => { const app = createBaseApp({ source: path.resolve(__dirname, '../__fixtures__/pages-with-404'), theme: { name: 'test' }, - bundler: {} as any, + bundler: {} as Bundler, }) app.markdown = createMarkdown() @@ -47,7 +48,7 @@ describe('core > app > resolveAppPages', () => { const app = createBaseApp({ source: path.resolve(__dirname, '../__fixtures__/pages-with-404'), theme: { name: 'test' }, - bundler: {} as any, + bundler: {} as Bundler, }) app.use({ @@ -71,7 +72,7 @@ describe('core > app > resolveAppPages', () => { const app = createBaseApp({ source: path.resolve(__dirname, '../__fixtures__/pages-with-404'), theme: { name: 'test' }, - bundler: {} as any, + bundler: {} as Bundler, }) app.use({ diff --git a/packages/core/tests/app/resolvePluginObject.spec.ts b/packages/core/tests/app/resolvePluginObject.spec.ts index 02d2441e81..d359120252 100644 --- a/packages/core/tests/app/resolvePluginObject.spec.ts +++ b/packages/core/tests/app/resolvePluginObject.spec.ts @@ -1,12 +1,12 @@ import { path } from '@vuepress/utils' import { describe, expect, it, vi } from 'vitest' +import type { Bundler, PluginFunction, PluginObject } from '../../src/index.js' import { createBaseApp, resolvePluginObject } from '../../src/index.js' -import type { PluginFunction, PluginObject } from '../../src/index.js' const app = createBaseApp({ source: path.resolve(__dirname, 'fake-source'), theme: { name: 'test' }, - bundler: {} as any, + bundler: {} as Bundler, }) describe('core > app > resolvePluginObject', () => { @@ -20,7 +20,7 @@ describe('core > app > resolvePluginObject', () => { }) it('should work with plugin function', () => { - const pluginFunction: PluginFunction = vi.fn((app) => ({ + const pluginFunction: PluginFunction = vi.fn(() => ({ name: 'plugin-function', })) diff --git a/packages/core/tests/app/resolveThemeInfo.spec.ts b/packages/core/tests/app/resolveThemeInfo.spec.ts index cc331816d6..631deb2e6a 100644 --- a/packages/core/tests/app/resolveThemeInfo.spec.ts +++ b/packages/core/tests/app/resolveThemeInfo.spec.ts @@ -1,55 +1,59 @@ import { importFileDefault, path } from '@vuepress/utils' import { describe, expect, it } from 'vitest' +import type { App, Bundler, Theme, ThemeObject } from '../../src/index.js' import { createBaseApp, resolveThemeInfo } from '../../src/index.js' -const fixtures = (...args: string[]) => +const fixtures = (...args: string[]): string => path.resolve(__dirname, '../__fixtures__/', ...args) -const createTestApp = async (themePath: string) => +const createTestApp = async (themePath: string): Promise => createBaseApp({ source: path.resolve(__dirname, 'fake-source'), theme: await importFileDefault(themePath), - bundler: {} as any, + bundler: {} as Bundler, }) -const themeEntryTypes = ['func', 'obj'] as const +const THEME_ENTRY_TYPES = ['func', 'obj'] as const -const getThemePlugin = async (themePath: string) => { - const theme = await importFileDefault(themePath) - return typeof theme === 'function' ? theme() : theme +const getThemePlugin = async ( + themePath: string, + app: App, +): Promise => { + const theme = await importFileDefault(themePath) + return typeof theme === 'function' ? theme(app) : theme } describe('core > app > resolveThemeInfo', () => { describe('plugins', () => { describe('should resolve theme info without plugins correctly', () => { - themeEntryTypes.forEach((item) => + THEME_ENTRY_TYPES.forEach((item) => { it(item, async () => { const themePath = fixtures(`themes/${item}-empty.js`) const app = await createTestApp(themePath) expect(resolveThemeInfo(app, app.options.theme).plugins).toEqual([ - await getThemePlugin(themePath), + await getThemePlugin(themePath, app), ]) - }), - ) + }) + }) }) describe('should resolve theme info with plugins correctly', () => { - themeEntryTypes.forEach((item) => + THEME_ENTRY_TYPES.forEach((item) => { it(item, async () => { const themePath = fixtures(`themes/${item}.js`) const app = await createTestApp(themePath) expect(resolveThemeInfo(app, app.options.theme).plugins).toEqual([ await importFileDefault(fixtures('plugins/obj.js')), - await getThemePlugin(themePath), + await getThemePlugin(themePath, app), ]) - }), - ) + }) + }) }) }) describe('extends', () => { describe('should resolve theme info with parent theme correctly', () => { - themeEntryTypes.forEach((item) => + THEME_ENTRY_TYPES.forEach((item) => { it(item, async () => { const themePath = fixtures(`themes/${item}-extends-parent.js`) const parentThemePath = fixtures(`themes/${item}.js`) @@ -58,19 +62,19 @@ describe('core > app > resolveThemeInfo', () => { expect(resolveThemeInfo(app, app.options.theme)).toEqual({ plugins: [ await importFileDefault(fixtures('plugins/obj.js')), - await getThemePlugin(parentThemePath), + await getThemePlugin(parentThemePath, app), await importFileDefault(fixtures('plugins/obj-foo.js')), - await getThemePlugin(themePath), + await getThemePlugin(themePath, app), ], templateBuild: `theme-${item}-extends-parent-template-build`, templateDev: `theme-${item}-template-dev`, }) - }), - ) + }) + }) }) describe('should resolve theme info with grandparent theme correctly', () => { - themeEntryTypes.forEach((item) => + THEME_ENTRY_TYPES.forEach((item) => { it(item, async () => { const themePath = fixtures(`themes/${item}-extends-grandparent.js`) const parentThemePath = fixtures(`themes/${item}-extends-parent.js`) @@ -80,17 +84,17 @@ describe('core > app > resolveThemeInfo', () => { expect(resolveThemeInfo(app, app.options.theme)).toEqual({ plugins: [ await importFileDefault(fixtures('plugins/obj.js')), - await getThemePlugin(grandparentThemePath), + await getThemePlugin(grandparentThemePath, app), await importFileDefault(fixtures('plugins/obj-foo.js')), - await getThemePlugin(parentThemePath), + await getThemePlugin(parentThemePath, app), await importFileDefault(fixtures('plugins/obj-bar.js')), - await getThemePlugin(themePath), + await getThemePlugin(themePath, app), ], templateBuild: `theme-${item}-extends-parent-template-build`, templateDev: `theme-${item}-extends-grandparent-template-dev`, }) - }), - ) + }) + }) }) }) }) diff --git a/packages/core/tests/page/createPage.spec.ts b/packages/core/tests/page/createPage.spec.ts index e2f832b2ab..313fed3876 100644 --- a/packages/core/tests/page/createPage.spec.ts +++ b/packages/core/tests/page/createPage.spec.ts @@ -1,18 +1,19 @@ import { path } from '@vuepress/utils' import { beforeAll, describe, expect, it, vi } from 'vitest' +import type { Bundler } from '../../src/index.js' import { createBaseApp, createPage } from '../../src/index.js' -const app = createBaseApp({ - source: path.resolve(__dirname, 'fake-source'), - theme: { name: 'test' }, - bundler: {} as any, -}) +describe('should work without plugins', () => { + const app = createBaseApp({ + source: path.resolve(__dirname, 'fake-source'), + theme: { name: 'test' }, + bundler: {} as Bundler, + }) -beforeAll(async () => { - await app.init() -}) + beforeAll(async () => { + await app.init() + }) -describe('core > page > createPage', () => { it('should throw an error', async () => { const consoleError = console.error console.error = vi.fn() @@ -86,12 +87,14 @@ describe('core > page > createPage', () => { ) expect(page.chunkName).toBeTruthy() }) +}) +describe('should work with plugins', () => { it('should be extended by plugin correctly', async () => { const app = createBaseApp({ source: path.resolve(__dirname, 'fake-source'), theme: { name: 'test' }, - bundler: {} as any, + bundler: {} as Bundler, }) app.use({ name: 'foo', diff --git a/packages/core/tests/page/inferPagePath.spec.ts b/packages/core/tests/page/inferPagePath.spec.ts index 5f34327047..f65aced2ad 100644 --- a/packages/core/tests/page/inferPagePath.spec.ts +++ b/packages/core/tests/page/inferPagePath.spec.ts @@ -1,11 +1,12 @@ import { path } from '@vuepress/utils' import { describe, expect, it } from 'vitest' +import type { Bundler } from '../../src/index.js' import { createBaseApp, inferPagePath } from '../../src/index.js' const app = createBaseApp({ source: path.resolve(__dirname, 'fake-source'), theme: { name: 'test' }, - bundler: {} as any, + bundler: {} as Bundler, locales: { '/': {}, '/en/': {}, @@ -16,10 +17,10 @@ const app = createBaseApp({ const appWithoutLocales = createBaseApp({ source: path.resolve(__dirname, 'fake-source'), theme: { name: 'test' }, - bundler: {} as any, + bundler: {} as Bundler, }) -const testCases: [string, ReturnType][] = [ +const TEST_CASES: [string, ReturnType][] = [ [ 'foo.md', { @@ -50,41 +51,39 @@ const testCases: [string, ReturnType][] = [ ], ] -describe('core > page > inferPagePath', () => { - describe('should infer page path according to relative path of page file', () => { - testCases.forEach(([source, expected]) => { - it(JSON.stringify(source), () => { - expect( - inferPagePath({ - app, - filePathRelative: source, - }), - ).toEqual(expected) - }) +describe('should infer page path according to relative path of page file', () => { + TEST_CASES.forEach(([source, expected]) => { + it(JSON.stringify(source), () => { + expect( + inferPagePath({ + app, + filePathRelative: source, + }), + ).toEqual(expected) }) }) +}) - it('should use `/` as the default locale path', () => { - expect( - inferPagePath({ - app: appWithoutLocales, - filePathRelative: 'en/foo/bar.md', - }), - ).toEqual({ - pathInferred: '/en/foo/bar.html', - pathLocale: '/', - }) +it('should use `/` as the default locale path', () => { + expect( + inferPagePath({ + app: appWithoutLocales, + filePathRelative: 'en/foo/bar.md', + }), + ).toEqual({ + pathInferred: '/en/foo/bar.html', + pathLocale: '/', }) +}) - it('should handle empty file relative path', () => { - expect( - inferPagePath({ - app, - filePathRelative: null, - }), - ).toEqual({ - pathInferred: null, - pathLocale: '/', - }) +it('should handle empty file relative path', () => { + expect( + inferPagePath({ + app, + filePathRelative: null, + }), + ).toEqual({ + pathInferred: null, + pathLocale: '/', }) }) diff --git a/packages/core/tests/page/renderPageContent.spec.ts b/packages/core/tests/page/renderPageContent.spec.ts index 055beaf0a1..c4c3e756fd 100644 --- a/packages/core/tests/page/renderPageContent.spec.ts +++ b/packages/core/tests/page/renderPageContent.spec.ts @@ -1,155 +1,154 @@ import { createMarkdown } from '@vuepress/markdown' import { path } from '@vuepress/utils' import { describe, expect, it } from 'vitest' +import type { Bundler } from '../../src/index.js' import { createBaseApp, renderPageContent } from '../../src/index.js' const app = createBaseApp({ source: path.resolve(__dirname, 'fake-source'), theme: { name: 'test' }, - bundler: {} as any, + bundler: {} as Bundler, }) app.markdown = createMarkdown() -describe('core > page > renderPageContent', () => { - it('should render page content correctly', () => { - const resolved = renderPageContent({ - app, - content: `\ +it('should render page content correctly', () => { + const resolved = renderPageContent({ + app, + content: `\ foobar `, - filePath: app.dir.source('foo.md'), - filePathRelative: 'foo.md', - options: {}, - }) + filePath: app.dir.source('foo.md'), + filePathRelative: 'foo.md', + options: {}, + }) - expect(resolved).toEqual({ - contentRendered: '

foobar

\n', - deps: [], - frontmatter: {}, - headers: [], - links: [], - markdownEnv: { excerpt: '' }, - sfcBlocks: { - template: { - type: 'template', - content: '', - contentStripped: '

foobar

\n', - tagClose: '', - tagOpen: '', + tagOpen: '