Wednesday, September 11, 2024

AstroJS 4.10 New Features with Example

Astro, renowned for its developer-friendly experience and focus on performance, has recently released version 4.10. This update brings two game-changing features that will revolutionize your development workflow and expand the possibilities with Astro:

1. The Experimental astro:env Module: A Refined Approach to Environment Variables

Environment variables are essential for any website or app, allowing us to tailor behavior based on the environment – development, staging, or production. While Astro previously offered ways to manage environment variables, developers faced challenges like:

  • Distinguishing client-side and server-side variables: This often resulted in convoluted configurations and unclear separation of concerns.

  • Protecting sensitive credentials: Ensuring secrets remained secure in all environments and were not exposed to clients proved to be a major hurdle.

  • Preventing secrets from being embedded in final build files: This security vulnerability could lead to unintended data exposure.

The new astro:env module tackles these challenges head-on. By defining a schema object within your Astro configuration file, you can manage environment variables in a more intuitive and structured way. The key benefits include:

  • Type Safety: Enforces proper variable types, reducing errors and improving code quality.

  • Client/Server Separation: Clearly distinguishes between variables accessible on the client and server, ensuring secure and appropriate data handling.

  • Secret Protection: Protects sensitive information by classifying variables as "secret" and limiting their access to the server-side only.

How to Set Up and Use astro:env Variables

  1. Create a new Astro project:

    npm create astro@latest myapp
    cd myapp
        

  2. Update your Astro configuration file:

    // astro.config.mjs
    import { defineConfig, envField } from "astro/config";
    
    export default defineConfig({
      experimental: {
        env: {
          schema: {
            API_URI: envField.string({
              context: "server",
              access: "secret",
              default: "https://fakestoreapi.com/products",
            }),
            USER_NAME: envField.string({
              context: "client",
              access: "public",
              default: "Melvin",
            }),
          },
        },
      },
    });
        

    In this example, we create two variables: API_URI (server-side, secret) and USER_NAME (client-side, public).

  3. Access variables within your Astro components:

    <!-- src/pages/index.astro -->
    ---
    import { USER_NAME } from 'astro:env/client';
    import {API_URI } from 'astro:env/server';
    
    await fetch(`${API_URI}`).then(res=>res.json()).then(json=>console.log(json))
    --- 
    <html>
      <body>
        <h1>{USER_NAME}</h1>
      </body>
    </html>
    
    <style>
      h1 {
        color: orange;
      }
    </style>
        

    This code demonstrates how to access variables based on their context (client or server) and their intended use.

Utilizing astro:env for Environment-Specific Builds

The astro:env module further shines when managing environment-specific configurations. Astro loads environment variables in a specific order:

  1. System environment variables

  2. .env file in the project root

  3. Environment-specific .env files (e.g., .env.development, .env.production)

Later files override values defined in earlier ones.

Here's an example:

// astro.config.mjs
import { defineConfig, envField } from 'astro/config';

export default defineConfig({
  experimental: {
    env: {
      schema: {
        API_URL: envField.string({
          default: 'http://localhost:3000/api', // Development default
          context: 'server',
        }),
        NODE_ENV: envField.string({
          default: 'development',
          context: 'client',
        }),
      },
    },
  },
});
    

In this case, API_URL defaults to http://localhost:3000/api in development. However, a .env.production file with the following content:

      API_URL=https://our-production-api.com
    

will override this value in production environments.

The NODE_ENV variable can be accessed client-side to conditionally render different UI elements based on the environment.

astro:env in CI/CD Workflows

astro:env further empowers developers in CI/CD pipelines:

  • Environment-specific variables: CI/CD pipelines can set variables for different stages (e.g., CI=true, DEPLOYMENT_ENV=production).

  • Secure access to sensitive information: Sensitive information can be securely stored and accessed within Astro.

  • Environment-specific build artifacts: Different build artifacts can be generated based on environment variables.

Limitations of astro:env (as of Astro 4.10)

It's important to note that astro:env is still an experimental feature in Astro 4.10 and may undergo changes in future releases. Keep an eye on the official documentation for updates.

2. The Astro Container API: Bridging the Gap to Other Frameworks

The Astro Container API is the second key feature in 4.10, enabling developers to seamlessly integrate Astro components with other frameworks. This powerful capability unlocks new possibilities for leveraging Astro's strengths within existing projects.

How the Astro Container API Works

The process involves two steps:

  1. Compilation: Use astro build to compile Astro components into standalone bundles.

  2. Integration: Import and use these bundles within your non-Astro project, with Astro handling client-side hydration.

Practical Example: Integrating Astro with Express

Let's demonstrate this integration by embedding an Astro component within an Express application using EJS templating.

  1. Set up a new project:

          npm init -y
    npm install astro express ejs
    npm install -D nodemon
        

  2. Create an Astro component:

          <!-- src/components/MyComponent.astro -->
    ---
    ---
    <h1>Hello from Astro component</h1>
        

  3. Export the component:

          // src/all.js
    export { default as MyComponent } from "./components/MyComponent.astro";
        

  4. Create a custom Astro adapter:

    // adapter/index.mjs
    export default function () {
      return {
        name: "myadapter",
        hooks: {
          "astro:config:done": ({ setAdapter }) => {
            setAdapter({
              name: "myadapter",
              serverEntrypoint: new URL("./server-entrypoint.mjs", import.meta.url)
                .pathname,
              supportedAstroFeatures: {
                serverOutput: "stable",
              },
              exports: ["manifest"],
            });
          },
          "astro:build:setup": ({ vite, target }) => {
            if (target === "server") {
              vite.build.rollupOptions.input.push("src/all.js");
            }
          },
        },
      };
    }
        

    The adapter configures the build process and specifies the server entry point.

  5. Define the server entry point:

    // adapter/server-entrypoint.mjs
    export function createExports(manifest) {
      return { manifest };
    }
        

    This function is responsible for generating the build output.

  6. Configure Astro for server-side rendering:

    // astro.config.mjs
    import { defineConfig } from "astro/config";
    import adapter from "./adapter/index.mjs";
    
    export default defineConfig({
      output: "server",
      adapter: adapter(),
      integrations: [],
    });
        

  7. Build the project:

          npm run build
        

  8. Set up the Express server:

          // server.mjs
    import * as components from "./dist/server/all.mjs";
    import { renderers } from "./dist/server/renderers.mjs";
    import { manifest } from "./dist/server/entry.mjs";
    import { experimental_AstroContainer as AstroContainer } from "astro/container";
    import express from "express";
    import { fileURLToPath } from "url";
    import path, { dirname } from "path";
    
    const __filename = fileURLToPath(import.meta.url);
    const __dirname = dirname(__filename);
    
    const container = await AstroContainer.create({
      manifest,
      renderers,
      resolve(s) {
        const found = manifest.entryModules[s];
        if (found) {
          return `/dist/client/${found}`;
        }
        return found;
      },
    });
    
    const app = express();
    app.set("view engine", "ejs");
    app.set("views", path.join(__dirname, "views"));
    const port = 5000;
    
    app.get("/", async (req, res) => {
      const html = await container.renderToString(components.MyComponent);
      res.render("index", { body: html, title: "Welcome to Astro-in-Express" });
    });
    
    app.listen(port, () => {
      console.log(`Server listening on port ${port}`);
    });
        

  9. Create an EJS template:

          <!-- views/index.ejs -->
    <!DOCTYPE html>
    <html>
      <head>
        <title><%= title %></title>
      </head>
      <body class="">
        <%- body %>
      </body>
    </html>
        

  10. Start the server:

          npm run serve
        

    Access http://localhost:5000/ to see your Astro component rendered in the Express environment.

The Future of Web Development with Astro

astro:env and the Container API are not just isolated features; they represent a vision for a more integrated and flexible web development ecosystem. As these features mature, we can anticipate:

  • Wider cross-framework compatibility: The Container API could pave the way for seamless integration between even more diverse web technologies.

  • Streamlined developer workflows: New tools and practices could emerge to simplify the development process for complex, multi-framework applications.

  • Influence on industry standards: astro:env could inspire other frameworks to adopt similar approaches to managing environment variables.

Astro's commitment to innovation positions it at the forefront of web development. The new features in 4.10 invite developers to experiment and rethink the boundaries of web development. Embrace these powerful tools, and unleash the potential of Astro to build the next generation of web experiences.

0 comments:

Post a Comment