diff --git a/docs/cli-application/commands.mdx b/docs/cli-application/commands.mdx index 7e99f048..c3873f86 100644 --- a/docs/cli-application/commands.mdx +++ b/docs/cli-application/commands.mdx @@ -1118,7 +1118,7 @@ the template, check the example: this.generator.properties({ name: 'Lenon' }) ``` -```edge +```typescript console.log('Hello {{ name }}') ``` diff --git a/docs/database/getting-started.mdx b/docs/database/getting-started.mdx index 1676027f..1e335099 100644 --- a/docs/database/getting-started.mdx +++ b/docs/database/getting-started.mdx @@ -44,7 +44,7 @@ your project: ## Configuration All the configuration options for your application's database -behavior is housed in the `Path.config('database.js')` +behavior is housed in the `Path.config('database.ts')` configuration file. This file allows you to configure your application's database connections, so be sure to review each of the available connections and their options. We'll review diff --git a/docs/rest-api-application/controllers.mdx b/docs/rest-api-application/controllers.mdx index 597a041c..1f49cf17 100644 --- a/docs/rest-api-application/controllers.mdx +++ b/docs/rest-api-application/controllers.mdx @@ -18,7 +18,7 @@ related request handling logic into a single class. For example, a `UserController` class might handle all incoming requests related to users, including showing, creating, updating, and deleting users. By default, controllers are -stored in the `app/http/controllers` directory. +stored in the `Path.controllers()` directory. ## Writing controllers @@ -71,7 +71,7 @@ node artisan make:controller PhotoController --resource ``` This command will generate a controller at -`app/http/controllers/PhotoController`. The controller will +`Path.controllers('PhotoController.ts')`. The controller will contain a method for each of the available resource operations. Next, you may register a resource route that points to the controller: diff --git a/docs/rest-api-application/middlewares.mdx b/docs/rest-api-application/middlewares.mdx index d2fde86c..10b86e71 100644 --- a/docs/rest-api-application/middlewares.mdx +++ b/docs/rest-api-application/middlewares.mdx @@ -35,7 +35,7 @@ node artisan make:middleware EnsureApiKeyIsValid ``` This command will place a new `EnsureApiKeyIsValid` class -within your `app/http/middlewares` directory. In this +within your `Path.middlewares()` directory. In this middleware, we will only allow access to the route if the supplied api key input matches a specified value. Otherwise, we will throw an unauthorized exception: diff --git a/docs/rest-api-application/rate-limiting.mdx b/docs/rest-api-application/rate-limiting.mdx index 3b630173..b1e0ff33 100644 --- a/docs/rest-api-application/rate-limiting.mdx +++ b/docs/rest-api-application/rate-limiting.mdx @@ -64,7 +64,7 @@ Route.group(() => { :::warning The `rateLimit()` method of route groups will never -subscribe the already set methods of routes. Use it +overwrite the already set methods of routes. Use it to create "defaults" configurations for all routes. ::: diff --git a/docs/rest-api-application/request-context.mdx b/docs/rest-api-application/request-context.mdx index 473c3eb1..0734cb0f 100644 --- a/docs/rest-api-application/request-context.mdx +++ b/docs/rest-api-application/request-context.mdx @@ -48,8 +48,7 @@ files that were submitted with the request. #### The `id` getter -With this getter, you will be able to get the id from the -request: +Get the id from the request: ```typescript Route.get('/welcome', ({ request }) => { @@ -64,8 +63,7 @@ Check the [tracing requests](/docs/rest-api-application/tracing-requests) docume #### The `ip` getter -With this getter, you will be able to get the ip from where -the requests were executed: +Get the ip from where the request were executed: ```typescript Route.get('/welcome', ({ request }) => { @@ -77,8 +75,7 @@ Route.get('/welcome', ({ request }) => { #### The `method` getter -With this getter, you will be able to get the -[`REST`](https://restfulapi.net/) method of your request: +Get the [`REST`](https://restfulapi.net/) method of your request: ```typescript Route.get('/welcome', ({ request }) => { @@ -90,9 +87,8 @@ Route.get('/welcome', ({ request }) => { #### The `hostUrl` getter -With this getter, you will be able to get the host url of -the request concatenating the host:port of your application -and the [`originalUrl`](/docs/the-basics/http/context#the-originalUrl-getter) +Get the host url of the request concatenating the host:port +of your application and the [`originalUrl`](/docs/the-basics/http/context#the-originalUrl-getter) of the request: ```typescript @@ -105,8 +101,7 @@ Route.get('/welcome', ({ request }) => { #### The `baseUrl` getter -With this getter, you will be able to get the url of the -route without the query params: +Get the url of the route without the query params: ```typescript Route.get('/welcome', ({ request }) => { @@ -118,8 +113,7 @@ Route.get('/welcome', ({ request }) => { #### The `originalUrl` getter -With this getter, you will be able to get the original url -with the query params: +Get the original url with the query params: ```typescript Route.get('/welcome', ({ request }) => { @@ -131,8 +125,7 @@ Route.get('/welcome', ({ request }) => { #### The `body`, `params`, `queries` and `headers` getters -With these getters, you will be able to retrieve all the -data inside each one of then: +Retrieve all the data inside each one of then: ```typescript Route.post('/welcome/:id', ({ request }) => { @@ -145,10 +138,9 @@ Route.post('/welcome/:id', ({ request }) => { }) ``` -#### The `input` and `payload` methods +#### The `input()` and `payload()` methods -With these methods you will be able to retrieve only one -value per call from the request body: +Retrieve only one value per call from the request body: ```typescript Route.post('/welcome/:id', ({ request }) => { @@ -179,10 +171,10 @@ nested within JSON arrays / objects: const name = request.input('user.name') ``` -#### The `only` and `except` methods +#### The `only()` and `except()` methods If you need to retrieve a subset of the input data, you may -use the `only` and `except` methods. Both of these methods +use the `only()` and `except()` methods. Both of these methods accept a single array or a dynamic list of arguments: ```typescript @@ -195,18 +187,17 @@ const input = request.except(['credit_card']) :::warning -The `only` method returns all the key / value pairs that you +The `only()` method returns all the key / value pairs that you request; however, it will not return key / value pairs that are not present on the request body. ::: -#### The `param`, `query` and `header` methods +#### The `param()`, `query()` and `header()` methods -With these methods you will be able to retrieve only one -value per call from the above methods. You can also set a -second parameter that will set the default value if the -first argument key doesn't exist: +Retrieve only one value of `params`, `queries` or `headers`. +You can also set a second parameter that will set the default +value if the first argument key doesn't exist: ```typescript Route.post('/welcome/:id', ({ request }) => { @@ -225,11 +216,10 @@ Route.post('/welcome/:id', ({ request }) => { }) ``` -#### The `getFastifyRequest` method +#### The `getFastifyRequest()` method -With this method, you will be able to retrieve the vanilla -Fastify request object to use more advanced getters and -methods from Fastify: +Retrieve the vanilla Fastify request object to use more +advanced getters and methods from Fastify: ```typescript Route.get('/welcome', ({ request }) => { @@ -246,10 +236,9 @@ interact with the current HTTP response being handled by your application as well set a status code and return the response to the client. -#### The `send` method +#### The `send()` method -With this method, you are going to terminate the request -sending a response body to the client: +Terminate the request sending a response body to the client: ```typescript Route.get('/welcome', ({ response }) => { @@ -257,10 +246,20 @@ Route.get('/welcome', ({ response }) => { }) ``` -#### The `helmet` method +#### The `view()` method -With this method you are going to apply all the -[`Helmet`](https://www.npmjs.com/package/helmet) response +Terminate the request rendering a view in the response +body to the client: + +```typescript +Route.get('/welcome', ({ response }) => { + response.view('welcome', { hello: 'world' }) +}) +``` + +#### The `helmet()` method + +Apply all the [`Helmet`](https://www.npmjs.com/package/helmet) response headers in your response: ```typescript @@ -275,10 +274,9 @@ Route.get('/welcome', async ({ response }) => { }) ``` -#### The `status` method +#### The `status()` method -With this method you are going to apply the status code of -your response: +Apply the status code of your response: ```typescript Route.get('/welcome', async ({ response }) => { @@ -286,13 +284,32 @@ Route.get('/welcome', async ({ response }) => { }) ``` -#### The `header`, `safeHeader` and `removeHeader` methods +#### The `sendFile()` method + +Serve files if the [static plugin](/docs/rest-api-application/static-files) +is enabled in your application: + +```typescript +Route.get('/welcome', async ({ response }) => { + response.status(200).sendFile('img.png') +}) +``` + +#### The `download()` method + +Serve files with a custom name if the [static plugin](/docs/rest-api-application/static-files) +is enabled in your application: + +```typescript +response.status(200).download('img.png', 'custom-img.png') +``` + +#### The `header()`, `safeHeader()` and `removeHeader()` methods -With these methods you can set custom header for your -response, the `header` method will subscribe the already -set headers, the `safeHeader` will only register the header -if the header is not yet registered and the `removeHeader` -will remove a header from the response: +Set custom header for your response, the `header()` method will +overwrite the already set headers, the `safeHeader()` will only +register the header if the header is not yet registered and +the `removeHeader()` will remove a header from the response: ```typescript Route.get('/welcome', async ({ response }) => { @@ -302,10 +319,9 @@ Route.get('/welcome', async ({ response }) => { }) ``` -#### The `redirectTo` method +#### The `redirectTo()` method -With this method, you can redirect your response to another -url and with a different status code: +Redirect your response to another url and with a different status code: ```typescript Route.get('/hello', ctx => ctx.response.status(200)) @@ -317,8 +333,7 @@ Route.get('/welcome', async ({ response }) => { #### The `sent` getter -With this getter, you can verify if your response has already -been sent to client, useful to be used in +Verify if your response has already been sent to client, useful to be used in [interceptors](/docs/rest-api-application/middlewares#intercept-middleware): ```typescript @@ -333,9 +348,8 @@ Route.get('/welcome', async ({ response }) => { #### The `body`, `statusCode` and `headers` getters -With these getters, you can get the content of the response -body, status code and headers if it exists. These values will -be available before you use `response.send()`, +Get the content of the response body, status code and headers if +it exists. These values will be available before you use `response.send()`, `response.status()` and `response.headers()` methods somewhere. These getters are useful when using [interceptors](/docs/rest-api-application/middlewares#intercept-middleware) and @@ -361,9 +375,9 @@ Route.get('/welcome', async ({ response }) => { #### The `responseTime` getter -With this getter, you will be able to get how much time your -request has taken until it finish and turn back to client. -This value will only be available in [terminators](/docs/rest-api-application/middlewares#terminate-middleware): +Get how much time your request has taken until it finish and +turn back to client. This value will only be available in +[terminators](/docs/rest-api-application/middlewares#terminate-middleware): ```typescript Route.get('/welcome', async ({ response }) => { @@ -373,11 +387,10 @@ Route.get('/welcome', async ({ response }) => { }) ``` -#### The `getFastifyResponse` method +#### The `getFastifyResponse()` method -With this method, you will be able to retrieve the vanilla -Fastify response object to use more advanced getters and -methods from Fastify: +Retrieve the vanilla Fastify response object to use more advanced +getters and methods from Fastify: ```typescript Route.get('/welcome', ({ response }) => { @@ -389,23 +402,21 @@ Route.get('/welcome', ({ response }) => { ### The params object -Athenna `params` is just a simple object that contains the -actual HTTP params of the request that is being handled by -your application. +The `params` object contains the actual HTTP params of the +request that is being handled by your application. ### The queries object -Athenna `queries` is just a simple object that contains the -actual HTTP queries of the request that is being handled by -your application. +The `queries` object contains the actual HTTP queries of the +request that is being handled by your application. ### The data object -Athenna `data` is just a simple object that you can use to -set data inside to transfer between middlewares. This is -really useful for some cases. Let's see an example setting -default pagination values if client has not sent page and -limit: +Use the `data` object to define properties that will be available +in your entire request flow. This is really useful for some cases +where you want to transfer data from a middleware to a controller +for example. Let's see an example setting default pagination values +if client has not sent page and limit: ```typescript import { Config } from '@athenna/config' diff --git a/docs/rest-api-application/security-with-helmet.mdx b/docs/rest-api-application/security-with-helmet.mdx index 23616aed..fa1d6e8c 100644 --- a/docs/rest-api-application/security-with-helmet.mdx +++ b/docs/rest-api-application/security-with-helmet.mdx @@ -48,6 +48,7 @@ file in the `helmet` object: ```typescript export default { helmet: { + enabled: true, global: true } } @@ -81,7 +82,7 @@ Route.group(() => { :::warning The `helmet()` method of route groups will never -subscribe the already set methods of routes. Use it +overwrite the already set methods of routes. Use it to create "defaults" configurations for all routes. ::: diff --git a/docs/rest-api-application/static-files.mdx b/docs/rest-api-application/static-files.mdx new file mode 100644 index 00000000..dd04075a --- /dev/null +++ b/docs/rest-api-application/static-files.mdx @@ -0,0 +1,111 @@ +--- +title: Static Files +sidebar_position: 12 +description: See how to serve static files in Athenna REST API application. +--- + +# Static Files + +See how to serve static files in Athenna REST API application. + +## Introduction + +In Athenna you can use the `response` object to serve static +files from a given directory. + +## Basic usage + +Athenna uses the [`@fastify/static`](https://github.com/fastify/fastify-static) +plugins inside `HttpKernel`. All the configurations that `@fastify/static` +supports can be set inside `Path.config('http.ts')` file in the `static` +object. + +```typescript title="Path.config('http.ts')" +import { Path } from '@athenna/common' + +export default { + static: { + enabled: true, + root: Path.public(), + prefix: '/public/' + } +} +``` + +Now you can use the `response.sendFile()` methods to serve +files from `Path.public()` directory: + +```typescript title="Path.route('http.ts')" +Route.get('/hello', ({ response }) => { + // Serve Path.public('image.png') directly + return response.sendFile('image.png') 👈 +}) +``` + +To serve a file from a different root location add the +root path as second parameter: + +```typescript title="Path.route('http.ts')" +Route.get('/hello', ({ response }) => { + return response.sendFile('image.png', Path.build()) 👈 +}) +``` + +You could also add options directly in the response: + +```typescript title="Path.route('http.ts')" +Route.get('/hello', ({ response }) => { + return response.sendFile('image.png', { + cacheControl: false, + etag: true, + dotfiles: 'ignore', + lastModified: true + }) +}) +``` + +### Custom file name + +You can use the `response.download()` to set a custom file name +by changing the `content-disposition` header: + +```typescript title="Path.route('http.ts')" +Route.get('/hello', ({ response }) => { + return response.download('image.png', 'custom-image.png') 👈 +}) +``` + +You could also add options directly in the response: + +```typescript title="Path.route('http.ts')" +Route.get('/hello', ({ response }) => { + return response.download('image.png', 'custom-image.png', { + cacheControl: false, + etag: true, + dotfiles: 'ignore', + lastModified: true + }) +}) +``` + +## Disabling static file server + +The `HttpKernel` class will automatically disable the +plugin registration if the package does not exist, so +to disable static file server in Athenna you need to +remove the `@fastify/static` package from your +application: + +```bash +npm remove @fastify/static +``` + +You can also disable by setting `http.static.enabled` to `false`: + +```typescript title="Path.config('http.ts')" +export default { + static: { + enabled: false + } +} +``` diff --git a/docs/rest-api-application/swagger-documentation.mdx b/docs/rest-api-application/swagger-documentation.mdx index 411d6ebf..c7f0c813 100644 --- a/docs/rest-api-application/swagger-documentation.mdx +++ b/docs/rest-api-application/swagger-documentation.mdx @@ -4,7 +4,7 @@ sidebar_position: 10 description: See how to create the Swagger documentation for Athenna REST API application. --- -# Swagger +# Swagger Documentation See how to create the Swagger documentation for Athenna REST API application. @@ -27,9 +27,10 @@ supports can be set inside `Path.config('http.ts')` file in the `swagger.ui` obj ```typescript title="Path.config('http.ts')" export default { swagger: { + enabled: true, ui: { staticCSP: true, - routePrefix: '/documentation' + routePrefix: '/docs' }, configurations: { mode: 'dynamic', @@ -116,9 +117,9 @@ Route.group(() => { :::warning -The swagger methods of route groups will never subscribe -the already set methods of routes. Use them to create "defaults" -configurations for all routes such as `security`. +The swagger methods of route groups will never overwrite +the already set methods of routes. Use them to create +"defaults" configurations for all routes such as `security`. ::: diff --git a/docs/rest-api-application/tracing-requests.mdx b/docs/rest-api-application/tracing-requests.mdx index 1e742555..e1a65f62 100644 --- a/docs/rest-api-application/tracing-requests.mdx +++ b/docs/rest-api-application/tracing-requests.mdx @@ -25,8 +25,8 @@ that `cls-rtracer` supports can be set inside ```typescript title="Path.config('http.ts')" export default { - trace: true, rTracer: { + enabled: true, echoHeader: false, useHeader: false, headerName: 'X-Request-Id', diff --git a/docs/rest-api-application/views.mdx b/docs/rest-api-application/views.mdx new file mode 100644 index 00000000..e1d4929c --- /dev/null +++ b/docs/rest-api-application/views.mdx @@ -0,0 +1,405 @@ +--- +title: Views +sidebar_position: 11 +description: Understand how you can use the Athenna view API for rendering HTML pages. +--- + +# Views + +Understand how you can use the Athenna view API for rendering HTML pages. + +## Introduction + +Of course, it's not practical to return entire HTML documents +strings directly from your routes and controllers. Thankfully, +views provide a convenient way to place all of our HTML in +separate files. + +Views separate your controller / application logic from your +presentation logic. When using Athenna, view templates are +usually written using the [Edge templating language](https://edgejs.dev/docs/introduction). +A simple view might look something like this: + +```html title="Path.views('greeting.edge')" + + +

Hello, {{ name }}

+ + +``` + +And we may return it in our routes using the `response.view()` method +like so: + +```typescript +Route.get('/', ({ response }) => { + return response.view('greeting', { name: 'Lenon' }) +}) +``` + +:::note + +Looking for more information on how to write Edge templates? +Check out the full [Edge documentation](https://edgejs.dev/docs/introduction) +to get started. + +::: + +## Views support + +Our focus at the moment is to be a framework for backend +applications development. But since the `View` API exists in +Athenna ecosystem for creating template files for `make` commands +of Artisan, providing this feature for you to create monolith +applications with Athenna is possible, but remember that +supporting this kind of application is not our focus right +now. + +If you think this is an interesting feature to improve, feel +free to open a [discussion](https://github.com/orgs/AthennaIO/discussions) +at Athenna organization, any contribution is welcome! + +## Installation + +By default, `@athenna/view` package will already be installed +in your Athenna project. But you still need to configure it +to be able to use all of it features. + +Artisan provides a very simple command to configure the view +library in your project. Simply run the following: + +```bash +node artisan configure @athenna/view +``` + +The view configurer will do the following operations in +your project: + +- Create the `view.ts` configuration file. +- Add all view providers in your `.athennarc.json` file. +- Add all view commands in your `.athennarc.json` file. +- Add all view template files in your `.athennarc.json` file. + +## Configuration + +All the configuration options for your application's views +behavior is housed in the `Path.config('view.ts')` +configuration file. We will see later on this documentation +the purpose of each option: + +```typescript +import { Path } from '@athenna/common' +import { Env, Config } from '@athenna/config' + +export default { + disk: Path.views(), + namedDisks: {}, + components: {}, + properties: { + env: Env, + config: Config + }, + edge: { + cache: Env('APP_ENV') === 'production' + } +} +``` + +## Defining & rendering views + +To create a new view, use the `make:view` command: + +```bash +node artisan make:view greeting +``` + +This command will place a new `greeting.edge` template +within your `Path.views()` directory. In this view we +will render a simple profile with an avatar and the user name: + +```html title="Path.views('greeting.edge') + + + + + + +

{{ name }}

+ + +``` + +Edge templates contain HTML as well as Edge directives that +allow you to easily log values, create "if" statements, +iterate over data, and more. Check [Edge documentation](https://edgejs.dev/docs/introduction) +for more details around the syntax of Edge templates. + +Once you have created a view, you may return it from one of your application's +routes or controllers using the `response.view()` method: + +```typescript title="Path.routes('http.ts')" +import { Route } from '@athenna/view' + +Route.get('greeting', ({ request, response }) => { + // Render the Path.views('greeting.edge') template + return response.view('greeting', { name: 'Lenon' }) 👈 +}) +``` + +Views may also be rendered using the `View` facade: + +```typescript +import { View } from '@athenna/view' + +await View.render('greeting') +``` + +As you can see, the first argument passed to the method corresponds +to the name of the view file in the `Path.views()` directory as defined +in the `disk` options of the [configuration file](/docs/rest-api-application/views#configuration) +of the view package. The second argument is an object of data that +should be made available to the view. In this case, we are passing +the `name` variable, which is displayed in the view using [Edge syntax](https://edgejs.dev/docs/syntax_specification). + +### Nested view directories + +Views may also be nested within subdirectories of your views +directory. "Slash" notation may be used to reference nested views. +For example, if your view is stored at `Path.views('admin/profile.edge')`, +you may return it from one of your application's routes / controllers +like so: + +```typescript +return response.view('admin/profile', data) 👈 +``` + +### Rendering raw string + +You can render raw template string values using the +`View.renderRaw()` or `View.renderRawSync()` methods: + +```typescript +await View.renderRaw('

Hello {{ name }}

', { + name: 'Lenon' +}) +``` + +:::note + +Raw strings do not enjoy the benefits of [template caching](/docs/rest-api-application/views#optimizing-views) +as there are not associated with a unique path. + +::: + +## Defining & rendering named disks + +Named disks could be defined inside `Path.config('view.ts')` +file, in the `namedDisks` configuration: + +```typescript +export default { + namedDisks: { + admin: Path.views('admin') + } +} +``` + +Now you can render templates by prefixing the disk name +before the template path: + +```typescript +return response.view('admin::profile', user) 👈 +``` + +For rendering nested views inside named disks you can use "slash" +syntax: + +```typescript +return response.view('admin::dashboard/home', user) 👈 +``` + +## Sharing data with all views + +As you saw in the previous examples, you may pass an object of data +to views to make that data available to the view: + +```typescript +return response.view('greeting', { name: 'Lenon' }) +``` + +But sometimes you may need to share data with all views that are +rendered by your application. You may do so by using the `properties` +option inside of the `Path.config('view.ts')` file: + +```typescript +export default { + properties: { + env: Env, + config: Config, + key: 'value', + appName: Env('APP_NAME', '@athenna/athenna') + } +} +``` + +You can also use `View.addProperty()` method for registering your global +properties: + +```typescript +import { View } from '@athenna/view' + +View.addProperty('hello', ) +``` + +## View components + +Components are regular views created with the purpose of reuse. +Components can access additional runtime properties like `$props` +and `$slots`, which are unavailable to other views. + +We recommend you create components inside the components directory +of your `disk` root path. This helps create a visual boundary between +the components and the rest of the templates used by your application. + +Let's start by creating a button component. We will store it inside +the `Path.views('components/button.edge')` file: + +```html + +``` + +### Using components + +You must use the `@component` tag to render a component inside your +templates. The component tag accepts the template path as the first +parameter and props as the second parameter: + +```html +
+ @!component('components/button', { text: 'Login' }) + @!component('components/button', { text: 'Cancel', type: 'reset' }) +
+``` + +Output: + +```html +
+ + +
+``` + +Components from named disks can be referenced by prefixing the disk name: + +```html +@!component('uikit::components/button', { text: 'Login' }) +``` + +:::tip + +For more information about components, check [Edge component documentation +page](https://edgejs.dev/docs/components/introduction#components). + +::: + +### In-memory components + +You can register in-memory views components by defining them +in the `components` property of you `Path.config('view.ts')` +file. You can find it useful whenever you want to split a large +template file into several smaller ones or also if you want to +provide some templates as part of a npm package: + +```typescript +export default { + components: { + 'myuikit/button': Path.node_modules('myuikit/button.edge') + } +} +``` + +You can also create in-memory components without defining any file +using the `View` facade directly: + +```typescript +import { View } from '@athenna/view' + +View.createComponent('myuikit/button', '') +``` + +:::tip + +You can also use the `View.createTemplate()` method to +register templates. The only difference between them is +that `View.createTemplate()` does not throws errors if +some template name already exists, instead the old template +will be replaced. + +::: + +You can render the template directly or use it as a component in +other views: + +```html +@!component('myuikit/button', { + title: 'Signup', + class: ['btn', 'btn-primary'], + id: 'signup' +}) +``` + +## Optimizing views + +Compiling a template to a JavaScript function is a +time-consuming process, and hence it is recommended to cache +the compiled templates in production. + +You can control the template caching using the `edge.cache` +property inside `Path.config('view.ts')` file. Just make sure to set +the value to `true` in the production environment: + +```typescript +export default { + edge: { + // Set to `true` in production only + cache: Env('APP_ENV') === 'production' 👈 + } +} +``` + +All the templates are cached within the memory. Currently, +we do not have any plans to support on-disk caching since +the value provided for the efforts is too low. + +The raw text does not take up too much space, and even +keeping thousands of pre-compiled templates in memory should +not be a problem. + +## More details about views syntax + +If you want to know more informations about the views syntax like loops, +"if" statements and declaring properties, check out the full [Edge documentation](https://edgejs.dev/docs/introduction) +for all the details around it syntax. + +## Customizing `make` commands templates + +The Artisan console's `make` commands are used to create +a variety of classes, such as controllers, services, +commands, and tests. These classes are generated using +`.edge` files that are populated with values based on +your input. However, you may want to make small changes +to files generated by Artisan. To accomplish this, you +may use the `template:customize` command to publish the +most common stubs to your application so that you can +customize them: + +```bash +node artisan template:customize +``` + +The customized templates will be located in the +`Path.resources('templates')` directory. Any change +you make to these files will be reflected when you +generate their correspoding files using Artisan's +`make` commands. diff --git a/docs/testing/getting-started.mdx b/docs/testing/getting-started.mdx index 6f5b3815..2ca3fb5f 100644 --- a/docs/testing/getting-started.mdx +++ b/docs/testing/getting-started.mdx @@ -106,7 +106,7 @@ node artisan make:test UserTest --console ``` To customize the test templates check the -[`template:customize`](/docs/the-basics/views#customizing-make-commands-templates) +[`template:customize`](/docs/rest-api-application/views#customizing-make-commands-templates) command. ::: diff --git a/docs/the-basics/views.mdx b/docs/the-basics/views.mdx deleted file mode 100644 index 5b4240c6..00000000 --- a/docs/the-basics/views.mdx +++ /dev/null @@ -1,230 +0,0 @@ ---- -title: Views -sidebar_position: 2 -description: Understand how you can use the Athenna view API. ---- - -# Views - -Understand how you can use the Athenna view API. - -## Introduction - -Athenna's view API is specifically for email templates and -templates used by Artisan commands to generate your -application files. Our focus at the moment is to be a focused -framework for development of backend applications, using -microservices or not. You can still use this API for creating -monolith application in Athenna, but it will require a little -bit more of configuration. Nothing prevents us from adding -support for writing monolithic applications in the future, -adding useful methods for working with views in your REST API -for example. - -If you think this is an interesting feature, and think you -can contribute to the project with it, feel free to open a -[discussion](https://github.com/orgs/AthennaIO/discussions) -at Athenna organization, any contribution is welcome! - -## Creating & rendering views - -You may create a view by placing a file with the `.edge` -extension in any directory of your application's, we recommend -the `resources/views` directory. -The `.edge` extension informs the framework that the file -contains an [Edge template](https://github.com/edge-js/edge). -Edge templates contain HTML as well as Edge directives that -allow you to easily log values, create "if" statements, -iterate over data, and more. - -Once you have created a view file, eg: `resources/views/welcome.edge`, -you may register your view disk inside `Path.config('view.ts')` file: - -```typescript -import { Path } from '@athenna/common' - -export default { - disks: { - disk: Path.views(), - }, -} -``` - -Now you may render it using the `View.render()` or -`View.renderSync()` methods from `View` facade: - -```typescript -import { View } from '@athenna/view' - -await View.render('disk::welcome', { greeting: 'Hello world' }) -``` - -As you can see, the first argument passed to the -`View.render()` method corresponds to the name of the view -disk following the name of the file in the `resources/views` -directory. The second argument is an object of data that -should be made available to the view. In this case, we are -passing the `greeting` variable, which is displayed in the -view using Edge syntax. - -### Nested view disks - -View disks may also be nested within subdirectories. "Slash" -notation may be used to reference nested view disks. For -example, if your view is stored at -`resources/views/admin/profile.edge`, you may like so: - -```typescript -import { View } from '@athenna/view' - -await View.render('disk::admin/profile', { name: 'Jacob Smith' }) -``` - -### Rendering raw string - -You can render raw template string values using the -`View.renderRaw()` or `View.renderRawSync()` methods: - -```typescript -await View.renderRaw('

Hello {{ name }}

', { - name: 'Jacob Smith' -}) -``` - -:::note - -Raw strings do not enjoy the benefits of [template caching](/docs/the-basics/views#optimizing-views) -as there are not associated with a unique path. - -::: - -### In-memory views - -You can register in-memory views without creating any file -on the disk. You can find it useful whenever you want -to split a large template file into several smaller ones -or also if you want to provide some templates as part of -a npm package: - -```typescript -import { View } from '@athenna/view' - -View.createComponent('myuikit/button', '') -``` - -You can render the template directly or use it as a -component with the exact name given to the -`View.createComponent()` method: - -```edge -@!component('myuikit/button', { - title: 'Signup', - class: ['btn', 'btn-primary'], - id: 'signup' -}) -``` - -:::tip - -You can also use the `View.createTemplate()` method to -register templates. The only difference between them is -that `View.createTemplate()` does not throws errors if -some template name already exists, instead the old template -will be replaced. - -::: - -## Passing data to views - -As you saw in the previous examples, you may pass an object of -data to views to make that data available to the view: - -```typescript -import { View } from '@athenna/view' - -await View.render('disk::welcome', { name: 'Jacob Smith' }) -``` - -When passing information in this manner, the data should be -an object with key / value pairs. After providing data to a -view, you can then access each value within your view like so: - -```edge -

Welcome {{ name }}!

-``` - -### Sharing data with all views - -Occasionally, you may need to share data with all views that -are rendered by your application. You may do so using the -`View` facade's `addProperty()` method. Typically, you should -place calls to the `View.addProperty()` method within a -service provider's `boot()` method. You are free to add them -to the `providers/AppProvider` class or generate a separate -service provider to house them: - -```typescript -import { View } from '@athenna/view' -import { ServiceProvider } from '@athenna/ioc' - -export default class AppProvider extends ServiceProvider { - public async boot() { - View.addProperty('key', 'value') - } -} -``` - -## Optimizing views - -Compiling a template to a JavaScript function is a -time-consuming process, and hence it is recommended to cache -the compiled templates in production. - -You can control the template caching using the `edge.cache` -property inside `Path.config('view.ts')` file. Just make sure to set -the value to `true` in the production environment: - -```typescript -import { Path } from '@athenna/common' - -export default { - disks: { - disk: Path.views(), - }, - - edge: { - // Set to `true` in production only - cache: true 👈 - } -} -``` - -All the templates are cached within the memory. Currently, -we do not have any plans to support on-disk caching since -the value provided for the efforts is too low. - -The raw text does not take up too much space, and even -keeping thousands of pre-compiled templates in memory should -not be a problem. - -## Customizing `make` commands templates - -The Artisan console's `make` commands are used to create -a variety of classes, such as controllers, services, -commands, and tests. These classes are generated using -`.edge` files that are populated with values based on -your input. However, you may want to make small changes -to files generated by Artisan. To accomplish this, you -may use the `template:customize` command to publish the -most common stubs to your application so that you can -customize them: - -```bash -node artissan template:customize -``` - -The customized templates will be located in the -`Path.resources('templates')` directory. Any change -you make to these stubs will be reflected when you -generate their correspoding classes using Artisan's -`make` commands. diff --git a/lib/prism-edge.js b/lib/prism-edge.js new file mode 100644 index 00000000..b6a5352c --- /dev/null +++ b/lib/prism-edge.js @@ -0,0 +1,38 @@ +module.exports = (Prism) => { + Prism.languages.edge = { + 'comment': /\{\{![\s\S]*?\}\}/, + // 'delimiter': { + // pattern: /^\{\{\{?|\}\}\}?$/, + // alias: 'punctuation' + // }, + 'html': { + pattern: /\/ + }, + 'string': /(["'])(?:\\.|(?!\1)[^\\\r\n])*\1/, + 'number': /\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee][+-]?\d+)?/, + 'boolean': /\b(?:false|true)\b/, + 'block': { + pattern: /^(\s*(?:~\s*)?)[@\/]\S+?(?=\s*(?:~\s*)?$|\s)/, + lookbehind: true, + alias: 'keyword' + }, + 'brackets': { + pattern: /\{[^\]]+\}/, + inside: { + punctuation: /\{|\}/, + variable: /[\s\S]+/ + } + }, + // 'punctuation': /[!"#%&':()*+,.\/;<=>@\[\\\]^`{|}~]/, + // 'variable': /[^!"#%&'()*+,\/;<=>@\[\\\]^`{|}~\s]+/ + } + + // Prism.hooks.add('before-tokenize', function (env) { + // var edgePattern = /\{\{\{[\s\S]+?\}\}\}|\{\{[\s\S]+?\}\}/g + // Prism.languages['markup-templating'].buildPlaceholders(env, 'edge', edgePattern) + // }) + + // Prism.hooks.add('after-tokenize', function (env) { + // Prism.languages['markup-templating'].tokenizePlaceholders(env, 'edge') + // }) +} diff --git a/lib/prism/dracula/html.js b/lib/prism/dracula/html.js new file mode 100644 index 00000000..96f2ed05 --- /dev/null +++ b/lib/prism/dracula/html.js @@ -0,0 +1,30 @@ +export default [ + { + types: ["string", "attr-value"], + style: { + color: "hsl(60, 100%, 75%)" + }, + languages: ["html"] + }, + { + types: ["tag"], + style: { + color: "hsl(330, 100%, 75%)" + }, + languages: ["html"] + }, + { + types: ["attr-name", "decorator", "closed-decorator"], + style: { + color: "hsl(115, 100%, 75%)" + }, + languages: ["html"] + }, + { + types: ["keyword", "variable", "char", "selector"], + style: { + color: "rgb(248, 248, 242)" + }, + languages: ["html"] + }, +] diff --git a/lib/prism/dracula/index.js b/lib/prism/dracula/index.js index e78e9c8c..22199a77 100644 --- a/lib/prism/dracula/index.js +++ b/lib/prism/dracula/index.js @@ -5,6 +5,7 @@ */ import bash from './bash' +import html from './html' import json from './json' import typescript from './typescript' @@ -13,5 +14,5 @@ export default { color: "#F8F8F2", backgroundColor: "#282a36" }, - styles: [...bash, ...json, ...typescript] + styles: [...bash, ...html, ...json, ...typescript] } diff --git a/src/theme/prism-include-languages.js b/src/theme/prism-include-languages.js index 4528dda4..b8451f3f 100644 --- a/src/theme/prism-include-languages.js +++ b/src/theme/prism-include-languages.js @@ -10,6 +10,22 @@ export default function prismIncludeLanguages(PrismObject) { require(`prismjs/components/prism-${lang}`) }) + Prism.languages.html.decorator = { + pattern: /@[$\w\xA0-\uFFFF]+/ + } + + Prism.languages.html['closed-decorator'] = { + pattern: /@![$\w\xA0-\uFFFF]+/ + } + + Prism.languages.html.brackets = { + pattern: /\{[^\]]+\}/, + inside: { + punctuation: /\{|\}/, + variable: /[\s\S]+/ + } + } + Prism.languages.bash.function.pattern = /(^|[\s;|&]|[<>]\()(?:npx|node|yourCliCommand|.greet|npm|nvm|athenna)(?=$|[)\s;|&])/ delete globalThis.Prism