Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
868a8c9
feat(testing): setup
romhml Aug 29, 2025
96bde28
Merge branch 'main' of github.com:romhml/compodium into feat/testing
romhml Sep 14, 2025
d58b6f5
feat(testing): nuxt support
romhml Sep 14, 2025
78502e2
Merge branch 'main' of github.com:romhml/compodium into feat/testing
romhml Sep 25, 2025
395297b
fix(nuxt): start vitest
romhml Sep 26, 2025
f8bd84f
chore(testing): remove e2e utils
romhml Sep 26, 2025
7fbff70
refactor(testing): use `@vitest/ws-client` and fix `@nuxt/test-utils`…
romhml Sep 28, 2025
655d05d
Merge branch 'main' into feat/testing
romhml Sep 28, 2025
1c33662
chore: remove package overrides
romhml Sep 28, 2025
9e00b57
test: set `nuxt.options.test` to false in test fixtures
romhml Sep 28, 2025
c98fc1a
Merge branch 'main' of github.com:romhml/compodium into feat/testing
romhml Sep 28, 2025
fbf3da3
fix(testing): invalid type reference
romhml Sep 28, 2025
1053435
test(vue): update types
romhml Sep 28, 2025
c9720d4
Merge branch 'main' of github.com:romhml/compodium into feat/testing
romhml Oct 11, 2025
51300b8
feat(testing): add describeComponents
romhml Oct 11, 2025
d64b126
Merge branch 'main' of github.com:romhml/compodium into feat/testing
romhml Oct 11, 2025
b233c8b
chore: up
romhml Oct 11, 2025
9093e96
fix(testing): set meta during test collection
romhml Oct 19, 2025
d65fb7d
chore(testing): fix recursion in build
romhml Oct 25, 2025
88194e5
Merge branch 'main' of github.com:romhml/compodium into feat/testing
romhml Oct 25, 2025
0ece820
Merge branch 'main' of github.com:romhml/compodium into feat/testing
romhml Oct 25, 2025
9787aa5
chore: up
romhml Oct 25, 2025
8e3eb20
chore(nuxt): fix build
romhml Oct 25, 2025
a1afde2
Merge branch 'main' of github.com:romhml/compodium into feat/testing
romhml Nov 1, 2025
a2f0c34
chore(nuxt): update playground tests
romhml Nov 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"@compodium/examples": "workspace:^",
"@compodium/meta": "workspace:^",
"@compodium/nuxt": "workspace:^",
"@compodium/vue": "workspace:^"
"@compodium/vue": "workspace:^",
"@compodium/testing": "workspace:^"
},
"pnpm": {
"onlyBuiltDependencies": [
Expand Down
1 change: 0 additions & 1 deletion packages/core/build.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export default defineBuildConfig({
{ builder: 'copy', input: './src/runtime', outDir: './dist/runtime' }
],
replace: {
'process.env.COMPODIUM_TEST': 'false',
'process.env.COMPODIUM_DEVTOOLS_URL': undefined
}
})
4 changes: 3 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@
"typecheck": "tsc --noEmit"
},
"peerDependencies": {
"@compodium/testing": "workspace:^",
"vite": ">=6",
"vitest": ">=3.2.4",
"vue": ">=3"
},
"peerDependenciesMeta": {
"vite": {
"@compodium/testing": {
"optional": true
}
},
Expand Down
18 changes: 16 additions & 2 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Plugin } from 'vite'
import { collectionsPlugin } from './plugins/collections'
import { extendMetaPlugin, metaPlugin } from './plugins/meta'
import { examplePlugin } from './plugins/examples'
Expand All @@ -8,8 +9,8 @@ import type { PluginOptions } from './types'

export * from './types'

export const compodium = /* #__PURE__ */ (options: PluginOptions) => {
return [
export const compodium = /* #__PURE__ */ async (options: PluginOptions) => {
const plugins: Plugin[] = [
collectionsPlugin(options),
metaPlugin(options),
extendMetaPlugin(options),
Expand All @@ -18,4 +19,17 @@ export const compodium = /* #__PURE__ */ (options: PluginOptions) => {
iconifyPlugin(options),
colorsPlugin(options)
]

if (options.testing?.enabled) {
try {
const { compodiumTesting } = await import('@compodium/testing')
compodiumTesting(options).forEach(p => plugins.push(p))
} catch {
throw new Error(
'The `@compodium/testing` package is required when tests are enabled.'
)
}
}

return plugins
}
127 changes: 76 additions & 51 deletions packages/core/src/plugins/collections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { resolve } from 'pathe'
import { joinURL } from 'ufo'
import type { ResolvedConfig } from 'vite'

const VIRTUAL_MODULE_ID = 'virtual:compodium/collections'
const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID

export function resolveCollections(options: PluginOptions, viteConfig: ResolvedConfig): Collection[] {
const rootDir = options.rootDir ?? viteConfig.root

Expand Down Expand Up @@ -54,52 +57,76 @@ export function resolveCollections(options: PluginOptions, viteConfig: ResolvedC
]
}

async function generateCollectionsData(collections: Collection[]) {
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: c.filePath,
componentName: c.pascalName,
collectionName: col.name
}))

const mainExample = componentExamples.find(e => e.pascalName === `${c.pascalName}Example`)
const component = mainExample ?? c

if (col.name !== 'Components' && !mainExample) return []

return [{
...component,
wrapperComponent: col.wrapperComponent,
docUrl: col.getDocUrl?.(c.pascalName),
examples: componentExamples.filter(e => e.pascalName !== mainExample?.pascalName),
collectionName: col.name
}]
})

return {
...col,
components: collectionComponents
}
}))

return result
}

export function collectionsPlugin(options: PluginOptions): VitePlugin {
let collections: Collection[]
let server: any

return {
name: 'compodium:collections',
apply: 'serve',
enforce: 'post',
enforce: 'pre',

configResolved(viteConfig) {
collections = resolveCollections(options, viteConfig)
},

configureServer(server) {
server.middlewares.use('/__compodium__/api/collections', async (_, res) => {
try {
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: c.filePath
}))

const mainExample = componentExamples.find(e => e.pascalName === `${c.pascalName}Example`)
const component = mainExample ?? c

// Hides third party library components if no example can be found.
if (col.name !== 'Components' && !mainExample) return []

return [{
...component,
wrapperComponent: col.wrapperComponent,
docUrl: col.getDocUrl?.(c.pascalName),
examples: componentExamples.filter(e => e.pascalName !== mainExample?.pascalName)
}]
})

return {
...col,
components: collectionComponents
}
}))
resolveId(id) {
if (id === VIRTUAL_MODULE_ID) {
return RESOLVED_VIRTUAL_MODULE_ID
}
},

async load(id) {
if (id === RESOLVED_VIRTUAL_MODULE_ID) {
const data = await generateCollectionsData(collections)
return `export default ${JSON.stringify(data, null, 2)}`
}
},

configureServer(_server) {
server = _server

// Existing middleware endpoint
server.middlewares.use('/__compodium__/api/collections', async (_: any, res: any) => {
try {
const result = await generateCollectionsData(collections)
res.setHeader('Content-Type', 'application/json')
res.write(JSON.stringify(result))
res.end()
Expand All @@ -116,7 +143,6 @@ export function collectionsPlugin(options: PluginOptions): VitePlugin {
componentCollection.exampleDir
].map(d => d.path)

// Watch for changes in example directory
const watcher = watch(watchedPaths, {
persistent: true,
awaitWriteFinish: {
Expand All @@ -125,34 +151,33 @@ export function collectionsPlugin(options: PluginOptions): VitePlugin {
}
})

watcher.on('add', async (filePath: string) => {
const handleFileChange = async (filePath: string, event: string) => {
if (watchedPaths.find(p => filePath.startsWith(p))) {
// Existing WebSocket HMR
server.ws.send({
type: 'custom',
event: 'compodium:hmr',
data: { path: filePath, event: 'component:added' }
data: { path: filePath, event }
})

// Virtual module invalidation
const module = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID)
if (module) {
server.reloadModule(module)
}
}
}

watcher.on('add', async (filePath: string) => {
await handleFileChange(filePath, 'component:added')
})

watcher.on('addDir', async (filePath: string) => {
if (watchedPaths.find(p => filePath.startsWith(p))) {
server.ws.send({
type: 'custom',
event: 'compodium:hmr',
data: { path: filePath, event: 'component:added' }
})
}
await handleFileChange(filePath, 'component:added')
})

watcher.on('unlink', async (filePath: string) => {
if (watchedPaths.find(p => filePath.startsWith(p))) {
server.ws.send({
type: 'custom',
event: 'compodium:hmr',
data: { path: filePath, event: 'component:removed' }
})
}
await handleFileChange(filePath, 'component:removed')
})
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/plugins/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export function colorsPlugin(options: PluginOptions): VitePlugin {
return {
name: 'compodium:colors',
apply: 'serve',
enforce: 'pre',
configureServer(server) {
server.middlewares.use('/__compodium__/api/colors', async (req, res) => {
try {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/plugins/devtools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export function devtoolsPlugin(options: PluginOptions): VitePlugin {

return {
name: 'compodium:devtools',
enforce: 'pre',
apply: 'serve',

configResolved(viteConfig) {
Expand All @@ -32,7 +33,7 @@ export function devtoolsPlugin(options: PluginOptions): VitePlugin {
},

configureServer(server) {
if (process.env.COMPODIUM_DEVTOOLS_URL || process.env.COMPODIUM_TEST) return
if (process.env.COMPODIUM_DEVTOOLS_URL || process.env.VITEST) return
server.middlewares.use('/__compodium__/devtools',
sirv(resolve(dirname(fileURLToPath(import.meta.url)), './client/devtools'),
{ single: true, setHeaders: res => res.setHeader('Cache-Control', 'public, max-age=3600, stale-while-revalidate=86400') }
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/plugins/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export function examplePlugin(options: PluginOptions): VitePlugin {

return {
name: 'compodium:examples',
enforce: 'pre',
apply: 'serve',

configResolved(viteConfig) {
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/plugins/iconify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { joinURL } from 'ufo'
export function iconifyPlugin(_options: PluginOptions): VitePlugin {
return {
name: 'compodium:iconify',
enforce: 'pre',
apply: 'serve',

configureServer(server) {
server.middlewares.use('/__compodium__/api/iconify', async (req, res) => {
try {
Expand Down
Loading
Loading