-
Notifications
You must be signed in to change notification settings - Fork 55
Expand file tree
/
Copy pathvite.config.ts
More file actions
215 lines (193 loc) · 7.04 KB
/
Copy pathvite.config.ts
File metadata and controls
215 lines (193 loc) · 7.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
import { defineConfig, type Plugin, type ResolvedConfig } from 'vite'
import react from '@vitejs/plugin-react'
import mkcert from 'vite-plugin-mkcert'
import path from 'node:path'
import fs from 'node:fs'
// Read plugin config from package.json
const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf-8'))
const PLUGIN_NAME: string = pkg.config.pluginName
const LOADER_PATH: string = pkg.config.loaderPath
const PLUGINS_DIR = path.resolve(LOADER_PATH, 'plugins', PLUGIN_NAME)
/**
* Pengu Loader Dev Server Plugin
* - Generates an index.js entry in the plugins directory
* - Points to the local Vite dev server for HMR
*/
function penguServe(): Plugin {
let port: number
return {
name: 'pengu-serve',
apply: 'serve',
configureServer(server) {
server.httpServer?.on('listening', () => {
const addr = server.httpServer!.address()
if (addr && typeof addr === 'object') {
port = addr.port
}
// Ensure plugins directory exists
fs.mkdirSync(PLUGINS_DIR, { recursive: true })
// Write dev entry index.js
// React Fast Refresh requires both the React preamble and `@vite/client`.
// Since Pengu loads the module directly instead of via Vite-transformed HTML,
// we must install the preamble manually before importing React modules.
const devEntry = [
`const reactPreamble = import("https://localhost:${port}/@react-refresh").then(({ injectIntoGlobalHook }) => {`,
` injectIntoGlobalHook(window);`,
` window.$RefreshReg$ = () => {};`,
` window.$RefreshSig$ = () => (type) => type;`,
` window.__vite_plugin_react_preamble_installed__ = true;`,
`}).catch(e => {`,
` console.error("[Sona] Failed to install react preamble:", e);`,
` throw e;`,
`});`,
`const viteClient = reactPreamble.then(() => import("https://localhost:${port}/@vite/client")).catch(e => {`,
` console.error("[Sona] Failed to load vite client:", e);`,
` throw e;`,
`});`,
`const pluginModule = () => viteClient.then(() => import("https://localhost:${port}/src/index.tsx")).catch(e => {`,
` console.error("[Sona] Failed to load plugin module:", e);`,
` throw e;`,
`});`,
``,
`export function init(context) {`,
`// console.log("[Sona] init called");`,
` pluginModule().then(m => m.init(context)).catch(e => console.error("[Sona] init error:", e));`,
`}`,
``,
`export function load() {`,
`// console.log("[Sona] load called");`,
` pluginModule().then(m => m.load?.()).catch(e => console.error("[Sona] load error:", e));`,
`}`,
].join('\n')
fs.writeFileSync(path.join(PLUGINS_DIR, 'index.js'), devEntry)
console.log(`\n ⚡ Pengu plugin "${PLUGIN_NAME}" dev entry generated`)
console.log(` 📁 ${PLUGINS_DIR}\n`)
})
},
// Transform asset paths in non-JS files to point to dev server
transform(code, id) {
if (/\.(ts|tsx|js|jsx)$/.test(id)) return
return code.replaceAll('/src/', `https://localhost:${port}/src/`)
},
}
}
/**
* Pengu Loader Build Plugin
* - Patches asset paths for PenguLoader's plugin protocol
* - Wraps code for proper load timing
* - Copies build output to plugins directory
*/
function penguBuild(): Plugin {
let config: ResolvedConfig
return {
name: 'pengu-build',
apply: 'build',
configResolved(resolvedConfig) {
config = resolvedConfig
},
closeBundle() {
const outDir = path.resolve(config.build.outDir)
// Remove index.html (not needed for plugins)
const htmlPath = path.join(outDir, 'index.html')
if (fs.existsSync(htmlPath)) {
fs.unlinkSync(htmlPath)
}
// Process index.js - patch asset paths and wrap in load listener
const jsPath = path.join(outDir, 'index.js')
if (fs.existsSync(jsPath)) {
let js = fs.readFileSync(jsPath, 'utf-8')
// Fix asset paths: /assets/ -> //plugins/sona/assets/
js = js.replaceAll('"/assets/', `"//plugins/${PLUGIN_NAME}/assets/`)
js = js.replaceAll("'/assets/", `'//plugins/${PLUGIN_NAME}/assets/`)
// Add CSS import at top
js = `import "./index.css";\n${js}`
// Pengu Loader 插件头注释(识别插件元信息用)
const banner = [
'/**',
' * @name Sona',
` * @version ${pkg.version}`,
' * @description 基于 Pengu Loader 的全服可用英雄联盟客户端增强插件',
' * @author WJZ_P',
' * @link https://github.com/WJZ-P/sona',
' */',
'',
].join('\n')
js = banner + js
fs.writeFileSync(jsPath, js)
}
// Process index.css - fix asset paths
const cssPath = path.join(outDir, 'index.css')
if (fs.existsSync(cssPath)) {
let css = fs.readFileSync(cssPath, 'utf-8')
css = css.replaceAll('url(/assets/', 'url(./assets/')
fs.writeFileSync(cssPath, css)
}
// Copy build output to plugins directory (skip if it's the project root)
const projectRoot = path.resolve(__dirname)
if (path.resolve(PLUGINS_DIR) === projectRoot) {
// Target is the project itself — just move dist contents to project root
copyDirSync(outDir, projectRoot)
console.log(`\n ✅ Plugin "${PLUGIN_NAME}" built in-place (project root)\n`)
} else {
if (fs.existsSync(PLUGINS_DIR)) {
fs.rmSync(PLUGINS_DIR, { recursive: true })
}
fs.mkdirSync(PLUGINS_DIR, { recursive: true })
copyDirSync(outDir, PLUGINS_DIR)
console.log(`\n ✅ Plugin "${PLUGIN_NAME}" built to ${PLUGINS_DIR}\n`)
}
},
}
}
function copyDirSync(src: string, dest: string) {
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
const srcPath = path.join(src, entry.name)
const destPath = path.join(dest, entry.name)
if (entry.isDirectory()) {
fs.mkdirSync(destPath, { recursive: true })
copyDirSync(srcPath, destPath)
} else {
fs.copyFileSync(srcPath, destPath)
}
}
}
export default defineConfig({
define: {
__PLUGIN_VERSION__: JSON.stringify(pkg.version),
'process.env.NODE_ENV': JSON.stringify('production'),
},
plugins: [
react(),
mkcert(),
penguServe(),
penguBuild(),
],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
server: {
port: 3000,
https: {},
origin: 'https://localhost:3000',// 必须加这个,否则引入图片资源等会失败
},
build: {
outDir: 'dist',
lib: {
entry: path.resolve(__dirname, 'src/index.tsx'),
formats: ['es'],
fileName: () => 'index.js',
},
rollupOptions: {
output: {
manualChunks: undefined,
assetFileNames: (assetInfo) => {
if (assetInfo.names?.[0]?.endsWith('.css')) return 'index.css'
return 'assets/[name]-[hash][extname]'
},
},
},
},
publicDir: false,
})