diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 3f14aeab..97623298 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -1,75 +1,21 @@
-import { joinURL } from 'ufo'
-
-import { libraryCollections as libraryCollectionsConfig } from '@compodium/examples'
import { collectionsPlugin } from './plugins/collections'
import { extendMetaPlugin, metaPlugin } from './plugins/meta'
import { examplePlugin } from './plugins/examples'
import { devtoolsPlugin } from './plugins/devtools'
import { colorsPlugin } from './plugins/colors'
import { iconifyPlugin } from './plugins/iconify'
-
-import type { Collection, PluginConfig, PluginOptions } from './types'
-import { resolve } from 'pathe'
+import type { PluginOptions } from './types'
export * from './types'
-export function resolveConfig(options: PluginOptions): PluginConfig {
- const exampleDir = {
- path: joinURL(options.rootDir, options.dir, 'examples'),
- pattern: '**/*.{vue,tsx}'
- }
-
- const componentDirs = options?.componentDirs.map((dir) => {
- const componentDir = typeof dir === 'string' ? { path: dir } : dir
- return {
- pattern: '**/*.{vue,tsx}',
- ...componentDir,
- path: resolve(options.rootDir, componentDir.path),
- ignore: (componentDir.ignore ?? []).concat(options.ignore ?? [])
- }
- }).filter(collection => !collection.path?.includes('node_modules/'))
-
- const componentCollection: Collection = {
- name: 'Components',
- exampleDir,
- dirs: componentDirs
- }
-
- const libraryCollections = options.includeLibraryCollections
- ? libraryCollectionsConfig.map(collection => ({
- ...collection,
- exampleDir: {
- path: collection.exampleDir,
- pattern: '**/*.{vue,tsx}',
- prefix: collection.prefix
- },
- dirs: [{
- path: collection.path,
- pattern: '**/*.{vue,tsx}',
- ignore: collection.ignore,
- prefix: collection.prefix
- }]
- }))
- : []
-
- return {
- ...options,
- libraryCollections,
- componentCollection,
- baseUrl: '/'
- }
-}
-
export const compodium = /* #__PURE__ */ (options: PluginOptions) => {
- const config = resolveConfig(options)
-
return [
- collectionsPlugin(config),
- metaPlugin(config),
- extendMetaPlugin(config),
- devtoolsPlugin(config),
- examplePlugin(config),
- iconifyPlugin(config),
- colorsPlugin(config)
+ collectionsPlugin(options),
+ metaPlugin(options),
+ extendMetaPlugin(options),
+ devtoolsPlugin(options),
+ examplePlugin(options),
+ iconifyPlugin(options),
+ colorsPlugin(options)
]
}
diff --git a/packages/core/src/plugins/collections.ts b/packages/core/src/plugins/collections.ts
index e852db01..c15eaf42 100644
--- a/packages/core/src/plugins/collections.ts
+++ b/packages/core/src/plugins/collections.ts
@@ -1,24 +1,83 @@
-import type { PluginConfig } from '../types'
+import { libraryCollections as libraryCollectionsConfig } from '@compodium/examples'
+import type { PluginOptions, Collection } from '../types'
import { scanComponents } from './utils'
import { watch } from 'chokidar'
import type { VitePlugin } from 'unplugin'
import { resolve } from 'pathe'
+import { joinURL } from 'ufo'
+import type { ResolvedConfig } from 'vite'
+
+export function resolveCollections(options: PluginOptions, viteConfig: ResolvedConfig): Collection[] {
+ const rootDir = options.rootDir ?? viteConfig.root
+
+ const exampleDir = {
+ path: joinURL(rootDir, options.dir, 'examples'),
+ pattern: '**/*.{vue,tsx}'
+ }
+
+ const componentDirs = options?.componentDirs.map((dir) => {
+ const componentDir = typeof dir === 'string' ? { path: dir } : dir
+ return {
+ pattern: '**/*.{vue,tsx}',
+ ...componentDir,
+ path: resolve(rootDir, componentDir.path),
+ ignore: (componentDir.ignore ?? []).concat(options.ignore ?? [])
+ }
+ }).filter(collection => !collection.path?.includes('node_modules/'))
+
+ const componentCollection: Collection = {
+ name: 'Components',
+ exampleDir,
+ dirs: componentDirs
+ }
+
+ const libraryCollections = options.includeLibraryCollections
+ ? libraryCollectionsConfig.map(collection => ({
+ ...collection,
+ exampleDir: {
+ path: collection.exampleDir,
+ pattern: '**/*.{vue,tsx}',
+ prefix: collection.prefix
+ },
+ dirs: [{
+ path: collection.path,
+ pattern: '**/*.{vue,tsx}',
+ ignore: collection.ignore,
+ prefix: collection.prefix
+ }]
+ }))
+ : []
+
+ return [
+ componentCollection,
+ ...libraryCollections
+ ]
+}
+
+export function collectionsPlugin(options: PluginOptions): VitePlugin {
+ let collections: Collection[]
-export function collectionsPlugin(config: PluginConfig): VitePlugin {
return {
name: 'compodium:collections',
+ apply: 'serve',
+ enforce: 'post',
+
+ configResolved(viteConfig) {
+ collections = resolveCollections(options, viteConfig)
+ },
+
configureServer(server) {
server.middlewares.use('/__compodium__/api/collections', async (_, res) => {
try {
- const collections = await Promise.all([config.componentCollection, ...config.libraryCollections].map(async (col) => {
- const components = await scanComponents(col.dirs, config.rootDir)
- const examples = await scanComponents([col.exampleDir], config.rootDir)
+ const result = await Promise.all(collections.map(async (col) => {
+ const components = await scanComponents(col.dirs)
+ const examples = await scanComponents([col.exampleDir])
const collectionComponents = components.flatMap((c) => {
const componentExamples = examples?.filter(e => e.pascalName.startsWith(`${c.pascalName}Example`)).map(e => ({
...e,
isExample: true,
- componentPath: resolve(config.rootDir, c.filePath)
+ componentPath: c.filePath
}))
const mainExample = componentExamples.find(e => e.pascalName === `${c.pascalName}Example`)
@@ -42,7 +101,7 @@ export function collectionsPlugin(config: PluginConfig): VitePlugin {
}))
res.setHeader('Content-Type', 'application/json')
- res.write(JSON.stringify(collections))
+ res.write(JSON.stringify(result))
res.end()
} catch {
res.statusCode = 500
@@ -50,10 +109,12 @@ export function collectionsPlugin(config: PluginConfig): VitePlugin {
}
})
+ const componentCollection = collections.find(c => c.name === 'Components') as Collection
+
const watchedPaths = [
- ...config.componentCollection.dirs,
- config.componentCollection.exampleDir
- ].map(d => resolve(config.rootDir, d.path))
+ ...componentCollection.dirs,
+ componentCollection.exampleDir
+ ].map(d => d.path)
// Watch for changes in example directory
const watcher = watch(watchedPaths, {
diff --git a/packages/core/src/plugins/colors.ts b/packages/core/src/plugins/colors.ts
index a17282d5..85e9888b 100644
--- a/packages/core/src/plugins/colors.ts
+++ b/packages/core/src/plugins/colors.ts
@@ -1,18 +1,19 @@
import type { VitePlugin } from 'unplugin'
-import type { PluginConfig } from '../types'
+import type { PluginOptions } from '../types'
/**
* This plugin is responsible for getting the generated virtual templates and
* making them available to the Vue build.
*/
-export function colorsPlugin(config: PluginConfig): VitePlugin {
+export function colorsPlugin(options: PluginOptions): VitePlugin {
return {
name: 'compodium:colors',
+ apply: 'serve',
configureServer(server) {
server.middlewares.use('/__compodium__/api/colors', async (req, res) => {
try {
res.setHeader('Content-Type', 'application/json')
- res.end(JSON.stringify(config.extras?.colors))
+ res.end(JSON.stringify(options.extras?.colors))
} catch (err) {
res.statusCode = 500
res.end(JSON.stringify({ error: `Failed to fetch iconify API: ${err}` }))
diff --git a/packages/core/src/plugins/devtools.ts b/packages/core/src/plugins/devtools.ts
index 64e79c31..a600b486 100644
--- a/packages/core/src/plugins/devtools.ts
+++ b/packages/core/src/plugins/devtools.ts
@@ -1,17 +1,22 @@
import type { VitePlugin } from 'unplugin'
import sirv from 'sirv'
-import type { PluginConfig } from '../types'
+import type { PluginOptions } from '../types'
import { resolve, dirname } from 'pathe'
import { fileURLToPath } from 'node:url'
import { existsSync } from 'node:fs'
import { joinURL } from 'ufo'
import { resolvePathSync } from 'mlly'
-export function devtoolsPlugin(config: PluginConfig): VitePlugin {
- const userPreview = resolve(joinURL(config.rootDir, config.dir, 'preview.vue'))
+export function devtoolsPlugin(options: PluginOptions): VitePlugin {
+ let userPreview: string
return {
name: 'compodium:devtools',
+ apply: 'serve',
+
+ configResolved(viteConfig) {
+ userPreview = resolve(joinURL(options.rootDir ?? viteConfig.root, options.dir, 'preview.vue'))
+ },
config(config) {
if (process.env.COMPODIUM_DEVTOOLS_URL) {
config.server ||= {}
diff --git a/packages/core/src/plugins/examples.ts b/packages/core/src/plugins/examples.ts
index 96c1dd15..4eac1e1a 100644
--- a/packages/core/src/plugins/examples.ts
+++ b/packages/core/src/plugins/examples.ts
@@ -1,18 +1,21 @@
import fs from 'node:fs/promises'
import type { VitePlugin } from 'unplugin'
-import type { PluginConfig } from '../types'
-import { resolve } from 'node:path'
+import type { Collection, PluginOptions } from '../types'
+import { resolveCollections } from './collections'
+
+export function examplePlugin(options: PluginOptions): VitePlugin {
+ let collections: Collection[]
-export function examplePlugin(config: PluginConfig): VitePlugin {
return {
name: 'compodium:examples',
+ apply: 'serve',
- configureServer(server) {
- const paths = [
- config.componentCollection.exampleDir,
- ...config.libraryCollections.map(c => c.exampleDir)
- ].map(d => resolve(config.rootDir, d.path))
+ configResolved(viteConfig) {
+ collections = resolveCollections(options, viteConfig)
+ },
+ configureServer(server) {
+ const allowedPaths = collections.map(c => c.exampleDir.path)
server.middlewares.use('/__compodium__/api/example', async (req, res) => {
try {
const url = new URL(req.url!, `http://${req.headers.host}`)
@@ -24,9 +27,9 @@ export function examplePlugin(config: PluginConfig): VitePlugin {
return
}
- if (!paths.find(p => path.startsWith(p))) {
+ if (!allowedPaths.find(p => path.startsWith(p))) {
res.statusCode = 403
- res.end(JSON.stringify({ error: 'Forbidden' }))
+ res.end(JSON.stringify({ error: 'Forbidden', message: `${allowedPaths}\n ${path}` }))
return
}
@@ -35,7 +38,7 @@ export function examplePlugin(config: PluginConfig): VitePlugin {
let result = exampleCode.toString()
.replace(/extendCompodiumMeta\s*\([\s\S]*?\)\s*;?/g, '')
- if (config._nuxt) {
+ if (options._nuxt) {
result = result
.replace(/import .* from 'vue'/, '')
}
diff --git a/packages/core/src/plugins/iconify.ts b/packages/core/src/plugins/iconify.ts
index 497b00cf..add34f71 100644
--- a/packages/core/src/plugins/iconify.ts
+++ b/packages/core/src/plugins/iconify.ts
@@ -1,11 +1,11 @@
import type { VitePlugin } from 'unplugin'
-import type { PluginConfig } from '../types'
+import type { PluginOptions } from '../types'
import { joinURL } from 'ufo'
-export function iconifyPlugin(_config: PluginConfig): VitePlugin {
+export function iconifyPlugin(_options: PluginOptions): VitePlugin {
return {
name: 'compodium:iconify',
-
+ apply: 'serve',
configureServer(server) {
server.middlewares.use('/__compodium__/api/iconify', async (req, res) => {
try {
diff --git a/packages/core/src/plugins/meta/index.ts b/packages/core/src/plugins/meta/index.ts
index b04eb4d2..37737e59 100644
--- a/packages/core/src/plugins/meta/index.ts
+++ b/packages/core/src/plugins/meta/index.ts
@@ -1,13 +1,13 @@
import { readFile } from 'node:fs/promises'
-import type { PluginConfig } from '../../types'
+import type { Collection, PluginOptions } from '../../types'
import { createChecker } from './checker'
import { watch } from 'chokidar'
import type { VitePlugin } from 'unplugin'
-import { resolve } from 'pathe'
import AST from 'unplugin-ast/vite'
+import { resolveCollections } from '../collections'
-export function extendMetaPlugin(_config: PluginConfig): VitePlugin {
+export function extendMetaPlugin(_options: PluginOptions): VitePlugin {
return AST({
include: [/\.[jt]sx?$/, /\.vue$/],
enforce: 'post',
@@ -24,20 +24,25 @@ export function extendMetaPlugin(_config: PluginConfig): VitePlugin {
})
}
-export function metaPlugin(config: PluginConfig): VitePlugin {
- const checkerDirs = [
- ...config.componentCollection.dirs,
- config.componentCollection.exampleDir,
- ...config.libraryCollections.flatMap(c => c.dirs),
- ...config.libraryCollections.map(c => c.exampleDir)
- ]
-
- const checker = createChecker(checkerDirs)
+export function metaPlugin(options: PluginOptions): VitePlugin {
+ let collections: Collection[]
return {
name: 'compodium:meta',
+ enforce: 'pre',
+ apply: 'serve',
+
+ configResolved(viteConfig) {
+ collections = resolveCollections(options, viteConfig)
+ },
configureServer(server) {
+ const checkerDirs = collections.flatMap(c => [
+ ...c.dirs,
+ c.exampleDir
+ ])
+ const checker = createChecker(checkerDirs)
+
server.middlewares.use('/__compodium__/api/meta', async (req, res) => {
try {
const url = new URL(req.url!, `http://${req.headers.host}`)
@@ -65,10 +70,12 @@ export function metaPlugin(config: PluginConfig): VitePlugin {
}
})
+ const componentCollection = collections.find(c => c.name === 'Components') as Collection
+
const watchedPaths = [
- ...config.componentCollection.dirs,
- config.componentCollection.exampleDir
- ].map(d => resolve(config.rootDir, d.path))
+ ...componentCollection.dirs,
+ componentCollection.exampleDir
+ ].map(d => d.path)
// Watch for changes in example directory
const watcher = watch(watchedPaths, {
diff --git a/packages/core/src/plugins/utils.ts b/packages/core/src/plugins/utils.ts
index 67da1c75..89552325 100644
--- a/packages/core/src/plugins/utils.ts
+++ b/packages/core/src/plugins/utils.ts
@@ -43,10 +43,9 @@ export function resolveComponentNameSegments(fileName: string, prefixParts: stri
* Scan the components inside different components folders
* and return a unique list of components
* @param dirs all folders where components are defined
- * @param srcDir src path of your app
* @returns {Promise} Component found promise
*/
-export async function scanComponents(dirs: ComponentsDir[], srcDir: string): Promise
` ) res.setHeader('Content-Type', 'text/html') - res.end(index) + res.end(rendererIndex) } catch { res.statusCode = 500 res.end('Internal Server Error') @@ -38,9 +64,12 @@ export function rendererPlugin(config: PluginConfig): VitePlugin { load(id) { if (id === '\0@compodium/renderer.ts') { // Read the user's main entrypoint file - const mainPath = resolve(config.rootDir, config.mainPath ?? 'src/main.ts') + if (!mainPath) { + throw new Error('[Compodium] Could not infer main script path. Use the mainPath option to specify the path to your Vue script file containing createApp().') + } + if (!existsSync(mainPath)) { - throw new Error(`[Compodium] failed to resolve main file ${config.mainPath}.`) + throw new Error(`[Compodium] failed to resolve main file ${mainPath}. Use the mainPath option to specify the path to your Vue script file containing createApp().`) } const mainDir = dirname(mainPath) diff --git a/packages/vue/src/plugins/vueDevtools.ts b/packages/vue/src/plugins/vueDevtools.ts index b9980652..f4949b2f 100644 --- a/packages/vue/src/plugins/vueDevtools.ts +++ b/packages/vue/src/plugins/vueDevtools.ts @@ -1,12 +1,19 @@ import type { VitePlugin } from 'unplugin' -import type { PluginConfig } from '@compodium/core' +import type { PluginOptions } from '@compodium/core' import { colors } from 'consola/utils' import { version } from '../../package.json' import { joinURL } from 'ufo' -export function vueDevtoolsPlugin(config: PluginConfig): VitePlugin { +export function vueDevtoolsPlugin(_config: PluginOptions): VitePlugin { + let baseUrl = '/' return { name: 'compodium:devtools:vue', + apply: 'serve', + + configResolved(viteConfig) { + baseUrl = viteConfig.base + }, + configureServer(server) { const _printUrls = server.printUrls @@ -59,7 +66,7 @@ export function vueDevtoolsPlugin(config: PluginConfig): VitePlugin { injectTo: 'head-prepend' as const, attrs: { type: 'module', - src: joinURL(config.baseUrl, `/@id/virtual:compodium:devtools`) + src: joinURL(baseUrl, `/@id/virtual:compodium:devtools`) } } ] diff --git a/packages/vue/test/renderer.spec.ts b/packages/vue/test/renderer.spec.ts index a2982409..e34e644e 100644 --- a/packages/vue/test/renderer.spec.ts +++ b/packages/vue/test/renderer.spec.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest' import { createViteServer } from './utils' +import { inferMainPath } from '../src/plugins/renderer' describe('renderer', async () => { const server = await createViteServer('./fixtures/basic') @@ -28,3 +29,47 @@ describe('renderer', async () => { }) }) }) + +describe('inferMainPath', async () => { + it('works', async () => { + expect(inferMainPath(` + + +
+ + + +
+ +
+