1
0

vite-plugin-vendure-dashboard.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. import { lingui } from '@lingui/vite-plugin';
  2. import tailwindcss from '@tailwindcss/vite';
  3. import { tanstackRouter } from '@tanstack/router-plugin/vite';
  4. import react from '@vitejs/plugin-react-swc';
  5. import path from 'path';
  6. import { PluginOption } from 'vite';
  7. import { PathAdapter } from './types.js';
  8. import { PackageScannerConfig } from './utils/compiler.js';
  9. import { adminApiSchemaPlugin } from './vite-plugin-admin-api-schema.js';
  10. import { configLoaderPlugin } from './vite-plugin-config-loader.js';
  11. import { viteConfigPlugin } from './vite-plugin-config.js';
  12. import { dashboardMetadataPlugin } from './vite-plugin-dashboard-metadata.js';
  13. import { gqlTadaPlugin } from './vite-plugin-gql-tada.js';
  14. import { hmrPlugin } from './vite-plugin-hmr.js';
  15. import { dashboardTailwindSourcePlugin } from './vite-plugin-tailwind-source.js';
  16. import { themeVariablesPlugin, ThemeVariablesPluginOptions } from './vite-plugin-theme.js';
  17. import { transformIndexHtmlPlugin } from './vite-plugin-transform-index.js';
  18. import { translationsPlugin } from './vite-plugin-translations.js';
  19. import { uiConfigPlugin, UiConfigPluginOptions } from './vite-plugin-ui-config.js';
  20. /**
  21. * @description
  22. * Options for the {@link vendureDashboardPlugin} Vite plugin.
  23. *
  24. * @docsCategory vite-plugin
  25. * @docsPage vendureDashboardPlugin
  26. * @since 3.4.0
  27. * @docsWeight 1
  28. */
  29. export type VitePluginVendureDashboardOptions = {
  30. /**
  31. * @description
  32. * The path to the Vendure server configuration file.
  33. */
  34. vendureConfigPath: string | URL;
  35. /**
  36. * @description
  37. * The {@link PathAdapter} allows you to customize the resolution of paths
  38. * in the compiled Vendure source code which is used as part of the
  39. * introspection step of building the dashboard.
  40. *
  41. * It enables support for more complex repository structures, such as
  42. * monorepos, where the Vendure server configuration file may not
  43. * be located in the root directory of the project.
  44. *
  45. * If you get compilation errors like "Error loading Vendure config: Cannot find module",
  46. * you probably need to provide a custom `pathAdapter` to resolve the paths correctly.
  47. *
  48. * @example
  49. * ```ts
  50. * vendureDashboardPlugin({
  51. * tempCompilationDir: join(__dirname, './__vendure-dashboard-temp'),
  52. * pathAdapter: {
  53. * getCompiledConfigPath: ({ inputRootDir, outputPath, configFileName }) => {
  54. * const projectName = inputRootDir.split('/libs/')[1].split('/')[0];
  55. * const pathAfterProject = inputRootDir.split(`/libs/${projectName}`)[1];
  56. * const compiledConfigFilePath = `${outputPath}/${projectName}${pathAfterProject}`;
  57. * return path.join(compiledConfigFilePath, configFileName);
  58. * },
  59. * transformTsConfigPathMappings: ({ phase, patterns }) => {
  60. * // "loading" phase is when the compiled Vendure code is being loaded by
  61. * // the plugin, in order to introspect the configuration of your app.
  62. * if (phase === 'loading') {
  63. * return patterns.map((p) =>
  64. * p.replace('libs/', '').replace(/.ts$/, '.js'),
  65. * );
  66. * }
  67. * return patterns;
  68. * },
  69. * },
  70. * // ...
  71. * }),
  72. * ```
  73. */
  74. pathAdapter?: PathAdapter;
  75. /**
  76. * @description
  77. * The name of the exported variable from the Vendure server configuration file, e.g. `config`.
  78. * This is only required if the plugin is unable to auto-detect the name of the exported variable.
  79. */
  80. vendureConfigExport?: string;
  81. /**
  82. * @description
  83. * The path to the directory where the generated GraphQL Tada files will be output.
  84. */
  85. gqlOutputPath?: string;
  86. tempCompilationDir?: string;
  87. /**
  88. * @description
  89. * Allows you to customize the location of node_modules & glob patterns used to scan for potential
  90. * Vendure plugins installed as npm packages. If not provided, the compiler will attempt to guess
  91. * the location based on the location of the `@vendure/core` package.
  92. */
  93. pluginPackageScanner?: PackageScannerConfig;
  94. /**
  95. * @description
  96. * Allows you to selectively disable individual plugins.
  97. * @example
  98. * ```ts
  99. * vendureDashboardPlugin({
  100. * vendureConfigPath: './config.ts',
  101. * disablePlugins: {
  102. * react: true,
  103. * lingui: true,
  104. * }
  105. * })
  106. * ```
  107. */
  108. disablePlugins?: {
  109. tanstackRouter?: boolean;
  110. react?: boolean;
  111. lingui?: boolean;
  112. themeVariables?: boolean;
  113. tailwindSource?: boolean;
  114. tailwindcss?: boolean;
  115. configLoader?: boolean;
  116. viteConfig?: boolean;
  117. adminApiSchema?: boolean;
  118. dashboardMetadata?: boolean;
  119. uiConfig?: boolean;
  120. gqlTada?: boolean;
  121. transformIndexHtml?: boolean;
  122. translations?: boolean;
  123. hmr?: boolean;
  124. };
  125. } & UiConfigPluginOptions &
  126. ThemeVariablesPluginOptions;
  127. /**
  128. * @description
  129. * This is a Vite plugin which configures a set of plugins required to build the Vendure Dashboard.
  130. */
  131. type PluginKey = keyof NonNullable<VitePluginVendureDashboardOptions['disablePlugins']>;
  132. type PluginMapEntry = {
  133. key: PluginKey;
  134. plugin: () => PluginOption | PluginOption[] | false | '';
  135. };
  136. /**
  137. * @description
  138. * This is the Vite plugin which powers the Vendure Dashboard, including:
  139. *
  140. * - Configuring routing, styling and React support
  141. * - Analyzing your VendureConfig file and introspecting your schema
  142. * - Loading your custom Dashboard extensions
  143. *
  144. * @docsCategory vite-plugin
  145. * @docsPage vendureDashboardPlugin
  146. * @since 3.4.0
  147. * @docsWeight 0
  148. */
  149. export function vendureDashboardPlugin(options: VitePluginVendureDashboardOptions): PluginOption[] {
  150. const tempDir = options.tempCompilationDir ?? path.join(import.meta.dirname, './.vendure-dashboard-temp');
  151. const normalizedVendureConfigPath = getNormalizedVendureConfigPath(options.vendureConfigPath);
  152. const packageRoot = getDashboardPackageRoot();
  153. const linguiConfigPath = path.join(packageRoot, 'lingui.config.js');
  154. const disabled = options.disablePlugins ?? {};
  155. if (process.env.IS_LOCAL_DEV !== 'true') {
  156. process.env.LINGUI_CONFIG = linguiConfigPath;
  157. }
  158. const pluginMap: PluginMapEntry[] = [
  159. {
  160. key: 'tanstackRouter',
  161. plugin: () =>
  162. tanstackRouter({
  163. autoCodeSplitting: true,
  164. routeFileIgnorePattern: '.graphql.ts|components|hooks|utils',
  165. routesDirectory: path.join(packageRoot, 'src/app/routes'),
  166. generatedRouteTree: path.join(packageRoot, 'src/app/routeTree.gen.ts'),
  167. }),
  168. },
  169. {
  170. key: 'react',
  171. plugin: () =>
  172. react({
  173. plugins: [['@lingui/swc-plugin', {}]],
  174. }),
  175. },
  176. {
  177. key: 'lingui',
  178. plugin: () => lingui({}),
  179. },
  180. {
  181. key: 'themeVariables',
  182. plugin: () => themeVariablesPlugin({ theme: options.theme }),
  183. },
  184. {
  185. key: 'tailwindSource',
  186. plugin: () => dashboardTailwindSourcePlugin(),
  187. },
  188. {
  189. key: 'tailwindcss',
  190. plugin: () => tailwindcss(),
  191. },
  192. {
  193. key: 'configLoader',
  194. plugin: () =>
  195. configLoaderPlugin({
  196. vendureConfigPath: normalizedVendureConfigPath,
  197. outputPath: tempDir,
  198. pathAdapter: options.pathAdapter,
  199. pluginPackageScanner: options.pluginPackageScanner,
  200. }),
  201. },
  202. {
  203. key: 'viteConfig',
  204. plugin: () => viteConfigPlugin({ packageRoot }),
  205. },
  206. {
  207. key: 'adminApiSchema',
  208. plugin: () => adminApiSchemaPlugin(),
  209. },
  210. {
  211. key: 'dashboardMetadata',
  212. plugin: () => dashboardMetadataPlugin(),
  213. },
  214. {
  215. key: 'uiConfig',
  216. plugin: () => uiConfigPlugin(options),
  217. },
  218. {
  219. key: 'gqlTada',
  220. plugin: () =>
  221. options.gqlOutputPath &&
  222. gqlTadaPlugin({ gqlTadaOutputPath: options.gqlOutputPath, tempDir, packageRoot }),
  223. },
  224. {
  225. key: 'transformIndexHtml',
  226. plugin: () => transformIndexHtmlPlugin(),
  227. },
  228. {
  229. key: 'translations',
  230. plugin: () =>
  231. translationsPlugin({
  232. packageRoot,
  233. }),
  234. },
  235. {
  236. key: 'hmr',
  237. plugin: () => hmrPlugin(),
  238. },
  239. ];
  240. const plugins: PluginOption[] = [];
  241. for (const entry of pluginMap) {
  242. if (!disabled[entry.key]) {
  243. const plugin = entry.plugin();
  244. if (plugin) {
  245. if (Array.isArray(plugin)) {
  246. plugins.push(...plugin);
  247. } else {
  248. plugins.push(plugin);
  249. }
  250. }
  251. }
  252. }
  253. return plugins;
  254. }
  255. /**
  256. * @description
  257. * Returns the path to the root of the `@vendure/dashboard` package.
  258. */
  259. function getDashboardPackageRoot(): string {
  260. const fileUrl = import.meta.resolve('@vendure/dashboard');
  261. const packagePath = fileUrl.startsWith('file:') ? new URL(fileUrl).pathname : fileUrl;
  262. return fixWindowsPath(path.join(packagePath, '../../../'));
  263. }
  264. /**
  265. * Get the normalized path to the Vendure config file given either a string or URL.
  266. */
  267. export function getNormalizedVendureConfigPath(vendureConfigPath: string | URL): string {
  268. const stringPath = typeof vendureConfigPath === 'string' ? vendureConfigPath : vendureConfigPath.href;
  269. if (stringPath.startsWith('file:')) {
  270. return fixWindowsPath(new URL(stringPath).pathname);
  271. }
  272. return fixWindowsPath(stringPath);
  273. }
  274. function fixWindowsPath(filePath: string): string {
  275. // Fix Windows paths that might start with a leading slash
  276. if (process.platform === 'win32') {
  277. // Remove leading slash before drive letter on Windows
  278. if (/^[/\\][A-Za-z]:/.test(filePath)) {
  279. return filePath.substring(1);
  280. }
  281. }
  282. return filePath;
  283. }