feat(plugin-netlify-functions): 新增 netlify-functions 插件
1. 站点部署在 netlify时,提供 netlify functions 支持; 2. 支持functions开发时调试; 3. 支持其他插件使用本插件开发功能; 3. 支持 dotenv 设置环境变量
This commit is contained in:
parent
f721129a35
commit
10cfbdb80f
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -23,9 +23,12 @@
|
|||||||
"commitlint",
|
"commitlint",
|
||||||
"composables",
|
"composables",
|
||||||
"Docsearch",
|
"Docsearch",
|
||||||
|
"esbuild",
|
||||||
"gsap",
|
"gsap",
|
||||||
|
"iarna",
|
||||||
"nprogress",
|
"nprogress",
|
||||||
"pnpm",
|
"pnpm",
|
||||||
|
"portfinder",
|
||||||
"Tongji",
|
"Tongji",
|
||||||
"tsbuildinfo",
|
"tsbuildinfo",
|
||||||
"vite",
|
"vite",
|
||||||
|
|||||||
21
packages/plugin-netlify-functions/LICENSE
Normal file
21
packages/plugin-netlify-functions/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (C) 2021 - PRESENT by pengzhanbo
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
17
packages/plugin-netlify-functions/README.md
Normal file
17
packages/plugin-netlify-functions/README.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# `@vuepress-plume/vuepress-plugin-netlify-functions`
|
||||||
|
|
||||||
|
## Install
|
||||||
|
```
|
||||||
|
yarn add @vuepress-plume/vuepress-plugin-netlify-functions
|
||||||
|
```
|
||||||
|
## Usage
|
||||||
|
``` js
|
||||||
|
// .vuepress/config.js
|
||||||
|
module.exports = {
|
||||||
|
//...
|
||||||
|
plugins: [
|
||||||
|
netlifyFunctionsPlugin()
|
||||||
|
]
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
53
packages/plugin-netlify-functions/package.json
Normal file
53
packages/plugin-netlify-functions/package.json
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"name": "@vuepress-plume/vuepress-plugin-netlify-functions",
|
||||||
|
"version": "1.0.0-beta.30",
|
||||||
|
"description": "The Plugin for VuePres 2, Support Netlify Functions",
|
||||||
|
"homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "pengzhanbo <volodymyr@foxmail.com>",
|
||||||
|
"main": "lib/node/index.js",
|
||||||
|
"files": [
|
||||||
|
"lib"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "pnpm run clean && pnpm run copy && pnpm run ts",
|
||||||
|
"clean": "rimraf lib *.tsbuildinfo",
|
||||||
|
"copy": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png}\" lib",
|
||||||
|
"copy:watch": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png}\" lib -w",
|
||||||
|
"dev": "concurrently \"pnpm copy:watch\" \"pnpm ts:watch\"",
|
||||||
|
"ts": "tsc -b tsconfig.build.json",
|
||||||
|
"ts:watch": "tsc -b tsconfig.build.json --watch"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@iarna/toml": "^2.2.5",
|
||||||
|
"@netlify/functions": "^1.0.0",
|
||||||
|
"@vuepress/core": "2.0.0-beta.43",
|
||||||
|
"@vuepress/shared": "2.0.0-beta.43",
|
||||||
|
"@vuepress/utils": "2.0.0-beta.43",
|
||||||
|
"chokidar": "^3.5.3",
|
||||||
|
"cpx2": "^4.2.0",
|
||||||
|
"dotenv": "^16.0.0",
|
||||||
|
"esbuild": "^0.14.38",
|
||||||
|
"execa": "5.1.1",
|
||||||
|
"netlify-cli": "^10.3.0",
|
||||||
|
"portfinder": "^1.0.28"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"keyword": [
|
||||||
|
"VuePress",
|
||||||
|
"vuepress plugin",
|
||||||
|
"netlify",
|
||||||
|
"netlify functions",
|
||||||
|
"netlifyFunctions",
|
||||||
|
"vuepress-plugin-plugin-netlify-functions"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
import type { App } from '@vuepress/core'
|
||||||
|
import type { NetlifyFunctionsPluginOptions } from '../shared'
|
||||||
|
export const extendsBundlerOptions = (
|
||||||
|
bundlerOption: any,
|
||||||
|
app: App,
|
||||||
|
options: NetlifyFunctionsPluginOptions,
|
||||||
|
server: string
|
||||||
|
): void => {
|
||||||
|
// 在 netlify-cli 的 function:serve 中,
|
||||||
|
// 默认就是 指向 /.netlify/functions
|
||||||
|
// 而配置的 --functions 仅作为源文件入口
|
||||||
|
const targetPath = '/.netlify/functions'
|
||||||
|
if (app.options.bundler.name === '@vuepress/bundler-vite') {
|
||||||
|
const rewriteRE = new RegExp(`^${options.proxyPrefix}`)
|
||||||
|
bundlerOption.viteOptions.server = bundlerOption.viteOptions.server || {}
|
||||||
|
const viteServer = bundlerOption.viteOptions.server
|
||||||
|
// 将 netlify functions server 代理到 当前的 vuepress 开发 服务器上
|
||||||
|
viteServer.proxy = Object.assign(viteServer.proxy || {}, {
|
||||||
|
[options.proxyPrefix as string]: {
|
||||||
|
target: server,
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (url: string) => url.replace(rewriteRE, targetPath),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (app.options.bundler.name === '@vuepress/bundler-webpack') {
|
||||||
|
const rewritePath = `^${options.proxyPrefix}`
|
||||||
|
bundlerOption.configureWebpack((config, isServer, isBuild) => {
|
||||||
|
if (isBuild) return
|
||||||
|
config.devServer = config.devServer || {}
|
||||||
|
config.devServer.proxy = Object.assign(config.devServer.proxy || {}, {
|
||||||
|
[options.proxyPrefix as string]: {
|
||||||
|
target: server,
|
||||||
|
changeOrigin: true,
|
||||||
|
pathRewrite: { [rewritePath]: targetPath },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
10
packages/plugin-netlify-functions/src/node/index.ts
Normal file
10
packages/plugin-netlify-functions/src/node/index.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import type { NetlifyFunctionsOptions } from '../shared'
|
||||||
|
import { netlifyFunctionsPlugin } from './plugin'
|
||||||
|
|
||||||
|
export * from './useNetlifyFunctionsPlugins'
|
||||||
|
|
||||||
|
export { NetlifyFunctionsOptions }
|
||||||
|
|
||||||
|
export { netlifyFunctionsPlugin }
|
||||||
|
|
||||||
|
export default netlifyFunctionsPlugin
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
export * from './initFunctions'
|
||||||
|
export * from './netlifyConfig'
|
||||||
|
export * from './netlifyServer'
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
import type { App } from '@vuepress/core'
|
||||||
|
import { path } from '@vuepress/utils'
|
||||||
|
import * as chokidar from 'chokidar'
|
||||||
|
import esbuild from 'esbuild'
|
||||||
|
import type { NetlifyFunctionsPluginOptions } from '../../shared'
|
||||||
|
import { readFileList } from '../utils'
|
||||||
|
|
||||||
|
export const generateFunctions = async (
|
||||||
|
app: App,
|
||||||
|
options: NetlifyFunctionsPluginOptions
|
||||||
|
): Promise<void> => {
|
||||||
|
const { directory } = options
|
||||||
|
const { source, dest } = directory
|
||||||
|
const userSource = source[0]
|
||||||
|
const files = readFileList(userSource)
|
||||||
|
|
||||||
|
if (files.length > 0) {
|
||||||
|
await esbuild.build({
|
||||||
|
entryPoints: files,
|
||||||
|
outbase: userSource,
|
||||||
|
outdir: dest,
|
||||||
|
platform: 'node',
|
||||||
|
format: 'cjs',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initialFunctions = async (
|
||||||
|
app: App,
|
||||||
|
options: NetlifyFunctionsPluginOptions
|
||||||
|
): Promise<void> => {
|
||||||
|
if (!app.env.isDev) return
|
||||||
|
const { directory } = options
|
||||||
|
const { source, temp } = directory
|
||||||
|
const userSource = source[0]
|
||||||
|
const files = readFileList(userSource)
|
||||||
|
|
||||||
|
if (files.length > 0) {
|
||||||
|
await esbuild.build({
|
||||||
|
entryPoints: files,
|
||||||
|
outbase: userSource,
|
||||||
|
outdir: temp,
|
||||||
|
platform: 'node',
|
||||||
|
format: 'cjs',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
watchFunctions(app, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const watchFunctions = (
|
||||||
|
app: App,
|
||||||
|
{ directory }: NetlifyFunctionsPluginOptions
|
||||||
|
): void => {
|
||||||
|
const { source, temp } = directory
|
||||||
|
const userSource = source[0]
|
||||||
|
const watcher = chokidar.watch('**/*.ts', {
|
||||||
|
cwd: userSource,
|
||||||
|
ignoreInitial: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
watcher.on('add', async (file: string) => {
|
||||||
|
await esbuild.build({
|
||||||
|
entryPoints: [path.join(userSource, file)],
|
||||||
|
outbase: userSource,
|
||||||
|
outdir: temp,
|
||||||
|
platform: 'node',
|
||||||
|
format: 'cjs',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
watcher.on('change', async (file: string) => {
|
||||||
|
await esbuild.build({
|
||||||
|
entryPoints: [path.join(userSource, file)],
|
||||||
|
outbase: userSource,
|
||||||
|
outdir: temp,
|
||||||
|
platform: 'node',
|
||||||
|
format: 'cjs',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
import type { JsonMap } from '@iarna/toml'
|
||||||
|
import { parse, stringify } from '@iarna/toml'
|
||||||
|
import type { App } from '@vuepress/core'
|
||||||
|
import { fs, path } from '@vuepress/utils'
|
||||||
|
import type { NetlifyFunctionsPluginOptions } from '../../shared'
|
||||||
|
|
||||||
|
export interface NetlifyConfig {
|
||||||
|
functions: Record<string, any>
|
||||||
|
redirects: Record<string, any>[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const configName = 'netlify.toml'
|
||||||
|
|
||||||
|
const readConfig = (filepath: string): NetlifyConfig => {
|
||||||
|
let netlifyConfig = ''
|
||||||
|
if (fs.existsSync(filepath)) {
|
||||||
|
netlifyConfig = fs.readFileSync(filepath, 'utf-8') || ''
|
||||||
|
}
|
||||||
|
return (parse(netlifyConfig) as unknown as NetlifyConfig) || {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const writeConfig = (filepath: string, netlifyConfig: NetlifyConfig): void => {
|
||||||
|
fs.writeFileSync(
|
||||||
|
filepath,
|
||||||
|
stringify(netlifyConfig as unknown as JsonMap),
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveFunctions = (
|
||||||
|
config: NetlifyConfig,
|
||||||
|
{ directory }: NetlifyFunctionsPluginOptions,
|
||||||
|
app: App
|
||||||
|
): void => {
|
||||||
|
const functions = (config.functions = config.functions || {})
|
||||||
|
functions.directory =
|
||||||
|
functions.directory || path.relative(app.dir.dest('../'), directory.dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveRedirects = (
|
||||||
|
config: NetlifyConfig,
|
||||||
|
{ proxyPrefix }: NetlifyFunctionsPluginOptions
|
||||||
|
): void => {
|
||||||
|
const funcDir = '/' + (config.functions.directory || '').replace(/^\//, '')
|
||||||
|
const redirects = (config.redirects = config.redirects || [])
|
||||||
|
if (!redirects.some((redirect) => redirect?.to?.startsWith(funcDir))) {
|
||||||
|
redirects.push({
|
||||||
|
from: path.join('/', proxyPrefix, '*'),
|
||||||
|
to: path.join(funcDir, ':splat'),
|
||||||
|
status: 200,
|
||||||
|
force: true,
|
||||||
|
Headers: {
|
||||||
|
'X-From': 'Netlify',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const generateNetlifyConfig = (
|
||||||
|
app: App,
|
||||||
|
options: NetlifyFunctionsPluginOptions
|
||||||
|
): NetlifyConfig => {
|
||||||
|
const configPath = path.join(process.cwd(), configName)
|
||||||
|
const config = readConfig(configPath)
|
||||||
|
|
||||||
|
resolveFunctions(config, options, app)
|
||||||
|
resolveRedirects(config, options)
|
||||||
|
|
||||||
|
writeConfig(configPath, config)
|
||||||
|
return config
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
import { fs, path } from '@vuepress/utils'
|
||||||
|
import dotenv from 'dotenv'
|
||||||
|
import * as execa from 'execa'
|
||||||
|
import * as portFinder from 'portfinder'
|
||||||
|
import type { NetlifyFunctionsPluginOptions } from '../../shared'
|
||||||
|
|
||||||
|
const loadEnvConfig = (): Record<string, string | undefined> => {
|
||||||
|
const configPath = path.resolve(process.cwd(), '.env')
|
||||||
|
if (!fs.existsSync(configPath)) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const content = fs.readFileSync(configPath, 'utf-8')
|
||||||
|
return dotenv.parse(Buffer.from(content))
|
||||||
|
} catch {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const netlifyServe = async ({
|
||||||
|
directory,
|
||||||
|
}: NetlifyFunctionsPluginOptions): Promise<string> => {
|
||||||
|
const port = await portFinder.getPortPromise({ port: 9000 })
|
||||||
|
|
||||||
|
const argv = [
|
||||||
|
'functions:serve',
|
||||||
|
'--port',
|
||||||
|
port + '',
|
||||||
|
'--functions',
|
||||||
|
path.join('./', path.relative(process.cwd(), directory.temp)),
|
||||||
|
// '--debug',
|
||||||
|
]
|
||||||
|
|
||||||
|
const { stdout } = execa(
|
||||||
|
path.resolve(__dirname, '../../../node_modules/.bin/netlify'),
|
||||||
|
argv,
|
||||||
|
{
|
||||||
|
cwd: process.cwd(),
|
||||||
|
env: {
|
||||||
|
...loadEnvConfig(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
stdout?.pipe(process.stdout)
|
||||||
|
|
||||||
|
return 'http://localhost:' + port
|
||||||
|
}
|
||||||
102
packages/plugin-netlify-functions/src/node/plugin.ts
Normal file
102
packages/plugin-netlify-functions/src/node/plugin.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/**
|
||||||
|
* 如果是把 vuepress 站点部署的 netlify上,那么可以借助 netlify 上的 functions 功能,
|
||||||
|
* 为我们的站点提供 serverless 能力。
|
||||||
|
*
|
||||||
|
* 比如,通过这个插件,在functions 中 连接 类似 firebase 等 数据存储服务,
|
||||||
|
* 可以用来存储一些如 页面访问次数、访客总数等。还可以基于此来开发如 博客 留言墙、说说 等功能
|
||||||
|
*
|
||||||
|
* 为了使这个插件能够更加通用,同时可以成为其他插件的依赖
|
||||||
|
* 在设计上,本插件仅作为 netlify functions server,
|
||||||
|
* 并将提代理到 vuepress 的开发服务器上。
|
||||||
|
* 同时收集 用户自定义的 functions 脚本,以及其他插件提供的 functions 脚本
|
||||||
|
*
|
||||||
|
* 在开发环境中,所有脚本 默认都被 编译到 .vuepress/.temp/functions 目录中。
|
||||||
|
* netlify 本地 functions server 也将指向这个目录。
|
||||||
|
* 启动的服务器, 请求functions的地址为:
|
||||||
|
* http://localhost:{port}/.netlify/functions/{function-name}
|
||||||
|
*
|
||||||
|
* 出于通用性考虑,本插件应该优先于 依赖本插件的其他插件 之前进行加载。
|
||||||
|
*
|
||||||
|
* 在其他插件中应该使用 useNetlifyFunctionsPlugin() 来获取 本插件的功能
|
||||||
|
*
|
||||||
|
* 待解决的问题:
|
||||||
|
* - 其他插件如果使用了非 用户定义的 proxyPrefix, 是否要在 netlify.toml 中
|
||||||
|
* 再生成一个 redirect 配置
|
||||||
|
* - 如何解决可能存在的 插件之间的 functions 命名冲突
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import type { App, Plugin } from '@vuepress/core'
|
||||||
|
import type {
|
||||||
|
NetlifyFunctionsOptions,
|
||||||
|
NetlifyFunctionsPluginOptions,
|
||||||
|
} from '../shared'
|
||||||
|
import { extendsBundlerOptions } from './extendsBundlerOptions'
|
||||||
|
import {
|
||||||
|
generateFunctions,
|
||||||
|
generateNetlifyConfig,
|
||||||
|
initialFunctions,
|
||||||
|
netlifyServe,
|
||||||
|
} from './netlify'
|
||||||
|
|
||||||
|
const initOptions = (
|
||||||
|
app: App,
|
||||||
|
{
|
||||||
|
sourceDirectory,
|
||||||
|
destDirectory,
|
||||||
|
proxyPrefix = '/api',
|
||||||
|
}: NetlifyFunctionsOptions
|
||||||
|
): NetlifyFunctionsPluginOptions => ({
|
||||||
|
directory: {
|
||||||
|
source: [sourceDirectory || app.dir.source('.vuepress/functions')],
|
||||||
|
dest: destDirectory || app.dir.dest('functions'),
|
||||||
|
temp: app.dir.temp('functions'),
|
||||||
|
},
|
||||||
|
proxyPrefix,
|
||||||
|
})
|
||||||
|
|
||||||
|
const cache = {
|
||||||
|
options: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getOptions = (): NetlifyFunctionsPluginOptions => {
|
||||||
|
return cache.options as NetlifyFunctionsPluginOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* netlify function 插件,提供 netlify functions 支持
|
||||||
|
*
|
||||||
|
* @param options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const netlifyFunctionsPlugin = (
|
||||||
|
options: NetlifyFunctionsOptions = {}
|
||||||
|
): Plugin => {
|
||||||
|
return (app: App) => {
|
||||||
|
const opts = initOptions(app, options)
|
||||||
|
cache.options = opts
|
||||||
|
let server = ''
|
||||||
|
return {
|
||||||
|
name: '@vuepress-plume/vuepress-plugin-netlify-functions',
|
||||||
|
|
||||||
|
onInitialized: async (app) => {
|
||||||
|
// 启动netlify functions server
|
||||||
|
if (!app.env.isBuild) {
|
||||||
|
server = await netlifyServe(opts)
|
||||||
|
// 初始化用户侧的 functions
|
||||||
|
await initialFunctions(app, opts)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
extendsBundlerOptions: (bundlerOption, app: App) => {
|
||||||
|
extendsBundlerOptions(bundlerOption, app, opts, server)
|
||||||
|
},
|
||||||
|
|
||||||
|
onGenerated: async (app: App) => {
|
||||||
|
// 生成配置文件
|
||||||
|
generateNetlifyConfig(app, opts)
|
||||||
|
await generateFunctions(app, opts)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
packages/plugin-netlify-functions/src/node/shim.d.ts
vendored
Normal file
5
packages/plugin-netlify-functions/src/node/shim.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
declare module 'cpx2' {
|
||||||
|
const watch: any
|
||||||
|
const copy: any
|
||||||
|
export { watch, copy }
|
||||||
|
}
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
import type { App, PluginObject } from '@vuepress/core'
|
||||||
|
import { path } from '@vuepress/utils'
|
||||||
|
import * as cpx2 from 'cpx2'
|
||||||
|
import type { UseNetlifyFunctionPluginsOptions } from '../shared'
|
||||||
|
import { getOptions, netlifyFunctionsPlugin } from './plugin'
|
||||||
|
|
||||||
|
interface UseNetlifyFunctionResult {
|
||||||
|
/**
|
||||||
|
* functions 请求前缀
|
||||||
|
*/
|
||||||
|
proxyPrefix: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在 插件的 onPrepare 钩子中调用,监听 当前插件的 functions 目录
|
||||||
|
*/
|
||||||
|
preparePluginFunctions: () => void
|
||||||
|
/**
|
||||||
|
* 在 插件的 onGenerate 钩子中调用,将 当前插件的 functions 文件推送到 dest中
|
||||||
|
*/
|
||||||
|
generatePluginFunctions: () => void
|
||||||
|
}
|
||||||
|
export const useNetlifyFunctionsPlugin = (
|
||||||
|
app: App,
|
||||||
|
options: UseNetlifyFunctionPluginsOptions
|
||||||
|
): UseNetlifyFunctionResult => {
|
||||||
|
if (typeof options === 'undefined') {
|
||||||
|
throw new Error('useNetlifyFunctionsPlugin [options] argument not found.')
|
||||||
|
}
|
||||||
|
if (typeof options.directory !== 'string' || !options.directory) {
|
||||||
|
throw new Error(
|
||||||
|
`useNetlifyFunctionsPlugin [options.directory] must be a string\n exp: path.join(__dirname, 'functions')`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const plugins = app.pluginApi.plugins
|
||||||
|
if (
|
||||||
|
!plugins.some(
|
||||||
|
(plugin: PluginObject) =>
|
||||||
|
plugin.name === '@vuepress-plume/vuepress-plugin-netlify-functions'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
app.use(netlifyFunctionsPlugin())
|
||||||
|
}
|
||||||
|
const { proxyPrefix, directory } = getOptions()
|
||||||
|
const source = path.join(options.directory, '**/*.js')
|
||||||
|
|
||||||
|
function preparePluginFunctions(): void {
|
||||||
|
if (!app.env.isBuild) {
|
||||||
|
cpx2.watch(source, directory.temp, {
|
||||||
|
ignore: ['!**/*.d.js'],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generatePluginFunctions(): void {
|
||||||
|
cpx2.copy(source, directory.dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { proxyPrefix, preparePluginFunctions, generatePluginFunctions }
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './readFileList'
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
import { fs, path } from '@vuepress/utils'
|
||||||
|
|
||||||
|
export const readFileList = (
|
||||||
|
source: string,
|
||||||
|
fileList: string[] = []
|
||||||
|
): string[] => {
|
||||||
|
const files = fs.readdirSync(source)
|
||||||
|
files.forEach((file: string) => {
|
||||||
|
const filepath = path.join(source, file)
|
||||||
|
const stat = fs.statSync(filepath)
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
if (file !== 'node_modules') {
|
||||||
|
readFileList(filepath, fileList)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fileList.push(filepath)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return fileList
|
||||||
|
}
|
||||||
39
packages/plugin-netlify-functions/src/shared/index.ts
Normal file
39
packages/plugin-netlify-functions/src/shared/index.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
export interface NetlifyFunctionsOptions {
|
||||||
|
/**
|
||||||
|
* netlify functions source directory
|
||||||
|
*
|
||||||
|
* netlify functions 源码目录
|
||||||
|
*
|
||||||
|
* @default `app.dir.source('.vuepress/functions')`
|
||||||
|
*/
|
||||||
|
sourceDirectory?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* netlify functions output directory
|
||||||
|
*
|
||||||
|
* netlify functions 输出目录
|
||||||
|
*
|
||||||
|
* @default `app.dir.dest('functions')`
|
||||||
|
*/
|
||||||
|
destDirectory?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求代理来源,将会转发到 functions directory
|
||||||
|
*
|
||||||
|
* @default `/api`
|
||||||
|
*/
|
||||||
|
proxyPrefix?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NetlifyFunctionsPluginOptions {
|
||||||
|
directory: {
|
||||||
|
dest: string
|
||||||
|
source: string[]
|
||||||
|
temp: string
|
||||||
|
}
|
||||||
|
proxyPrefix: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseNetlifyFunctionPluginsOptions {
|
||||||
|
directory: string
|
||||||
|
}
|
||||||
9
packages/plugin-netlify-functions/tsconfig.build.json
Normal file
9
packages/plugin-netlify-functions/tsconfig.build.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.cjs.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"files": []
|
||||||
|
}
|
||||||
9
packages/plugin-netlify-functions/tsconfig.cjs.json
Normal file
9
packages/plugin-netlify-functions/tsconfig.cjs.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "CommonJS",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"outDir": "./lib"
|
||||||
|
},
|
||||||
|
"include": ["./src/node", "./src/shared"]
|
||||||
|
}
|
||||||
6468
pnpm-lock.yaml
generated
6468
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,7 @@
|
|||||||
"references": [
|
"references": [
|
||||||
{ "path": "./packages/theme/tsconfig.build.json" },
|
{ "path": "./packages/theme/tsconfig.build.json" },
|
||||||
{ "path": "./packages/plugin-caniuse/tsconfig.build.json" },
|
{ "path": "./packages/plugin-caniuse/tsconfig.build.json" },
|
||||||
{ "path": "./packages/plugin-copy-code/tsconfig.build.json" }
|
{ "path": "./packages/plugin-copy-code/tsconfig.build.json" },
|
||||||
|
{ "path": "./packages/plugin-netlify-functions/tsconfig.build.json" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user