From d8ab7f47162c41586505e2006ff3d846ea092e94 Mon Sep 17 00:00:00 2001 From: zoubingwu Date: Fri, 22 Sep 2023 14:07:36 +0800 Subject: [PATCH] fix(ui): move vite plugin to repo --- dm/ui/.eslintrc.js | 2 +- dm/ui/package.json | 4 +- .../vite-plugin-i18next-scanner/index.js | 300 ++++++++++++++ .../vite-plugin-i18next-scanner/package.json | 37 ++ .../vite-plugin-i18next-scanner/worker.js | 85 ++++ .../vite-plugin-next-react-router/index.js | 385 ++++++++++++++++++ .../package.json | 22 + dm/ui/tsconfig.json | 2 +- dm/ui/vite.config.js | 8 +- dm/ui/yarn.lock | 10 +- 10 files changed, 840 insertions(+), 15 deletions(-) create mode 100644 dm/ui/plugins/vite-plugin-i18next-scanner/index.js create mode 100644 dm/ui/plugins/vite-plugin-i18next-scanner/package.json create mode 100644 dm/ui/plugins/vite-plugin-i18next-scanner/worker.js create mode 100644 dm/ui/plugins/vite-plugin-next-react-router/index.js create mode 100644 dm/ui/plugins/vite-plugin-next-react-router/package.json diff --git a/dm/ui/.eslintrc.js b/dm/ui/.eslintrc.js index 2b9b96096a2..565ca478df2 100644 --- a/dm/ui/.eslintrc.js +++ b/dm/ui/.eslintrc.js @@ -1,6 +1,6 @@ module.exports = { extends: ['@ti-fe/eslint-config'], - ignorePatterns: ['src/routes.tsx'], + ignorePatterns: ['src/routes.tsx', 'plugins/**/*'], env: { // Your custom env variables, e.g., browser: true, jest: true }, diff --git a/dm/ui/package.json b/dm/ui/package.json index 5fbcd139b95..61332c6cf51 100644 --- a/dm/ui/package.json +++ b/dm/ui/package.json @@ -55,9 +55,9 @@ "rollup-plugin-visualizer": "^5.5.2", "typescript": "^5.1.6", "vite": "^2.9.16", - "vite-plugin-i18next-scanner": "^0.4.0", + "vite-plugin-i18next-scanner": "file:./plugin/vite-plugin-i18next-scanner", "vite-plugin-imp": "^2.1.3", - "vite-plugin-next-react-router": "^0.6.1", + "vite-plugin-next-react-router": "file:./plugin/vite-plugin-next-react-router", "vite-plugin-windicss": "^1.5.4", "vite-tsconfig-paths": "^3.3.17", "windicss": "^3.3.0" diff --git a/dm/ui/plugins/vite-plugin-i18next-scanner/index.js b/dm/ui/plugins/vite-plugin-i18next-scanner/index.js new file mode 100644 index 00000000000..461dc495f8d --- /dev/null +++ b/dm/ui/plugins/vite-plugin-i18next-scanner/index.js @@ -0,0 +1,300 @@ +// src/context.ts +import fs2 from 'fs' +import { Parser } from 'i18next-scanner' +import debug from 'debug' +import workerpool from 'workerpool' + +// src/options.ts +import path from 'path' +import { defaults, defaultsDeep } from 'lodash' +var defaultOptions = { + input: ['src/**/*.{js,jsx,ts,tsx}'], + output: './', + options: { + debug: false, + removeUnusedKeys: true, + sort: true, + attr: { + list: ['data-i18n'], + extensions: ['.html', '.htm'], + }, + func: { + list: ['t', 'i18next.t', 'i18n.t'], + extensions: ['.ts', '.tsx', '.js', '.jsx'], + }, + trans: { + component: 'Trans', + i18nKey: 'i18nKey', + defaultsKey: 'defaults', + extensions: [], + fallbackKey: false, + }, + lngs: ['en'], + defaultLng: 'en', + defaultValue: function (_, __, key) { + return key + }, + resource: { + loadPath: './locales/{{lng}}.json', + savePath: './locales/{{lng}}.json', + jsonIndent: 2, + lineEnding: '\n', + }, + nsSeparator: ':', + keySeparator: '.', + pluralSeparator: '_', + contextSeparator: '_', + contextDefaultValues: [], + interpolation: { + prefix: '{{', + suffix: '}}', + }, + }, +} +var defaultPluginOptions = { + langs: ['en'], + outDir: 'locales', + includes: ['src/**/*.{js,jsx,ts,tsx}'], +} +function mergePluginOptionToScannerOption(a, b) { + const o = defaults(b, defaultPluginOptions) + a.input = o.includes + a.options.lngs = o.langs + a.options.resource.savePath = path.join(o.outDir, '{{lng}}.json') + a.options.resource.loadPath = path.join(o.outDir, '{{lng}}.json') + return a +} +function normalizeOptions(o = {}) { + const options = defaultsDeep({}, defaultOptions) + return mergePluginOptionToScannerOption(options, o) +} + +// src/context.ts +import path2 from 'path' + +// src/fs.ts +import fs from 'fs' + +// node_modules/.pnpm/detect-indent@7.0.0/node_modules/detect-indent/index.js +var INDENT_REGEX = /^(?:( )+|\t+)/ +var INDENT_TYPE_SPACE = 'space' +var INDENT_TYPE_TAB = 'tab' +function makeIndentsMap(string, ignoreSingleSpaces) { + const indents = new Map() + let previousSize = 0 + let previousIndentType + let key + for (const line of string.split(/\n/g)) { + if (!line) { + continue + } + let indent + let indentType + let weight + let entry + const matches = line.match(INDENT_REGEX) + if (matches === null) { + previousSize = 0 + previousIndentType = '' + } else { + indent = matches[0].length + indentType = matches[1] ? INDENT_TYPE_SPACE : INDENT_TYPE_TAB + if ( + ignoreSingleSpaces && + indentType === INDENT_TYPE_SPACE && + indent === 1 + ) { + continue + } + if (indentType !== previousIndentType) { + previousSize = 0 + } + previousIndentType = indentType + weight = 0 + const indentDifference = indent - previousSize + previousSize = indent + if (indentDifference === 0) { + weight++ + } else { + const absoluteIndentDifference = + indentDifference > 0 ? indentDifference : -indentDifference + key = encodeIndentsKey(indentType, absoluteIndentDifference) + } + entry = indents.get(key) + entry = entry === void 0 ? [1, 0] : [++entry[0], entry[1] + weight] + indents.set(key, entry) + } + } + return indents +} +function encodeIndentsKey(indentType, indentAmount) { + const typeCharacter = indentType === INDENT_TYPE_SPACE ? 's' : 't' + return typeCharacter + String(indentAmount) +} +function decodeIndentsKey(indentsKey) { + const keyHasTypeSpace = indentsKey[0] === 's' + const type = keyHasTypeSpace ? INDENT_TYPE_SPACE : INDENT_TYPE_TAB + const amount = Number(indentsKey.slice(1)) + return { type, amount } +} +function getMostUsedKey(indents) { + let result + let maxUsed = 0 + let maxWeight = 0 + for (const [key, [usedCount, weight]] of indents) { + if (usedCount > maxUsed || (usedCount === maxUsed && weight > maxWeight)) { + maxUsed = usedCount + maxWeight = weight + result = key + } + } + return result +} +function makeIndentString(type, amount) { + const indentCharacter = type === INDENT_TYPE_SPACE ? ' ' : ' ' + return indentCharacter.repeat(amount) +} +function detectIndent(string) { + if (typeof string !== 'string') { + throw new TypeError('Expected a string') + } + let indents = makeIndentsMap(string, true) + if (indents.size === 0) { + indents = makeIndentsMap(string, false) + } + const keyOfMostUsedIndent = getMostUsedKey(indents) + let type + let amount = 0 + let indent = '' + if (keyOfMostUsedIndent !== void 0) { + ;({ type, amount } = decodeIndentsKey(keyOfMostUsedIndent)) + indent = makeIndentString(type, amount) + } + return { + amount, + type, + indent, + } +} + +// src/fs.ts +var DEFAULT_INDENT = ' ' +function readJsonFile(path3) { + const file = fs.readFileSync(path3, 'utf8') || '{}' + const indent = detectIndent(path3).indent || DEFAULT_INDENT + return { + path: path3, + json: JSON.parse(file), + indent, + } +} + +// src/context.ts +var dbg = debug('vite-plugin-i18next-scanner:context') +var Context = class { + constructor(options = {}) { + this.server = null + this.pool = null + this.pluginOptions = options + this.scannerOptions = normalizeOptions(options) + dbg('scannerOptions: %o', this.scannerOptions) + } + async startScanner(server) { + if (this.server === server) { + return + } + if (this.pool) { + await this.pool.terminate() + } + this.server = server + this.pool = workerpool.pool(__dirname + '/worker.js', { + minWorkers: 'max', + maxWorkers: 1, + }) + await this.scanAll() + this.watch(server.watcher) + } + watch(watcher) { + watcher.on('change', p => this.handleFileChange(p)) + watcher.on('unlink', p => this.handleFileUnlink(p)) + } + passExtensionCheck(p) { + const extname = path2.extname(p) + return ( + this.scannerOptions.options.func.extensions.includes(extname) || + this.scannerOptions.options.attr.extensions.includes(extname) || + this.scannerOptions.options.trans.extensions.includes(extname) + ) + } + async handleFileUnlink(p) { + if (this.passExtensionCheck(p)) { + await this.scanAll() + } + } + async handleFileChange(p) { + dbg(`scanning ${p}`) + if (!this.passExtensionCheck(p)) { + return + } + const content = fs2.readFileSync(p, 'utf8') + const parser = new Parser(this.scannerOptions.options) + if (!content) { + return + } + parser.parseFuncFromString(content) + const translations = parser.get() + const resourceFromFile = Object.keys(translations).reduce((acc, key) => { + acc[key] = translations[key].translation + return acc + }, {}) + dbg('resource from file: %o', resourceFromFile) + const hasKey = Object.keys(resourceFromFile).some(lang => { + return Object.keys(resourceFromFile[lang]).length > 0 + }) + if (!hasKey) { + dbg('no key found') + return + } + let shouldScanAll = false + Object.keys(resourceFromFile).forEach(lang => { + const languageResource = path2.resolve( + this.scannerOptions.options.resource.savePath.replace('{{lng}}', lang) + ) + const { json } = readJsonFile(languageResource) + if (Object.keys(resourceFromFile[lang]).some(key => !(key in json))) { + shouldScanAll = true + } + }) + if (shouldScanAll) { + await this.scanAll() + } else { + dbg('no need to scan all') + } + } + async scanAll() { + if (!this.pool) { + return + } + dbg('scanning and regenerating all resources...') + const worker = await this.pool.proxy() + await worker.scanAndGenerateResource( + this.scannerOptions.input, + this.scannerOptions.output, + this.pluginOptions + ) + dbg('done scanning and regenerating all resources') + } +} + +// src/index.ts +function i18nextScanner(options) { + const ctx = new Context(options) + return { + name: 'vite-plugin-i18next-scanner', + apply: 'serve', + async configureServer(server) { + await ctx.startScanner(server) + }, + } +} +export { i18nextScanner } diff --git a/dm/ui/plugins/vite-plugin-i18next-scanner/package.json b/dm/ui/plugins/vite-plugin-i18next-scanner/package.json new file mode 100644 index 00000000000..96b3abd7f8b --- /dev/null +++ b/dm/ui/plugins/vite-plugin-i18next-scanner/package.json @@ -0,0 +1,37 @@ +{ + "name": "vite-plugin-i18next-scanner", + "version": "0.4.0", + "main": "index.js", + "module": "./index.mjs", + "exports": { + ".": { + "require": "./index.js", + "import": "./index.mjs", + "types": "./index.d.ts" + } + }, + "dependencies": { + "debug": "^4.3.3", + "i18next-scanner": "^3.1.0", + "lodash": "^4.17.21", + "vinyl-fs": "^3.0.3", + "workerpool": "^6.1.5" + }, + "devDependencies": { + "@types/debug": "^4.1.7", + "@types/lodash": "^4.14.177", + "@types/node": "^16.11.10", + "@types/vinyl-fs": "^2.4.12", + "@types/workerpool": "^6.1.0", + "detect-indent": "^7.0.0", + "husky": "^7.0.4", + "prettier": "^2.5.0", + "pretty-quick": "^3.1.2", + "tsup": "^5.10.0", + "typescript": "^4.5.2", + "vite": "^2.6.14" + }, + "peerDependencies": { + "vite": ">=2" + } +} diff --git a/dm/ui/plugins/vite-plugin-i18next-scanner/worker.js b/dm/ui/plugins/vite-plugin-i18next-scanner/worker.js new file mode 100644 index 00000000000..82522beb4c2 --- /dev/null +++ b/dm/ui/plugins/vite-plugin-i18next-scanner/worker.js @@ -0,0 +1,85 @@ +// src/worker.ts +const workerpool = require('workerpool') +const vfs = require('vinyl-fs') +const { createStream } = require('i18next-scanner') + +// src/options.ts +const path = require('path') +const { defaults, defaultsDeep } = require('lodash') +var defaultOptions = { + input: ['src/**/*.{js,jsx,ts,tsx}'], + output: './', + options: { + debug: false, + removeUnusedKeys: true, + sort: true, + attr: { + list: ['data-i18n'], + extensions: ['.html', '.htm'], + }, + func: { + list: ['t', 'i18next.t', 'i18n.t'], + extensions: ['.ts', '.tsx', '.js', '.jsx'], + }, + trans: { + component: 'Trans', + i18nKey: 'i18nKey', + defaultsKey: 'defaults', + extensions: [], + fallbackKey: false, + }, + lngs: ['en'], + defaultLng: 'en', + defaultValue: function (_, __, key) { + return key + }, + resource: { + loadPath: './locales/{{lng}}.json', + savePath: './locales/{{lng}}.json', + jsonIndent: 2, + lineEnding: '\n', + }, + nsSeparator: ':', + keySeparator: '.', + pluralSeparator: '_', + contextSeparator: '_', + contextDefaultValues: [], + interpolation: { + prefix: '{{', + suffix: '}}', + }, + }, +} +var defaultPluginOptions = { + langs: ['en'], + outDir: 'locales', + includes: ['src/**/*.{js,jsx,ts,tsx}'], +} +function mergePluginOptionToScannerOption(a, b) { + const o = defaults(b, defaultPluginOptions) + a.input = o.includes + a.options.lngs = o.langs + a.options.resource.savePath = path.join(o.outDir, '{{lng}}.json') + a.options.resource.loadPath = path.join(o.outDir, '{{lng}}.json') + return a +} +function normalizeOptions(o = {}) { + const options = defaultsDeep({}, defaultOptions) + return mergePluginOptionToScannerOption(options, o) +} + +// src/worker.ts +async function scanAndGenerateResource(input, output, options) { + const scannerOptions = normalizeOptions(options) + return new Promise((resolve, reject) => { + vfs + .src(input) + .pipe(createStream(scannerOptions.options)) + .pipe(vfs.dest(output)) + .on('finish', () => resolve()) + .on('error', e => reject(e)) + }) +} +workerpool.worker({ + scanAndGenerateResource, +}) diff --git a/dm/ui/plugins/vite-plugin-next-react-router/index.js b/dm/ui/plugins/vite-plugin-next-react-router/index.js new file mode 100644 index 00000000000..6786480f06e --- /dev/null +++ b/dm/ui/plugins/vite-plugin-next-react-router/index.js @@ -0,0 +1,385 @@ +// src/context.ts +import path4 from 'path' +import fs2 from 'fs' + +// src/codegen.ts +import fs from 'fs' +import path3 from 'path' + +// src/resolver.ts +import path2 from 'path' +import { normalizePath } from 'vite' +import fg from 'fast-glob' + +// src/utils.ts +import path from 'path' +import consola from 'consola' +import { upperFirst, camelCase } from 'lodash' + +// package.json +var name = 'vite-plugin-next-react-router' + +// src/const.ts +var MATCH_ALL_ROUTE = '*' +var DEFAULT_EXT = ['tsx', 'ts', 'jsx', 'js'] +var DEFAULT_PAGE_DIR = 'src/pages' + +// src/utils.ts +var debug = (message, ...args) => { + if (process.env.DEBUG === name) { + consola.info(message, ...args) + } +} +function isCatchAll(filename) { + return /^\[\.{3}/.test(filename) +} +function isDynamic(filename) { + return /^\[(.+)\]$/.test(filename) +} +function normalizeFilenameToRoute(filename) { + if (isCatchAll(filename)) { + return MATCH_ALL_ROUTE + } + if (filename === 'index') { + return '/' + } + return isDynamic(filename) ? parameterizeDynamicRoute(filename) : filename +} +function parameterizeDynamicRoute(s) { + return s.replace(/^\[(.+)\]$/, (_, p) => `:${p}`) +} +function normalizeDirPathToRoute(dirPath) { + return dirPath + .split('/') + .map(s => (isDynamic(s) ? parameterizeDynamicRoute(s) : s)) + .join('/') +} +function normalizePathToRoute(p) { + const { dir, name: name2 } = path.parse(p) + const route = normalizeFilenameToRoute(name2) + if (route === MATCH_ALL_ROUTE) { + return route + } + return path.resolve(path.join('/', normalizeDirPathToRoute(dir), route)) +} +function countLength(p) { + return path.resolve(p).split('/').filter(Boolean).length +} +function sorter(a, b) { + const len = countLength(a) - countLength(b) + if (len !== 0) { + return len + } + return a.localeCompare(b) +} +function sortRoutes(routes) { + return [...routes].sort(sorter) +} +function getComponentName(filePath) { + const segments = filePath.split(path.sep) + const extname = path.extname(filePath) + const fileName = path.basename(filePath, extname) + segments[segments.length - 1] = fileName + const name2 = segments.reduce((acc, segment) => { + if (isDynamic(segment)) { + return acc + upperFirst(camelCase(segment.replace(/^\[(.+)\]$/, '$1'))) + } + return acc + upperFirst(camelCase(segment)) + }, '') + return name2 +} + +// src/resolver.ts +function resolvePages(options) { + const { root, pageDir, extensions } = options + const files = scan(pageDir, extensions, root) + return fileToRouteMap(files) +} +function resolveOptions(userOptions, viteRoot) { + var _a, _b, _c, _d + const root = viteRoot != null ? viteRoot : normalizePath(process.cwd()) + return { + root, + async: + (_a = userOptions == null ? void 0 : userOptions.async) != null + ? _a + : true, + pageDir: + (_b = userOptions == null ? void 0 : userOptions.pageDir) != null + ? _b + : DEFAULT_PAGE_DIR, + extensions: + (_c = userOptions == null ? void 0 : userOptions.extensions) != null + ? _c + : DEFAULT_EXT, + output: + (_d = userOptions == null ? void 0 : userOptions.output) != null + ? _d + : path2.join(root, 'src', 'routes.tsx'), + } +} +function scan(targetDir, extensions, root) { + const fullPathOfTargetDir = path2.resolve(root, targetDir) + return fg.sync([`**/*.{${extensions.join()}}`, `!_layout.*`], { + onlyFiles: true, + cwd: fullPathOfTargetDir, + }) +} +function fileToRouteMap(files) { + const map = new Map() + files.forEach(file => { + const route = normalizePathToRoute(file) + map.set(route, file) + }) + return map +} +function resolveRoutes(map, options) { + const sortedRoutes = sortRoutes([...map.keys()]) + return sortedRoutes.map(route => { + const filePath = map.get(route) + const absolutePath = path2.resolve(options.pageDir, filePath) + const routeObj = { + path: route, + componentPath: absolutePath, + componentName: getComponentName(filePath), + } + if (path2.basename(filePath, path2.extname(filePath)) === 'index') { + routeObj.index = true + } + return routeObj + }) +} +function resolveGlobalLayout(options) { + const globalLayoutFiles = fg.sync( + [`_layout.{${options.extensions.join()}}`], + { + onlyFiles: true, + cwd: options.pageDir, + } + ) + if (globalLayoutFiles.length === 1) { + const filePath = globalLayoutFiles[0] + return { + componentPath: path2.resolve(options.pageDir, globalLayoutFiles[0]), + componentName: 'GlobalLayout', + path: filePath, + } + } else if (globalLayoutFiles.length > 1) { + throw new Error('Multiple _layout files found') + } + return null +} + +// src/template.ts +var generateRoutesCode = ({ + layoutImport: layoutImport2, + pageImports, + layoutElement, + routes, + staticPageMetaImports, + pages, +}) => ` +import React from 'react'; +${layoutImport2} +${staticPageMetaImports} +${pageImports} + +export const routes = [ + { + path: '/', + element: ${layoutElement}, + children: [ + ${routes} + ] + } +] + +export const pages = [ + ${pages} +] +` + +// src/codegen.ts +function generateComponentCall(route, options) { + return `<${ + options.async + ? `Dynamic${route.componentName}` + : `Static${route.componentName}` + } />` +} +function stripExt(p) { + return p.replace(path3.extname(p), '') +} +function transformToRelativePath(to, from) { + return './' + stripExt(path3.relative(path3.dirname(path3.resolve(from)), to)) +} +var dynamicPageImports = (routes, options) => + routes.reduce((acc, route) => { + return ( + acc + + `const Dynamic${ + route.componentName + } = React.lazy(() => import('${transformToRelativePath( + route.componentPath, + options.output + )}')); +` + ) + }, '') +var staticPageImports = (routes, options) => + routes.reduce((acc, route) => { + return ( + acc + + `import Static${route.componentName} from '${transformToRelativePath( + route.componentPath, + options.output + )}' +` + ) + }, '') +var staticMetaImports = (routes, options) => { + return routes.reduce((acc, route) => { + var _a + const routeComponentCode = fs.readFileSync(route.componentPath, 'utf8') + if (routeComponentCode.includes('export const meta')) { + return ( + acc + + `import { meta as Static${ + route.componentName + }Meta } from '${transformToRelativePath( + route.componentPath, + (_a = options.output) != null + ? _a + : path3.join(options.root, 'src', 'pages.ts') + )}' +` + ) + } + return acc + }, '') +} +var layoutImport = options => { + const layout = resolveGlobalLayout(options) + let imports = '' + if (!layout) { + imports += `import { Outlet } from 'react-router-dom';` + } + imports = layout + ? `import ${layout.componentName} from '${transformToRelativePath( + layout.componentPath, + options.output + )}'` + : '' + const element = `<${layout ? layout.componentName : 'Outlet'} />` + return { imports, element } +} +var routeObjects = (routes, options) => + routes + .map(route => { + return `{ path: '${route.path}', element: ${generateComponentCall( + route, + options + )}, ${route.index ? 'index: true' : ''}}, +` + }) + .join(' '.repeat(6)) + .trim() +var pageObjects = routes => + routes + .filter(r => r.path !== '*') + .map(route => { + const routeComponentCode = fs.readFileSync(route.componentPath, 'utf8') + if (routeComponentCode.includes('export const meta')) { + return `{ route: '${route.path}', meta: Static${route.componentName}Meta }, +` + } + return `{ route: '${route.path}' }, +` + }) + .join(' '.repeat(2)) + .trim() +function generateRoutesModuleCode(routes, options) { + const { imports, element } = layoutImport(options) + const staticPageMetaImports = staticMetaImports(routes, options) + const pages = pageObjects(routes) + return generateRoutesCode({ + layoutImport: imports, + pageImports: options.async + ? dynamicPageImports(routes, options) + : staticPageImports(routes, options), + layoutElement: element, + routes: routeObjects(routes, options), + pages, + staticPageMetaImports, + }) +} + +// src/context.ts +function isTarget(p, options) { + return ( + p.startsWith(path4.resolve(options.pageDir)) && + options.extensions.some(ext => p.endsWith(ext)) + ) +} +var Context = class { + constructor(userOptions) { + this._pages = new Map() + this._server = null + this.root = '.' + this._userOptions = userOptions + } + resolveOptions() { + this._resolvedOptions = resolveOptions(this._userOptions, this.root) + } + search() { + if (!this._resolvedOptions) { + this.resolveOptions() + } + this._pages = resolvePages(this._resolvedOptions) + debug('pages: ', this._pages) + } + configureServer(server) { + this._server = server + this._server.watcher.on('unlink', filaPath => + this.invalidateAndRegenerateRoutes(filaPath) + ) + this._server.watcher.on('add', filaPath => + this.invalidateAndRegenerateRoutes(filaPath) + ) + } + invalidateAndRegenerateRoutes(filaPath) { + if (!isTarget(filaPath, this._resolvedOptions)) { + return + } + this._pages.clear() + this.generateRoutesModuleCode() + } + generateRoutesModuleCode() { + if (this._pages.size === 0) { + this.search() + } + const routes = resolveRoutes(this._pages, this._resolvedOptions) + const code = generateRoutesModuleCode(routes, this._resolvedOptions) + debug('generateVirtualRoutesCode: \n', code) + fs2.writeFileSync(this._resolvedOptions.output, code) + } +} + +// src/index.ts +function reactRouterPlugin(userOptions) { + const ctx = new Context(userOptions) + return { + name: 'vite-plugin-next-router', + enforce: 'pre', + async configResolved({ root }) { + ctx.root = root + ctx.resolveOptions() + ctx.search() + ctx.generateRoutesModuleCode() + }, + configureServer(server) { + ctx.configureServer(server) + }, + } +} +export { reactRouterPlugin } diff --git a/dm/ui/plugins/vite-plugin-next-react-router/package.json b/dm/ui/plugins/vite-plugin-next-react-router/package.json new file mode 100644 index 00000000000..88d66168c0e --- /dev/null +++ b/dm/ui/plugins/vite-plugin-next-react-router/package.json @@ -0,0 +1,22 @@ +{ + "name": "vite-plugin-next-react-router", + "version": "0.6.2", + "main": "index.js", + "module": "./index.mjs", + "exports": { + ".": { + "require": "./index.js", + "import": "./index.mjs", + "types": "./index.d.ts" + } + }, + "dependencies": { + "consola": "^2.15.3", + "fast-glob": "^3.2.7", + "lodash": "^4.17.21" + }, + "peerDependencies": { + "react-router-dom": ">=6", + "vite": ">=2" + } +} diff --git a/dm/ui/tsconfig.json b/dm/ui/tsconfig.json index ffe36b7cf22..15af531ae75 100644 --- a/dm/ui/tsconfig.json +++ b/dm/ui/tsconfig.json @@ -19,5 +19,5 @@ "~/*": ["src/*"] } }, - "include": ["src/"] + "include": ["src/", "plugin"] } diff --git a/dm/ui/vite.config.js b/dm/ui/vite.config.js index fd766060704..7ffe4932b1d 100644 --- a/dm/ui/vite.config.js +++ b/dm/ui/vite.config.js @@ -1,12 +1,12 @@ import { defineConfig } from 'vite' import WindiCSS from 'vite-plugin-windicss' -import { i18nextScanner } from 'vite-plugin-i18next-scanner' import tsConfigPath from 'vite-tsconfig-paths' -import { reactRouterPlugin } from 'vite-plugin-next-react-router' import reactPlugin from '@vitejs/plugin-react' import { visualizer } from 'rollup-plugin-visualizer' -import { dependencies } from './package.json' +import { reactRouterPlugin } from './plugin/vite-plugin-next-react-router' +import { i18nextScanner } from './plugin/vite-plugin-i18next-scanner' +import packageJson from './package.json' function renderChunks(deps) { let chunks = {} @@ -49,7 +49,7 @@ export default defineConfig({ output: { manualChunks: { vendor: ['react', 'react-router-dom', 'react-dom'], - ...renderChunks(dependencies), + ...renderChunks(packageJson.dependencies), }, }, }, diff --git a/dm/ui/yarn.lock b/dm/ui/yarn.lock index 4f5fa85f461..39ecbdfc924 100644 --- a/dm/ui/yarn.lock +++ b/dm/ui/yarn.lock @@ -5116,10 +5116,8 @@ vinyl@^2.0.0, vinyl@^2.2.0: remove-trailing-separator "^1.0.1" replace-ext "^1.0.0" -vite-plugin-i18next-scanner@^0.4.0: +"vite-plugin-i18next-scanner@file:./plugin/vite-plugin-i18next-scanner": version "0.4.0" - resolved "https://registry.yarnpkg.com/vite-plugin-i18next-scanner/-/vite-plugin-i18next-scanner-0.4.0.tgz#3684101c7a89afffa22c2bb9e108e5b40ff4d087" - integrity sha512-2GBfXypPpH4oZfE0/gpDs6Bl4lUO/oxJqde0Sivn1g4ei5AMOmayDWOyISdmoyuEtZAV0Zz8VExlOS13JGauog== dependencies: debug "^4.3.3" i18next-scanner "^3.1.0" @@ -5140,10 +5138,8 @@ vite-plugin-imp@^2.1.3: param-case "^3.0.4" pascal-case "^3.1.2" -vite-plugin-next-react-router@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/vite-plugin-next-react-router/-/vite-plugin-next-react-router-0.6.1.tgz#5aa5a7e82261a12e36a675cb46a93886daf395e7" - integrity sha512-Ha/G6SQWajAj3fm2WVpNBD+z6ZQv9NHw8EcOYJbCbLkdLBbkwh1EI73jewG+LetCggk8om+zEqGh8hA+fHrpXg== +"vite-plugin-next-react-router@file:./plugin/vite-plugin-next-react-router": + version "0.6.2" dependencies: consola "^2.15.3" fast-glob "^3.2.7"