Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support format config #238

Merged
merged 3 commits into from
Oct 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions docs/guide/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,8 @@ console.log(posts) // => [{ title: 'Hello world', slug: 'hello-world', ... }, ..

Velite is **Framework Agnostic**, you can use it output with any framework or library you like.

From version `0.2.0`, Velite will output the entry file in the format you specified in the config. so you can choose the format you like.

:::

For more information about using collections, see [Using Collections](using-collections.md).
7 changes: 7 additions & 0 deletions docs/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ The extensions blacklist of the assets, such as `['.md', '.yml']`, will be ignor

Whether to clean the output directories before build.

### `output.format`

- Type: `'esm' | 'cjs'`
- Default: `'esm'`

The output format of the entry file.

## `collections`

- Type: `{ [name: string]: Collection }`
Expand Down
2 changes: 1 addition & 1 deletion src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ export const build = async (options: Options = {}): Promise<Record<string, unkno
await mkdir(output.data, { recursive: true })
await mkdir(output.assets, { recursive: true })

await outputEntry(output.data, configPath, collections)
await outputEntry(output.data, output.format, configPath, collections)

logger.log('initialized', begin)

Expand Down
3 changes: 2 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ export const resolveConfig = async (path?: string, options: { strict?: boolean;
assets: resolve(cwd, loadedConfig.output?.assets ?? 'public/static'),
base: loadedConfig.output?.base ?? '/static/',
name: loadedConfig.output?.name ?? '[name]-[hash:8].[ext]',
clean: options.clean ?? loadedConfig.output?.clean ?? false
clean: options.clean ?? loadedConfig.output?.clean ?? false,
format: loadedConfig.output?.format ?? 'esm'
},
loaders: [...(loadedConfig.loaders ?? []), ...loaders],
strict: options.strict ?? loadedConfig.strict ?? false
Expand Down
22 changes: 13 additions & 9 deletions src/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { join, relative } from 'node:path'

import { logger } from './logger'

import type { Collections } from './types'
import type { Collections, Output } from './types'

const isProduction = process.env.NODE_ENV === 'production'

const emitted = new Map<string, string>()

Expand All @@ -29,20 +31,21 @@ export const emit = async (path: string, content: string, log?: string): Promise
* @param configPath resolved config file path
* @param collections collection options
*/
export const outputEntry = async (dest: string, configPath: string, collections: Collections): Promise<void> => {
export const outputEntry = async (dest: string, format: Output['format'], configPath: string, collections: Collections): Promise<void> => {
const begin = performance.now()

// generate entry according to `config.collections`
const configModPath = relative(dest, configPath)
.replace(/\\/g, '/') // replace windows path separator
.replace(/\.[mc]?[jt]s$/i, '') // remove extension
const configModPath = relative(dest, configPath).replace(/\\/g, '/')

const entry: string[] = []
const dts: string[] = [`import config from '${configModPath}'\n`]
const dts: string[] = [`import type config from '${configModPath}'\n`]
dts.push('type Collections = typeof config.collections\n')

Object.entries(collections).map(([name, collection]) => {
entry.push(`export { default as ${name} } from './${name}.json'`)
if (format === 'cjs') {
entry.push(`exports.${name} = require('./${name}.json')`)
} else {
entry.push(`export { default as ${name} } from './${name}.json'`)
}
Comment on lines +44 to +48
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Validate the format parameter to ensure robustness

Currently, the code conditionally handles 'cjs' and defaults to ES module syntax for other values of format. To prevent potential issues with unexpected format values, consider validating the format parameter and providing a default or throwing an error for unsupported formats.

You could implement a validation mechanism like this:

 export const outputEntry = async (dest: string, format: Output['format'], configPath: string, collections: Collections): Promise<void> => {
+  if (!['cjs', 'esm'].includes(format)) {
+    throw new Error(`Unsupported format '${format}'. Expected 'cjs' or 'esm'.`)
+  }
   const begin = performance.now()

This ensures that only supported formats are processed and helps catch configuration errors early.

Committable suggestion was skipped due to low confidence.

dts.push(`export type ${collection.name} = Collections['${name}']['schema']['_output']`)
dts.push(`export declare const ${name}: ${collection.name + (collection.single ? '' : '[]')}\n`)
})
Expand Down Expand Up @@ -71,7 +74,8 @@ export const outputData = async (dest: string, result: Record<string, any>): Pro
if (data == null) return
const target = join(dest, name + '.json')
// TODO: output each record separately to a single file to improve fast refresh performance in app
await emit(target, JSON.stringify(data, null, 2), `wrote '${target}' with ${data.length ?? 1} ${name}`)
const content = isProduction ? JSON.stringify(data) : JSON.stringify(data, null, 2)
await emit(target, content, `wrote '${target}' with ${data.length ?? 1} ${name}`)
logs.push(`${data.length ?? 1} ${name}`)
})
)
Expand Down
5 changes: 5 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ export interface Output {
* @default false
*/
clean: boolean
/**
* Output entry file format
* @default 'esm'
*/
format: 'esm' | 'cjs'
}

/**
Expand Down
8 changes: 4 additions & 4 deletions test/basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@ test('standalone fixtures', async t => {
equal(entry.length, 288, 'entry output length should be 288')

const dts = await readFile('examples/basic/.velite/index.d.ts', 'utf8')
equal(dts.length, 628, 'dts output length should be 628')
equal(dts.length, 636, 'dts output length should be 636')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Missing index.d.ts File in .velite Directory

The expected examples/basic/.velite/index.d.ts file was not found in the repository. Please verify that the build process correctly generates this declaration file.

🔗 Analysis chain

Approve the updated dts output length.

The change in the expected length of the dts output from 628 to 636 likely reflects updates in the generated TypeScript declaration file. This aligns with the PR objectives of supporting format config and addressing JSON import issues.

To ensure the accuracy of this change, please run the following script to verify the actual length of the generated .velite/index.d.ts file:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the length of the generated .velite/index.d.ts file

# Test: Check the length of the .velite/index.d.ts file
dts_length=$(wc -c < examples/basic/.velite/index.d.ts)
echo "Actual dts length: $dts_length"
if [ "$dts_length" -eq 636 ]; then
  echo "Length matches the expected value."
else
  echo "Length does not match the expected value."
fi

Length of output: 375


Script:

#!/bin/bash
# Description: Locate all instances of index.d.ts in the repository

fd 'index\.d\.ts$'

Length of output: 20


Script:

#!/bin/bash
# Description: Search for all TypeScript declaration files in the repository

fd '\.d\.ts$' --exclude 'node_modules' --exclude 'dist' --exclude 'build'

Length of output: 69


const options = await readFile('examples/basic/.velite/options.json', 'utf8')
equal(options.length, 1121, 'options output length should be 1121')

const categories = await readFile('examples/basic/.velite/categories.json', 'utf8')
equal(categories.length, 880, 'categories output length should be 880')

const tags = await readFile('examples/basic/.velite/tags.json', 'utf8')
equal(tags.length, 315, 'tags output length should be 315')

const pages = await readFile('examples/basic/.velite/pages.json', 'utf8')
equal(pages.length, 6182, 'pages output length should be 6182')

const posts = await readFile('examples/basic/.velite/posts.json', 'utf8')
equal(posts.length, 14165, 'posts output length should be 14165')

const tags = await readFile('examples/basic/.velite/tags.json', 'utf8')
equal(tags.length, 315, 'tags output length should be 315')

await rm('examples/basic/.velite', { recursive: true, force: true })
})
16 changes: 8 additions & 8 deletions test/nextjs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,22 @@ test('integration with nextjs fixtures', async t => {
equal(entry.length, 288, 'entry output length should be 288')

const dts = await readFile('examples/nextjs/.velite/index.d.ts', 'utf8')
equal(dts.length, 628, 'dts output length should be 628')
equal(dts.length, 636, 'dts output length should be 636')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Issue Detected: Missing index.d.ts File

The test test/nextjs.ts includes an assertion for index.d.ts, but the file does not exist in the repository. This discrepancy may lead to failing tests or incomplete functionality.

  • File Missing: examples/nextjs/.velite/index.d.ts does not exist.
🔗 Analysis chain

LGTM. Verify the content of index.d.ts.

The expected length for index.d.ts has been updated from 628 to 636 bytes. This small increase aligns with the PR's objective of supporting a new format configuration.

To ensure the change is correct, please verify the content of the index.d.ts file:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the content of index.d.ts file
# Expected: The file should contain new type definitions or modifications related to the format configuration

cat examples/nextjs/.velite/index.d.ts

Length of output: 190


Script:

#!/bin/bash
# Description: Check if index.d.ts exists and display its contents if it does.

if [ -f examples/nextjs/.velite/index.d.ts ]; then
  echo "File exists. Displaying contents:"
  cat examples/nextjs/.velite/index.d.ts
else
  echo "File examples/nextjs/.velite/index.d.ts does not exist."
fi

Length of output: 168


Script:

#!/bin/bash
# Description: Locate the index.d.ts file in the repository and display its contents if found.

index_file=$(fd index.d.ts .)

if [ -n "$index_file" ]; then
  echo "Found index.d.ts at: $index_file"
  echo "Displaying contents of index.d.ts:"
  cat "$index_file"
else
  echo "index.d.ts file does not exist in the repository."
fi

Length of output: 157


const options = await readFile('examples/nextjs/.velite/options.json', 'utf8')
equal(options.length, 1121, 'options output length should be 1121')
equal(options.length, 775, 'options output length should be 775')

const categories = await readFile('examples/nextjs/.velite/categories.json', 'utf8')
equal(categories.length, 880, 'categories output length should be 880')

const tags = await readFile('examples/nextjs/.velite/tags.json', 'utf8')
equal(tags.length, 315, 'tags output length should be 315')
equal(categories.length, 649, 'categories output length should be 649')

const pages = await readFile('examples/nextjs/.velite/pages.json', 'utf8')
equal(pages.length, 5003, 'pages output length should be 5003')
equal(pages.length, 4942, 'pages output length should be 4942')

const posts = await readFile('examples/nextjs/.velite/posts.json', 'utf8')
equal(posts.length, 20171, 'posts output length should be 20171')
equal(posts.length, 17991, 'posts output length should be 17991')

const tags = await readFile('examples/nextjs/.velite/tags.json', 'utf8')
equal(tags.length, 212, 'tags output length should be 212')

await rm('examples/nextjs/.velite', { recursive: true, force: true })
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Add test cases for the new format config in NextJS setup

The test doesn't explicitly check for the new format config. Consider adding test cases to verify that both 'esm' and 'cjs' formats are correctly applied when specified in the config.

const config = await readFile('examples/nextjs/velite.config.ts', 'utf8')
t.true(config.includes("format: ['esm', 'cjs']"), 'Config includes both esm and cjs formats')

const esmEntry = await readFile('examples/nextjs/.velite/index.mjs', 'utf8')
t.true(esmEntry.length > 0, 'ESM entry file is generated')

const cjsEntry = await readFile('examples/nextjs/.velite/index.js', 'utf8')
t.true(cjsEntry.length > 0, 'CJS entry file is generated')

equal(entry.length, 288, 'entry output length should be 288')

})