Skip to content
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
1 change: 1 addition & 0 deletions examples/basic-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@h3ravel/router": "workspace:^",
"@h3ravel/shared": "workspace:^",
"@h3ravel/support": "workspace:^",
"@h3ravel/view": "workspace:^",
"cross-env": "^10.0.0",
"h3": "2.0.0-beta.4",
"reflect-metadata": "^0.2.2",
Expand Down
2 changes: 2 additions & 0 deletions examples/basic-app/src/bootstrap/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { QueueServiceProvider } from '@h3ravel/queue'
import { MailServiceProvider } from '@h3ravel/mail'
import { ConfigServiceProvider } from '@h3ravel/config'
import { FilesystemProvider } from '@h3ravel/filesystem'
import { ViewServiceProvider } from '@h3ravel/view'
import { AppServiceProvider } from 'src/app/Providers/AppServiceProvider'

/**
Expand All @@ -18,6 +19,7 @@ import { AppServiceProvider } from 'src/app/Providers/AppServiceProvider'
export default <Array<new (_app: Application) => ServiceProvider>>[
HttpServiceProvider,
ConfigServiceProvider,
ViewServiceProvider,
RouteServiceProvider,
AssetsServiceProvider,
DatabaseServiceProvider,
Expand Down
4 changes: 3 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,15 @@
"detect-port": "^2.1.0",
"dotenv": "^17.2.2",
"dotenv-expand": "^12.0.3",
"edge.js": "^6.3.0",
"h3": "2.0.0-beta.4",
"reflect-metadata": "^0.2.2",
"semver": "^7.7.2",
"srvx": "^0.8.7",
"tslib": "^2.8.1"
},
"optionalDependencies": {
"@h3ravel/view": "workspace:^"
},
"devDependencies": {
"@types/semver": "^7.7.1",
"typescript": "^5.9.2"
Expand Down
33 changes: 22 additions & 11 deletions packages/core/src/Providers/ViewServiceProvider.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
import { Edge } from 'edge.js'
import { ServiceProvider } from '../ServiceProvider'

export class ViewServiceProvider extends ServiceProvider {
public static priority = 995

register (): void {
const config = this.app.make('config')
const edge = Edge.create({
cache: process.env.NODE_ENV === 'production'
})
// Try to load the view package if available
try {
const { EdgeViewEngine } = require('@h3ravel/view')

const config = this.app.make('config')
const viewEngine = new EdgeViewEngine({
viewsPath: this.app.getPath('views'),
cache: process.env.NODE_ENV === 'production'
})

edge.mount(this.app.getPath('views'))
viewEngine.global('config', config.get)
viewEngine.global('app', this.app)

edge.global('config', config.get)
edge.global('app', this.app)

this.app.bind('edge', () => edge)
this.app.bind('edge', () => viewEngine.getEdge())
} catch (error) {
// View package not available - provide stub implementations
console.warn('[@h3ravel/core] View package not found. Install @h3ravel/view for template rendering support.')

// Bind stub implementations to satisfy the contract
this.app.bind('edge', () => {
throw new Error('View engine not available. Install @h3ravel/view package.')
})
}
}
}
}
19 changes: 19 additions & 0 deletions packages/view/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# @h3ravel/view

## 1.0.0

### Major Changes

- Initial release of the view package
- Extracted view system from `@h3ravel/core`
- Added Edge.js template engine integration
- Implemented ViewManager for template rendering
- Added ViewServiceProvider for framework integration

### Features

- Template rendering with Edge.js
- Laravel-like template syntax
- Production-ready caching
- Global helpers support
- Pluggable architecture
119 changes: 119 additions & 0 deletions packages/view/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# @h3ravel/view

A view rendering system for the H3ravel framework, providing template rendering capabilities using Edge.js.

## Installation

```bash
npm install @h3ravel/view
# or
pnpm add @h3ravel/view
# or
yarn add @h3ravel/view
```

## Usage

### Basic Usage

```typescript
import { ViewManager } from '@h3ravel/view'

const viewManager = new ViewManager({
viewsPath: './resources/views',
cache: process.env.NODE_ENV === 'production'
})

// Render a template
const html = await viewManager.render('welcome', { name: 'John' })
```

### With H3ravel Framework

The view package integrates seamlessly with the H3ravel framework:

```typescript
// In your controller
export class HomeController extends Controller {
public async index() {
return await view('home', {
title: 'Welcome to H3ravel',
user: { name: 'John Doe' }
})
}
}
```

### Service Provider

The package includes a service provider that automatically registers the view system:

```typescript
import { ViewServiceProvider } from '@h3ravel/view'

// The provider is automatically registered when the package is installed
```

## Features

- **Edge.js Integration**: Built on top of the powerful Edge.js template engine
- **Laravel-like Syntax**: Familiar template syntax for Laravel developers
- **Caching**: Production-ready template caching
- **Global Helpers**: Access to framework helpers within templates
- **Pluggable**: Can be used independently or as part of H3ravel

## Template Syntax

The view system uses Edge.js syntax:

```edge
{{-- resources/views/welcome.edge --}}
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>Hello, {{ user.name }}!</h1>

@if(user.isAdmin)
<p>Welcome, admin!</p>
@endif

@each(item in items)
<div>{{ item.name }}</div>
@endeach
</body>
</html>
```

## Configuration

```typescript
interface ViewConfig {
viewsPath: string // Path to views directory
cache?: boolean // Enable/disable caching
globals?: object // Global variables available in all templates
}
```

## API Reference

### ViewManager

The main class for managing view rendering.

#### Methods

- `render(template: string, data?: object): Promise<string>` - Render a template
- `exists(template: string): boolean` - Check if template exists
- `mount(path: string): void` - Mount additional view directory
- `global(key: string, value: any): void` - Add global variable

### ViewServiceProvider

Service provider for automatic integration with H3ravel framework.

## License

MIT License. See [LICENSE](../../LICENSE) for more information.
53 changes: 53 additions & 0 deletions packages/view/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "@h3ravel/view",
"version": "1.0.0",
"description": "View rendering system for H3ravel framework",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"keywords": [
"h3ravel",
"view",
"template",
"edge",
"rendering"
],
"author": "H3ravel Team",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/tusharshah21/framework.git",
"directory": "packages/view"
},
"bugs": {
"url": "https://github.com/tusharshah21/framework/issues"
},
"homepage": "https://h3ravel.toneflix.net",
"scripts": {
"build": "tsdown",
"dev": "tsdown --watch",
"test": "vitest",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"edge.js": "^6.3.0"
},
"devDependencies": {
"@h3ravel/shared": "workspace:*",
"typescript": "^5.0.0",
"vitest": "^2.0.0"
},
"peerDependencies": {
"@h3ravel/shared": "workspace:*"
}
}
54 changes: 54 additions & 0 deletions packages/view/src/Commands/MakeViewCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { mkdir, writeFile } from 'node:fs/promises'
import { dirname } from 'node:path'

/**
* Command to create new view files
*/
export class MakeViewCommand {
/**
* Create a new view file
*
* @param name - View name (can include directories like 'auth/login')
* @param options - Command options
*/
static async make(
name: string,
options: {
force?: boolean
basePath?: string
} = {}
): Promise<void> {
const { force = false, basePath = 'src/resources/views' } = options

const path = `${basePath}/${name}.edge`

// The view is scoped to a path make sure to create the associated directories
if (name.includes('/')) {
await mkdir(dirname(path), { recursive: true })
}

// Check if the view already exists
if (!force) {
try {
const { FileSystem } = await import('@h3ravel/shared')
if (await FileSystem.fileExists(path)) {
throw new Error(`View ${name} already exists`)
}
} catch (error) {
if (error instanceof Error && error.message.includes('already exists')) {
throw error
}
// FileSystem not available, continue
}
}

// Create the view file
const content = `{{-- ${path} --}}
<div>
<!-- Your view content here -->
<h1>{{ title ?? 'Welcome' }}</h1>
</div>`

await writeFile(path, content)
}
}
36 changes: 36 additions & 0 deletions packages/view/src/Contracts/ViewContract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Contract for view rendering engines
*/
export interface ViewContract {
/**
* Render a template with the given data
*
* @param template - Template name/path
* @param data - Data to pass to the template
* @returns Promise resolving to rendered HTML string
*/
render(template: string, data?: Record<string, any>): Promise<string>

/**
* Check if a template exists
*
* @param template - Template name/path
* @returns True if template exists
*/
exists(template: string): boolean

/**
* Mount a directory for template lookup
*
* @param path - Path to mount
*/
mount(path: string): void

/**
* Register a global variable/helper
*
* @param key - Global variable name
* @param value - Value or function
*/
global(key: string, value: any): void
}
Loading