Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/bundler-helper/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# @vuepress/bundler-utils

[![npm](https://badgen.net/npm/v/@vuepress/bundler-utils/next)](https://www.npmjs.com/package/@vuepress/bundler-utils)
[![license](https://badgen.net/github/license/vuepress/core)](https://github.com/vuepress/core/blob/main/LICENSE)

## Documentation

https://vuepress.vuejs.org

## License

[MIT](https://github.com/vuepress/core/blob/main/LICENSE)
66 changes: 66 additions & 0 deletions packages/bundler-helper/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"name": "@vuepress/bundlerhelper",
"version": "2.0.0-rc.30",
"description": "Helper package of VuePress bundlers",
"keywords": [
"bundler",
"utils",
"vuepress"
],
"homepage": "https://github.com/vuepress",
"bugs": {
"url": "https://github.com/vuepress/core/issues"
},
"license": "MIT",
"author": "Mister-Hope",
"repository": {
"type": "git",
"url": "git+https://github.com/vuepress/core.git"
},
"files": [
"dist"
],
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": "./dist/index.js",
"./package.json": "./package.json"
},
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "tsdown",
"clean": "rimraf dist"
},
"dependencies": {
"@vuepress/core": "workspace:*",
"@vuepress/shared": "workspace:*",
"@vuepress/utils": "workspace:*"
},
"devDependencies": {
"@types/connect": "^3.4.38"
},
"peerDependencies": {
"@vuepress/bundler-vite": "workspace:*",
"@vuepress/bundler-webpack": "workspace:*"
},
"peerDependenciesMeta": {
"@vuepress/bundler-vite": {
"optional": true
},
"@vuepress/bundler-webpack": {
"optional": true
}
},
"tsdown": {
"dts": true,
"entry": "./src/index.ts",
"fixedExtension": false,
"format": "esm",
"target": "es2023",
"tsconfig": "../../tsconfig.dts.json"
}
}
96 changes: 96 additions & 0 deletions packages/bundler-helper/src/addCustomElement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import type { ViteBundlerOptions } from '@vuepress/bundler-vite'
import type { WebpackBundlerOptions } from '@vuepress/bundler-webpack'
import type { App } from '@vuepress/core'
import { isString } from '@vuepress/shared'

import { getBundlerName } from './getBundlerName.js'

/**
* Add tags as customElement
*
* @example
* // Add single custom element
* addCustomElement(bundlerOptions, app, 'my-element')
*
* // Add multiple custom elements
* addCustomElement(bundlerOptions, app, ['element1', 'element2'])
*
* // Add elements matching a pattern
* addCustomElement(bundlerOptions, app, /^my-/)
*
* @param bundlerOptions - VuePress Bundler config
* @param app - VuePress Node App
* @param customElement - Tags recognized as custom element
*/
export const addCustomElement = (
bundlerOptions: unknown,
app: App,
customElement: RegExp | string[] | string,
): void => {
const customElements = isString(customElement)
? [customElement]
: customElement
const bundlerName = getBundlerName(app)

switch (bundlerName) {
// for vite
case 'vite': {
const viteBundlerConfig = bundlerOptions as ViteBundlerOptions

viteBundlerConfig.vuePluginOptions ??= {}
viteBundlerConfig.vuePluginOptions.template ??= {}
viteBundlerConfig.vuePluginOptions.template.compilerOptions ??= {}
const { isCustomElement } =
viteBundlerConfig.vuePluginOptions.template.compilerOptions

/**
* @param tag - The tag name to check
* @returns Whether the tag is a custom element
* @see https://github.com/vitejs/vite-plugin-vue/blob/main/packages/plugin-vue/README.md
*/
viteBundlerConfig.vuePluginOptions.template.compilerOptions.isCustomElement =
(tag: string): boolean | void => {
if (
customElements instanceof RegExp
? customElements.test(tag)
: customElements.includes(tag)
)
return true

return isCustomElement?.(tag)
}
break
}
// for webpack
case 'webpack': {
const webpackBundlerConfig = bundlerOptions as WebpackBundlerOptions

webpackBundlerConfig.vue ??= {}
webpackBundlerConfig.vue.compilerOptions ??= {}
const { isCustomElement } = webpackBundlerConfig.vue.compilerOptions

/**
* @param tag - The tag name to check
* @returns Whether the tag is a custom element
* @see https://vue-loader.vuejs.org/options.html#compileroptions
*/
webpackBundlerConfig.vue.compilerOptions.isCustomElement = (
tag: string,
): boolean | void => {
if (
customElements instanceof RegExp
? customElements.test(tag)
: customElements.includes(tag)
)
return true

return isCustomElement?.(tag)
}
break
}
default: {
// eslint-disable-next-line no-console
console.error(`[addCustomElement]: ${bundlerName} is not supported yet.`)
}
}
}
134 changes: 134 additions & 0 deletions packages/bundler-helper/src/customizeDevServer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import type { IncomingMessage, ServerResponse } from 'node:http'

import type { ViteBundlerOptions } from '@vuepress/bundler-vite'
import type {
WebpackBundlerOptions,
WebpackDevServer,
} from '@vuepress/bundler-webpack'
import type { App } from '@vuepress/core'
import { removeLeadingSlash } from '@vuepress/shared'
import type { HandleFunction } from 'connect'
import type { Plugin } from 'vite'

import { getBundlerName } from './getBundlerName.js'
import { mergeViteConfig } from './vite/index.js'

/** Options for customizing VuePress Dev Server */
export interface DevServerOptions {
/** Path to be responded */
path: string
/** Respond handler */
response: (
request: IncomingMessage,
response: ServerResponse,
) => Promise<Buffer | string>

/** Error msg */
errMsg?: string
}

/**
* Handle specific path when running VuePress Dev Server
*
* @example
* // handle `/api/` path
* useCustomDevServer(bundlerOptions, app, {
* path: '/api/',
* response: async () => {
* const data = await prepareYourData();
* return JSON.stringify({ message: 'Hello from custom dev server!' })
* },
* errMsg: 'Unexpected api error',
* })
*
* @param bundlerOptions - VuePress Bundler config
* @param app - VuePress Node App
* @param options - Dev server options
*/
export const customizeDevServer = (
bundlerOptions: unknown,
app: App,
{
errMsg = 'The server encountered an error',
response: responseHandler,
path,
}: DevServerOptions,
): void => {
// must in dev
if (!app.env.isDev) return

const { base } = app.siteData
const bundlerName = getBundlerName(app)

switch (bundlerName) {
// for vite
case 'vite': {
const viteBundlerOptions = bundlerOptions as ViteBundlerOptions
const handler: HandleFunction = (
request: IncomingMessage,
response: ServerResponse,
) => {
responseHandler(request, response)
.then((data) => {
response.statusCode = 200
response.end(data)
})
.catch(() => {
response.statusCode = 500
response.end(errMsg)
})
}

const viteMockRequestPlugin: Plugin = {
name: `virtual:dev-server-mock/${path}`,
configureServer: ({ middlewares }) => {
middlewares.use(`${base}${removeLeadingSlash(path)}`, handler)
},
}

viteBundlerOptions.viteOptions = mergeViteConfig(
viteBundlerOptions.viteOptions ?? {},
{ plugins: [viteMockRequestPlugin] },
)
break
}
// for webpack
case 'webpack': {
const webpackBundlerOptions = bundlerOptions as WebpackBundlerOptions

const { devServerSetupMiddlewares } = webpackBundlerOptions

/**
* @param middlewares - Existing middlewares
* @param server - Webpack Dev Server instance
* @returns Updated middlewares
* @see https://webpack.js.org/configuration/dev-server/#devserversetupmiddlewares
*/
webpackBundlerOptions.devServerSetupMiddlewares = (
middlewares: WebpackDevServer.Middleware[],
server: WebpackDevServer,
): WebpackDevServer.Middleware[] => {
server.app?.get(
`${base}${removeLeadingSlash(path)}`,
(request, response) => {
responseHandler(request, response)
.then((data) => response.status(200).send(data))
.catch(() => response.status(500).send(errMsg))
},
)

return devServerSetupMiddlewares
? devServerSetupMiddlewares(middlewares, server)
: middlewares
}
break
}

default: {
// eslint-disable-next-line no-console
console.error(
`[customizeDevServer]: ${bundlerName} is not supported yet.`,
)
}
}
}
19 changes: 19 additions & 0 deletions packages/bundler-helper/src/getBundlerName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { App } from '@vuepress/core'

/**
* Get short bundler name
*
* @example
* // With @vuepress/bundler-vite
* getBundlerName(app) // 'vite'
* // With @vuepress/bundler-webpack
* getBundlerName(app) // 'webpack'
*
* @param app - VuePress Node App
* @returns Short bundler name
*/
export const getBundlerName = (app: App): string => {
const { name } = app.options.bundler

return /^@vuepress\/bundler-(.*)$/u.exec(name)?.[1] ?? name
}
5 changes: 5 additions & 0 deletions packages/bundler-helper/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './addCustomElement.js'
export * from './customizeDevServer.js'
export * from './getBundlerName.js'
export * from './vite/index.js'
export * from './webpack/index.js'
2 changes: 2 additions & 0 deletions packages/bundler-helper/src/vite/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './mergeViteConfig.js'
export * from './viteHelper.js'
Loading
Loading