github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/ui/plugins/vite-plugin-next-react-router/index.js (about) 1 // src/context.ts 2 import path4 from 'path' 3 import fs2 from 'fs' 4 5 // src/codegen.ts 6 import fs from 'fs' 7 import path3 from 'path' 8 9 // src/resolver.ts 10 import path2 from 'path' 11 import { normalizePath } from 'vite' 12 import fg from 'fast-glob' 13 14 // src/utils.ts 15 import path from 'path' 16 import consola from 'consola' 17 import { upperFirst, camelCase } from 'lodash' 18 19 // package.json 20 var name = 'vite-plugin-next-react-router' 21 22 // src/const.ts 23 var MATCH_ALL_ROUTE = '*' 24 var DEFAULT_EXT = ['tsx', 'ts', 'jsx', 'js'] 25 var DEFAULT_PAGE_DIR = 'src/pages' 26 27 // src/utils.ts 28 var debug = (message, ...args) => { 29 if (process.env.DEBUG === name) { 30 consola.info(message, ...args) 31 } 32 } 33 function isCatchAll(filename) { 34 return /^\[\.{3}/.test(filename) 35 } 36 function isDynamic(filename) { 37 return /^\[(.+)\]$/.test(filename) 38 } 39 function normalizeFilenameToRoute(filename) { 40 if (isCatchAll(filename)) { 41 return MATCH_ALL_ROUTE 42 } 43 if (filename === 'index') { 44 return '/' 45 } 46 return isDynamic(filename) ? parameterizeDynamicRoute(filename) : filename 47 } 48 function parameterizeDynamicRoute(s) { 49 return s.replace(/^\[(.+)\]$/, (_, p) => `:${p}`) 50 } 51 function normalizeDirPathToRoute(dirPath) { 52 return dirPath 53 .split('/') 54 .map(s => (isDynamic(s) ? parameterizeDynamicRoute(s) : s)) 55 .join('/') 56 } 57 function normalizePathToRoute(p) { 58 const { dir, name: name2 } = path.parse(p) 59 const route = normalizeFilenameToRoute(name2) 60 if (route === MATCH_ALL_ROUTE) { 61 return route 62 } 63 return path.resolve(path.join('/', normalizeDirPathToRoute(dir), route)) 64 } 65 function countLength(p) { 66 return path.resolve(p).split('/').filter(Boolean).length 67 } 68 function sorter(a, b) { 69 const len = countLength(a) - countLength(b) 70 if (len !== 0) { 71 return len 72 } 73 return a.localeCompare(b) 74 } 75 function sortRoutes(routes) { 76 return [...routes].sort(sorter) 77 } 78 function getComponentName(filePath) { 79 const segments = filePath.split(path.sep) 80 const extname = path.extname(filePath) 81 const fileName = path.basename(filePath, extname) 82 segments[segments.length - 1] = fileName 83 const name2 = segments.reduce((acc, segment) => { 84 if (isDynamic(segment)) { 85 return acc + upperFirst(camelCase(segment.replace(/^\[(.+)\]$/, '$1'))) 86 } 87 return acc + upperFirst(camelCase(segment)) 88 }, '') 89 return name2 90 } 91 92 // src/resolver.ts 93 function resolvePages(options) { 94 const { root, pageDir, extensions } = options 95 const files = scan(pageDir, extensions, root) 96 return fileToRouteMap(files) 97 } 98 function resolveOptions(userOptions, viteRoot) { 99 var _a, _b, _c, _d 100 const root = viteRoot != null ? viteRoot : normalizePath(process.cwd()) 101 return { 102 root, 103 async: 104 (_a = userOptions == null ? void 0 : userOptions.async) != null 105 ? _a 106 : true, 107 pageDir: 108 (_b = userOptions == null ? void 0 : userOptions.pageDir) != null 109 ? _b 110 : DEFAULT_PAGE_DIR, 111 extensions: 112 (_c = userOptions == null ? void 0 : userOptions.extensions) != null 113 ? _c 114 : DEFAULT_EXT, 115 output: 116 (_d = userOptions == null ? void 0 : userOptions.output) != null 117 ? _d 118 : path2.join(root, 'src', 'routes.tsx'), 119 } 120 } 121 function scan(targetDir, extensions, root) { 122 const fullPathOfTargetDir = path2.resolve(root, targetDir) 123 return fg.sync([`**/*.{${extensions.join()}}`, `!_layout.*`], { 124 onlyFiles: true, 125 cwd: fullPathOfTargetDir, 126 }) 127 } 128 function fileToRouteMap(files) { 129 const map = new Map() 130 files.forEach(file => { 131 const route = normalizePathToRoute(file) 132 map.set(route, file) 133 }) 134 return map 135 } 136 function resolveRoutes(map, options) { 137 const sortedRoutes = sortRoutes([...map.keys()]) 138 return sortedRoutes.map(route => { 139 const filePath = map.get(route) 140 const absolutePath = path2.resolve(options.pageDir, filePath) 141 const routeObj = { 142 path: route, 143 componentPath: absolutePath, 144 componentName: getComponentName(filePath), 145 } 146 if (path2.basename(filePath, path2.extname(filePath)) === 'index') { 147 routeObj.index = true 148 } 149 return routeObj 150 }) 151 } 152 function resolveGlobalLayout(options) { 153 const globalLayoutFiles = fg.sync( 154 [`_layout.{${options.extensions.join()}}`], 155 { 156 onlyFiles: true, 157 cwd: options.pageDir, 158 } 159 ) 160 if (globalLayoutFiles.length === 1) { 161 const filePath = globalLayoutFiles[0] 162 return { 163 componentPath: path2.resolve(options.pageDir, globalLayoutFiles[0]), 164 componentName: 'GlobalLayout', 165 path: filePath, 166 } 167 } else if (globalLayoutFiles.length > 1) { 168 throw new Error('Multiple _layout files found') 169 } 170 return null 171 } 172 173 // src/template.ts 174 var generateRoutesCode = ({ 175 layoutImport: layoutImport2, 176 pageImports, 177 layoutElement, 178 routes, 179 staticPageMetaImports, 180 pages, 181 }) => ` 182 import React from 'react'; 183 ${layoutImport2} 184 ${staticPageMetaImports} 185 ${pageImports} 186 187 export const routes = [ 188 { 189 path: '/', 190 element: ${layoutElement}, 191 children: [ 192 ${routes} 193 ] 194 } 195 ] 196 197 export const pages = [ 198 ${pages} 199 ] 200 ` 201 202 // src/codegen.ts 203 function generateComponentCall(route, options) { 204 return `<${ 205 options.async 206 ? `Dynamic${route.componentName}` 207 : `Static${route.componentName}` 208 } />` 209 } 210 function stripExt(p) { 211 return p.replace(path3.extname(p), '') 212 } 213 function transformToRelativePath(to, from) { 214 return './' + stripExt(path3.relative(path3.dirname(path3.resolve(from)), to)) 215 } 216 var dynamicPageImports = (routes, options) => 217 routes.reduce((acc, route) => { 218 return ( 219 acc + 220 `const Dynamic${ 221 route.componentName 222 } = React.lazy(() => import('${transformToRelativePath( 223 route.componentPath, 224 options.output 225 )}')); 226 ` 227 ) 228 }, '') 229 var staticPageImports = (routes, options) => 230 routes.reduce((acc, route) => { 231 return ( 232 acc + 233 `import Static${route.componentName} from '${transformToRelativePath( 234 route.componentPath, 235 options.output 236 )}' 237 ` 238 ) 239 }, '') 240 var staticMetaImports = (routes, options) => { 241 return routes.reduce((acc, route) => { 242 var _a 243 const routeComponentCode = fs.readFileSync(route.componentPath, 'utf8') 244 if (routeComponentCode.includes('export const meta')) { 245 return ( 246 acc + 247 `import { meta as Static${ 248 route.componentName 249 }Meta } from '${transformToRelativePath( 250 route.componentPath, 251 (_a = options.output) != null 252 ? _a 253 : path3.join(options.root, 'src', 'pages.ts') 254 )}' 255 ` 256 ) 257 } 258 return acc 259 }, '') 260 } 261 var layoutImport = options => { 262 const layout = resolveGlobalLayout(options) 263 let imports = '' 264 if (!layout) { 265 imports += `import { Outlet } from 'react-router-dom';` 266 } 267 imports = layout 268 ? `import ${layout.componentName} from '${transformToRelativePath( 269 layout.componentPath, 270 options.output 271 )}'` 272 : '' 273 const element = `<${layout ? layout.componentName : 'Outlet'} />` 274 return { imports, element } 275 } 276 var routeObjects = (routes, options) => 277 routes 278 .map(route => { 279 return `{ path: '${route.path}', element: ${generateComponentCall( 280 route, 281 options 282 )}, ${route.index ? 'index: true' : ''}}, 283 ` 284 }) 285 .join(' '.repeat(6)) 286 .trim() 287 var pageObjects = routes => 288 routes 289 .filter(r => r.path !== '*') 290 .map(route => { 291 const routeComponentCode = fs.readFileSync(route.componentPath, 'utf8') 292 if (routeComponentCode.includes('export const meta')) { 293 return `{ route: '${route.path}', meta: Static${route.componentName}Meta }, 294 ` 295 } 296 return `{ route: '${route.path}' }, 297 ` 298 }) 299 .join(' '.repeat(2)) 300 .trim() 301 function generateRoutesModuleCode(routes, options) { 302 const { imports, element } = layoutImport(options) 303 const staticPageMetaImports = staticMetaImports(routes, options) 304 const pages = pageObjects(routes) 305 return generateRoutesCode({ 306 layoutImport: imports, 307 pageImports: options.async 308 ? dynamicPageImports(routes, options) 309 : staticPageImports(routes, options), 310 layoutElement: element, 311 routes: routeObjects(routes, options), 312 pages, 313 staticPageMetaImports, 314 }) 315 } 316 317 // src/context.ts 318 function isTarget(p, options) { 319 return ( 320 p.startsWith(path4.resolve(options.pageDir)) && 321 options.extensions.some(ext => p.endsWith(ext)) 322 ) 323 } 324 var Context = class { 325 constructor(userOptions) { 326 this._pages = new Map() 327 this._server = null 328 this.root = '.' 329 this._userOptions = userOptions 330 } 331 resolveOptions() { 332 this._resolvedOptions = resolveOptions(this._userOptions, this.root) 333 } 334 search() { 335 if (!this._resolvedOptions) { 336 this.resolveOptions() 337 } 338 this._pages = resolvePages(this._resolvedOptions) 339 debug('pages: ', this._pages) 340 } 341 configureServer(server) { 342 this._server = server 343 this._server.watcher.on('unlink', filaPath => 344 this.invalidateAndRegenerateRoutes(filaPath) 345 ) 346 this._server.watcher.on('add', filaPath => 347 this.invalidateAndRegenerateRoutes(filaPath) 348 ) 349 } 350 invalidateAndRegenerateRoutes(filaPath) { 351 if (!isTarget(filaPath, this._resolvedOptions)) { 352 return 353 } 354 this._pages.clear() 355 this.generateRoutesModuleCode() 356 } 357 generateRoutesModuleCode() { 358 if (this._pages.size === 0) { 359 this.search() 360 } 361 const routes = resolveRoutes(this._pages, this._resolvedOptions) 362 const code = generateRoutesModuleCode(routes, this._resolvedOptions) 363 debug('generateVirtualRoutesCode: \n', code) 364 fs2.writeFileSync(this._resolvedOptions.output, code) 365 } 366 } 367 368 // src/index.ts 369 function reactRouterPlugin(userOptions) { 370 const ctx = new Context(userOptions) 371 return { 372 name: 'vite-plugin-next-router', 373 enforce: 'pre', 374 async configResolved({ root }) { 375 ctx.root = root 376 ctx.resolveOptions() 377 ctx.search() 378 ctx.generateRoutesModuleCode() 379 }, 380 configureServer(server) { 381 ctx.configureServer(server) 382 }, 383 } 384 } 385 export { reactRouterPlugin }